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