1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.currysrc.api.process.ast;
17 
18 import com.google.common.base.Joiner;
19 import com.google.common.base.Splitter;
20 import com.google.common.collect.Lists;
21 import com.google.currysrc.api.match.TypeName;
22 
23 import org.eclipse.jdt.core.dom.ASTNode;
24 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
25 import org.eclipse.jdt.core.dom.BodyDeclaration;
26 import org.eclipse.jdt.core.dom.CompilationUnit;
27 
28 import java.util.Iterator;
29 import java.util.List;
30 
31 /**
32  * Locates the {@link org.eclipse.jdt.core.dom.BodyDeclaration} associated with an class
33  * declaration.
34  */
35 public final class TypeLocator implements BodyDeclarationLocator {
36 
37   static final String LOCATOR_TYPE_NAME = "type";
38 
39   private final PackageMatcher packageMatcher;
40   private final List<String> classNameElements;
41 
42   /**
43    * Creates a {@code TypeLocator} for a fully-qualified class name.
44    *
45    * @param fullyQualifiedClassName the package name (if any) and the class,
46    *     e.g. foo.bar.baz.FooBar$Baz
47    */
TypeLocator(String fullyQualifiedClassName)48   public TypeLocator(String fullyQualifiedClassName) {
49     this(TypeName.fromFullyQualifiedClassName(fullyQualifiedClassName));
50   }
51 
52   /**
53    * Creates a {@code TypeLocator} for a {@link TypeName}.
54    */
TypeLocator(TypeName typeName)55   public TypeLocator(TypeName typeName) {
56     this(typeName.packageName(), typeName.className());
57   }
58 
59   /**
60    * Creates a {@code TypeLocator} using an explicit package and class name spec.
61    *
62    * @param packageName the fully-qualified package name. e.g. foo.bar.baz, or ""
63    * @param className the class name with $ as the separator for nested/inner classes. e.g. FooBar,
64    *     FooBar$Baz.
65    */
TypeLocator(String packageName, String className)66   public TypeLocator(String packageName, String className) {
67     this.packageMatcher = new PackageMatcher(packageName);
68     this.classNameElements = classNameElements(className);
69     if (classNameElements.isEmpty()) {
70       throw new IllegalArgumentException("Empty className");
71     }
72   }
73 
74   /**
75    * Creates a {@code TypeLocator} for the specified {@link ASTNode}.
76    */
TypeLocator(final AbstractTypeDeclaration typeDeclaration)77   public TypeLocator(final AbstractTypeDeclaration typeDeclaration) {
78     if (typeDeclaration.isLocalTypeDeclaration()) {
79       throw new IllegalArgumentException("Local types not supported: " + typeDeclaration);
80     }
81 
82     CompilationUnit cu = (CompilationUnit) typeDeclaration.getRoot();
83     this.packageMatcher = new PackageMatcher(PackageMatcher.getPackageName(cu));
84 
85     // Traverse the type declarations towards the root, building up a list of type names in
86     // reverse order.
87     List<String> typeNames = Lists.newArrayList();
88     AbstractTypeDeclaration currentNode = typeDeclaration;
89     while (typeDeclaration != null) {
90       typeNames.add(currentNode.getName().getFullyQualifiedName());
91       // Handle nested / inner classes.
92       ASTNode parentNode = currentNode.getParent();
93       if (parentNode != null) {
94         if (parentNode == cu) {
95           break;
96         }
97         if (!(parentNode instanceof AbstractTypeDeclaration)) {
98           throw new AssertionError(
99               "Unexpected parent for nested/inner class: parent=" + parentNode + " of "
100                   + currentNode);
101         }
102       }
103       currentNode = (AbstractTypeDeclaration) parentNode;
104     }
105     this.classNameElements = Lists.reverse(typeNames);
106   }
107 
getTypeLocator()108   @Override public TypeLocator getTypeLocator() {
109     return this;
110   }
111 
112   @Override
matches(BodyDeclaration node)113   public boolean matches(BodyDeclaration node) {
114     if (!(node instanceof AbstractTypeDeclaration)) {
115       return false;
116     }
117     if (!packageMatcher.matches((CompilationUnit) node.getRoot())) {
118       return false;
119     }
120 
121     Iterable<String> reverseClassNames = Lists.reverse(classNameElements);
122     Iterator<String> reverseClassNamesIterator = reverseClassNames.iterator();
123     return matchNested(reverseClassNamesIterator, (AbstractTypeDeclaration) node);
124   }
125 
matchNested(Iterator<String> reverseClassNamesIterator, AbstractTypeDeclaration node)126   private boolean matchNested(Iterator<String> reverseClassNamesIterator,
127       AbstractTypeDeclaration node) {
128     String subClassName = reverseClassNamesIterator.next();
129     if (!node.getName().getFullyQualifiedName().equals(subClassName)) {
130       return false;
131     }
132     if (!reverseClassNamesIterator.hasNext()) {
133       return true;
134     }
135 
136     ASTNode parentNode = node.getParent();
137     // This won't work with method-declared types. But they're not documented so...?
138     if (!(parentNode instanceof AbstractTypeDeclaration)) {
139       return false;
140     }
141     return matchNested(reverseClassNamesIterator, (AbstractTypeDeclaration) parentNode);
142   }
143 
144   @Override
find(CompilationUnit cu)145   public AbstractTypeDeclaration find(CompilationUnit cu) {
146     if (!packageMatcher.matches(cu)) {
147       return null;
148     }
149 
150     Iterator<String> classNameIterator = classNameElements.iterator();
151     String topLevelClassName = classNameIterator.next();
152     for (AbstractTypeDeclaration abstractTypeDeclaration
153         : (List<AbstractTypeDeclaration>) cu.types()) {
154       if (abstractTypeDeclaration.getName().getFullyQualifiedName().equals(topLevelClassName)) {
155         // Top-level interface / class / enum match.
156         return findNested(classNameIterator, abstractTypeDeclaration);
157       }
158     }
159     return null;
160   }
161 
getStringFormType()162   @Override public String getStringFormType() {
163     return LOCATOR_TYPE_NAME;
164   }
165 
getStringFormTarget()166   @Override public String getStringFormTarget() {
167     return packageMatcher.toStringForm() + "." + Joiner.on('$').join(classNameElements);
168   }
169 
findNested(Iterator<String> classNameIterator, AbstractTypeDeclaration typeDeclaration)170   private AbstractTypeDeclaration findNested(Iterator<String> classNameIterator,
171       AbstractTypeDeclaration typeDeclaration) {
172     if (!classNameIterator.hasNext()) {
173       return typeDeclaration;
174     }
175 
176     String subClassName = classNameIterator.next();
177     for (BodyDeclaration bodyDeclaration
178         : (List<BodyDeclaration>) typeDeclaration.bodyDeclarations()) {
179       if (bodyDeclaration instanceof AbstractTypeDeclaration) {
180         AbstractTypeDeclaration subTypeDeclaration = (AbstractTypeDeclaration) bodyDeclaration;
181         if (subTypeDeclaration.getName().getFullyQualifiedName().equals(subClassName)) {
182           return findNested(classNameIterator, subTypeDeclaration);
183         }
184       }
185     }
186     return null;
187   }
188 
189   @Override
toString()190   public String toString() {
191     return "TypeLocator{" +
192         "packageMatcher=" + packageMatcher +
193         ", classNameElements=" + classNameElements +
194         '}';
195   }
196 
197   /** Returns the enclosing type for nested/inner classes, {@code null} otherwise. */
findEnclosingTypeDeclaration( AbstractTypeDeclaration typeDeclaration)198   public static AbstractTypeDeclaration findEnclosingTypeDeclaration(
199       AbstractTypeDeclaration typeDeclaration) {
200     if (typeDeclaration.isPackageMemberTypeDeclaration()) {
201       return null;
202     }
203     ASTNode enclosingNode = typeDeclaration.getParent();
204     return enclosingNode instanceof AbstractTypeDeclaration
205         ? (AbstractTypeDeclaration) enclosingNode : null;
206   }
207 
208   /**
209    * Finds the type declaration associated with the supplied {@code bodyDeclaration}. Can
210    * return {@code bodyDeclaration} if the node itself is a type declaration. Can return null (e.g.)
211    * if the bodyDeclaration is declared on an anonymous type.
212    */
findTypeDeclarationNode(BodyDeclaration bodyDeclaration)213   public static AbstractTypeDeclaration findTypeDeclarationNode(BodyDeclaration bodyDeclaration) {
214     if (bodyDeclaration instanceof AbstractTypeDeclaration) {
215       return (AbstractTypeDeclaration) bodyDeclaration;
216     }
217     ASTNode parentNode = bodyDeclaration.getParent();
218     if (parentNode instanceof AbstractTypeDeclaration) {
219       return (AbstractTypeDeclaration) parentNode;
220     }
221     return null;
222   }
223 
classNameElements(String className)224   private static List<String> classNameElements(String className) {
225     return Splitter.on('$').splitToList(className);
226   }
227 }
228