Java tutorial
/* * Copyright 2003-2009 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 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.GenericsType; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.expr.VariableExpression; 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.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.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.groovy.core.util.ReflectionUtils; 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.JavaElement; import org.eclipse.jdt.internal.core.LocalVariable; import org.eclipse.jdt.internal.core.util.Util; /** * A type requestor for Code selection (i.e., hovers and open declaration) * @author Andrew Eisenberg * @created Nov 4, 2009 * */ public class CodeSelectRequestor implements ITypeRequestor { private final ASTNode nodeToLookFor; private IJavaElement requestedElement; private ASTNode requestedNode; private final GroovyProjectFacade project; private final GroovyCompilationUnit unit; public CodeSelectRequestor(ASTNode nodeToLookFor, GroovyCompilationUnit unit) { this.nodeToLookFor = nodeToLookFor; this.unit = unit; this.project = new GroovyProjectFacade(unit); } public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result, IJavaElement enclosingElement) { // check to see if the enclosing element does not enclose the nodeToLookFor if (!interestingElement(enclosingElement)) { return VisitStatus.CANCEL_MEMBER; } if (node instanceof ImportNode) { node = ((ImportNode) node).getType(); if (node == null) { return VisitStatus.CONTINUE; } } if (doTest(node)) { requestedNode = result.declaration; if (requestedNode instanceof ClassNode) { requestedNode = ((ClassNode) requestedNode).redirect(); } if (requestedNode != null) { if (result.declaration instanceof VariableExpression) { // look in the local scope VariableExpression var = (VariableExpression) result.declaration; requestedElement = createLocalVariable(result, enclosingElement, var); } else if (result.declaration instanceof Parameter) { // look in the local scope Parameter var = (Parameter) result.declaration; int position = var.getStart() - 1; if (position < 0) { // could be an implicit variable like 'it' position = nodeToLookFor.getStart() - 1; } try { requestedElement = createLocalVariable(result, (JavaElement) unit.getElementAt(position), var); } catch (JavaModelException e) { Util.log(e, "Problem getting element at " + position + " for file " + unit.getElementName()); } } else { ClassNode declaringType = findDeclaringType(result); if (declaringType != null) { // find it in the java model IType type = project.groovyClassToJavaType(declaringType); if (type == null && !unit.isOnBuildPath()) { // try to find it in the current compilation unit type = unit.getType(declaringType.getNameWithoutPackage()); if (!type.exists()) { type = null; } } if (type != null) { try { // find the requested java element IJavaElement maybeRequested = findRequestedElement(result, type); // try to resolve the type of the requested element. this will add the proper type parameters, etc to the hover requestedElement = resolveRequestedElement(result, maybeRequested); } catch (JavaModelException e) { GroovyCore.logException("Problem with code selection for ASTNode: " + node, e); } } } } } return VisitStatus.STOP_VISIT; } return VisitStatus.CONTINUE; } /** * @param enclosingElement * @return true iff enclosingElement's source location contains the source location of {@link #nodeToLookFor} */ private boolean interestingElement(IJavaElement enclosingElement) { // the clinit is always interesting since the clinit contains static initializers if (enclosingElement.getElementName().equals("<clinit>")) { return true; } if (enclosingElement instanceof ISourceReference) { try { ISourceRange range = ((ISourceReference) enclosingElement).getSourceRange(); return range.getOffset() <= nodeToLookFor.getStart() && range.getOffset() + range.getLength() >= nodeToLookFor.getEnd(); } catch (JavaModelException e) { Util.log(e); } } return false; } /** * find the declaring type, removing any array * @param result * @return */ private ClassNode findDeclaringType(TypeLookupResult result) { ClassNode declaringType = null; if (result.declaringType != null) { declaringType = removeArray(result.declaringType); } 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 ClassNode) { declaringType = removeArray((ClassNode) result.declaration); } } return declaringType; } /** * @param result * @param type * @throws JavaModelException */ private IJavaElement findRequestedElement(TypeLookupResult result, IType type) throws JavaModelException { IJavaElement maybeRequested = null; ASTNode declaration = result.declaration; if (declaration instanceof ClassNode) { maybeRequested = type; } else if (type.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 IJavaElement[] children = type.getChildren(); int start = declaration.getStart(); int end = declaration.getEnd(); String name; if (declaration instanceof MethodNode) { name = ((MethodNode) declaration).getName(); } else if (declaration instanceof FieldNode) { name = ((FieldNode) declaration).getName(); } else if (declaration instanceof PropertyNode) { name = ((PropertyNode) declaration).getName(); } else { name = declaration.getText(); } for (IJavaElement child : children) { 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; int preferredParamNumber = -1; if (declaration instanceof MethodNode) { name = ((MethodNode) declaration).getName(); Parameter[] parameters = ((MethodNode) declaration).getParameters(); preferredParamNumber = parameters == null ? 0 : parameters.length; } else if (declaration instanceof PropertyNode) { name = ((PropertyNode) declaration).getName(); } else if (declaration instanceof FieldNode) { name = ((FieldNode) declaration).getName(); } if (name != null) { maybeRequested = findElement(type, name, preferredParamNumber); } if (maybeRequested == null) { // still couldn't find anything maybeRequested = type; } } } 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 = (MethodNode) declaration.getDeclaringClass().getMethods(getterName) .get(0); declaration = maybeDeclaration == null ? declaration : maybeDeclaration; } 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; } private LocalVariable createLocalVariable(TypeLookupResult result, IJavaElement enclosingElement, Variable var) { ASTNode node = (ASTNode) var; ClassNode type = result.type != null ? result.type : var.getType(); int start; if (node instanceof Parameter) { start = ((Parameter) node).getNameStart(); } else { start = node.getStart(); } // be compatible between 3.6 and 3.7+ return ReflectionUtils.createLocalVariable(enclosingElement, var.getName(), start, Signature.createTypeSignature(createGenericsAwareName(type, true), false)); } /** * Creates the type signature for local variables * @param node * @return */ private String createGenericsAwareName(ClassNode node, boolean useSimple) { StringBuilder sb = new StringBuilder(); String name = node.getName(); StringBuilder sbArr; if (name.charAt(0) == '[') { sbArr = new StringBuilder(); int arrayCount = 0; while (name.charAt(arrayCount) == '[') { sbArr.append("[]"); node = node.getComponentType(); arrayCount++; } } else { sbArr = null; } if (useSimple) { sb.append(node.getNameWithoutPackage()); } else { sb.append(node.getName()); } // recur down the generics GenericsType[] genericsTypes = node.getGenericsTypes(); if (genericsTypes != null && genericsTypes.length > 0) { sb.append('<'); for (GenericsType gt : genericsTypes) { ClassNode genericsType = gt.getType(); // determine whether we should use the name of the type parameter // or the name of the resolved type if (genericsType == null || !genericsType.getName().equals(gt.getName())) { sb.append(useSimple ? genericsType.getNameWithoutPackage() : genericsType.getName()); } else { sb.append(createGenericsAwareName(genericsType, useSimple)); } sb.append(','); } sb.replace(sb.length() - 1, sb.length(), ">"); } if (sbArr != null) { sb.append(sbArr); } return sb.toString(); } /** * Creates the unique key for classes, fields and methods * @param node * @param maybeRequested * @return */ 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; } } StringBuilder sb = new StringBuilder(); if (node instanceof PropertyNode) { node = ((PropertyNode) node).getField(); } if (node instanceof FieldNode) { return createUniqueKeyForField((FieldNode) node, resolvedType, resolvedDeclaringType).toString(); } else if (node instanceof MethodNode) { if (maybeRequested.getElementType() == IJavaElement.FIELD) { // this is likely a generated getter or setter return createUniqueKeyForGeneratedAccessor((MethodNode) node, resolvedType, resolvedDeclaringType, (IField) maybeRequested).toString(); } else { return createUniqueKeyForMethod((MethodNode) node, resolvedType, resolvedDeclaringType).toString(); } } else if (node instanceof ClassNode) { return createUniqueKeyForClass(resolvedType, resolvedDeclaringType).toString(); } return sb.toString(); } private StringBuilder createUniqueKeyForMethod(MethodNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType) { StringBuilder sb = new StringBuilder(); sb.append(createUniqueKeyForClass(node.getDeclaringClass(), resolvedDeclaringType)); sb.append('.').append(node.getName()); sb.append('('); if (node.getParameters() != null) { for (Parameter param : node.getParameters()) { ClassNode paramType = param.getType() != null ? param.getType() : VariableScope.OBJECT_CLASS_NODE; sb.append(createUniqueKeyForClass(paramType, resolvedDeclaringType)); } } sb.append(')'); sb.append(createUniqueKeyForResolvedClass(resolvedType)); return sb; } private StringBuilder createUniqueKeyForField(FieldNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType) { StringBuilder sb = new StringBuilder(); sb.append(createUniqueKeyForClass(node.getDeclaringClass(), resolvedDeclaringType)); sb.append('.').append(node.getName()).append(')'); sb.append(createUniqueKeyForResolvedClass(resolvedType)); return sb; } private StringBuilder createUniqueKeyForGeneratedAccessor(MethodNode node, ClassNode resolvedType, ClassNode resolvedDeclaringType, IField actualField) { StringBuilder sb = new StringBuilder(); sb.append(createUniqueKeyForClass(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; sb.append(createUniqueKeyForResolvedClass(typeOfField)); return sb; } private StringBuilder createUniqueKeyForResolvedClass(ClassNode resolvedType) { if (resolvedType.getName().equals("java.lang.Void")) { resolvedType = VariableScope.VOID_CLASS_NODE; } return new StringBuilder( Signature.createTypeSignature(createGenericsAwareName(resolvedType, false/*fully qualified*/), true/*must resolve*/).replace('.', '/')); } /** * 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. * @return */ private StringBuilder createUniqueKeyForClass(ClassNode unresolvedType, ClassNode resolvedDeclaringType) { GenericsMapper mapper = GenericsMapper.gatherGenerics(resolvedDeclaringType, resolvedDeclaringType.redirect()); ClassNode resolvedType = VariableScope.resolveTypeParameterization(mapper, VariableScope.clone(unresolvedType)); return createUniqueKeyForResolvedClass(resolvedType); } private boolean doTest(ASTNode node) { return node.getClass() == nodeToLookFor.getClass() && nodeToLookFor.getStart() == node.getStart() && nodeToLookFor.getEnd() == node.getEnd(); } /** * @param declaration * @return */ private ClassNode removeArray(ClassNode declaration) { return declaration.getComponentType() != null ? removeArray(declaration.getComponentType()) : declaration; } /** * May return null * @param type * @param text * @param preferredParamNumber * @return * @throws JavaModelException */ private IJavaElement findElement(IType type, String text, int preferredParamNumber) throws JavaModelException { if (text.equals(type.getElementName())) { return type; } // check for methods first, then fields, and then getter/setter variants of the name // these values might be null String setMethod = AccessorSupport.SETTER.createAccessorName(text); String getMethod = AccessorSupport.GETTER.createAccessorName(text); String isMethod = AccessorSupport.ISSER.createAccessorName(text); IMethod lastFound = null; for (IMethod method : type.getMethods()) { if (method.getElementName().equals(text)) { // prefer methods with the appropriate number of parameters // GRECLIPSE-1233 this is not quite right since we really should be // trying to find the original methods when default parameters are used if (method.getParameterTypes().length == preferredParamNumber) { return method; } else { lastFound = method; } } } if (lastFound != null) { return lastFound; } IField field = type.getField(text); String prefix; if (!field.exists() && (prefix = extractPrefix(text)) != null) { // this is a property String newName = Character.toLowerCase(text.charAt(prefix.length())) + text.substring(prefix.length() + 1); field = type.getField(newName); } if (field.exists()) { return field; } for (IMethod method : type.getMethods()) { if (method.getElementName().equals(setMethod) || method.getElementName().equals(getMethod) || method.getElementName().equals(isMethod)) { return method; } } return null; } private 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; } public ASTNode getRequestedNode() { return requestedNode; } public IJavaElement getRequestedElement() { return requestedElement; } }