| 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<Insertion> { |
| 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<Insertion> byASTRecord = |
| new Comparator<Insertion>() { |
| @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<String, Map<String, Set<Insertion>>> store; |
| private int size; |
| |
| private Pair<String, String> 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<String, Map<String, Set<Insertion>>>(); |
| size = 0; |
| } |
| |
| // auxiliary for following two methods |
| private void forClass(CompilationUnitTree cut, |
| String qualifiedClassName, Set<Insertion> result) { |
| Pair<String, String> pair = nameSplit(qualifiedClassName); |
| Map<String, Set<Insertion>> map = store.get(pair.fst); |
| if (map != null) { |
| Set<Insertion> set = new TreeSet<Insertion>(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<Insertion> forClass(CompilationUnitTree cut, |
| String qualifiedClassName) { |
| Set<Insertion> set = new LinkedHashSet<Insertion>(); |
| 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<Insertion> forOuterClass(CompilationUnitTree cut, |
| String qualifiedOuterClassName) { |
| Map<String, Set<Insertion>> map = store.get(qualifiedOuterClassName); |
| if (map == null || map.isEmpty()) { |
| return Collections.<Insertion>emptySet(); |
| } else { |
| Set<Insertion> set = new LinkedHashSet<Insertion>(); |
| 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<String, Set<Insertion>> map; |
| Set<Insertion> set; |
| |
| if (icc != null) { |
| Pair<String, String> triple = nameSplit(icc.className); |
| k1 = triple.fst; |
| k2 = triple.snd; |
| } |
| map = store.get(k1); |
| if (map == null) { |
| map = new HashMap<String, Set<Insertion>>(); |
| store.put(k1, map); |
| } |
| set = map.get(k2); |
| if (set == null) { |
| set = new LinkedHashSet<Insertion>(); |
| 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<? extends Insertion> 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<Insertion> iterator() { |
| return new Iterator<Insertion>() { |
| private Iterator<Map<String, Set<Insertion>>> miter = |
| store.values().iterator(); |
| private Iterator<Set<Insertion>> siter = |
| Collections.<Set<Insertion>>emptySet().iterator(); |
| private Iterator<Insertion> iiter = |
| Collections.<Insertion>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<Insertion> toList() { |
| List<Insertion> list = new ArrayList<Insertion>(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<Insertion> organizeTypedInsertions(CompilationUnitTree cut, |
| String className, Collection<Insertion> insertions) { |
| ASTRecordMap<TypedInsertion> map = new ASTRecordMap<TypedInsertion>(); |
| Set<Insertion> organized = new LinkedHashSet<Insertion>(); |
| Set<Insertion> unorganized = new LinkedHashSet<Insertion>(); |
| List<Insertion> list = new ArrayList<Insertion>(); |
| |
| // 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<TypePathEntry> loc0 = new ArrayList<TypePathEntry>(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<TypePathEntry> loc = |
| new ArrayList<TypePathEntry>(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<? extends ExpressionTree> 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<TypePathEntry> 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<TypePathEntry> tpes = new ArrayList<TypePathEntry>(); |
| 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<ASTPath> astack = new ArrayDeque<ASTPath>(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<TypePathEntry> loc = null; |
| List<Insertion> inners = new ArrayList<Insertion>(); |
| 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 ("<init>".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<D> 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<? extends Tree> 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<TypePathEntry> loc) { |
| ASTRecord r = rec; |
| Iterator<TypePathEntry> 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<TypePathEntry> loc, Tree node) { |
| ASTRecord r = rec; |
| Tree t = node; |
| Iterator<TypePathEntry> 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<Type> tps0 = dt0.getTypeParameters(); |
| List<Type> 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<E> implements Map<ASTRecord, E> { |
| Map<ASTRecord, SortedMap<ASTPath, E>> back; |
| |
| ASTRecordMap() { |
| back = new HashMap<ASTRecord, SortedMap<ASTPath, E>>(); |
| } |
| |
| private SortedMap<ASTPath, E> getMap(ASTRecord rec) { |
| ASTRecord key = rec.replacePath(ASTPath.empty()); |
| SortedMap<ASTPath, E> map = back.get(key); |
| if (map == null) { |
| map = new TreeMap<ASTPath, E>(); |
| back.put(key, map); |
| } |
| return map; |
| } |
| |
| @Override |
| public int size() { |
| int n = 0; |
| for (SortedMap<ASTPath, E> 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<ASTPath, E> m = getMap(rec); |
| return m != null && m.containsKey(rec.astPath); |
| } |
| |
| @Override |
| public boolean containsValue(Object value) { |
| @SuppressWarnings("unchecked") |
| E e = (E) value; |
| for (SortedMap<ASTPath, E> map : back.values()) { |
| if (map.containsValue(e)) { return true; } |
| } |
| return false; |
| } |
| |
| @Override |
| public E get(Object key) { |
| ASTRecord rec = (ASTRecord) key; |
| SortedMap<ASTPath, E> map = getMap(rec); |
| return map == null ? null : map.get(rec.astPath); |
| } |
| |
| @Override |
| public E put(ASTRecord key, E value) { |
| ASTRecord rec = key; |
| SortedMap<ASTPath, E> map = getMap(rec); |
| return map == null ? null : map.put(rec.astPath, value); |
| } |
| |
| @Override |
| public E remove(Object key) { |
| ASTRecord rec = (ASTRecord) key; |
| SortedMap<ASTPath, E> map = getMap(rec); |
| return map == null ? null : map.remove(rec.astPath); |
| } |
| |
| @Override |
| public void putAll(Map<? extends ASTRecord, ? extends E> m) { |
| for (Map.Entry<? extends ASTRecord, ? extends E> entry : m.entrySet()) { |
| put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @Override |
| public void clear() { |
| back.clear(); |
| } |
| |
| @Override |
| public Set<ASTRecord> keySet() { |
| return back.keySet(); |
| } |
| |
| @Override |
| public Collection<E> values() { |
| Set<E> ret = new LinkedHashSet<E>(); |
| for (SortedMap<ASTPath, E> m : back.values()) { |
| ret.addAll(m.values()); |
| } |
| return ret; |
| } |
| |
| @Override |
| public Set<Map.Entry<ASTRecord, E>> entrySet() { |
| final int size = size(); |
| return new AbstractSet<Map.Entry<ASTRecord, E>>() { |
| @Override |
| public Iterator<Map.Entry<ASTRecord, E>> iterator() { |
| return new Iterator<Map.Entry<ASTRecord, E>>() { |
| Iterator<Map.Entry<ASTRecord, SortedMap<ASTPath, E>>> iter0 = |
| back.entrySet().iterator(); |
| Iterator<Map.Entry<ASTPath, E>> iter1 = |
| Collections.<Map.Entry<ASTPath, E>>emptyIterator(); |
| ASTRecord rec = null; |
| |
| @Override |
| public boolean hasNext() { |
| if (iter1.hasNext()) { return true; } |
| while (iter0.hasNext()) { |
| Map.Entry<ASTRecord, SortedMap<ASTPath, E>> entry = |
| iter0.next(); |
| rec = entry.getKey(); |
| iter1 = entry.getValue().entrySet().iterator(); |
| if (iter1.hasNext()) { return true; } |
| } |
| iter1 = Collections.<Map.Entry<ASTPath, E>>emptyIterator(); |
| return false; |
| } |
| |
| @Override |
| public Map.Entry<ASTRecord, E> next() { |
| if (!hasNext()) { throw new NoSuchElementException(); } |
| final Map.Entry<ASTPath, E> e0 = iter1.next(); |
| return new Map.Entry<ASTRecord, E>() { |
| 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<String, TypeTag> primTags = |
| new HashMap<String, TypeTag>(); |
| { |
| 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<JCExpression> typeArgs = |
| ((JCTree.JCTypeApply) jt).getTypeArguments(); |
| List<Tree> args = new ArrayList<Tree>(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<Type> params = dtype.getTypeParameters(); |
| DeclaredType inner = dtype.getInnerType(); |
| if (!params.isEmpty()) { |
| final List<Tree> typeArgs = new ArrayList<Tree>(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, D> R accept(TreeVisitor<R, D> 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, D> R accept(TreeVisitor<R, D> 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<? extends Tree> typeArgs; |
| |
| ParT(TypeTree base, List<? extends Tree> typeArgs) { |
| this.base = base; |
| this.typeArgs = typeArgs; |
| } |
| |
| @Override |
| public Kind getKind() { return Kind.PARAMETERIZED_TYPE; } |
| |
| @Override |
| public <R, D> R accept(TreeVisitor<R, D> visitor, D data) { |
| return visitor.visitParameterizedType(this, data); |
| } |
| |
| @Override |
| public TypeTree getType() { return base; } |
| |
| @Override |
| public List<? extends Tree> 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, D> R accept(TreeVisitor<R, D> 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, D> R accept(TreeVisitor<R, D> 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, D> R accept(TreeVisitor<R, D> 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, D> R accept(TreeVisitor<R, D> visitor, D data) { |
| return visitor.visitTypeParameter(this, data); |
| } |
| |
| @Override |
| public Name getName() { return new TypeName(bname); } |
| |
| @Override |
| public List<? extends Tree> getBounds() { |
| return Collections.singletonList(bound); |
| } |
| |
| @Override |
| public List<? extends AnnotationTree> 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; } |
| } |
| } |
| } |