blob: 9bb79399d471b4b787232cd54f8ad15101062d19 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.currysrc.api.transform.ast;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.currysrc.api.match.TypeName;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import java.util.Iterator;
import java.util.List;
/**
* Locates the {@link org.eclipse.jdt.core.dom.BodyDeclaration} associated with an class
* declaration.
*/
public final class TypeLocater implements BodyDeclarationLocater {
private final PackageMatcher packageMatcher;
private final List<String> classNameElements;
/**
* Creates a {@code TypeLocater} for a fully-qualified class name.
*
* @param fullyQualifiedClassName the package name (if any) and the class,
* e.g. foo.bar.baz.FooBar$Baz
*/
public TypeLocater(String fullyQualifiedClassName) {
this(TypeName.fromFullyQualifiedClassName(fullyQualifiedClassName));
}
/**
* Creates a {@code TypeLocater} for a {@link TypeName}.
*/
public TypeLocater(TypeName typeName) {
this(typeName.packageName(), typeName.className());
}
/**
* Creates a {@code TypeLocater} using an explicit package and class name spec.
*
* @param packageName the fully-qualified package name. e.g. foo.bar.baz, or ""
* @param className the class name with $ as the separator for nested/inner classes. e.g. FooBar,
* FooBar$Baz.
*/
public TypeLocater(String packageName, String className) {
this.packageMatcher = new PackageMatcher(packageName);
this.classNameElements = classNameElements(className);
if (classNameElements.isEmpty()) {
throw new IllegalArgumentException("Empty className");
}
}
@Override
public boolean matches(BodyDeclaration node) {
if (!(node instanceof AbstractTypeDeclaration)) {
return false;
}
if (!packageMatcher.matches((CompilationUnit) node.getRoot())) {
return false;
}
Iterable<String> reverseClassNames = Lists.reverse(classNameElements);
Iterator<String> reverseClassNamesIterator = reverseClassNames.iterator();
return matchNested(reverseClassNamesIterator, (AbstractTypeDeclaration) node);
}
private boolean matchNested(Iterator<String> reverseClassNamesIterator,
AbstractTypeDeclaration node) {
String subClassName = reverseClassNamesIterator.next();
if (!node.getName().getFullyQualifiedName().equals(subClassName)) {
return false;
}
if (!reverseClassNamesIterator.hasNext()) {
return true;
}
ASTNode parentNode = node.getParent();
// This won't work with method-declared types. But they're not documented so...?
if (!(parentNode instanceof AbstractTypeDeclaration)) {
return false;
}
return matchNested(reverseClassNamesIterator, (AbstractTypeDeclaration) parentNode);
}
@Override
public AbstractTypeDeclaration find(CompilationUnit cu) {
if (!packageMatcher.matches(cu)) {
return null;
}
Iterator<String> classNameIterator = classNameElements.iterator();
String topLevelClassName = classNameIterator.next();
for (AbstractTypeDeclaration abstractTypeDeclaration : (List<AbstractTypeDeclaration>) cu
.types()) {
if (abstractTypeDeclaration.getName().getFullyQualifiedName().equals(topLevelClassName)) {
// Top-level interface / class / enum match.
return findNested(classNameIterator, abstractTypeDeclaration);
}
}
return null;
}
private AbstractTypeDeclaration findNested(Iterator<String> classNameIterator,
AbstractTypeDeclaration typeDeclaration) {
if (!classNameIterator.hasNext()) {
return typeDeclaration;
}
String subClassName = classNameIterator.next();
for (BodyDeclaration bodyDeclaration : (List<BodyDeclaration>) typeDeclaration
.bodyDeclarations()) {
if (bodyDeclaration instanceof AbstractTypeDeclaration) {
AbstractTypeDeclaration subTypeDeclaration = (AbstractTypeDeclaration) bodyDeclaration;
if (subTypeDeclaration.getName().getFullyQualifiedName().equals(subClassName)) {
return findNested(classNameIterator, subTypeDeclaration);
}
}
}
return null;
}
@Override
public String toString() {
return "TypeLocater{" +
"packageMatcher=" + packageMatcher +
", classNameElements=" + classNameElements +
'}';
}
private static List<String> classNameElements(String className) {
return Splitter.on("$").splitToList(className);
}
}