blob: 53553afb35d6296db22e9040abad76c2b788369a [file] [log] [blame]
/* *******************************************************************
* Copyright (c) 2002-2019 Contributors
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/epl-v10.html
* ******************************************************************/
package org.aspectj.weaver.patterns;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.WeaverMessages;
import org.aspectj.weaver.World;
/**
* @author Thomas Kiviaho
* @author Andy Clement
* @author PARC
*/
public class DeclareParents extends Declare {
protected TypePattern child;
protected TypePatternList parents;
private boolean isWildChild = false;
protected boolean isExtends = true;
public DeclareParents(TypePattern child, List<TypePattern> parents, boolean isExtends) {
this(child, new TypePatternList(parents), isExtends);
}
protected DeclareParents(TypePattern child, TypePatternList parents, boolean isExtends) {
this.child = child;
this.parents = parents;
this.isExtends = isExtends;
WildChildFinder wildChildFinder = new WildChildFinder();
child.accept(wildChildFinder, null);
isWildChild = wildChildFinder.containedWildChild();
}
public boolean match(ResolvedType typeX) {
if (!child.matchesStatically(typeX)) {
return false;
}
if (typeX.getWorld().getLint().typeNotExposedToWeaver.isEnabled() && !typeX.isExposedToWeaver()) {
typeX.getWorld().getLint().typeNotExposedToWeaver.signal(typeX.getName(), getSourceLocation());
}
return true;
}
@Override
public Object accept(PatternNodeVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public Declare parameterizeWith(Map<String,UnresolvedType> typeVariableBindingMap, World w) {
DeclareParents ret = new DeclareParents(child.parameterizeWith(typeVariableBindingMap, w), parents.parameterizeWith(
typeVariableBindingMap, w), isExtends);
ret.copyLocationFrom(this);
return ret;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("declare parents: ");
buf.append(child);
buf.append(isExtends ? " extends " : " implements "); // extends and implements are treated equivalently
buf.append(parents);
buf.append(";");
return buf.toString();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof DeclareParents)) {
return false;
}
DeclareParents o = (DeclareParents) other;
return o.child.equals(child) && o.parents.equals(parents);
}
// ??? cache this
@Override
public int hashCode() {
int result = 23;
result = 37 * result + child.hashCode();
result = 37 * result + parents.hashCode();
return result;
}
@Override
public void write(CompressingDataOutputStream s) throws IOException {
s.writeByte(Declare.PARENTS);
child.write(s);
parents.write(s);
writeLocation(s);
}
public static Declare read(VersionedDataInputStream s, ISourceContext context) throws IOException {
DeclareParents ret = new DeclareParents(TypePattern.read(s, context), TypePatternList.read(s, context), true);
ret.readLocation(context, s);
return ret;
}
public boolean parentsIncludeInterface(World w) {
for (int i = 0; i < parents.size(); i++) {
if (parents.get(i).getExactType().resolve(w).isInterface()) {
return true;
}
}
return false;
}
public boolean parentsIncludeClass(World w) {
for (int i = 0; i < parents.size(); i++) {
if (parents.get(i).getExactType().resolve(w).isClass()) {
return true;
}
}
return false;
}
@Override
public void resolve(IScope scope) {
TypePattern resolvedChild = child.resolveBindings(scope, Bindings.NONE, false, false);
if (!resolvedChild.equals(child)) {
WildChildFinder wildChildFinder = new WildChildFinder();
resolvedChild.accept(wildChildFinder, null);
isWildChild = wildChildFinder.containedWildChild();
this.child = resolvedChild;
}
parents = parents.resolveBindings(scope, Bindings.NONE, false, true);
// Could assert this ...
// for (int i=0; i < parents.size(); i++) {
// parents.get(i).assertExactType(scope.getMessageHandler());
// }
}
public TypePatternList getParents() {
return parents;
}
public TypePattern getChild() {
return child;
}
// note - will always return true after deserialization, this doesn't affect weaver
public boolean isExtends() {
return this.isExtends;
}
@Override
public boolean isAdviceLike() {
return false;
}
private ResolvedType maybeGetNewParent(ResolvedType targetType, TypePattern typePattern, World world, boolean reportErrors) {
if (typePattern == TypePattern.NO) {
return null; // already had an error here
}
// isWildChild = (child instanceof WildTypePattern);
UnresolvedType iType = typePattern.getExactType();
ResolvedType parentType = iType.resolve(world);
if (targetType.equals(world.getCoreType(UnresolvedType.OBJECT))) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.DECP_OBJECT), this.getSourceLocation(), null);
return null;
}
// Ensure the target doesn't already have an
// alternate parameterization of the generic type on it
if (parentType.isParameterizedType() || parentType.isRawType()) {
// Let's take a look at the parents we already have
boolean isOK = verifyNoInheritedAlternateParameterization(targetType, parentType, world);
if (!isOK) {
return null;
}
}
if (parentType.isAssignableFrom(targetType)) {
return null; // already a parent
}
// Enum types that are targetted for decp through a wild type pattern get linted
if (reportErrors && isWildChild && targetType.isEnum()) {
world.getLint().enumAsTargetForDecpIgnored.signal(targetType.toString(), getSourceLocation());
}
// Annotation types that are targetted for decp through a wild type pattern get linted
if (reportErrors && isWildChild && targetType.isAnnotation()) {
world.getLint().annotationAsTargetForDecpIgnored.signal(targetType.toString(), getSourceLocation());
}
// 1. Can't use decp to make an enum/annotation type implement an interface
if (targetType.isEnum() && parentType.isInterface()) {
if (reportErrors && !isWildChild) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_DECP_ON_ENUM_TO_IMPL_INTERFACE,
targetType), getSourceLocation(), null);
}
return null;
}
if (targetType.isAnnotation() && parentType.isInterface()) {
if (reportErrors && !isWildChild) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_DECP_ON_ANNOTATION_TO_IMPL_INTERFACE,
targetType), getSourceLocation(), null);
}
return null;
}
// 2. Can't use decp to change supertype of an enum/annotation
if (targetType.isEnum() && parentType.isClass()) {
if (reportErrors && !isWildChild) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_DECP_ON_ENUM_TO_EXTEND_CLASS,
targetType), getSourceLocation(), null);
}
return null;
}
if (targetType.isAnnotation() && parentType.isClass()) {
if (reportErrors && !isWildChild) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_DECP_ON_ANNOTATION_TO_EXTEND_CLASS,
targetType), getSourceLocation(), null);
}
return null;
}
// 3. Can't use decp to declare java.lang.Enum/java.lang.annotation.Annotation as the parent of a type
if (parentType.getSignature().equals(UnresolvedType.ENUM.getSignature())) {
if (reportErrors && !isWildChild) {
world.showMessage(IMessage.ERROR, WeaverMessages
.format(WeaverMessages.CANT_DECP_TO_MAKE_ENUM_SUPERTYPE, targetType), getSourceLocation(), null);
}
return null;
}
if (parentType.getSignature().equals(UnresolvedType.ANNOTATION.getSignature())) {
if (reportErrors && !isWildChild) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_DECP_TO_MAKE_ANNOTATION_SUPERTYPE,
targetType), getSourceLocation(), null);
}
return null;
}
if (parentType.isAssignableFrom(targetType)) {
return null; // already a parent
}
if (targetType.isAssignableFrom(parentType)) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_EXTEND_SELF, targetType.getName()), this
.getSourceLocation(), null);
return null;
}
if (parentType.isClass()) {
if (targetType.isInterface()) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.INTERFACE_CANT_EXTEND_CLASS), this
.getSourceLocation(), null);
return null;
// how to handle xcutting errors???
}
if (!targetType.getSuperclass().isAssignableFrom(parentType)) {
world.showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.DECP_HIERARCHY_ERROR, iType.getName(),
targetType.getSuperclass().getName()), this.getSourceLocation(), null);
return null;
} else {
return parentType;
}
} else {
return parentType;
}
}
/**
* This method looks through the type hierarchy for some target type - it is attempting to find an existing parameterization
* that clashes with the new parent that the user wants to apply to the type. If it finds an existing parameterization that
* matches the new one, it silently completes, if it finds one that clashes (e.g. a type already has A<String> when the user
* wants to add A<Number>) then it will produce an error.
*
* It uses recursion and exits recursion on hitting 'jlObject'
*
* Related bugzilla entries: pr110788
*/
private boolean verifyNoInheritedAlternateParameterization(ResolvedType typeToVerify, ResolvedType newParent, World world) {
if (typeToVerify.equals(ResolvedType.OBJECT)) {
return true;
}
ResolvedType newParentGenericType = newParent.getGenericType();
Iterator<ResolvedType> iter = typeToVerify.getDirectSupertypes();
while (iter.hasNext()) {
ResolvedType supertype = iter.next();
if (((supertype.isRawType() && newParent.isParameterizedType()) || (supertype.isParameterizedType() && newParent
.isRawType()))
&& newParentGenericType.equals(supertype.getGenericType())) {
// new parent is a parameterized type, but this is a raw type
world.getMessageHandler().handleMessage(
new Message(WeaverMessages.format(WeaverMessages.CANT_DECP_MULTIPLE_PARAMETERIZATIONS, newParent.getName(),
typeToVerify.getName(), supertype.getName()), getSourceLocation(), true,
new ISourceLocation[] { typeToVerify.getSourceLocation() }));
return false;
}
if (supertype.isParameterizedType()) {
ResolvedType generictype = supertype.getGenericType();
// If the generic types are compatible but the parameterizations aren't then we have a problem
if (generictype.isAssignableFrom(newParentGenericType) && !supertype.isAssignableFrom(newParent)) {
world.getMessageHandler().handleMessage(
new Message(WeaverMessages.format(WeaverMessages.CANT_DECP_MULTIPLE_PARAMETERIZATIONS, newParent
.getName(), typeToVerify.getName(), supertype.getName()), getSourceLocation(), true,
new ISourceLocation[] { typeToVerify.getSourceLocation() }));
return false;
}
}
if (!verifyNoInheritedAlternateParameterization(supertype, newParent, world)) {
return false;
}
}
return true;
}
public List<ResolvedType> findMatchingNewParents(ResolvedType onType, boolean reportErrors) {
if (onType.isRawType()) {
onType = onType.getGenericType();
}
if (!match(onType)) {
return Collections.emptyList();
}
List<ResolvedType> ret = new ArrayList<ResolvedType>();
for (int i = 0; i < parents.size(); i++) {
ResolvedType t = maybeGetNewParent(onType, parents.get(i), onType.getWorld(), reportErrors);
if (t != null) {
ret.add(t);
}
}
return ret;
}
@Override
public String getNameSuffix() {
return "parents";
}
public boolean isMixin() {
return false;
}
}