blob: e052d4be3691925e2c98629e6a76e76284c6dcd6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 SpringSource and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andrew Eisenberg - initial API and implementation
*******************************************************************************/
package org.eclipse.ajdt.internal.core.search;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.aspectj.asm.IProgramElement.Kind;
import org.aspectj.org.eclipse.jdt.core.dom.AST;
import org.aspectj.org.eclipse.jdt.core.dom.ASTParser;
import org.aspectj.org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.aspectj.org.eclipse.jdt.core.dom.BodyDeclaration;
import org.aspectj.org.eclipse.jdt.core.dom.CompilationUnit;
import org.aspectj.org.eclipse.jdt.core.dom.InterTypeFieldDeclaration;
import org.aspectj.org.eclipse.jdt.core.dom.InterTypeMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ModuleBinding;
import org.eclipse.ajdt.core.ReflectionUtils;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.AspectElement;
import org.eclipse.ajdt.core.javaelements.IntertypeElement;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.FieldDeclarationMatch;
import org.eclipse.jdt.core.search.MethodDeclarationMatch;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.core.hierarchy.HierarchyResolver;
import org.eclipse.jdt.internal.core.search.matching.ConstructorPattern;
import org.eclipse.jdt.internal.core.search.matching.FieldPattern;
import org.eclipse.jdt.internal.core.search.matching.MethodPattern;
import org.eclipse.jdt.internal.core.search.matching.PossibleMatch;
import org.eclipse.jdt.internal.core.search.matching.VariablePattern;
/**
* Helps find ITD references and declarations of a {@link SearchPattern} inside of an {@link AspectElement}
*
* @author Andrew Eisenberg
* @created Aug 6, 2010
*/
public class ExtraITDFinder implements IExtraMatchFinder<SearchPattern> {
public List<SearchMatch> findExtraMatches(PossibleMatch match,
SearchPattern pattern, HierarchyResolver resolver)
throws JavaModelException {
List<SearchMatch> extraMatches = new ArrayList<SearchMatch>();
if ((match.openable instanceof AJCompilationUnit)) {
boolean findDeclarations = findDeclarations(pattern);
boolean findReferences = findReferences(pattern);
AJCompilationUnit unit = (AJCompilationUnit) match.openable;
List<IntertypeElement> allRelevantItds = findRelevantITDs(pattern,
resolver, unit);
if (allRelevantItds.size() > 0) {
if (findReferences) {
extraMatches.addAll(findExtraReferenceMatches(unit, allRelevantItds, pattern, match));
}
// for method and constructor patterns, also look for declarations
// can't do this for field patterns because there would be a class cast exception
// when trying to cast a IntertypeDeclaration to an IField.
// Note that IntertypeDeclaration happens to implement IMethod.
if (findDeclarations) {
extraMatches.addAll(findExtraDeclarationMatches(unit, allRelevantItds, pattern, match));
}
}
}
return extraMatches;
}
private List<IntertypeElement> findRelevantITDs(SearchPattern pattern,
HierarchyResolver resolver, AJCompilationUnit unit)
throws JavaModelException {
List<IntertypeElement> allItds = getAllItds(unit);
if (allItds.size() == 0) {
return Collections.emptyList();
}
// find target type
char[] targetTypeName = null;
if (pattern instanceof FieldPattern) {
targetTypeName = TargetTypeUtils.getName(TargetTypeUtils.getQualName((FieldPattern) pattern), TargetTypeUtils.getSimpleName((FieldPattern) pattern));
} else if (pattern instanceof MethodPattern) {
targetTypeName = TargetTypeUtils.getName(((MethodPattern) pattern).declaringQualification, ((MethodPattern) pattern).declaringSimpleName);
}
// if target type is not known, then this means that a target
// type is not specified and therefore do not remove any
// potential matches.
if (targetTypeName != null && targetTypeName.length > 0) {
if (resolver != null) {
resolver.setFocusType(CharOperation.splitOn('.', targetTypeName));
}
for (Iterator<IntertypeElement> itdIter = allItds.iterator(); itdIter.hasNext();) {
IntertypeElement itd = itdIter.next();
if (!isSubtypeOfSearchPattern(targetTypeName, itd, resolver)) {
itdIter.remove();
}
}
}
return allItds;
}
private boolean findReferences(SearchPattern pattern) {
if (pattern instanceof MethodPattern) {
return (Boolean) ReflectionUtils.getPrivateField(MethodPattern.class, "findReferences", (MethodPattern) pattern);
} else if (pattern instanceof FieldPattern) {
// note that findReferences is actually declared in VariablePattern
return (Boolean) ReflectionUtils.getPrivateField(VariablePattern.class, "findReferences", (VariablePattern)pattern);
} else if (pattern instanceof ConstructorPattern) {
return (Boolean) ReflectionUtils.getPrivateField(ConstructorPattern.class, "findReferences", (ConstructorPattern) pattern);
} else {
return false;
}
}
private boolean findDeclarations(SearchPattern pattern) {
if (pattern instanceof MethodPattern) {
return (Boolean) ReflectionUtils.getPrivateField(MethodPattern.class, "findDeclarations", (MethodPattern) pattern);
} else if (pattern instanceof FieldPattern) {
// note that findDeclarations is actually declared in VariablePattern
return (Boolean) ReflectionUtils.getPrivateField(VariablePattern.class, "findDeclarations", (VariablePattern)pattern);
} else if (pattern instanceof ConstructorPattern) {
return (Boolean) ReflectionUtils.getPrivateField(ConstructorPattern.class, "findDeclarations", (ConstructorPattern) pattern);
} else {
return false;
}
}
/**
* Perform searching inside of ITDs if appropriate.
* Two situations that we care about:
* 1. field reference pattern inside of ITD.
* 2. method reference pattern inside of ITD
*
* If the {@link PossibleMatch} is an {@link AJCompilationUnit}, then
* look to see if it has any ITDs whose target type is of the type of the search
* pattern. And if so, then delegate for further possible matching matching.
* @throws JavaModelException
*/
private List<SearchMatch> findExtraReferenceMatches(AJCompilationUnit unit, List<IntertypeElement> allRelevantItds, SearchPattern pattern, PossibleMatch match)
throws JavaModelException {
CompilationUnit ajDomUnit = getDom(unit);
List<SearchMatch> matches = walkITDs(ajDomUnit, allRelevantItds, pattern, match);
return matches;
}
/**
* Return all declaration matches that are ITDs of the proper type or in the type hierarchy of the expected type
* @throws JavaModelException
*/
private List<SearchMatch> findExtraDeclarationMatches(
AJCompilationUnit unit, List<IntertypeElement> allRelevantItds, SearchPattern pattern, PossibleMatch match) throws JavaModelException {
List<SearchMatch> extraDeclarationMatches = new ArrayList<SearchMatch>();
// At this point, we know that the itds passed in have the same declaring type or are a subtype of the target type
// So, just need to check selector and parameters
// it is too time consuming to get the qualified parameters of the types, so
// just match on simple names.
if (pattern instanceof MethodPattern) {
MethodPattern methPatt = (MethodPattern) pattern;
char[] selector = methPatt.selector;
char[][] simpleParamTypes = methPatt.parameterSimpleNames;
for (IntertypeElement itd : allRelevantItds) {
if (itd.getAJKind() == Kind.INTER_TYPE_METHOD &&
CharOperation.equals(selector, itd.getTargetName().toCharArray())) {
char[][] itdSimpleParamNames = extractSimpleParamNames(itd);
if (CharOperation.equals(simpleParamTypes, itdSimpleParamNames)) {
ISourceRange sourceRange = itd.getNameRange();
extraDeclarationMatches.add(new MethodDeclarationMatch(itd, SearchMatch.A_ACCURATE, sourceRange.getOffset(), sourceRange.getLength(),
match.document.getParticipant(), itd.getCompilationUnit().getResource()));
}
}
}
} else if (pattern instanceof ConstructorPattern) {
ConstructorPattern consPatt = (ConstructorPattern) pattern;
// must match the exact type
char[] targetTypeName = TargetTypeUtils.getName(consPatt.declaringQualification, consPatt.declaringSimpleName);
char[][] simpleParamTypes = consPatt.parameterSimpleNames;
for (IntertypeElement itd : allRelevantItds) {
if (itd.getAJKind() == Kind.INTER_TYPE_CONSTRUCTOR &&
targetTypeName != null && CharOperation.equals(targetTypeName, fullyQualifiedTargetTypeName(itd))) {
char[][] itdSimpleParamNames = extractSimpleParamNames(itd);
if (CharOperation.equals(simpleParamTypes, itdSimpleParamNames)) {
ISourceRange sourceRange = itd.getNameRange();
extraDeclarationMatches.add(new MethodDeclarationMatch(itd, SearchMatch.A_ACCURATE, sourceRange.getOffset(), sourceRange.getLength(),
match.document.getParticipant(), itd.getCompilationUnit().getResource()));
}
}
}
} else if (pattern instanceof FieldPattern) {
FieldPattern fieldPatt = (FieldPattern) pattern;
char[] targetTypeName = TargetTypeUtils.getName(TargetTypeUtils.getQualName(fieldPatt), TargetTypeUtils.getSimpleName(fieldPatt));
char[] fieldName = fieldPatt.getIndexKey();
for (IntertypeElement itd : allRelevantItds) {
if (itd.getAJKind() == Kind.INTER_TYPE_FIELD && CharOperation.equals(fieldName, itd.getTargetName().toCharArray()) &&
// must match the exact type, but only if a type exists
(targetTypeName == null || CharOperation.equals(targetTypeName, fullyQualifiedTargetTypeName(itd)))) {
ISourceRange sourceRange = itd.getNameRange();
extraDeclarationMatches.add(new FieldDeclarationMatch(itd, SearchMatch.A_ACCURATE, sourceRange.getOffset(), sourceRange.getLength(),
match.document.getParticipant(), itd.getCompilationUnit().getResource()));
}
}
}
return extraDeclarationMatches;
}
private char[][] extractSimpleParamNames(IntertypeElement itd) {
String[] parameterTypes = itd.getParameterTypes();
if (parameterTypes == null) {
return new char[0][];
}
char[][] simpleNames = new char[parameterTypes.length][];
for (int i = 0; i < parameterTypes.length; i++) {
try {
simpleNames[i] = Signature.getSignatureSimpleName(Signature.getTypeErasure(parameterTypes[i].toCharArray()));
} catch (Exception e) {
}
}
return simpleNames;
}
private boolean isSubtypeOfSearchPattern(char[] targetTypeName,
IntertypeElement itd, HierarchyResolver resolver) throws JavaModelException {
char[] itdTargetTypeName = fullyQualifiedTargetTypeName(itd);
if (CharOperation.equals(targetTypeName, itdTargetTypeName)) {
return true;
}
if (resolver != null) {
LookupEnvironment env = ((LookupEnvironment) ReflectionUtils.getPrivateField(HierarchyResolver.class, "lookupEnvironment", resolver));
ReferenceBinding targetBinding =
env.askForType(CharOperation.splitOn('.', itdTargetTypeName),env.getModule(ModuleBinding.ANY));
if (targetBinding != null) {
return resolver.subOrSuperOfFocus(targetBinding);
}
}
return false;
}
private char[] fullyQualifiedTargetTypeName(IntertypeElement itd) {
IType targetType = itd.findTargetType();
char[] itdTargetTypeName = targetType == null ? new char[0] : targetType.getFullyQualifiedName().replace('$', '.').toCharArray();
return itdTargetTypeName;
}
private List<SearchMatch> walkITDs(CompilationUnit ajDomUnit,
List<IntertypeElement> allItds, SearchPattern pattern,
PossibleMatch possibleMatch) throws JavaModelException {
List<SearchMatch> allExtraMatches = new LinkedList<SearchMatch>();
for (IntertypeElement itd : allItds) {
BodyDeclaration decl = findITDInDom(ajDomUnit, itd);
// we only care about ITD methods
if (decl instanceof InterTypeMethodDeclaration) {
allExtraMatches.addAll(findPatternInITD((InterTypeMethodDeclaration) decl, itd, pattern, possibleMatch));
}
}
return allExtraMatches;
}
private Collection<SearchMatch> findPatternInITD(
InterTypeMethodDeclaration decl, IntertypeElement itd,
SearchPattern pattern, PossibleMatch possibleMatch) {
ITDReferenceVisitor visitor = new ITDReferenceVisitor(itd,
pattern, possibleMatch.document.getParticipant());
Collection<SearchMatch> matches = visitor.doVisit(decl);
// tentative matches are found elsewhere
// matches.addAll(visitor.getTentativeMatches());
return matches;
}
private BodyDeclaration findITDInDom(CompilationUnit ajDomUnit,
IntertypeElement itd) throws JavaModelException {
List<AbstractTypeDeclaration> types = ajDomUnit.types();
for (AbstractTypeDeclaration type : types) {
BodyDeclaration maybeDecl = findITDInDom(type, itd);
if (maybeDecl != null) return maybeDecl;
}
return null;
}
private BodyDeclaration findITDInDom(AbstractTypeDeclaration type,
IntertypeElement itd) throws JavaModelException {
List<BodyDeclaration> decls = type.bodyDeclarations();
for (BodyDeclaration decl : decls) {
BodyDeclaration maybeDecl = null;
if (decl instanceof AbstractTypeDeclaration) {
maybeDecl = findITDInDom((AbstractTypeDeclaration) decl, itd);
} else if (decl instanceof InterTypeMethodDeclaration || decl instanceof InterTypeFieldDeclaration) {
int domStart = decl.getStartPosition();
int domEnd = domStart + decl.getLength();
ISourceRange sourceRange = itd.getSourceRange();
int eltStart = sourceRange.getOffset();
int eltEnd = eltStart + sourceRange.getLength();
// return this decl if one contains the other
if ((domStart <= eltStart && domEnd >= eltEnd) ||
(eltStart <= domStart && eltEnd >= domEnd)) {
maybeDecl = decl;
}
}
if (maybeDecl != null) {
return maybeDecl;
}
}
return null;
}
private CompilationUnit getDom(AJCompilationUnit unit) throws JavaModelException {
ASTParser parser = ASTParser.newParser(AST.JLS3);
unit.requestOriginalContentMode();
parser.setSource(unit.getContents());
parser.setCompilerOptions(unit.getJavaProject().getOptions(true));
unit.discardOriginalContentMode();
return (CompilationUnit) parser.createAST(new NullProgressMonitor());
}
private List<IntertypeElement> getAllItds(IParent parent) throws JavaModelException {
IJavaElement[] children = parent.getChildren();
List<IntertypeElement> allItds = new LinkedList<IntertypeElement>();
for (IJavaElement elt : children) {
if (elt instanceof IntertypeElement) {
allItds.add((IntertypeElement) elt);
} else if (elt.getElementType() == IJavaElement.TYPE) {
allItds.addAll(getAllItds((IParent) elt));
}
}
return allItds;
}
}