| /* ******************************************************************* |
| * 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; |
| } |
| } |