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