/*
* 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.codebrowsing.requestor;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.eclipse.GroovyLogManager;
import org.codehaus.groovy.eclipse.TraceCategory;
import org.codehaus.groovy.eclipse.codebrowsing.elements.GroovyResolvedBinaryField;
import org.codehaus.groovy.eclipse.codebrowsing.elements.GroovyResolvedBinaryMethod;
import org.codehaus.groovy.eclipse.codebrowsing.elements.GroovyResolvedBinaryType;
import org.codehaus.groovy.eclipse.codebrowsing.elements.GroovyResolvedSourceField;
import org.codehaus.groovy.eclipse.codebrowsing.elements.GroovyResolvedSourceMethod;
import org.codehaus.groovy.eclipse.codebrowsing.elements.GroovyResolvedSourceType;
import org.codehaus.groovy.eclipse.core.GroovyCore;
import org.codehaus.groovy.eclipse.core.model.GroovyProjectFacade;
import org.codehaus.groovy.eclipse.core.util.ArrayUtils;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.groovy.core.util.GroovyUtils;
import org.eclipse.jdt.groovy.search.AccessorSupport;
import org.eclipse.jdt.groovy.search.GenericsMapper;
import org.eclipse.jdt.groovy.search.ITypeRequestor;
import org.eclipse.jdt.groovy.search.TypeLookupResult;
import org.eclipse.jdt.groovy.search.VariableScope;
import org.eclipse.jdt.internal.core.BinaryType;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.LocalVariable;
import org.eclipse.jdt.internal.core.SourceType;
import org.eclipse.jdt.internal.core.util.Util;
/**
* Type requestor for code selection (i.e., hovers and open declaration).
*/
public class CodeSelectRequestor implements ITypeRequestor {
/** The AST node of interest. */
private final ASTNode nodeToLookFor;
private final Region nodeRegion;
private final Region selectRegion;
private final GroovyCompilationUnit gunit;
private final GroovyProjectFacade project;
private ASTNode requestedNode;
private IJavaElement requestedElement;
public CodeSelectRequestor(ASTNode node, GroovyCompilationUnit unit) {
this(node, null, new Region(Integer.MIN_VALUE, 0), unit);
}
public CodeSelectRequestor(ASTNode node, Region nodeRegion, Region selectRegion, GroovyCompilationUnit unit) {
nodeToLookFor = node;
this.nodeRegion = nodeRegion;
this.selectRegion = selectRegion;
gunit = unit;
project = new GroovyProjectFacade(unit);
}
public ASTNode getRequestedNode() {
return requestedNode;
}
public IJavaElement getRequestedElement() {
return requestedElement;
}
public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result, IJavaElement enclosingElement) {
boolean found = false;
try {
// check if enclosingElement does not enclose nodeToLookFor
if (!interestingElement(enclosingElement)) {
return VisitStatus.CANCEL_MEMBER;
}
if (node instanceof ImportNode && node != nodeToLookFor) {
node = ((ImportNode) node).getType();
if (node == null) { // wildcard?
return VisitStatus.CONTINUE;
}
}
found = (node == nodeToLookFor);
if (!found && node.getEnd() > 0) {
found = (node.getStart() == nodeToLookFor.getStart()) &&
(node.getEnd() == nodeToLookFor.getEnd()) &&
(typeOf(node).equals(typeOf(nodeToLookFor)));
}
if (found) {
handleMatch(result, enclosingElement);
}
} catch (JavaModelException e) {
GroovyCore.logException("Problem with code selection for ASTNode: " + node, e);
}
return !found ? VisitStatus.CONTINUE : VisitStatus.STOP_VISIT;
}
/**
* @return {@code true} iff {@code enclosingElement}'s source location
* contains the source location of {@link #nodeToLookFor}
*/
private boolean interestingElement(IJavaElement enclosingElement) throws JavaModelException {
// the clinit is always interesting since the clinit contains static initializers
if (enclosingElement.getElementName().equals("<clinit>")) {
return true;
}
if (enclosingElement instanceof ISourceReference) {
ISourceRange range = ((ISourceReference) enclosingElement).getSourceRange();
int start = range.getOffset(), until = range.getOffset() + range.getLength();
boolean covers = start <= nodeToLookFor.getStart() && until >= nodeToLookFor.getEnd();
covers = covers || (start <= selectRegion.getOffset() && until >= selectRegion.getEnd());
return covers;
}
return false;
}
/**
* Fills in {@link #requestedNode} and {@link #requestedElement} from the
* specified type lookup result and enclosing element that match up with
* {@link #nodeToLookFor}.
*/
private void handleMatch(TypeLookupResult result, IJavaElement enclosingElement) throws JavaModelException {
requestedNode = result.declaration;
if (requestedNode instanceof ClassNode) {
ClassNode classNode = (ClassNode) requestedNode;
if (!GroovyUtils.getBaseType(classNode).isGenericsPlaceHolder()) {
requestedNode = classNode.redirect();
} else {
requestedElement = findTypeParam(
GroovyUtils.getBaseType(classNode).getUnresolvedName(), enclosingElement);
return;
}
}
if (requestedNode != null) {
if (result.declaration instanceof VariableExpression) {
VariableExpression varExp = (VariableExpression) result.declaration;
// look in the local scope
requestedElement = createLocalVariable(result, enclosingElement, varExp);
} else if (result.declaration instanceof Parameter) {
Parameter param = (Parameter) result.declaration;
// look in the local scope
int position = param.getStart() - 1;
if (position < 0) {
// could be implicit parameter like 'it'
position = nodeToLookFor.getStart() - 1;
}
try {
requestedElement = createLocalVariable(result, gunit.getElementAt(position), param);
} catch (JavaModelException e) {
Util.log(e, "Problem getting element at " + position + " for file " + gunit.getElementName());
}
} else if (nodeToLookFor instanceof PackageNode) {
int start = nodeToLookFor.getStart(), until = selectRegion.getEnd();
if (start < until) {
String pack = gunit.getSource().substring(start, until);
IPackageFragmentRoot root = gunit.getPackageFragmentRoot();
requestedElement = root.getPackageFragment(pack);
}
} else if (nodeToLookFor instanceof ImportNode &&
((ImportNode) nodeToLookFor).isStar() && !((ImportNode) nodeToLookFor).isStatic()) {
int start = nodeToLookFor.getStart(), until = selectRegion.getEnd();
if (start < until) {
String pack = gunit.getSource().substring(start, until).replaceFirst("^import\\s+", "");
for (IPackageFragmentRoot root : gunit.getJavaProject().getPackageFragmentRoots()) {
IPackageFragment frag = root.getPackageFragment(pack);
if (frag != null && frag.exists()) {
requestedElement = frag;
break;
}
}
}
requestedNode = nodeToLookFor; // result.declaration should be java.lang.Object here
} else {
String qualifier = checkQualifiedType(result, enclosingElement);
ClassNode declaringType = findDeclaringType(result);
if (declaringType != null) {
ClassNode effectiveDeclaringType = declaringType;
if (declaringType.getEnclosingMethod() != null) {
// inner class, assume anonymous
effectiveDeclaringType = declaringType.getEnclosingMethod().getDeclaringClass();
}
// find it in the java model
IType type = project.groovyClassToJavaType(declaringType);
if (type == null && !gunit.isOnBuildPath()) {
// try to find it in the current compilation unit
type = gunit.getType(effectiveDeclaringType.getNameWithoutPackage());
if (!type.exists()) {
type = null;
}
}
if (type != null) {
if (qualifier == null) {
// find the requested java element
IJavaElement maybeRequested = findRequestedElement(result.declaration, effectiveDeclaringType, type);
// try to resolve the type of the requested element; this will add the proper metadata to the hover
requestedElement = resolveRequestedElement(result, maybeRequested);
} else {
// try to resolve as a type (outer class) then as a package
IType candidate = gunit.getJavaProject().findType(qualifier);
if (candidate != null) {
requestedElement = candidate;
} else {
IPackageFragmentRoot root;
if (type instanceof BinaryType) {
root = (IPackageFragmentRoot) ((BinaryType) type).getPackageFragment().getParent();
} else {
root = (IPackageFragmentRoot) ((SourceType) type).getPackageFragment().getParent();
}
requestedElement = root.getPackageFragment(qualifier);
}
requestedNode = nodeToLookFor;
}
}
} else {
String message = "Could not proceed due to null declaring type for " + requestedNode;
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.CODE_SELECT, message);
} else {
System.err.println(getClass().getSimpleName() + ": " + message);
}
}
}
}
}
private LocalVariable createLocalVariable(TypeLookupResult result, IJavaElement enclosingElement, Variable var) {
ClassNode type = result.type != null ? result.type : var.getType();
int start;
if (var instanceof Parameter) {
start = ((Parameter) var).getNameStart();
} else {
start = ((VariableExpression) var).getStart();
}
int until = start + var.getName().length() - 1;
String signature = GroovyUtils.getTypeSignature(type, false, false);
return new LocalVariable((JavaElement) enclosingElement, var.getName(), start, until, start, until, signature, null, 0, false);
}
private String checkQualifiedType(TypeLookupResult result, IJavaElement enclosingElement) throws JavaModelException {
if (result.declaration instanceof ClassNode ||
result.declaration instanceof ConstructorNode /*||
result.declaration instanceof DeclarationExpression*/) {
ClassNode type = result.type;
if (type == null) type = result.declaringType;
if (type == null) type = (ClassNode) result.declaration;
int typeStart = startOffset(type), typeEnd = endOffset(type);
type = GroovyUtils.getBaseType(type); // unpack type now that position is known
if (typeStart >= 0 && typeEnd > typeStart) {
String gunitSource = gunit.getSource();
if (typeEnd == gunitSource.length() + 1)
typeEnd = gunitSource.length(); // off by one?
else if (typeEnd > gunitSource.length()) return null;
String source = gunitSource.substring(typeStart, typeEnd);
int nameStart = typeStart + source.indexOf(GroovyUtils.splitName(type)[1]);
// check for code selection on the type name's qualifier string
if (nameStart > typeStart && nameStart > selectRegion.getEnd() && selectRegion.getEnd() > typeStart) {
String selected = gunitSource.substring(typeStart, selectRegion.getEnd());
selected = selected.replaceAll("\\.$", ""); // remove any trailing dot
String qualifier = GroovyUtils.splitName(type)[0].replace('$', '.');
// check for selection in fully-qualified name like 'java.lang.String'
Pattern pattern = Pattern.compile("^\\Q" + selected + "\\E\\w*");
Matcher matcher = pattern.matcher(qualifier);
if (matcher.find()) {
return matcher.group();
}
// check for selection in qualified name like 'Map.Entry'
pattern = Pattern.compile("\\b\\Q" + selected + "\\E(\\b|$)");
matcher = pattern.matcher(qualifier);
if (matcher.find()) {
return qualifier.substring(0, matcher.end());
}
// check for selection in aliased name like 'Foo.Entry' with 'import java.util.Map as Foo'
ImportNode alias = findImportAlias(selected, enclosingElement);
if (alias != null) {
// decode 'Foo' to 'Map' and try again, because qualifier could be 'java.util.Map'
selected = selected.replace(alias.getAlias(), alias.getType().getNameWithoutPackage());
pattern = Pattern.compile("\\b\\Q" + selected + "\\E(\\b|$)");
matcher = pattern.matcher(qualifier);
if (matcher.find()) {
return qualifier.substring(0, matcher.end());
}
}
}
}
}
return null;
}
/**
* Finds the declaring type of the specified lookup result.
*/
private ClassNode findDeclaringType(TypeLookupResult result) {
ClassNode declaringType = null;
if (result.declaringType != null) {
declaringType = GroovyUtils.getBaseType(result.declaringType);
} else if (result.declaration instanceof ClassNode) {
declaringType = GroovyUtils.getBaseType((ClassNode) result.declaration);
} else if (result.declaration instanceof FieldNode) {
declaringType = ((FieldNode) result.declaration).getDeclaringClass();
} else if (result.declaration instanceof MethodNode) {
declaringType = ((MethodNode) result.declaration).getDeclaringClass();
} else if (result.declaration instanceof PropertyNode) {
declaringType = ((PropertyNode) result.declaration).getDeclaringClass();
} else if (result.declaration instanceof DeclarationExpression) {
declaringType = GroovyUtils.getBaseType(((DeclarationExpression) result.declaration).getLeftExpression().getType());
}
return declaringType;
}
private ImportNode findImportAlias(String name, IJavaElement enclosingElement) {
IJavaElement elem = enclosingElement;
while (!(elem instanceof GroovyCompilationUnit)) {
elem = elem.getParent();
}
int dot = name.indexOf('.');
return ((GroovyCompilationUnit) elem).getModuleNode().getImport(dot < 0 ? name : name.substring(0, dot));
}
private ITypeParameter findTypeParam(String name, IJavaElement enclosingElement) throws JavaModelException {
ITypeParameter typeParam = null;
if (enclosingElement instanceof IType) {
for (ITypeParameter tp : ((IType) enclosingElement).getTypeParameters()) {
if (tp.getElementName().equals(name)) {
typeParam = tp;
break;
}
}
} else if (enclosingElement instanceof IMethod) {
for (ITypeParameter tp : ((IMethod) enclosingElement).getTypeParameters()) {
if (tp.getElementName().equals(name)) {
typeParam = tp;
break;
}
}
}
if (typeParam == null && enclosingElement.getParent() != null) {
typeParam = findTypeParam(name, enclosingElement.getParent());
}
return typeParam;
}
private IJavaElement findRequestedElement(ASTNode declaration, ClassNode declaringType, IType jdtDeclaringType) throws JavaModelException {
IJavaElement maybeRequested = null;
if (declaration instanceof ClassNode) {
maybeRequested = jdtDeclaringType;
} else if (jdtDeclaringType.getTypeRoot() != null) {
if (declaration.getEnd() > 0) {
// GRECLIPSE-1233 can't use getEltAt because of default parameters.
// instead, just iterate through children. Method variants
// are always after the original method
int start = declaration.getStart();
int end = declaration.getEnd();
String name;
if (declaration instanceof MethodNode) {
name = ((MethodNode) declaration).getName();
if (name.equals("<init>")) {
name = jdtDeclaringType.getElementName();
}
// check for @Trait method
@SuppressWarnings("unchecked")
List<MethodNode> traitMethods = (List<MethodNode>) declaringType.getNodeMetaData("trait.methods");
if (traitMethods != null) {
for (MethodNode method : traitMethods) {
if (method == declaration) {
return jdtDeclaringType.getMethod(name, null);
}
}
}
} else if (declaration instanceof FieldNode) {
name = ((FieldNode) declaration).getName();
// check for @Lazy field
Iterable<AnnotationNode> annos = ((FieldNode) declaration).getAnnotations();
if (annos != null) {
for (AnnotationNode anno : annos) {
if (anno.getClassNode().getNameWithoutPackage().equals("Lazy") && name.charAt(0) == '$') {
name = name.substring(1); // strip the leading $
}
}
}
// check for @Trait field
@SuppressWarnings("unchecked")
List<FieldNode> traitFields = (List<FieldNode>) declaringType.getNodeMetaData("trait.fields");
if (traitFields != null) {
for (FieldNode field : traitFields) {
if (field == declaration) {
return jdtDeclaringType.getField(name);
}
}
}
} else if (declaration instanceof PropertyNode) {
name = ((PropertyNode) declaration).getName();
} else {
name = declaration.getText();
}
for (IJavaElement child : jdtDeclaringType.getChildren()) {
ISourceRange range = ((ISourceReference) child).getSourceRange();
if (range.getOffset() <= start && range.getOffset() + range.getLength() >= end && child.getElementName().equals(name)) {
maybeRequested = child;
break;
} else if (start + end < range.getOffset()) {
// since children are listed incrementally no need to go further
break;
}
}
}
if (maybeRequested == null) { // try something else because source location not set right
String name = null;
Parameter[] parameters = null;
if (declaration instanceof MethodNode) {
name = ((MethodNode) declaration).getName();
parameters = ((MethodNode) declaration).getParameters();
} else if (declaration instanceof PropertyNode) {
name = ((PropertyNode) declaration).getName();
} else if (declaration instanceof FieldNode) {
name = ((FieldNode) declaration).getName();
}
if (name != null) {
maybeRequested = findElement(jdtDeclaringType, name, parameters);
}
if (maybeRequested == null) {
// still couldn't find anything
maybeRequested = jdtDeclaringType;
}
}
}
return maybeRequested;
}
/**
* Converts the maybeRequested element into a resolved element by creating a unique key for it.
*/
private IJavaElement resolveRequestedElement(TypeLookupResult result, IJavaElement maybeRequested) {
AnnotatedNode declaration = (AnnotatedNode) result.declaration;
if (declaration instanceof PropertyNode && maybeRequested instanceof IMethod) {
// the field associated with this property does not exist, use the method instead
String getterName = maybeRequested.getElementName();
MethodNode maybeDeclaration = declaration.getDeclaringClass().getMethods(getterName).get(0);
declaration = maybeDeclaration == null ? declaration : maybeDeclaration;
}
if (declaration instanceof ConstructorNode && maybeRequested.getElementType() == IJavaElement.TYPE) {
// implicit default constructor. use type instead
declaration = declaration.getDeclaringClass();
}
String uniqueKey = createUniqueKey(declaration, result.type, result.declaringType, maybeRequested);
IJavaElement candidate;
// Create the Groovy Resolved Element, which is like a resolved element, but contains extraDoc, as
// well as the inferred declaration (which may not be the same as the actual declaration)
switch (maybeRequested.getElementType()) {
case IJavaElement.FIELD:
if (maybeRequested.isReadOnly()) {
candidate = new GroovyResolvedBinaryField((JavaElement) maybeRequested.getParent(), maybeRequested.getElementName(), uniqueKey, result.extraDoc, result.declaration);
} else {
candidate = new GroovyResolvedSourceField((JavaElement) maybeRequested.getParent(), maybeRequested.getElementName(), uniqueKey, result.extraDoc, result.declaration);
}
break;
case IJavaElement.METHOD:
if (maybeRequested.isReadOnly()) {
candidate = new GroovyResolvedBinaryMethod((JavaElement) maybeRequested.getParent(), maybeRequested.getElementName(), ((IMethod) maybeRequested).getParameterTypes(), uniqueKey, result.extraDoc, result.declaration);
} else {
candidate = new GroovyResolvedSourceMethod((JavaElement) maybeRequested.getParent(), maybeRequested.getElementName(), ((IMethod) maybeRequested).getParameterTypes(), uniqueKey, result.extraDoc, result.declaration);
}
break;
case IJavaElement.TYPE:
if (maybeRequested.isReadOnly()) {
candidate = new GroovyResolvedBinaryType((JavaElement) maybeRequested.getParent(), maybeRequested.getElementName(), uniqueKey, result.extraDoc, result.declaration);
} else {
candidate = new GroovyResolvedSourceType((JavaElement) maybeRequested.getParent(), maybeRequested.getElementName(), uniqueKey, result.extraDoc, result.declaration);
}
break;
default:
candidate = maybeRequested;
}
requestedElement = candidate;
return requestedElement;
}
/**
* Creates the unique key for classes, fields and methods.
*/
private String createUniqueKey(AnnotatedNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType, IJavaElement maybeRequested) {
if (resolvedDeclaringType == null) {
resolvedDeclaringType = node.getDeclaringClass();
if (resolvedDeclaringType == null) {
resolvedDeclaringType = VariableScope.OBJECT_CLASS_NODE;
}
}
if (node instanceof PropertyNode) {
node = ((PropertyNode) node).getField();
}
StringBuilder sb = new StringBuilder();
if (node instanceof FieldNode) {
appendUniqueKeyForField(sb, (FieldNode) node, resolvedType, resolvedDeclaringType);
} else if (node instanceof MethodNode) {
if (maybeRequested.getElementType() == IJavaElement.FIELD) {
// this is likely a generated getter or setter
appendUniqueKeyForGeneratedAccessor(sb, (MethodNode) node, resolvedType, resolvedDeclaringType, (IField) maybeRequested);
} else {
appendUniqueKeyForMethod(sb, (MethodNode) node, resolvedType, resolvedDeclaringType);
}
} else if (node instanceof ClassNode) {
appendUniqueKeyForClass(sb, resolvedType, resolvedDeclaringType);
}
return sb.toString();
}
private void appendUniqueKeyForField(StringBuilder sb, FieldNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType) {
appendUniqueKeyForClass(sb, node.getDeclaringClass(), resolvedDeclaringType);
sb.append('.').append(node.getName()).append(')');
appendUniqueKeyForResolvedClass(sb, resolvedType);
}
private void appendUniqueKeyForMethod(StringBuilder sb, MethodNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType) {
// TODO: This method does not handle capture types like Java does; example for Plain.class.newInstance():
// LPlain;&Ljava/lang/Class<!Ljava/lang/Class;{0}+LPlain;152;>;.newInstance()!+LPlain;|Ljava/lang/InstantiationException;|Ljava/lang/IllegalAccessException;
// declaring type
appendUniqueKeyForClass(sb, node.getDeclaringClass(), resolvedDeclaringType);
// method name
String methodName = node.getName();
sb.append(Signature.C_DOT).append(methodName.equals("<init>") ? node.getDeclaringClass().getNameWithoutPackage() : methodName);
// generic types
GenericsType[] generics = GroovyUtils.getGenericsTypes(node);
if (generics.length > 0) {
sb.append(Signature.C_GENERIC_START);
for (GenericsType gt : generics) {
appendUniqueKeyForGenericsType(sb, gt);
}
sb.append(Signature.C_GENERIC_END);
}
// parameters
sb.append(Signature.C_PARAM_START);
Parameter[] parameters = node.getOriginal().getParameters();
if (parameters != null) {
for (Parameter param : parameters) {
ClassNode paramType = param.getType();
appendUniqueKeyForClass(sb, paramType, resolvedDeclaringType);
}
}
sb.append(Signature.C_PARAM_END);
// return type
appendUniqueKeyForClass(sb, node.getOriginal().getReturnType(), resolvedDeclaringType);
// generic type resolution
if (generics.length > 0) {
GenericsMapper mapper = GenericsMapper.gatherGenerics(GroovyUtils.getParameterTypes(node.getParameters()), resolvedDeclaringType, node.getOriginal());
sb.append('%');
sb.append(Signature.C_GENERIC_START);
for (GenericsType gt : generics) {
gt = VariableScope.clone(gt, 0);
ClassNode rt = VariableScope.resolveTypeParameterization(mapper, gt, gt.getType());
sb.append(GroovyUtils.getTypeSignatureWithoutGenerics(rt, true, true).replace('.', '/'));
}
sb.append(Signature.C_GENERIC_END);
}
// exceptions
if (node.getExceptions() != null) {
for (ClassNode exception : node.getExceptions()) {
sb.append(Signature.C_INTERSECTION);
appendUniqueKeyForClass(sb, exception, resolvedDeclaringType);
}
}
}
private void appendUniqueKeyForGeneratedAccessor(StringBuilder sb, MethodNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType, IField actualField) {
appendUniqueKeyForClass(sb, node.getDeclaringClass(), resolvedDeclaringType);
sb.append('.').append(actualField.getElementName()).append(')');
ClassNode typeOfField = node.getName().startsWith("set") && node.getParameters() != null && node.getParameters().length > 0 ? node.getParameters()[0].getType(): resolvedType;
appendUniqueKeyForResolvedClass(sb, typeOfField);
}
/**
* Tries to resolve any type parameters in unresolvedType based on those in resolvedDeclaringType.
*
* @param unresolvedType unresolved type whose type parameters need to be resolved
* @param resolvedDeclaringType the resolved type that is the context in which to resolve it.
*/
private void appendUniqueKeyForClass(StringBuilder sb, ClassNode unresolvedType, ClassNode resolvedDeclaringType) {
GenericsMapper mapper = GenericsMapper.gatherGenerics(resolvedDeclaringType, resolvedDeclaringType.redirect());
ClassNode resolvedType = VariableScope.resolveTypeParameterization(mapper, VariableScope.clone(unresolvedType));
appendUniqueKeyForResolvedClass(sb, resolvedType);
}
private void appendUniqueKeyForResolvedClass(StringBuilder sb, ClassNode resolvedType) {
String signature = GroovyUtils.getTypeSignature(resolvedType, true, true);
sb.append(signature.replace('.', '/'));
}
private void appendUniqueKeyForGenericsType(StringBuilder sb, GenericsType gt) {
String[] bounds = CharOperation.NO_STRINGS;
if (gt.getLowerBound() != null) {
ClassNode lb = gt.getLowerBound();
bounds = (String[]) ArrayUtils.add(bounds, GroovyUtils.getTypeSignature(lb, true, true));
} else if (gt.getUpperBounds() != null) {
for (ClassNode ub : gt.getUpperBounds()) {
bounds = (String[]) ArrayUtils.add(bounds, GroovyUtils.getTypeSignature(ub, true, true));
}
}
String signature = Signature.createTypeParameterSignature(gt.getName(), bounds);
sb.append(signature.replace('.', '/'));
}
private IJavaElement findElement(IType type, String text, Parameter[] parameters) throws JavaModelException {
if (text.equals(type.getElementName())) {
return type;
}
if (text.equals("<init>")) {
text = type.getElementName();
}
// check for methods first, then fields, and finally accessor variants of the name
StringBuilder checked = new StringBuilder();
IMethod closestMatch = null;
next_method: for (IMethod method : type.getMethods()) {
if (method.getElementName().equals(text)) {
closestMatch = method;
// prefer methods with the same parameter list
if (parameters != null && parameters.length == method.getParameterTypes().length) {
checked.append("\n\t").append(text).append('(');
for (int i = 0, n = parameters.length; i < n; i += 1) {
// remove generics from the type signatures to make matching simpler
String jdtMethodParam = removeGenerics(method.getParameterTypes()[i]);
String astMethodParam = GroovyUtils.getTypeSignatureWithoutGenerics(
parameters[i].getOriginType(), jdtMethodParam.indexOf('.') > 0, type.isBinary());
checked.append(jdtMethodParam).append(' ');
if (!astMethodParam.equals(jdtMethodParam)) {
continue next_method;
}
}
return method;
}
}
}
if (closestMatch != null) {
String message = String.format("%s.findElement: no exact match found for %s(%s); options considered:%s",
getClass().getSimpleName(), text, parameters == null ? "" : GroovyUtils.getParameterTypes(parameters), checked);
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.CODE_SELECT, message);
} else {
System.err.println(getClass().getSimpleName() + ": " + message);
}
return closestMatch;
}
IField field = type.getField(text);
String prefix;
if (!field.exists() && (prefix = extractPrefix(text)) != null) {
// try as a property
String newName = Character.toLowerCase(text.charAt(prefix.length())) + text.substring(prefix.length() + 1);
field = type.getField(newName);
}
if (field.exists()) {
return field;
}
String setMethod = AccessorSupport.SETTER.createAccessorName(text);
String getMethod = AccessorSupport.GETTER.createAccessorName(text);
String isMethod = AccessorSupport.ISSER.createAccessorName(text);
for (IMethod method : type.getMethods()) {
String methodName = method.getElementName();
if (methodName.equals(setMethod) || methodName.equals(getMethod) || methodName.equals(isMethod)) {
return method;
}
}
return null;
}
private static String removeGenerics(String param) {
// TODO: Check for nested generics
int genericStart = param.indexOf('<');
if (genericStart > 0) {
param = param.substring(0, genericStart) + param.substring(param.indexOf('>') + 1, param.length());
}
return param;
}
private static String extractPrefix(String text) {
if (text.startsWith("is")) {
if (text.length() > 2) {
return "is";
}
} else if (text.startsWith("get")) {
if (text.length() > 3) {
return "get";
}
} else if (text.startsWith("set")) {
if (text.length() > 3) {
return "set";
}
}
return null;
}
private int startOffset(ClassNode type) {
int start = type.getStart();
if (nodeToLookFor instanceof ImportNode) {
// recover the qualifier position for imports
start = endOffset(type) - type.getName().length();
} else if (type.getEnd() < 1) {
if (nodeRegion != null) {
start = nodeRegion.getOffset();
} else {
start = nodeToLookFor.getStart();
}
}
return start;
}
private int endOffset(ClassNode type) {
int end = type.getEnd();
if (nodeToLookFor instanceof ImportNode) {
end = ((ImportNode) nodeToLookFor).getTypeEnd();
} else if (end < 1) {
if (nodeRegion != null) {
end = nodeRegion.getEnd();
} else {
end = nodeToLookFor.getEnd();
}
}
return end;
}
private Object typeOf(ASTNode node) {
if (node instanceof ClassNode) {
return ((ClassNode) node).getNameWithoutPackage();
}
return node.getClass();
}
}