package annotator.find; import java.util.AbstractSet; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import javax.lang.model.element.Name; import javax.lang.model.type.TypeKind; import annotations.el.InnerTypeLocation; import annotations.io.ASTIndex; import annotations.io.ASTPath; import annotations.io.ASTRecord; import type.*; import com.sun.source.tree.AnnotatedTypeTree; import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.PrimitiveTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TreeVisitor; import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.WildcardTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind; import com.sun.tools.javac.code.TypeTag; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.util.Pair; /** * @author dbro * * An indexed collection (though not {@link java.util.Collection}, only * {@link java.lang.Iterable}) of {@link Insertion}s with methods to * select those specified for a given class or for an outer class along * with its local classes. This class is especially useful when a * single JAIF stores annotations for many source files, as it reduces * the number of insertions to be considered for any AST node. * * The class now serves a second purpose, which should probably be * separated out (according to OO dogma, at least): It attaches * {@link annotations.io.ASTPath}-based inner type {@link Insertion}s to * a {@link TypedInsertion} on the outer type if one exists (see * {@link #organizeTypedInsertions(CompilationUnitTree, String, Collection)}. * Since getting these insertions right depends on this organization, * this class is now essential for correctness, not merely for * performance. */ public class Insertions implements Iterable { private static int kindLevel(Insertion i) { // ordered so insertion that depends on another gets inserted after other switch (i.getKind()) { case CONSTRUCTOR: return 3; case NEW: case RECEIVER: return 2; case CAST: return 1; // case ANNOTATION: // case CLOSE_PARENTHESIS: default: return 0; } } private static final Comparator byASTRecord = new Comparator() { @Override public int compare(Insertion o1, Insertion o2) { Criteria c1 = o1.getCriteria(); Criteria c2 = o2.getCriteria(); ASTPath p1 = c1.getASTPath(); ASTPath p2 = c2.getASTPath(); ASTRecord r1 = new ASTRecord(null, c1.getClassName(), c1.getMethodName(), c1.getFieldName(), p1 == null ? ASTPath.empty() : p1); ASTRecord r2 = new ASTRecord(null, c2.getClassName(), c2.getMethodName(), c2.getFieldName(), p2 == null ? ASTPath.empty() : p2); int c = r1.compareTo(r2); if (c == 0) { // c = o1.getKind().compareTo(o2.getKind()); c = Integer.compare(kindLevel(o2), kindLevel(o1)); // descending if (c == 0) { c = o1.toString().compareTo(o2.toString()); } } return c; } }; // store indexes insertions by (qualified) outer class name and inner // class path (if any) private Map>> store; private int size; private Pair nameSplit(String name) { int i = name.indexOf('$'); // FIXME: don't split on '$' in source return i < 0 ? Pair.of(name, "") : Pair.of(name.substring(0, i), name.substring(i)); } public Insertions() { store = new HashMap>>(); size = 0; } // auxiliary for following two methods private void forClass(CompilationUnitTree cut, String qualifiedClassName, Set result) { Pair pair = nameSplit(qualifiedClassName); Map> map = store.get(pair.fst); if (map != null) { Set set = new TreeSet(byASTRecord); set.addAll(map.get(pair.snd)); if (set != null) { set = organizeTypedInsertions(cut, qualifiedClassName, set); result.addAll(set); } } } /** * Selects {@link Insertion}s relevant to a given class. * * @param cut the current compilation unit * @param qualifiedClassName the fully qualified class name * @return {@link java.util.Set} of {@link Insertion}s with an * {@link InClassCriterion} for the given class */ public Set forClass(CompilationUnitTree cut, String qualifiedClassName) { Set set = new LinkedHashSet(); forClass(cut, qualifiedClassName, set); return set; } /** * Selects {@link Insertion}s relevant to a given outer class and its * local classes. * * @param cut the current compilation unit * @param qualifiedOuterClassName the fully qualified outer class name * @return {@link java.util.Set} of {@link Insertion}s with an * {@link InClassCriterion} for the given outer class or one * of its local classes */ public Set forOuterClass(CompilationUnitTree cut, String qualifiedOuterClassName) { Map> map = store.get(qualifiedOuterClassName); if (map == null || map.isEmpty()) { return Collections.emptySet(); } else { Set set = new LinkedHashSet(); for (String key : map.keySet()) { String qualifiedClassName = qualifiedOuterClassName + key; forClass(cut, qualifiedClassName, set); } return set; } } /** * Add an {@link Insertion} to this collection. */ public void add(Insertion ins) { InClassCriterion icc = ins.getCriteria().getInClass(); String k1 = ""; String k2 = ""; Map> map; Set set; if (icc != null) { Pair triple = nameSplit(icc.className); k1 = triple.fst; k2 = triple.snd; } map = store.get(k1); if (map == null) { map = new HashMap>(); store.put(k1, map); } set = map.get(k2); if (set == null) { set = new LinkedHashSet(); map.put(k2, set); } size -= set.size(); set.add(ins); size += set.size(); } /** * Add all {@link Insertion}s in the given * {@link java.util.Collection} to this collection. */ public void addAll(Collection c) { for (Insertion ins : c) { add(ins); } } /** * Returns the number of {@link Insertion}s in this collection. */ public int size() { return size; } @Override public Iterator iterator() { return new Iterator() { private Iterator>> miter = store.values().iterator(); private Iterator> siter = Collections.>emptySet().iterator(); private Iterator iiter = Collections.emptySet().iterator(); @Override public boolean hasNext() { if (iiter.hasNext()) { return true; } while (siter.hasNext()) { iiter = siter.next().iterator(); if (iiter.hasNext()) { return true; } } while (miter.hasNext()) { siter = miter.next().values().iterator(); while (siter.hasNext()) { iiter = siter.next().iterator(); if (iiter.hasNext()) { return true; } } } return false; } @Override public Insertion next() { if (hasNext()) { return iiter.next(); } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Returns a {@link java.util.List} containing all {@link Insertion}s * in this collection. */ public List toList() { List list = new ArrayList(size); for (Insertion ins : this) { list.add(ins); } return null; } /* * This method detects inner type relationships among ASTPath-based * insertion specifications and organizes the insertions accordingly. * This step is necessary because 1) insertion proceeds from the end to * the beginning of the source and 2) the insertion location does not * always exist prior to the top-level type insertion. */ private Set organizeTypedInsertions(CompilationUnitTree cut, String className, Collection insertions) { ASTRecordMap map = new ASTRecordMap(); Set organized = new LinkedHashSet(); Set unorganized = new LinkedHashSet(); List list = new ArrayList(); // First divide the insertions into three buckets: TypedInsertions // on outer types (map), ASTPath-based insertions on local types // (unorganized -- built as list and then sorted, since building as // a set spuriously removes "duplicates" according to the // comparator), and everything else (organized -- where all // eventually land). for (Insertion ins : insertions) { if (ins.getInserted()) { continue; } Criteria criteria = ins.getCriteria(); GenericArrayLocationCriterion galc = criteria.getGenericArrayLocation(); ASTPath p = criteria.getASTPath(); if (p == null || p.isEmpty() || galc != null && !galc.getLocation().isEmpty() || ins instanceof CastInsertion || ins instanceof CloseParenthesisInsertion) { organized.add(ins); } else { ASTRecord rec = new ASTRecord(cut, criteria.getClassName(), criteria.getMethodName(), criteria.getFieldName(), p); ASTPath.ASTEntry entry = rec.astPath.get(-1); Tree node; if (entry.getTreeKind() == Tree.Kind.NEW_ARRAY && entry.childSelectorIs(ASTPath.TYPE) && entry.getArgument() == 0) { ASTPath temp = rec.astPath.getParentPath(); node = ASTIndex.getNode(cut, rec.replacePath(temp)); node = node instanceof JCTree.JCNewArray ? TypeTree.fromType(((JCTree.JCNewArray) node).type) : null; } else { node = ASTIndex.getNode(cut, rec); } if (ins instanceof TypedInsertion) { TypedInsertion tins = map.get(rec); if (ins instanceof NewInsertion) { NewInsertion nins = (NewInsertion) ins; if (entry.getTreeKind() == Tree.Kind.NEW_ARRAY && entry.childSelectorIs(ASTPath.TYPE)) { int a = entry.getArgument(); List loc0 = new ArrayList(a); ASTRecord rec0 = null; if (a == 0) { rec0 = rec.replacePath(p.getParentPath()); Tree t = ASTIndex.getNode(cut, rec0); if (t == null || t.toString().startsWith("{")) { rec0 = null; } else { rec = rec0; rec0 = rec.extend(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0); } } else if (node != null && !nins.getInnerTypeInsertions().isEmpty()) { if (node.getKind() == Tree.Kind.IDENTIFIER) { node = ASTIndex.getNode(cut, rec.replacePath(p.getParentPath())); } if ((node.getKind() == Tree.Kind.NEW_ARRAY || node.getKind() == Tree.Kind.ARRAY_TYPE) && !node.toString().startsWith("{")) { rec = rec.replacePath(p.getParentPath()); Collections.fill(loc0, TypePathEntry.ARRAY); // irec = rec; // if (node.getKind() == Tree.Kind.NEW_ARRAY) { rec0 = rec.extend(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0); // } } } if (rec0 != null) { for (Insertion inner : nins.getInnerTypeInsertions()) { Criteria icriteria = inner.getCriteria(); GenericArrayLocationCriterion igalc = icriteria.getGenericArrayLocation(); if (igalc != null) { ASTRecord rec1; int b = igalc.getLocation().size(); List loc = new ArrayList(a + b); loc.addAll(loc0); loc.addAll(igalc.getLocation()); rec1 = extendToInnerType(rec0, loc, node); icriteria.add(new GenericArrayLocationCriterion()); icriteria.add(new ASTPathCriterion(rec1.astPath)); inner.setInserted(false); organized.add(inner); } } nins.getInnerTypeInsertions().clear(); } } } if (tins == null) { map.put(rec, (TypedInsertion) ins); } else if (tins.getType().equals(((TypedInsertion) ins).getType())) { mergeTypedInsertions(tins, (TypedInsertion) ins); } } else { int d = newArrayInnerTypeDepth(p); if (d > 0) { ASTPath temp = p; while (!temp.isEmpty() && (node == null || node.getKind() != Tree.Kind.NEW_ARRAY)) { // TODO: avoid repeating work of newArrayInnerTypeDepth() temp = temp.getParentPath(); node = ASTIndex.getNode(cut, rec.replacePath(temp)); } if (node == null) { // TODO: ??? } temp = temp.extend( new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0)); if (node.toString().startsWith("{")) { TypedInsertion tins = map.get(rec.replacePath(temp)); if (tins == null) { // TODO } else { tins.getInnerTypeInsertions().add(ins); ins.setInserted(true); } } else { List dims = ((NewArrayTree) node).getDimensions(); ASTRecord irec = rec.replacePath(p.getParentPath()) .extend(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0); GenericArrayLocationCriterion igalc = criteria.getGenericArrayLocation(); for (int i = 0 ; i < d; i++) { irec = irec.extend(Tree.Kind.ARRAY_TYPE, ASTPath.TYPE); } if (igalc != null) { List loc = igalc.getLocation(); if (!loc.isEmpty()) { try { Tree dim = dims.get(d-1); irec = extendToInnerType(irec, loc, dim); criteria.add(new ASTPathCriterion(irec.astPath)); criteria.add(new GenericArrayLocationCriterion()); } catch (RuntimeException e) {} } } } } list.add(ins); } } } // if (map.isEmpty()) { // organized.addAll(unorganized); // return organized; // } Collections.sort(list, byASTRecord); unorganized.addAll(list); // Each Insertion in unorganized gets attached to a TypedInsertion // in map if possible; otherwise, it gets dumped into organized. for (Insertion ins : unorganized) { Criteria criteria = ins.getCriteria(); String methodName = criteria.getMethodName(); String fieldName = criteria.getFieldName(); ASTPath ap1 = criteria.getASTPath(); List tpes = new ArrayList(); if (ap1 == null) { // || methodName == null && fieldName == null) organized.add(ins); continue; } // First find the relevant "top-level" insertion, if any. // ap0: path to top-level type; ap1: path to local type ASTRecord rec; Tree.Kind kind; Deque astack = new ArrayDeque(ap1.size()); ASTPath ap0 = ap1; do { astack.push(ap0); ap0 = ap0.getParentPath(); } while (!ap0.isEmpty()); do { ap0 = astack.pop(); kind = ap0.get(-1).getTreeKind(); rec = new ASTRecord(cut, className, methodName, fieldName, ap0); } while (!(astack.isEmpty() || map.containsKey(rec))); TypedInsertion tins = map.get(rec); TreePath path = ASTIndex.getTreePath(cut, rec); Tree node = path == null ? null : path.getLeaf(); if (node == null && ap0.isEmpty()) { organized.add(ins); continue; } // Try to create a top-level insertion if none exists (e.g., if // there is an insertion for NewArray.type 1 but not for 0). if (tins == null) { GenericArrayLocationCriterion galc = criteria.getGenericArrayLocation(); if (node == null) { // TODO: figure out from rec? organized.add(ins); continue; } else { Tree t = path.getLeaf(); switch (t.getKind()) { case NEW_ARRAY: int d = 0; ASTPath.ASTEntry e = ap1.get(-1); List loc = null; List inners = new ArrayList(); Type type = TypeTree.conv(((JCTree.JCNewArray) t).type); if (e.getTreeKind() == Tree.Kind.NEW_ARRAY) { d += e.getArgument(); } if (galc != null) { loc = galc.getLocation(); int n = loc.size(); while (--n >= 0 && loc.get(n).tag == TypePathEntryKind.ARRAY) { ++d; } loc = n < 0 ? null : loc.subList(0, ++n); } criteria.add(new ASTPathCriterion( rec.astPath.getParentPath().extendNewArray(d))); criteria.add(loc == null || loc.isEmpty() ? new GenericArrayLocationCriterion() : new GenericArrayLocationCriterion( new InnerTypeLocation(loc))); inners.add(ins); tins = new NewInsertion(type, criteria, inners); tins.setInserted(true); map.put(rec, tins); break; default: break; } path = path.getParentPath(); } } // The sought node may or may not be found in the tree; if not, it // may need to be created later. Use whatever part of the path // exists already to distinguish MEMBER_SELECT nodes that indicate // qualifiers from those that indicate local types. Assume any // MEMBER_SELECTs in the AST path that don't correspond to // existing nodes are part of a type use. if (node == null) { ASTPath ap = ap0; if (!ap.isEmpty()) { do { ap = ap.getParentPath(); node = ASTIndex.getNode(cut, rec.replacePath(ap)); } while (node == null && !ap.isEmpty()); } if (node == null) { organized.add(ins); continue; } // find actual type ClassSymbol csym = null; switch (tins.getKind()) { case CONSTRUCTOR: if (node instanceof JCTree.JCMethodDecl) { MethodSymbol msym = ((JCTree.JCMethodDecl) node).sym; csym = (ClassSymbol) msym.owner; node = TypeTree.fromType(csym.type); break; } else if (node instanceof JCTree.JCClassDecl) { csym = ((JCTree.JCClassDecl) node).sym; if (csym.owner instanceof ClassSymbol) { csym = (ClassSymbol) csym.owner; node = TypeTree.fromType(csym.type); break; } } throw new RuntimeException(); case NEW: if (node instanceof JCTree.JCNewArray) { if (node.toString().startsWith("{")) { node = TypeTree.fromType(((JCTree.JCNewArray) node).type); break; } else { organized.add(ins); continue; } } throw new RuntimeException(); case RECEIVER: if (node instanceof JCTree.JCMethodDecl) { JCTree.JCMethodDecl jmd = (JCTree.JCMethodDecl) node; csym = (ClassSymbol) jmd.sym.owner; if ("".equals(jmd.name.toString())) { csym = (ClassSymbol) csym.owner; } } else if (node instanceof JCTree.JCClassDecl) { csym = ((JCTree.JCClassDecl) node).sym; } if (csym != null) { node = TypeTree.fromType(csym.type); break; } throw new RuntimeException(); default: throw new RuntimeException(); } } /* * Inner types require special consideration due to the * structural differences between an AST that represents a type * (subclass of com.sun.source.Tree) and the type's logical * representation (subclass of type.Type). The differences are * most prominent in the case of a type with a parameterized * local type. For example, the AST for A.B.C looks like * this: * * ParameterizedType * / \ * MemberSelect Identifier * / \ | * MemberSelect (Name) D * / \ | * Identifier (Name) C * | | * A B * * (Technically, the Names are not AST nodes but rather * attributes of their parent MemberSelect nodes.) The logical * representation seems more intuitive: * * DeclaredType * / | \ * Name Params Inner * | | | * A - DeclaredType * / | \ * Name Params Inner * | | | * B - DeclaredType * / | \ * Name Params Inner * | | | * C D - * * The opposing "chirality" of local type nesting means that the * usual recursive descent strategy doesn't work for finding a * logical type path in an AST; in effect, local types have to * be "turned inside-out". * * Worse yet, the actual tree structure may not exist in the tree! * It is possible to recover the actual type from the symbol * table, but the methods to create AST nodes are not visible * here. Hence, the conversion relies on custom implementations * of the interfaces in com.sun.source.tree.Tree, which are * defined in the local class TypeTree. */ int i = ap0.size(); int n = ap1.size(); int actualDepth = 0; // inner type levels seen int expectedDepth = 0; // inner type levels anticipated // skip any declaration nodes while (i < n) { ASTPath.ASTEntry entry = ap1.get(i); kind = entry.getTreeKind(); if (kind != Tree.Kind.METHOD && kind != Tree.Kind.VARIABLE) { break; } ++i; } // now build up the type path in JVM's format while (i < n) { ASTPath.ASTEntry entry = ap1.get(i); rec = rec.extend(entry); kind = entry.getTreeKind(); while (node.getKind() == Tree.Kind.ANNOTATED_TYPE) { // skip node = ((AnnotatedTypeTree) node).getUnderlyingType(); } if (expectedDepth == 0) { expectedDepth = localDepth(node); } switch (kind) { case ARRAY_TYPE: if (expectedDepth == 0 && node.getKind() == kind) { node = ((ArrayTypeTree) node).getType(); while (--actualDepth >= 0) { tpes.add(TypePathEntry.INNER_TYPE); } tpes.add(TypePathEntry.ARRAY); break; } throw new RuntimeException(); case MEMBER_SELECT: if (--expectedDepth >= 0) { // otherwise, shouldn't have MEMBER_SELECT node = ((MemberSelectTree) node).getExpression(); ++actualDepth; break; } throw new RuntimeException(); case NEW_ARRAY: assert tpes.isEmpty(); ap0 = ap0.add(new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0)); if (expectedDepth == 0 && node.getKind() == kind) { if (node instanceof JCTree.JCNewArray) { int arg = entry.getArgument(); if (arg > 0) { node = ((JCTree.JCNewArray) node).elemtype; tpes.add(TypePathEntry.ARRAY); while (--arg > 0 && node instanceof JCTree.JCArrayTypeTree) { node = ((JCTree.JCArrayTypeTree) node).elemtype; tpes.add(TypePathEntry.ARRAY); } if (arg > 0) { throw new RuntimeException(); } } else { node = TypeTree.fromType(((JCTree.JCNewArray) node).type); } } else { throw new RuntimeException("NYI"); // TODO } break; } throw new RuntimeException(); case PARAMETERIZED_TYPE: if (node.getKind() == kind) { ParameterizedTypeTree ptt = (ParameterizedTypeTree) node; if (entry.childSelectorIs(ASTPath.TYPE)) { node = ptt.getType(); break; // ParameterizedType.type is "transparent" wrt type path } else if (expectedDepth == 0 && entry.childSelectorIs(ASTPath.TYPE_ARGUMENT)) { List typeArgs = ptt.getTypeArguments(); int j = entry.getArgument(); if (j >= 0 && j < typeArgs.size()) { // make sure any inner types are accounted for actualDepth = 0; expectedDepth = localDepth(ptt.getType()); while (--expectedDepth >= 0) { tpes.add(TypePathEntry.INNER_TYPE); } node = typeArgs.get(j); tpes.add( new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, j)); break; } } } throw new RuntimeException(); case UNBOUNDED_WILDCARD: if (ASTPath.isWildcard(node.getKind())) { if (expectedDepth == 0 && (i < 1 || ap1.get(i-1).getTreeKind() != Tree.Kind.INSTANCE_OF) && (i < 2 || ap1.get(i-2).getTreeKind() != Tree.Kind.ARRAY_TYPE)) { while (--actualDepth >= 0) { tpes.add(TypePathEntry.INNER_TYPE); } tpes.add(TypePathEntry.WILDCARD); break; } } throw new RuntimeException(); default: node = ASTIndex.getNode(cut, rec); break; } ++i; } while (--actualDepth >= 0) { tpes.add(TypePathEntry.INNER_TYPE); } organized.add(ins); if (tpes.isEmpty()) { // assert ap1.equals(ap0) && !map.containsKey(ap0); // organized.add(ins); // map.put(rec, (TypedInsertion) ins); } else { criteria.add(new ASTPathCriterion(ap0)); criteria.add(new GenericArrayLocationCriterion( new InnerTypeLocation(tpes))); tins.getInnerTypeInsertions().add(ins); } } organized.addAll(map.values()); return organized; } private int newArrayInnerTypeDepth(ASTPath path) { int d = 0; if (path != null) { while (!path.isEmpty()) { ASTPath.ASTEntry entry = path.get(-1); switch (entry.getTreeKind()) { case ANNOTATED_TYPE: case MEMBER_SELECT: case PARAMETERIZED_TYPE: case UNBOUNDED_WILDCARD: d = 0; break; case ARRAY_TYPE: ++d; break; case NEW_ARRAY: if (entry.childSelectorIs(ASTPath.TYPE) && entry.hasArgument()) { d += entry.getArgument(); } return d; default: return 0; } path = path.getParentPath(); } } return 0; } /** * Find an {@link ASTRecord} for the tree corresponding to a nested * type of the type (use) to which the given record corresponds. * * @param rec record of (outer) type AST to be annotated * @param loc inner type path * @return record that locates the (nested) type in the source */ private ASTRecord extendToInnerType(ASTRecord rec, List loc) { ASTRecord r = rec; Iterator iter = loc.iterator(); int depth = 0; while (iter.hasNext()) { TypePathEntry tpe = iter.next(); switch (tpe.tag) { case ARRAY: while (depth-- > 0) { r = r.extend(Tree.Kind.MEMBER_SELECT, ASTPath.EXPRESSION); } r = r.extend(Tree.Kind.ARRAY_TYPE, ASTPath.TYPE); break; case INNER_TYPE: ++depth; break; case TYPE_ARGUMENT: depth = 0; r = r.extend(Tree.Kind.PARAMETERIZED_TYPE, ASTPath.TYPE_ARGUMENT, tpe.arg); break; case WILDCARD: while (depth-- > 0) { r = r.extend(Tree.Kind.MEMBER_SELECT, ASTPath.EXPRESSION); } r = r.extend(Tree.Kind.UNBOUNDED_WILDCARD, ASTPath.BOUND); break; default: throw new RuntimeException(); } } while (depth-- > 0) { r = r.extend(Tree.Kind.MEMBER_SELECT, ASTPath.EXPRESSION); } return r; } /** * Find an {@link ASTRecord} for the tree corresponding to a nested * type of the type (use) to which the given tree and record * correspond. * * @param rec record that locates {@code node} in the source * @param loc inner type path * @param node starting point for inner type path * @return record that locates the nested type in the source */ private ASTRecord extendToInnerType(ASTRecord rec, List loc, Tree node) { ASTRecord r = rec; Tree t = node; Iterator iter = loc.iterator(); TypePathEntry tpe = iter.next(); outer: while (true) { int d = localDepth(node); switch (t.getKind()) { case ANNOTATED_TYPE: r = r.extend(Tree.Kind.ANNOTATED_TYPE, ASTPath.TYPE); t = ((JCTree.JCAnnotatedType) t).getUnderlyingType(); break; case ARRAY_TYPE: if (d == 0 && tpe.tag == TypePathEntryKind.ARRAY) { int a = 0; if (!r.astPath.isEmpty()) { ASTPath.ASTEntry e = r.astPath.get(-1); if (e.getTreeKind() == Tree.Kind.NEW_ARRAY && e.childSelectorIs(ASTPath.TYPE)) { a = 1 + e.getArgument(); } } r = a > 0 ? r.replacePath(r.astPath.getParentPath()) .extend(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, a) : r.extend(Tree.Kind.ARRAY_TYPE, ASTPath.TYPE); t = ((ArrayTypeTree) t).getType(); break; } throw new RuntimeException(); case MEMBER_SELECT: if (d > 0 && tpe.tag == TypePathEntryKind.INNER_TYPE) { Tree temp = t; do { temp = ((JCTree.JCFieldAccess) temp).getExpression(); if (!iter.hasNext()) { do { r = r.extend(Tree.Kind.MEMBER_SELECT, ASTPath.EXPRESSION); } while (--d > 0); return r; } tpe = iter.next(); if (--d == 0) { continue outer; // avoid next() at end of loop } } while (tpe.tag == TypePathEntryKind.INNER_TYPE); } throw new RuntimeException(); case NEW_ARRAY: if (d == 0) { if (!r.astPath.isEmpty()) { ASTPath.ASTEntry e = r.astPath.get(-1); if (e.getTreeKind() == Tree.Kind.NEW_ARRAY) { int a = 0; while (tpe.tag == TypePathEntryKind.ARRAY) { ++a; if (!iter.hasNext()) { break; } tpe = iter.next(); } r = r.replacePath(r.astPath.getParentPath()) .extend(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, a); break; } } r = r.extend(Tree.Kind.ARRAY_TYPE, ASTPath.TYPE); t = ((JCTree.JCArrayTypeTree) t).getType(); break; } throw new RuntimeException(); case PARAMETERIZED_TYPE: if (d == 0 && tpe.tag == TypePathEntryKind.TYPE_ARGUMENT) { r = r.extend(Tree.Kind.PARAMETERIZED_TYPE, ASTPath.TYPE_ARGUMENT, tpe.arg); t = ((JCTree.JCTypeApply) t).getTypeArguments().get(tpe.arg); break; } else if (d > 0 && tpe.tag == TypePathEntryKind.INNER_TYPE) { Tree temp = ((JCTree.JCTypeApply) t).getType(); r = r.extend(Tree.Kind.PARAMETERIZED_TYPE, ASTPath.TYPE); t = temp; do { temp = ((JCTree.JCFieldAccess) temp).getExpression(); if (!iter.hasNext()) { do { r = r.extend(Tree.Kind.MEMBER_SELECT, ASTPath.EXPRESSION); } while (--d > 0); return r; } tpe = iter.next(); if (--d == 0) { continue outer; // avoid next() at end of loop } } while (tpe.tag == TypePathEntryKind.INNER_TYPE); } throw new RuntimeException(); case EXTENDS_WILDCARD: case SUPER_WILDCARD: case UNBOUNDED_WILDCARD: if (tpe.tag == TypePathEntryKind.WILDCARD) { t = ((JCTree.JCWildcard) t).getBound(); break; } throw new RuntimeException(); default: if (iter.hasNext()) { throw new RuntimeException(); } } if (!iter.hasNext()) { return r; } tpe = iter.next(); } } // merge annotations, assuming types are structurally identical private void mergeTypedInsertions(TypedInsertion ins0, TypedInsertion ins1) { mergeTypes(ins0.getType(), ins1.getType()); } private void mergeTypes(Type t0, Type t1) { if (t0 == t1) { return; } switch (t0.getKind()) { case ARRAY: { ArrayType at0 = (ArrayType) t0; ArrayType at1 = (ArrayType) t1; mergeTypes(at0.getComponentType(), at1.getComponentType()); return; } case BOUNDED: { BoundedType bt0 = (BoundedType) t0; BoundedType bt1 = (BoundedType) t1; if (bt0.getBoundKind() != bt1.getBoundKind()) { break; } mergeTypes(bt0.getBound(), bt1.getBound()); mergeTypes(bt0.getName(), bt1.getName()); return; } case DECLARED: { DeclaredType dt0 = (DeclaredType) t0; DeclaredType dt1 = (DeclaredType) t1; List tps0 = dt0.getTypeParameters(); List tps1 = dt1.getTypeParameters(); int n = tps0.size(); if (tps1.size() != n) { break; } mergeTypes(dt0.getInnerType(), dt1.getInnerType()); for (String anno : dt1.getAnnotations()) { if (!dt0.getAnnotations().contains(anno)) { dt0.addAnnotation(anno); } } for (int i = 0; i < n; i++) { mergeTypes(tps0.get(i), tps1.get(i)); } return; } } throw new RuntimeException(); } // Returns the depth of the innermost local type of a type AST. private int localDepth(Tree node) { Tree t = node; int n = 0; loop: while (t != null) { switch (t.getKind()) { case ANNOTATED_TYPE: t = ((AnnotatedTypeTree) t).getUnderlyingType(); break; case MEMBER_SELECT: if (t instanceof JCTree.JCFieldAccess) { JCTree.JCFieldAccess jfa = (JCTree.JCFieldAccess) t; if (jfa.sym.kind == Kinds.PCK) { t = jfa.getExpression(); continue; } } t = ((MemberSelectTree) t).getExpression(); ++n; break; default: break loop; } } return n; } // Provides an additional level of indexing. class ASTRecordMap implements Map { Map> back; ASTRecordMap() { back = new HashMap>(); } private SortedMap getMap(ASTRecord rec) { ASTRecord key = rec.replacePath(ASTPath.empty()); SortedMap map = back.get(key); if (map == null) { map = new TreeMap(); back.put(key, map); } return map; } @Override public int size() { int n = 0; for (SortedMap map : back.values()) { n += map.size(); } return n; } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean containsKey(Object key) { ASTRecord rec = (ASTRecord) key; SortedMap m = getMap(rec); return m != null && m.containsKey(rec.astPath); } @Override public boolean containsValue(Object value) { @SuppressWarnings("unchecked") E e = (E) value; for (SortedMap map : back.values()) { if (map.containsValue(e)) { return true; } } return false; } @Override public E get(Object key) { ASTRecord rec = (ASTRecord) key; SortedMap map = getMap(rec); return map == null ? null : map.get(rec.astPath); } @Override public E put(ASTRecord key, E value) { ASTRecord rec = key; SortedMap map = getMap(rec); return map == null ? null : map.put(rec.astPath, value); } @Override public E remove(Object key) { ASTRecord rec = (ASTRecord) key; SortedMap map = getMap(rec); return map == null ? null : map.remove(rec.astPath); } @Override public void putAll(Map m) { for (Map.Entry entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public void clear() { back.clear(); } @Override public Set keySet() { return back.keySet(); } @Override public Collection values() { Set ret = new LinkedHashSet(); for (SortedMap m : back.values()) { ret.addAll(m.values()); } return ret; } @Override public Set> entrySet() { final int size = size(); return new AbstractSet>() { @Override public Iterator> iterator() { return new Iterator>() { Iterator>> iter0 = back.entrySet().iterator(); Iterator> iter1 = Collections.>emptyIterator(); ASTRecord rec = null; @Override public boolean hasNext() { if (iter1.hasNext()) { return true; } while (iter0.hasNext()) { Map.Entry> entry = iter0.next(); rec = entry.getKey(); iter1 = entry.getValue().entrySet().iterator(); if (iter1.hasNext()) { return true; } } iter1 = Collections.>emptyIterator(); return false; } @Override public Map.Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } final Map.Entry e0 = iter1.next(); return new Map.Entry() { final ASTRecord key = rec.replacePath(e0.getKey()); final E val = e0.getValue(); @Override public ASTRecord getKey() { return key; } @Override public E getValue() { return val; } @Override public E setValue(E value) { throw new UnsupportedOperationException(); } }; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return size; } }; } } // Simple AST implementation used only in determining type paths. static abstract class TypeTree implements ExpressionTree { private static Map primTags = new HashMap(); { primTags.put("byte", TypeTag.BYTE); primTags.put("char", TypeTag.CHAR); primTags.put("short", TypeTag.SHORT); primTags.put("long", TypeTag.LONG); primTags.put("float", TypeTag.FLOAT); primTags.put("int", TypeTag.INT); primTags.put("double", TypeTag.DOUBLE); primTags.put("boolean", TypeTag.BOOLEAN); } static TypeTree fromJCTree(JCTree jt) { if (jt != null) { Kind kind = jt.getKind(); switch (kind) { case ANNOTATED_TYPE: return fromJCTree( ((JCTree.JCAnnotatedType) jt).getUnderlyingType()); case IDENTIFIER: return new IdenT( ((JCTree.JCIdent) jt).sym.getSimpleName().toString()); case ARRAY_TYPE: return new ArrT( fromJCTree(((JCTree.JCArrayTypeTree) jt).getType())); case MEMBER_SELECT: return new LocT( fromJCTree(((JCTree.JCFieldAccess) jt).getExpression()), ((JCTree.JCFieldAccess) jt).getIdentifier()); case EXTENDS_WILDCARD: case SUPER_WILDCARD: return new WildT(kind, fromJCTree(((JCTree.JCWildcard) jt).getBound())); case UNBOUNDED_WILDCARD: return new WildT(); case PARAMETERIZED_TYPE: com.sun.tools.javac.util.List typeArgs = ((JCTree.JCTypeApply) jt).getTypeArguments(); List args = new ArrayList(typeArgs.size()); for (JCTree.JCExpression typeArg : typeArgs) { args.add(fromJCTree(typeArg)); } return new ParT( fromJCTree(((JCTree.JCTypeApply) jt).getType()), args); default: break; } } return null; } static TypeTree fromType(final Type type) { switch (type.getKind()) { case ARRAY: final ArrayType atype = (ArrayType) type; final TypeTree componentType = fromType(atype.getComponentType()); return new ArrT(componentType); case BOUNDED: final BoundedType btype = (BoundedType) type; final BoundedType.BoundKind bk = btype.getBoundKind(); final String bname = btype.getName().getName(); final TypeTree bound = fromType(btype.getBound()); return new Param(bname, bk, bound); case DECLARED: final DeclaredType dtype = (DeclaredType) type; if (dtype.isWildcard()) { return new WildT(); } else { final String dname = dtype.getName(); TypeTag typeTag = primTags.get(dname); if (typeTag == null) { final TypeTree base = new IdenT(dname); TypeTree ret = base; List params = dtype.getTypeParameters(); DeclaredType inner = dtype.getInnerType(); if (!params.isEmpty()) { final List typeArgs = new ArrayList(params.size()); for (Type t : params) { typeArgs.add(fromType(t)); } ret = new ParT(base, typeArgs); } return inner == null ? ret : meld(fromType(inner), ret); } else { final TypeKind typeKind = typeTag.getPrimitiveTypeKind(); return new PrimT(typeKind); } } default: throw new RuntimeException("unknown type kind " + type.getKind()); } } static TypeTree fromType(final com.sun.tools.javac.code.Type type) { return fromType(conv(type)); } /** * @param jtype * @return */ static Type conv(final com.sun.tools.javac.code.Type jtype) { Type type = null; DeclaredType d; com.sun.tools.javac.code.Type t; switch (jtype.getKind()) { case ARRAY: t = ((com.sun.tools.javac.code.Type.ArrayType) jtype).elemtype; type = new ArrayType(conv(t)); break; case DECLARED: t = jtype; d = null; do { DeclaredType d0 = d; com.sun.tools.javac.code.Type.ClassType ct = (com.sun.tools.javac.code.Type.ClassType) t; d = new DeclaredType(ct.tsym.name.toString()); d.setInnerType(d0); d0 = d; for (com.sun.tools.javac.code.Type a : ct.getTypeArguments()) { d.addTypeParameter(conv(a)); } t = ct.getEnclosingType(); } while (t.getKind() == TypeKind.DECLARED); type = d; break; case WILDCARD: BoundedType.BoundKind k; t = ((com.sun.tools.javac.code.Type.WildcardType) jtype).bound; switch (((com.sun.tools.javac.code.Type.WildcardType) jtype).kind) { case EXTENDS: k = BoundedType.BoundKind.EXTENDS; break; case SUPER: k = BoundedType.BoundKind.SUPER; break; case UNBOUND: k = null; type = new DeclaredType("?"); break; default: throw new RuntimeException(); } if (k != null) { d = new DeclaredType(jtype.tsym.name.toString()); type = new BoundedType(d, k, (DeclaredType) conv(t)); } break; case TYPEVAR: t = ((com.sun.tools.javac.code.Type.TypeVar) jtype).getUpperBound(); type = conv(t); if (type.getKind() == Type.Kind.DECLARED) { type = new BoundedType(new DeclaredType(jtype.tsym.name.toString()), BoundedType.BoundKind.EXTENDS, (DeclaredType) type); } // otherwise previous conv should have been here already break; case INTERSECTION: t = jtype.tsym.erasure_field; // ??? type = new DeclaredType(t.tsym.name.toString()); break; case UNION: // TODO break; case BOOLEAN: case BYTE: case CHAR: case DOUBLE: case LONG: case SHORT: case FLOAT: case INT: type = new DeclaredType(jtype.tsym.name.toString()); break; // case ERROR: // case EXECUTABLE: // case NONE: // case NULL: // case OTHER: // case PACKAGE: // case VOID: default: break; } return type; } private static TypeTree meld(final TypeTree t0, final TypeTree t1) { switch (t0.getKind()) { case IDENTIFIER: IdenT it = (IdenT) t0; return new LocT(t1, it.getName()); case MEMBER_SELECT: LocT lt = (LocT) t0; return new LocT(meld(lt.getExpression(), t1), lt.getIdentifier()); case PARAMETERIZED_TYPE: ParT pt = (ParT) t0; return new ParT(meld(pt.getType(), t1), pt.getTypeArguments()); default: throw new IllegalArgumentException("unexpected type " + t0); } } static final class ArrT extends TypeTree implements ArrayTypeTree { private final TypeTree componentType; ArrT(TypeTree componentType) { this.componentType = componentType; } @Override public Kind getKind() { return Kind.ARRAY_TYPE; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitArrayType(this, data); } @Override public TypeTree getType() { return componentType; } @Override public String toString() { return componentType + "[]"; } } static final class LocT extends TypeTree implements MemberSelectTree { private final TypeTree expr; private final Name name; LocT(TypeTree expr, Name name) { this.expr = expr; this.name = name; } @Override public Kind getKind() { return Kind.MEMBER_SELECT; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitMemberSelect(this, data); } @Override public TypeTree getExpression() { return expr; } @Override public Name getIdentifier() { return name; } @Override public String toString() { return expr + "." + name; } } static final class ParT extends TypeTree implements ParameterizedTypeTree { private final TypeTree base; private final List typeArgs; ParT(TypeTree base, List typeArgs) { this.base = base; this.typeArgs = typeArgs; } @Override public Kind getKind() { return Kind.PARAMETERIZED_TYPE; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitParameterizedType(this, data); } @Override public TypeTree getType() { return base; } @Override public List getTypeArguments() { return typeArgs; } @Override public String toString() { StringBuilder sb = new StringBuilder(base.toString()); String s = "<"; for (Tree t : typeArgs) { sb.append(s); sb.append(t.toString()); s = ", "; } sb.append('>'); return sb.toString(); } } static final class PrimT extends TypeTree implements PrimitiveTypeTree { private final TypeKind typeKind; PrimT(TypeKind typeKind) { this.typeKind = typeKind; } @Override public Kind getKind() { return Kind.PRIMITIVE_TYPE; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitPrimitiveType(this, data); } @Override public TypeKind getPrimitiveTypeKind() { return typeKind; } @Override public String toString() { switch (typeKind) { case BOOLEAN: return "boolean"; case BYTE: return "byte"; case CHAR: return "char"; case DOUBLE: return "double"; case FLOAT: return "float"; case INT: return "int"; case LONG: return "long"; case SHORT: return "short"; // case VOID: return "void"; // case WILDCARD: return "?"; default: throw new IllegalArgumentException("unexpected type kind " + typeKind); } } } static final class IdenT extends TypeTree implements IdentifierTree { private final String name; IdenT(String dname) { this.name = dname; } @Override public Kind getKind() { return Kind.IDENTIFIER; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitIdentifier(this, data); } @Override public Name getName() { return new TypeName(name); } @Override public String toString() { return name; } } static final class WildT extends TypeTree implements WildcardTree { private final TypeTree bound; private final Kind kind; WildT() { this(Kind.UNBOUNDED_WILDCARD, null); } WildT(TypeTree bound, BoundedType.BoundKind bk) { this(bk == BoundedType.BoundKind.SUPER ? Kind.SUPER_WILDCARD : Kind.EXTENDS_WILDCARD, bound); } WildT(Kind kind, TypeTree bound) { this.kind = kind; this.bound = bound; } @Override public Kind getKind() { return kind; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitWildcard(this, data); } @Override public Tree getBound() { return bound; } @Override public String toString() { return "?"; } } static final class Param extends TypeTree implements TypeParameterTree { private final String bname; private final BoundedType.BoundKind bk; private final Tree bound; Param(String bname, BoundedType.BoundKind bk, TypeTree bound) { this.bname = bname; this.bk = bk; this.bound = bound; } @Override public Kind getKind() { return Kind.TYPE_PARAMETER; } @Override public R accept(TreeVisitor visitor, D data) { return visitor.visitTypeParameter(this, data); } @Override public Name getName() { return new TypeName(bname); } @Override public List getBounds() { return Collections.singletonList(bound); } @Override public List getAnnotations() { return Collections.emptyList(); } @Override public String toString() { return bname + " " + bk.toString() + " " + bound.toString(); } } static final class TypeName implements Name { private final String str; TypeName(String str) { this.str = str; } @Override public int length() { return str.length(); } @Override public char charAt(int index) { return str.charAt(index); } @Override public CharSequence subSequence(int start, int end) { return str.subSequence(start, end); } @Override public boolean contentEquals(CharSequence cs) { if (cs != null) { int n = length(); if (cs.length() == n) { for (int i = 0; i < n; i++) { if (charAt(i) != cs.charAt(i)) { return false; } } return true; } } return false; } @Override public String toString() { return str; } } } }