package org.checkerframework.framework.type; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.IntersectionType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; import org.checkerframework.framework.util.PluginUtil; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.TypesUtils; /** * BoundsInitializer creates AnnotatedTypeMirrors (without annotations) for the bounds of type * variables and wildcards. Its static helper methods are called from AnnotatedTypeMirror. When an * initializer method is called for a particular bound, the entirety of that bound, including * circular references, will be created. */ public class BoundsInitializer { //================================================================================================================== // Static helper methods called from AnnotatedTypeMirror to initialize bounds of wildcards or type variables //================================================================================================================== /** * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a * typeVar is recursive the appropriate cycles will be introduced in the type * * @param typeVar the type variable whose lower bound is being initialized */ public static void initializeBounds(final AnnotatedTypeVariable typeVar) { final Set<AnnotationMirror> annos = saveAnnotations(typeVar); InitializerVisitor visitor = new InitializerVisitor(new TypeVariableStructure(null, typeVar)); visitor.initializeLowerBound(typeVar); visitor.resolveTypeVarReferences(typeVar); InitializerVisitor visitor2 = new InitializerVisitor(new TypeVariableStructure(null, typeVar)); visitor2.initializeUpperBound(typeVar); visitor2.resolveTypeVarReferences(typeVar); restoreAnnotations(typeVar, annos); } /** * If we are initializing a type variable with a primary annotation than we should first * initialize it as if it were a declaration (i.e. as if it had no primary annotations) and then * apply the primary annotations. We do this so that when we make copies of the original type to * represent recursive references the recursive references don't have the primary annotation. * * <pre>{@code * e.g. given the declaration {@code <E extends List<E>>} * if we do not do this, the NonNull on the use @NonNull E * would be copied to the primary annotation on E in the bound {@code List<E>} * i.e. the use would be {@code <@NonNull E extends @NonNull List<@NonNull E>>} * rather than {@code <@NonNull E extends @NonNull List<E>>} * }</pre> */ private static Set<AnnotationMirror> saveAnnotations(final AnnotatedTypeMirror type) { if (!type.getAnnotationsField().isEmpty()) { final Set<AnnotationMirror> annos = new HashSet<>(type.getAnnotations()); type.clearAnnotations(); return annos; } return null; } private static void restoreAnnotations( final AnnotatedTypeMirror type, final Set<AnnotationMirror> annos) { if (annos != null) { type.addAnnotations(annos); } } /** * Create the entire super bound, with no missing information, for wildcard. If a wildcard is * recursive the appropriate cycles will be introduced in the type * * @param wildcard the wildcard whose lower bound is being initialized */ public static void initializeSuperBound(final AnnotatedWildcardType wildcard) { final Set<AnnotationMirror> annos = saveAnnotations(wildcard); InitializerVisitor visitor = new InitializerVisitor(new WildcardStructure()); visitor.initializeSuperBound(wildcard); visitor.resolveTypeVarReferences(wildcard); restoreAnnotations(wildcard, annos); } /** * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is * recursive the appropriate cycles will be introduced in the type * * @param wildcard the wildcard whose extends bound is being initialized */ public static void initializeExtendsBound(final AnnotatedWildcardType wildcard) { final Set<AnnotationMirror> annos = saveAnnotations(wildcard); InitializerVisitor visitor = new InitializerVisitor(new WildcardStructure()); visitor.initializeExtendsBound(wildcard); visitor.resolveTypeVarReferences(wildcard); restoreAnnotations(wildcard, annos); } //================================================================================================================== // Classes and methods used to make the above static helper methods work //================================================================================================================== /** * Creates the AnnotatedTypeMirrors (without annotations) for the bounds of all type variables * and wildcards in a given type. If the type is recursive, {@code T extends Comparable<T>}, * then all references to the same type variable are references to the same AnnotatedTypeMirror. */ private static class InitializerVisitor implements AnnotatedTypeVisitor<Void, Void> { /** * The BoundStructure starting from the first wildcard or type variable bound initialization * that kicked this visitation off */ private final BoundStructure topLevelStructure; private BoundStructure currentStructure = null; private final Map<TypeVariable, TypeVariableStructure> typeVarToStructure = new HashMap<>(); // private final Map<TypeVariable, TypeVariableRecord> typeVarToRecord = new HashMap<>(); private final Map<WildcardType, AnnotatedWildcardType> wildcards = new HashMap<>(); private final Map<IntersectionType, AnnotatedIntersectionType> intersections = new HashMap<>(); // need current bound path public InitializerVisitor(final BoundStructure boundStructure) { this.topLevelStructure = boundStructure; this.currentStructure = boundStructure; } public InitializerVisitor(final TypeVariableStructure typeVarStruct) { this((BoundStructure) typeVarStruct); typeVarToStructure.put(typeVarStruct.typeVar, typeVarStruct); } //-------------------------------------------------------------------------------------------------------------- // Visit methods that keep track of the path traversed through type variable bounds, and the // wildcards/intersections that have been encountered. //-------------------------------------------------------------------------------------------------------------- @Override public Void visit(AnnotatedTypeMirror type) { type.accept(this, null); return null; } @Override public Void visit(AnnotatedTypeMirror type, Void aVoid) { visit(type); return null; } @Override public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { initializeTypeArgs(type); return null; } @Override public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { if (intersections.containsKey(type.getUnderlyingType())) { return null; } intersections.put((IntersectionType) type.getUnderlyingType(), type); final List<AnnotatedDeclaredType> supertypes = type.directSuperTypes(); for (int i = 0; i < supertypes.size(); i++) { final AnnotatedDeclaredType supertype = supertypes.get(i); final BoundPathNode node = addPathNode(new IntersectionNode(i)); visit(supertype); removePathNode(node); } return null; } @Override public Void visitUnion(AnnotatedUnionType type, Void aVoid) { final List<AnnotatedDeclaredType> alts = type.getAlternatives(); for (int i = 0; i < alts.size(); i++) { final AnnotatedDeclaredType alt = alts.get(i); final BoundPathNode node = addPathNode(new UnionNode(i)); visit(alt); removePathNode(node); } return null; } @Override public Void visitArray(AnnotatedArrayType type, Void aVoid) { if (!TypesUtils.isPrimitive(type.getComponentType().getUnderlyingType())) { // Only recur on component type if it's not a primitive. // Array component types are the only place a primitive is allowed in bounds final BoundPathNode componentNode = addPathNode(new ArrayComponentNode()); type.setComponentType(replaceOrVisit(type.getComponentType())); removePathNode(componentNode); } return null; } @Override public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { this.currentStructure.addTypeVar(type.getUnderlyingType()); if (!haveSeenTypeVar(type)) { pushNewTypeVarStruct(type); initializeUpperBound(type); initializeLowerBound(type); popCurrentTypeVarStruct(type); } return null; } @Override public Void visitNull(AnnotatedNullType type, Void aVoid) { return null; } @Override public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { if (wildcard.getSuperBoundField() == null) { initializeSuperBound(wildcard); } else { ErrorReporter.errorAbort( "Wildcard super field should not be initialized:\n" + "wildcard=" + wildcard.toString() + "currentPath=" + currentStructure.currentPath); } if (wildcard.getExtendsBoundField() == null) { initializeExtendsBound(wildcard); } else { ErrorReporter.errorAbort( "Wildcard extends field should not be initialized:\n" + "wildcard=" + wildcard.toString() + "currentPath=" + currentStructure.currentPath); } return null; } @Override public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { return invalidType(type); } @Override public Void visitNoType(AnnotatedNoType type, Void aVoid) { return invalidType(type); } @Override public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { return invalidType(type); } public AnnotatedTypeMirror replaceOrVisit(final AnnotatedTypeMirror type) { if (type.getKind() == TypeKind.WILDCARD) { final AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; if (wildcards.containsKey(wildcard.getUnderlyingType())) { return wildcards.get(wildcard.getUnderlyingType()); } else { visit(wildcard); } return wildcard; } else if (type.getKind() == TypeKind.INTERSECTION) { if (intersections.containsKey(type.getUnderlyingType())) { return intersections.get(type.getUnderlyingType()); } visit(type); return type; } else { visit(type); return type; } } //-------------------------------------------------------------------------------------------------------------- // public void initializeUpperBound(final AnnotatedTypeVariable typeVar) { final AnnotatedTypeMirror upperBound = createAndSetUpperBound(typeVar); final BoundPathNode pathNode = new UpperBoundNode(); addPathNode(pathNode); visit(upperBound); removePathNode(pathNode); } public void initializeLowerBound(final AnnotatedTypeVariable typeVar) { final AnnotatedTypeMirror lowerBound = createAndSetLowerBound(typeVar); final BoundPathNode pathNode = new LowerBoundNode(); addPathNode(pathNode); visit(lowerBound); removePathNode(pathNode); } public void initializeSuperBound(final AnnotatedWildcardType wildcard) { final AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; final WildcardType underlyingType = wildcard.getUnderlyingType(); TypeMirror underlyingSuperBound = underlyingType.getSuperBound(); if (underlyingSuperBound == null) { underlyingSuperBound = TypesUtils.wildLowerBound( wildcard.atypeFactory.processingEnv, underlyingType); } final AnnotatedTypeMirror superBound = AnnotatedTypeMirror.createType(underlyingSuperBound, typeFactory, false); wildcard.setSuperBound(superBound); this.wildcards.put(wildcard.getUnderlyingType(), wildcard); final BoundPathNode superNode = addPathNode(new SuperNode()); visit(superBound); removePathNode(superNode); } public void initializeExtendsBound(final AnnotatedWildcardType wildcard) { final AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; final WildcardType underlyingType = wildcard.getUnderlyingType(); TypeMirror underlyingExtendsBound = underlyingType.getExtendsBound(); if (underlyingExtendsBound == null) { // Take the upper bound of the type variable the wildcard is bound to. underlyingExtendsBound = TypesUtils.wildUpperBound( wildcard.atypeFactory.processingEnv, underlyingType); } final AnnotatedTypeMirror extendsBound = AnnotatedTypeMirror.createType(underlyingExtendsBound, typeFactory, false); wildcard.setExtendsBound(extendsBound); this.wildcards.put(wildcard.getUnderlyingType(), wildcard); final BoundPathNode extendsNode = addPathNode(new ExtendsNode()); visit(extendsBound); removePathNode(extendsNode); } private void initializeTypeArgs(final AnnotatedDeclaredType declaredType) { if (declaredType.typeArgs == null) { final DeclaredType actualType = (DeclaredType) declaredType.actualType; final List<AnnotatedTypeMirror> typeArgs = new ArrayList<>(); if (!actualType.getTypeArguments().isEmpty()) { // lazy init final List<? extends TypeMirror> actualTypeArgs = actualType.getTypeArguments(); for (int i = 0; i < actualTypeArgs.size(); i++) { final AnnotatedTypeMirror annoTypeArg = AnnotatedTypeMirror.createType( actualTypeArgs.get(i), declaredType.atypeFactory, false); final BoundPathNode node = addPathNode(new TypeArgNode(i)); typeArgs.add(replaceOrVisit(annoTypeArg)); removePathNode(node); } } declaredType.setTypeArguments(typeArgs); } else { final List<AnnotatedTypeMirror> typeArgs = new ArrayList<>(declaredType.getTypeArguments()); final List<AnnotatedTypeMirror> typeArgReplacements = new ArrayList<>(typeArgs.size()); for (int i = 0; i < typeArgs.size(); i++) { final AnnotatedTypeMirror typeArg = typeArgs.get(i); final BoundPathNode node = addPathNode(new TypeArgNode(i)); typeArgReplacements.add(replaceOrVisit(typeArg)); removePathNode(node); } declaredType.setTypeArguments(typeArgReplacements); } } public static Void invalidType(final AnnotatedTypeMirror atm) { ErrorReporter.errorAbort( "Unexpected type in Wildcard bound:\n" + "kind=" + atm.getKind() + "\n" + "atm=" + atm); return null; // dead code } public BoundPathNode addPathNode(final BoundPathNode node) { currentStructure.currentPath.add(node); return node; } public BoundPathNode removePathNode(final BoundPathNode node) { if (currentStructure.currentPath.getLast() != node) { ErrorReporter.errorAbort( "Cannot remove node: " + node + " It is not the last item.\n" + "node=" + node + "\n" + "currentPath=" + currentStructure.currentPath); } // else currentStructure.currentPath.removeLast(); return node; } public void pushNewTypeVarStruct(final AnnotatedTypeVariable typeVar) { if (typeVarToStructure.containsKey(typeVar.getUnderlyingType())) { ErrorReporter.errorAbort( "Starting a TypeVarStructure that already exists!\n" + "typeVar=" + typeVar + "\n" + "currentStructure=" + currentStructure); } final TypeVariableStructure typeVarStruct = new TypeVariableStructure(currentStructure, typeVar); typeVarToStructure.put(typeVar.getUnderlyingType(), typeVarStruct); this.currentStructure = typeVarStruct; } public boolean haveSeenTypeVar(final AnnotatedTypeVariable typeVariable) { return typeVarToStructure.containsKey(typeVariable.getUnderlyingType()); } public void popCurrentTypeVarStruct(final AnnotatedTypeVariable typeVar) { if (!(this.currentStructure instanceof TypeVariableStructure)) { ErrorReporter.errorAbort( "Trying to pop WildcardStructure!\n" + "typeVar=" + typeVar + "\n" + "currentStucture=" + currentStructure + "\n"); } // else final TypeVariableStructure toPop = (TypeVariableStructure) this.currentStructure; if (toPop.typeVar != typeVar) { this.currentStructure = toPop.parent; } } public ReferenceMap createReferenceMap(final BoundStructure boundStruct) { final ReferenceMap refMap = new ReferenceMap(); for (Entry<BoundPath, TypeVariable> entry : boundStruct.pathToTypeVar.entrySet()) { TypeVariableStructure targetStructure = typeVarToStructure.get(entry.getValue()); AnnotatedTypeVariable template = targetStructure.annotatedTypeVar; refMap.put(entry.getKey(), template.deepCopy().asUse()); addImmediateTypeVarPaths(refMap, entry.getKey(), targetStructure); } return refMap; } public void addImmediateTypeVarPaths( ReferenceMap refMap, BoundPath basePath, TypeVariableStructure targetStruct) { // explain typevar sleds for (BoundPath path : targetStruct.immediateBoundTypeVars) { final BoundPath newPath = basePath.copy(); newPath.add(path.getFirst()); TypeVariable immTypeVar = targetStruct.pathToTypeVar.get(path); TypeVariableStructure immTvStructure = typeVarToStructure.get(immTypeVar); AnnotatedTypeVariable template = immTvStructure.annotatedTypeVar; refMap.put(newPath, template.deepCopy()); } } /** * A mapping of paths to the type that should be placed at the end of that path for all atvs * that of sourceType */ @SuppressWarnings("serial") private static class ReferenceMap extends LinkedHashMap<BoundPath, AnnotatedTypeVariable> { //TODO: EXPLAINED LINK DUE TO TYPEVAR SLED } public void resolveTypeVarReferences(final AnnotatedTypeMirror boundedType) { final List<AnnotatedTypeVariable> annotatedTypeVars = new ArrayList<>(); final Map<TypeVariable, ReferenceMap> typeVarToRefMap = new HashMap<>(); for (final TypeVariableStructure typeVarStruct : typeVarToStructure.values()) { ReferenceMap refMap = createReferenceMap(typeVarStruct); typeVarToRefMap.put(typeVarStruct.typeVar, refMap); annotatedTypeVars.addAll(refMap.values()); } for (final AnnotatedTypeVariable atv : annotatedTypeVars) { fixTypeVarPathReference(atv, typeVarToRefMap); } if (topLevelStructure instanceof WildcardStructure) { fixWildcardPathReference((AnnotatedWildcardType) boundedType, typeVarToRefMap); } else { final AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) boundedType; fixTypeVarPathReference(typeVar, typeVarToRefMap); } } public void fixWildcardPathReference( final AnnotatedWildcardType wildcard, final Map<TypeVariable, ReferenceMap> typeVarToRefMap) { final ReferenceMap topLevelMap = createReferenceMap(topLevelStructure); for (AnnotatedTypeVariable typeVar : topLevelMap.values()) { fixTypeVarPathReference(typeVar, typeVarToRefMap); } for (Entry<BoundPath, AnnotatedTypeVariable> pathToRef : topLevelMap.entrySet()) { final AnnotatedTypeMirror parent = traverseToParent(wildcard, pathToRef.getKey()); final BoundPathNode terminus = pathToRef.getKey().getLast(); terminus.setType(parent, pathToRef.getValue()); } } public void fixTypeVarPathReference( final AnnotatedTypeVariable type, Map<TypeVariable, ReferenceMap> typeVarToRefMap) { final ReferenceMap refMap = typeVarToRefMap.get(type.getUnderlyingType()); for (final Entry<BoundPath, AnnotatedTypeVariable> pathToRef : refMap.entrySet()) { final BoundPath path = pathToRef.getKey(); final AnnotatedTypeVariable replacement = pathToRef.getValue().asUse(); AnnotatedTypeMirror parent = traverseToParent(type, path); BoundPathNode terminus = path.getLast(); terminus.replaceType(parent, replacement); } } public AnnotatedTypeMirror traverseToParent( final AnnotatedTypeMirror start, final List<BoundPathNode> path) { AnnotatedTypeMirror current = start; for (int i = 0; i < path.size() - 1; i++) { current = path.get(i).next(current); } return current; } } private static AnnotatedTypeMirror createAndSetUpperBound(final AnnotatedTypeVariable typeVar) { final AnnotatedTypeMirror upperBound = AnnotatedTypeMirror.createType( typeVar.getUnderlyingType().getUpperBound(), typeVar.atypeFactory, false); typeVar.setUpperBound(upperBound); return upperBound; } private static AnnotatedTypeMirror createAndSetLowerBound(final AnnotatedTypeVariable typeVar) { TypeMirror lb = typeVar.getUnderlyingType().getLowerBound(); if (lb == null) { // Use bottom type to ensure there is a lower bound. Context context = ((JavacProcessingEnvironment) typeVar.atypeFactory.processingEnv).getContext(); Symtab syms = Symtab.instance(context); lb = syms.botType; } final AnnotatedTypeMirror lowerBound = AnnotatedTypeMirror.createType(lb, typeVar.atypeFactory, false); typeVar.setLowerBound(lowerBound); return lowerBound; } private static boolean isImmediateBoundPath(final BoundPath path) { if (path.size() == 1) { switch (path.getFirst().kind) { case UpperBound: case LowerBound: return true; default: // do nothing } } return false; } private abstract static class BoundStructure { /** * A mapping of all BoundPaths to TypeVariables for all type variables contained within * annotatedTypeVar */ public final Map<BoundPath, TypeVariable> pathToTypeVar = new LinkedHashMap<>(); public final BoundPath currentPath = new BoundPath(); public BoundStructure() {} public void addTypeVar(final TypeVariable typeVariable) { pathToTypeVar.put(this.currentPath.copy(), typeVariable); } } private static class WildcardStructure extends BoundStructure {} private static class TypeVariableStructure extends BoundStructure { /** The type variable whose structure is being described */ public final TypeVariable typeVar; /** * The first annotated type variable that was encountered and traversed in order to describe * typeVar. It is expanded during visitation and it is later used as a template for other * uses of typeVar */ public final AnnotatedTypeVariable annotatedTypeVar; /** The boundStructure that was active before this one */ private final BoundStructure parent; /** * If this type variable is upper or lower bounded by another type variable (not a declared * type or intersection) then this variable will contain the path to that type variable * //TODO: Add link to explanation * * <p>e.g. {@code T extends E} ⇒ The structure for T will have an * immediateBoundTypeVars = List(UpperBound) The BoundPaths here must exist in pathToTypeVar */ public Set<BoundPath> immediateBoundTypeVars = new LinkedHashSet<>(); public TypeVariableStructure( final BoundStructure parent, final AnnotatedTypeVariable annotatedTypeVar) { this.parent = parent; this.typeVar = annotatedTypeVar.getUnderlyingType(); this.annotatedTypeVar = annotatedTypeVar; } @Override public void addTypeVar(TypeVariable typeVariable) { final BoundPath copy = currentPath.copy(); pathToTypeVar.put(copy, typeVariable); if (isImmediateBoundPath(copy)) { immediateBoundTypeVars.add(copy); } } } /** An array list of BoundPathNodes whose equals method is a referential equality check */ @SuppressWarnings("serial") private static class BoundPath extends LinkedList<BoundPathNode> { @Override public boolean equals(final Object obj) { return this == obj; } @Override public String toString() { return PluginUtil.join(",", this); } public BoundPath copy() { final BoundPath copy = new BoundPath(); for (final BoundPathNode node : this) { copy.add(node.copy()); } return copy; } } // BoundPathNode's are a step in a "type path" that are used to private abstract static class BoundPathNode { enum Kind { Extends, Super, UpperBound, LowerBound, ArrayComponent, Intersection, Union, TypeArg } public Kind kind; public TypeKind typeKind; BoundPathNode() {} BoundPathNode(final BoundPathNode template) { this.kind = template.kind; this.typeKind = template.typeKind; } @Override public String toString() { return kind.toString(); } public AnnotatedTypeMirror next(final AnnotatedTypeMirror parent) { abortIfParentNotKind(typeKind, null, parent); return getType(parent); } public void replaceType( final AnnotatedTypeMirror parent, final AnnotatedTypeVariable replacement) { abortIfParentNotKind(typeKind, replacement, parent); setType(parent, replacement); } public abstract void setType( final AnnotatedTypeMirror parent, final AnnotatedTypeVariable replacement); public abstract AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent); public abstract BoundPathNode copy(); } private static class ExtendsNode extends BoundPathNode { ExtendsNode() { kind = Kind.Extends; typeKind = TypeKind.WILDCARD; } ExtendsNode(ExtendsNode template) { super(template); } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ((AnnotatedWildcardType) parent).setExtendsBound(replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { return ((AnnotatedWildcardType) parent).getExtendsBound(); } @Override public BoundPathNode copy() { return new ExtendsNode(this); } } private static class SuperNode extends BoundPathNode { SuperNode() { kind = Kind.Super; typeKind = TypeKind.WILDCARD; } SuperNode(SuperNode template) { super(template); } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ((AnnotatedWildcardType) parent).setSuperBound(replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { return ((AnnotatedWildcardType) parent).getSuperBound(); } @Override public BoundPathNode copy() { return new SuperNode(this); } } private static class UpperBoundNode extends BoundPathNode { UpperBoundNode() { kind = Kind.UpperBound; typeKind = TypeKind.TYPEVAR; } UpperBoundNode(UpperBoundNode template) { super(template); } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ((AnnotatedTypeVariable) parent).setUpperBound(replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { final AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; if (parentAtv.getUpperBoundField() != null) { return parentAtv.getUpperBoundField(); } return createAndSetUpperBound((AnnotatedTypeVariable) parent); } @Override public BoundPathNode copy() { return new UpperBoundNode(this); } } private static class LowerBoundNode extends BoundPathNode { LowerBoundNode() { kind = Kind.LowerBound; typeKind = TypeKind.TYPEVAR; } LowerBoundNode(LowerBoundNode template) { super(template); } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ((AnnotatedTypeVariable) parent).setLowerBound(replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { final AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; if (parentAtv.getLowerBoundField() != null) { return parentAtv.getLowerBoundField(); } //else //TODO: I think this should never happen at this point, throw exception return createAndSetLowerBound((AnnotatedTypeVariable) parent); } @Override public BoundPathNode copy() { return new LowerBoundNode(this); } } private static class ArrayComponentNode extends BoundPathNode { ArrayComponentNode() { kind = Kind.ArrayComponent; typeKind = TypeKind.ARRAY; } ArrayComponentNode(ArrayComponentNode template) { super(template); } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ((AnnotatedArrayType) parent).setComponentType(replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { return ((AnnotatedArrayType) parent).getComponentType(); } @Override public BoundPathNode copy() { return new ArrayComponentNode(this); } } private static class IntersectionNode extends BoundPathNode { public final int superIndex; IntersectionNode(int superIndex) { this.superIndex = superIndex; kind = Kind.Intersection; typeKind = TypeKind.INTERSECTION; } IntersectionNode(IntersectionNode template) { super(template); superIndex = template.superIndex; } @Override public String toString() { return super.toString() + "( superIndex=" + superIndex + " )"; } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ErrorReporter.errorAbort( "Type variables cannot be intersection bounds!\n" + "parent=" + parent + "\n" + "replacement=" + replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { final AnnotatedIntersectionType isect = (AnnotatedIntersectionType) parent; if (parent.directSuperTypes().size() <= superIndex) { ErrorReporter.errorAbort( "Invalid superIndex( " + superIndex + " ):\n" + "parent=" + parent); } return isect.directSuperTypes().get(superIndex); } @Override public BoundPathNode copy() { return new IntersectionNode(this); } } private static class UnionNode extends BoundPathNode { public final int altIndex; UnionNode(int altIndex) { this.altIndex = altIndex; kind = Kind.Union; typeKind = TypeKind.UNION; } UnionNode(UnionNode template) { super(template); altIndex = template.altIndex; } @Override public String toString() { return super.toString() + "( altIndex=" + altIndex + " )"; } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { ErrorReporter.errorAbort( "Union types cannot be intersection bounds!\n" + "parent=" + parent + "\n" + "replacement=" + replacement); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { final AnnotatedUnionType isect = (AnnotatedUnionType) parent; if (parent.directSuperTypes().size() <= altIndex) { ErrorReporter.errorAbort( "Invalid altIndex( " + altIndex + " ):\n" + "parent=" + parent); } return isect.directSuperTypes().get(altIndex); } @Override public BoundPathNode copy() { return new UnionNode(this); } } private static class TypeArgNode extends BoundPathNode { public final int argIndex; TypeArgNode(int argIndex) { this.argIndex = argIndex; kind = Kind.TypeArg; typeKind = TypeKind.DECLARED; } TypeArgNode(TypeArgNode template) { super(template); argIndex = template.argIndex; } @Override public String toString() { return super.toString() + "( argIndex=" + argIndex + " )"; } @Override public void setType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { abortIfParentNotKind(TypeKind.DECLARED, replacement, parent); final AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; List<AnnotatedTypeMirror> typeArgs = new ArrayList<>(parentAdt.getTypeArguments()); if (argIndex >= typeArgs.size()) { ErrorReporter.errorAbort( "Invalid type arg index!\n" + "parent=" + parent + "\n" + "replacement=" + replacement + "\n" + "argIndex=" + argIndex); } typeArgs.add(argIndex, replacement); typeArgs.remove(argIndex + 1); parentAdt.setTypeArguments(typeArgs); } @Override public AnnotatedTypeMirror getType(final AnnotatedTypeMirror parent) { final AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; List<AnnotatedTypeMirror> typeArgs = parentAdt.getTypeArguments(); if (argIndex >= typeArgs.size()) { ErrorReporter.errorAbort( "Invalid type arg index!\n" + "parent=" + parent + "\n" + "argIndex=" + argIndex); } return typeArgs.get(argIndex); } @Override public BoundPathNode copy() { return new TypeArgNode(this); } } public static void abortIfParentNotKind( final TypeKind typeKind, final AnnotatedTypeVariable type, final AnnotatedTypeMirror parent) { if (parent.getKind().equals(typeKind)) { return; } final StringBuilder builder = new StringBuilder(); builder.append("Unexpected parent kind:\n"); builder.append("parent="); builder.append(parent); builder.append("\n"); builder.append("replacement="); builder.append(type); builder.append("\n"); builder.append("expected="); builder.append(typeKind); builder.append("\n"); ErrorReporter.errorAbort(builder.toString()); } }