/*
* Copyright 2009-2017 the original author or authors.
*
* 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 org.codehaus.groovy.eclipse.search;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.eclipse.codebrowsing.requestor.CodeSelectHelper;
import org.codehaus.groovy.eclipse.core.search.FindAllReferencesRequestor;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.groovy.core.util.ReflectionUtils;
import org.eclipse.jdt.groovy.search.TypeInferencingVisitorFactory;
import org.eclipse.jdt.groovy.search.TypeInferencingVisitorWithRequestor;
import org.eclipse.jdt.internal.ui.search.FindOccurrencesEngine;
public class GroovyOccurrencesFinder {
private AnnotatedNode nodeToLookFor;
private GroovyCompilationUnit gunit;
private CompilationUnit cunit;
private String elementName;
public String getElementName() {
if (elementName == null) {
if (nodeToLookFor instanceof ClassNode) {
// handle any potential inner classes
String name = ((ClassNode) nodeToLookFor).getNameWithoutPackage();
int lastDollar = name.lastIndexOf('$');
elementName = name.substring(lastDollar + 1);
} else if (nodeToLookFor instanceof MethodNode) {
elementName = ((MethodNode) nodeToLookFor).getName();
} else if (nodeToLookFor instanceof FieldNode) {
elementName = ((FieldNode) nodeToLookFor).getName();
} else if (nodeToLookFor instanceof Variable) {
elementName = ((Variable) nodeToLookFor).getName();
}
}
return elementName;
}
public ASTNode getNodeToLookFor() {
return nodeToLookFor;
}
public OccurrenceLocation[] getOccurrences() {
Map<ASTNode, Integer> occurences = internalFindOccurences();
OccurrenceLocation[] locations = new OccurrenceLocation[occurences.size()];
int i = 0;
for (Entry<ASTNode, Integer> entry : occurences.entrySet()) {
ASTNode node = entry.getKey();
int flag = entry.getValue();
OccurrenceLocation occurrenceLocation;
if (node instanceof FieldNode) {
FieldNode c = (FieldNode) node;
occurrenceLocation = new OccurrenceLocation(c.getNameStart(), c.getNameEnd() - c.getNameStart() + 1, flag, "Occurrence of ''" + getElementName() + "''");
} else if (node instanceof MethodNode) {
MethodNode c = (MethodNode) node;
occurrenceLocation = new OccurrenceLocation(c.getNameStart(), c.getNameEnd() - c.getNameStart() + 1, flag, "Occurrence of ''" + getElementName() + "''");
} else if (node instanceof Parameter) {
// should be finding the start and end of the name region only,
// but this finds the entire declaration
Parameter c = (Parameter) node;
int start = c.getNameStart();
int length = c.getNameEnd() - c.getNameStart();
occurrenceLocation = new OccurrenceLocation(start, length, flag, "Occurrence of ''" + getElementName() + "''");
} else if (node instanceof ClassNode && ((ClassNode) node).getNameEnd() > 0) {
// class declaration
ClassNode c = (ClassNode) node;
occurrenceLocation = new OccurrenceLocation(c.getNameStart(), c.getNameEnd() - c.getNameStart() + 1, flag, "Occurrence of ''" + getElementName() + "''");
} else if (node instanceof StaticMethodCallExpression) {
// special case...for static method calls, the start and end are
// of the entire expression, but we just want the name.
StaticMethodCallExpression smce = (StaticMethodCallExpression) node;
occurrenceLocation = new OccurrenceLocation(smce.getStart(), Math.min(smce.getLength(), smce.getMethod().length()), flag, "Occurrence of ''" + getElementName() + "''");
} else {
SourceRange range = getSourceRange(node);
occurrenceLocation = new OccurrenceLocation(range.getOffset(), range.getLength(), flag, "Occurrence of ''" + getElementName() + "''");
}
locations[i++] = occurrenceLocation;
}
return locations;
}
private SourceRange getSourceRange(ASTNode node) {
if (node instanceof ConstructorCallExpression) {
// want to select the type name, not the entire expression
node = ((ConstructorCallExpression) node).getType();
}
if (node instanceof ClassNode) {
// handle inner classes referenced semi-qualified
String semiQualifiedName = ((ClassNode) node).getNameWithoutPackage();
if (semiQualifiedName.contains("$")) {
// it is semiqualified if the length is larger than the name
String name = getElementName();
if (name.length() < node.getLength() - 1) {
// we know that the name is semiqualified
// find the simple name inside the semi-qualified name
int simpleNameStart = semiQualifiedName.indexOf(name);
if (simpleNameStart >= 0) {
return new SourceRange(node.getStart() + simpleNameStart, name.length());
}
}
}
}
return new SourceRange(node.getStart(), node.getLength());
}
private Map<ASTNode, Integer> internalFindOccurences() {
if (nodeToLookFor != null &&
!(nodeToLookFor instanceof ConstantExpression) &&
!(nodeToLookFor instanceof ClosureExpression) &&
!(nodeToLookFor instanceof DeclarationExpression) &&
!(nodeToLookFor instanceof BinaryExpression) &&
!(nodeToLookFor instanceof MethodCallExpression)) {
FindAllReferencesRequestor requestor = new FindAllReferencesRequestor(nodeToLookFor);
TypeInferencingVisitorWithRequestor visitor = new TypeInferencingVisitorFactory().createVisitor(gunit);
visitor.visitCompilationUnit(requestor);
Map<ASTNode, Integer> occurences = requestor.getReferences();
return occurences;
}
return Collections.emptyMap();
}
/**
* Finds the {@link ASTNode} to look for.
*/
public String initialize(CompilationUnit root, int offset, int length) {
cunit = root;
if (gunit == null) {
ITypeRoot typeRoot = cunit.getTypeRoot();
if (!(typeRoot instanceof GroovyCompilationUnit)) {
return "Can't find occurrenes...not a Groovy file.";
}
gunit = (GroovyCompilationUnit) typeRoot;
}
ModuleNode moduleNode = gunit.getModuleNode();
if (moduleNode == null) {
return "Can't find occurrences...no module node.";
}
CodeSelectHelper helper = new CodeSelectHelper();
ASTNode node = helper.selectASTNode(gunit, offset, length);
if (!(node instanceof AnnotatedNode)) {
return "Can't find occurrences...invalid selection";
}
if (node instanceof PropertyNode) {
PropertyNode property = (PropertyNode) node;
// hmmm...how do we handle synthetic field nodes based on a getter or setter?
node = property.getField();
}
nodeToLookFor = (AnnotatedNode) node;
return null;
}
public String initialize(CompilationUnit root, org.eclipse.jdt.core.dom.ASTNode node) {
return initialize(root, node.getStartPosition(), node.getLength());
}
//--------------------------------------------------------------------------
// for FindOccurrencesTests only!
public final void setGroovyCompilationUnit(GroovyCompilationUnit gunit) {
this.gunit = gunit;
}
// for GroovyEditor only!
public static Object findOccurrences(CompilationUnit cunit, int offset, int length) {
GroovyOccurrencesFinder finder = new GroovyOccurrencesFinder();
finder.initialize(cunit, offset, length);
OccurrenceLocation[] occurrences = finder.getOccurrences();
// convert to required types reflectively
try {
if (OCCURRENCE_LOCATION == null) {
try {
OCCURRENCE_LOCATION = Class.forName("org.eclipse.jdt.internal.ui.search.IOccurrencesFinder$OccurrenceLocation");
} catch (ClassNotFoundException e) {
OCCURRENCE_LOCATION = Class.forName("org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder$OccurrenceLocation");
}
OCCURRENCE_LOCATION_CTOR = OCCURRENCE_LOCATION.getConstructor(int.class, int.class, int.class, String.class);
}
Object arr = Array.newInstance(OCCURRENCE_LOCATION, occurrences.length);
for (int idx = 0; idx < occurrences.length; idx += 1) {
OccurrenceLocation loc = occurrences[idx];
Array.set(arr, idx, OCCURRENCE_LOCATION_CTOR.newInstance(
loc.getOffset(), loc.getLength(), loc.getFlags(), loc.getDescription()));
}
return arr;
} catch (Throwable t) {
t.printStackTrace();
throw new RuntimeException(t);
}
}
/**
* FindOccurrencesEngine expects an instance of IOccurrencesFinder, which
* has been relocated sometime between Eclipse 4.7m4 and 4.7m5. In order
* to support both interfaces, a dynamic proxy is supplied to the engine.
*/
public static FindOccurrencesEngine newFinderEngine() {
try {
if (I_OCCURRENCES_FINDER == null) {
// load the interface
try {
I_OCCURRENCES_FINDER = new Class[] {Class.forName("org.eclipse.jdt.internal.ui.search.IOccurrencesFinder")};
} catch (ClassNotFoundException e) {
I_OCCURRENCES_FINDER = new Class[] {Class.forName("org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder")};
}
}
final GroovyOccurrencesFinder finder = new GroovyOccurrencesFinder();
// create invocation handler
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("getASTRoot".equals(methodName)) {
return finder.cunit;
}
if ("getID".equals(methodName)) {
return "GroovyOccurrencesFinder";
}
if ("getJobLabel".equals(methodName)) {
return "Search for Occurrences in File (Groovy)";
}
if ("getSearchKind".equals(methodName)) {
return 5; //IOccurrencesFinder.K_OCCURRENCE
}
if ("getUnformattedPluralLabel".equals(methodName)) {
return "''{0}'' - {1} occurrences in ''{2}''";
}
if ("getUnformattedSingularLabel".equals(methodName)) {
return "''{0}'' - 1 occurrence in ''{1}''";
}
return GroovyOccurrencesFinder.class.getMethod(methodName, method.getParameterTypes()).invoke(finder, args);
}
};
// create dynamic proxy to connect IOccurrencesFinder with GroovyOccurrencesFinder
Object proxy = Proxy.newProxyInstance(GroovyOccurrencesFinder.class.getClassLoader(), I_OCCURRENCES_FINDER, handler);
//return FindOccurrencesEngine.create(proxy of IOccurrencesFinder);
return (FindOccurrencesEngine) ReflectionUtils.throwableExecutePrivateMethod(
FindOccurrencesEngine.class, "create", I_OCCURRENCES_FINDER, FindOccurrencesEngine.class, new Object[] {proxy});
} catch (Throwable t) {
t.printStackTrace();
throw new RuntimeException(t);
}
}
// copied from IOccurrencesFinder
public static class OccurrenceLocation {
private final int fOffset;
private final int fLength;
private final int fFlags;
private final String fDescription;
public OccurrenceLocation(int offset, int length, int flags, String description) {
fOffset= offset;
fLength= length;
fFlags= flags;
fDescription= description;
}
public int getOffset() {
return fOffset;
}
public int getLength() {
return fLength;
}
public int getFlags() {
return fFlags;
}
public String getDescription() {
return fDescription;
}
@Override
public String toString() {
return "[" + fOffset + " / " + fLength + "] " + fDescription;
}
}
private static Class<?> OCCURRENCE_LOCATION;
private static Class<?>[] I_OCCURRENCES_FINDER;
private static Constructor<?> OCCURRENCE_LOCATION_CTOR;
}