• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ImmutableList;
21 import com.google.common.collect.Lists;
22 import com.google.currysrc.api.match.TypeName;
23 
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.util.Objects;
28 import org.eclipse.jdt.core.dom.ASTNode;
29 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
30 import org.eclipse.jdt.core.dom.BodyDeclaration;
31 import org.eclipse.jdt.core.dom.CompilationUnit;
32 
33 import java.util.Iterator;
34 import java.util.List;
35 
36 /**
37  * Locates the {@link org.eclipse.jdt.core.dom.BodyDeclaration} associated with an class
38  * declaration.
39  */
40 public final class TypeLocator implements BodyDeclarationLocator {
41 
42   static final String LOCATOR_TYPE_NAME = "type";
43 
44   private final PackageMatcher packageMatcher;
45   private final List<String> classNameElements;
46 
47   /**
48    * Creates a {@code TypeLocator} for a fully-qualified class name.
49    *
50    * @param fullyQualifiedClassName the package name (if any) and the class,
51    *     e.g. foo.bar.baz.FooBar$Baz
52    */
TypeLocator(String fullyQualifiedClassName)53   public TypeLocator(String fullyQualifiedClassName) {
54     this(TypeName.fromFullyQualifiedClassName(fullyQualifiedClassName));
55   }
56 
57   /**
58    * Creates a {@code TypeLocator} for a {@link TypeName}.
59    */
TypeLocator(TypeName typeName)60   public TypeLocator(TypeName typeName) {
61     this(typeName.packageName(), typeName.className());
62   }
63 
64   /**
65    * Creates a {@code TypeLocator} using an explicit package and class name spec.
66    *
67    * @param packageName the fully-qualified package name. e.g. foo.bar.baz, or ""
68    * @param className the class name with $ as the separator for nested/inner classes. e.g. FooBar,
69    *     FooBar$Baz.
70    */
TypeLocator(String packageName, String className)71   public TypeLocator(String packageName, String className) {
72     this.packageMatcher = new PackageMatcher(packageName);
73     this.classNameElements = classNameElements(className);
74     if (classNameElements.isEmpty()) {
75       throw new IllegalArgumentException("Empty className");
76     }
77   }
78 
79   /**
80    * Creates a {@code TypeLocator} for the specified {@link ASTNode}.
81    */
TypeLocator(final AbstractTypeDeclaration typeDeclaration)82   public TypeLocator(final AbstractTypeDeclaration typeDeclaration) {
83     if (typeDeclaration.isLocalTypeDeclaration()) {
84       throw new IllegalArgumentException("Local types not supported: " + typeDeclaration);
85     }
86 
87     CompilationUnit cu = (CompilationUnit) typeDeclaration.getRoot();
88     this.packageMatcher = new PackageMatcher(PackageMatcher.getPackageName(cu));
89 
90     // Traverse the type declarations towards the root, building up a list of type names in
91     // reverse order.
92     List<String> typeNames = Lists.newArrayList();
93     AbstractTypeDeclaration currentNode = typeDeclaration;
94     while (currentNode != null) {
95       typeNames.add(currentNode.getName().getFullyQualifiedName());
96       // Handle nested / inner classes.
97       ASTNode parentNode = currentNode.getParent();
98       if (parentNode != null) {
99         if (parentNode == cu) {
100           break;
101         }
102         if (!(parentNode instanceof AbstractTypeDeclaration)) {
103           throw new AssertionError(
104               "Unexpected parent for nested/inner class: parent=" + parentNode + " of "
105                   + currentNode);
106         }
107       }
108       currentNode = (AbstractTypeDeclaration) parentNode;
109     }
110     this.classNameElements = Lists.reverse(typeNames);
111   }
112 
createLocatorsFromStrings(String[] classes)113   public static List<TypeLocator> createLocatorsFromStrings(String[] classes) {
114     ImmutableList.Builder<TypeLocator> apiClassesAllowlistBuilder = ImmutableList.builder();
115     for (String publicClassName : classes) {
116       apiClassesAllowlistBuilder.add(new TypeLocator(publicClassName));
117     }
118     return apiClassesAllowlistBuilder.build();
119   }
120 
121   /**
122    * Read type locators from a file.
123    *
124    * <p>Blank lines and lines starting with a {@code #} are ignored.
125    *
126    * @param path the path to the file.
127    * @return The list of {@link TypeLocator} instances.
128    */
readTypeLocators(Path path)129   public static List<TypeLocator> readTypeLocators(Path path) {
130     String[] lines = readLines(path);
131     return createLocatorsFromStrings(lines);
132   }
133 
134   /**
135    * Read lines from a file.
136    *
137    * <p>Blank lines and lines starting with a {@code #} are ignored.
138    *
139    * @param path the path to the file.
140    * @return The array of lines.
141    */
readLines(Path path)142   static String[] readLines(Path path) {
143     try {
144       return Files.lines(path)
145           .filter(l -> !l.startsWith("#"))
146           .filter(l -> !l.isEmpty())
147           .toArray(String[]::new);
148     } catch (IOException e) {
149       throw new IllegalStateException("Could not read lines from " + path, e);
150     }
151   }
152 
getTypeLocator()153   @Override public TypeLocator getTypeLocator() {
154     return this;
155   }
156 
157   @Override
matches(BodyDeclaration node)158   public boolean matches(BodyDeclaration node) {
159     if (!(node instanceof AbstractTypeDeclaration)) {
160       return false;
161     }
162     if (!packageMatcher.matches((CompilationUnit) node.getRoot())) {
163       return false;
164     }
165 
166     Iterable<String> reverseClassNames = Lists.reverse(classNameElements);
167     Iterator<String> reverseClassNamesIterator = reverseClassNames.iterator();
168     return matchNested(reverseClassNamesIterator, (AbstractTypeDeclaration) node);
169   }
170 
matchNested(Iterator<String> reverseClassNamesIterator, AbstractTypeDeclaration node)171   private boolean matchNested(Iterator<String> reverseClassNamesIterator,
172       AbstractTypeDeclaration node) {
173     String subClassName = reverseClassNamesIterator.next();
174     if (!node.getName().getFullyQualifiedName().equals(subClassName)) {
175       return false;
176     }
177     if (!reverseClassNamesIterator.hasNext()) {
178       return true;
179     }
180 
181     ASTNode parentNode = node.getParent();
182     // This won't work with method-declared types. But they're not documented so...?
183     if (!(parentNode instanceof AbstractTypeDeclaration)) {
184       return false;
185     }
186     return matchNested(reverseClassNamesIterator, (AbstractTypeDeclaration) parentNode);
187   }
188 
189   @Override
find(CompilationUnit cu)190   public AbstractTypeDeclaration find(CompilationUnit cu) {
191     if (!packageMatcher.matches(cu)) {
192       return null;
193     }
194 
195     Iterator<String> classNameIterator = classNameElements.iterator();
196     String topLevelClassName = classNameIterator.next();
197     @SuppressWarnings("unchecked")
198     List<AbstractTypeDeclaration> types = cu.types();
199     for (AbstractTypeDeclaration abstractTypeDeclaration : types) {
200       if (abstractTypeDeclaration.getName().getFullyQualifiedName().equals(topLevelClassName)) {
201         // Top-level interface / class / enum match.
202         return findNested(classNameIterator, abstractTypeDeclaration);
203       }
204     }
205     return null;
206   }
207 
getStringFormType()208   @Override public String getStringFormType() {
209     return LOCATOR_TYPE_NAME;
210   }
211 
getStringFormTarget()212   @Override public String getStringFormTarget() {
213     return packageMatcher.toStringForm() + "." + Joiner.on('$').join(classNameElements);
214   }
215 
findNested(Iterator<String> classNameIterator, AbstractTypeDeclaration typeDeclaration)216   private AbstractTypeDeclaration findNested(Iterator<String> classNameIterator,
217       AbstractTypeDeclaration typeDeclaration) {
218     if (!classNameIterator.hasNext()) {
219       return typeDeclaration;
220     }
221 
222     String subClassName = classNameIterator.next();
223     @SuppressWarnings("unchecked")
224     List<BodyDeclaration> bodyDeclarations = typeDeclaration.bodyDeclarations();
225     for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
226       if (bodyDeclaration instanceof AbstractTypeDeclaration) {
227         AbstractTypeDeclaration subTypeDeclaration = (AbstractTypeDeclaration) bodyDeclaration;
228         if (subTypeDeclaration.getName().getFullyQualifiedName().equals(subClassName)) {
229           return findNested(classNameIterator, subTypeDeclaration);
230         }
231       }
232     }
233     return null;
234   }
235 
236   @Override
equals(Object o)237   public boolean equals(Object o) {
238     if (this == o) {
239       return true;
240     }
241     if (!(o instanceof TypeLocator)) {
242       return false;
243     }
244     TypeLocator that = (TypeLocator) o;
245     return Objects.equals(packageMatcher, that.packageMatcher) &&
246         Objects.equals(classNameElements, that.classNameElements);
247   }
248 
249   @Override
hashCode()250   public int hashCode() {
251     return Objects.hash(packageMatcher, classNameElements);
252   }
253 
254   @Override
toString()255   public String toString() {
256     return "TypeLocator{" +
257         "packageMatcher=" + packageMatcher +
258         ", classNameElements=" + classNameElements +
259         '}';
260   }
261 
262   /** Returns the enclosing type for nested/inner classes, {@code null} otherwise. */
findEnclosingTypeDeclaration( AbstractTypeDeclaration typeDeclaration)263   public static AbstractTypeDeclaration findEnclosingTypeDeclaration(
264       AbstractTypeDeclaration typeDeclaration) {
265     if (typeDeclaration.isPackageMemberTypeDeclaration()) {
266       return null;
267     }
268     ASTNode enclosingNode = typeDeclaration.getParent();
269     return enclosingNode instanceof AbstractTypeDeclaration
270         ? (AbstractTypeDeclaration) enclosingNode : null;
271   }
272 
273   /**
274    * Finds the type declaration associated with the supplied {@code bodyDeclaration}. Can
275    * return {@code bodyDeclaration} if the node itself is a type declaration. Can return null (e.g.)
276    * if the bodyDeclaration is declared on an anonymous type.
277    */
findTypeDeclarationNode(BodyDeclaration bodyDeclaration)278   public static AbstractTypeDeclaration findTypeDeclarationNode(BodyDeclaration bodyDeclaration) {
279     if (bodyDeclaration instanceof AbstractTypeDeclaration) {
280       return (AbstractTypeDeclaration) bodyDeclaration;
281     }
282     ASTNode parentNode = bodyDeclaration.getParent();
283     if (parentNode instanceof AbstractTypeDeclaration) {
284       return (AbstractTypeDeclaration) parentNode;
285     }
286     return null;
287   }
288 
classNameElements(String className)289   private static List<String> classNameElements(String className) {
290     return Splitter.on('$').splitToList(className);
291   }
292 }
293