/* * 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.codeassist.completions; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.codehaus.groovy.eclipse.core.model.GroovyProjectFacade; import org.codehaus.jdt.groovy.internal.SimplifiedExtendedCompletionContext; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.groovy.core.util.GroovyUtils; import org.eclipse.jdt.groovy.search.VariableScope; import org.eclipse.jdt.groovy.search.VariableScope.VariableInfo; import org.eclipse.jdt.internal.core.JavaElement; import org.eclipse.jdt.internal.core.LocalVariable; import org.eclipse.jdt.internal.core.SourceField; public class GroovyExtendedCompletionContext extends SimplifiedExtendedCompletionContext { private static class PropertyVariant extends SourceField implements IField { private final IMethod baseMethod; PropertyVariant(IMethod method) { super((JavaElement) method.getParent(), toFieldName(method)); baseMethod = method; } @Override public boolean exists() { return true; } @Override public String getTypeSignature() throws JavaModelException { return baseMethod.getReturnType(); } @Override public int getFlags() throws JavaModelException { return baseMethod.getFlags(); } } private static String toFieldName(IMethod method) { return ProposalUtils.createMockFieldName(method.getElementName()); } private final ContentAssistContext context; private final VariableScope currentScope; // computed after initialization private IJavaElement enclosingElement; // computed after initialization private final Map<String, IJavaElement[]> visibleElements; public GroovyExtendedCompletionContext(ContentAssistContext context, VariableScope currentScope) { this.context = context; this.currentScope = currentScope; this.visibleElements = new HashMap<String, IJavaElement[]>(); } @Override public IJavaElement getEnclosingElement() { if (enclosingElement == null) { try { enclosingElement = context.unit.getElementAt(context.completionLocation); } catch (JavaModelException e) { GroovyContentAssist.logError("Exception computing content assist proposals", e); } if (enclosingElement == null) { enclosingElement = context.unit; } } return enclosingElement; } @Override public IJavaElement[] getVisibleElements(String typeSignature) { // let's not work with parameterized sigs typeSignature = Signature.getTypeErasure(typeSignature); IJavaElement[] elements = visibleElements.get(typeSignature); if (elements == null) { elements = computeVisibleElements(typeSignature); visibleElements.put(typeSignature, elements); } return elements; } private IJavaElement[] computeVisibleElements(String typeSignature) { ClassNode targetType = toClassNode(typeSignature); boolean isEnum = targetType.isEnum(); // look at all local variables in scope Map<String, IJavaElement> nameElementMap = new LinkedHashMap<String, IJavaElement>(); if (currentScope != null) { for (VariableInfo varInfo : currentScope) { // GRECLIPSE-1268 currently, no good way to get to the actual declaration of the variable. // This can cause ordering problems for the guessed parameters. // don't put elements in a second time since we are moving from inner scope to outer scope String varName = varInfo.name; // ignore synthetic getters and setters that are put in the scope // looking at prefix is a good approximation if (!varName.startsWith("get") && !varName.startsWith("set") && !varName.equals("super") && !varName.startsWith("<") && !nameElementMap.containsKey(varName)) { ClassNode type = varInfo.type; if (GroovyUtils.isAssignable(type, targetType)) { // NOTE: parent, source location, typeSignature, etc. are not important here int start = 0, until = varName.length() - 1; nameElementMap.put(varName, new LocalVariable( (JavaElement) getEnclosingElement(), varName, start, until, start, until, typeSignature, null, 0, false)); } } } } // now check fields IType enclosingType = (IType) getEnclosingElement().getAncestor(IJavaElement.TYPE); if (enclosingType != null) { try { addFields(targetType, nameElementMap, enclosingType); ITypeHierarchy typeHierarchy = enclosingType.newSupertypeHierarchy(null); IType[] allTypes = typeHierarchy.getAllSupertypes(enclosingType); for (IType superType : allTypes) { addFields(targetType, nameElementMap, superType); } } catch (JavaModelException e) { GroovyContentAssist.logError(e); } } if (isEnum) { IType targetIType = new GroovyProjectFacade(enclosingElement).groovyClassToJavaType(targetType); List<FieldNode> fields = targetType.getFields(); for (FieldNode enumVal : fields) { String name = enumVal.getName(); if (name.equals("MIN_VALUE") || name.equals("MAX_VALUE")) { continue; } if (!enumVal.getType().equals(targetType)) { continue; } nameElementMap.put(targetIType.getElementName() + "." + name, targetIType.getField(name)); nameElementMap.put(name, targetIType.getField(name)); } } return nameElementMap.values().toArray(new IJavaElement[0]); } public void addFields(ClassNode targetType, Map<String, IJavaElement> nameElementMap, IType type) throws JavaModelException { IField[] fields = type.getFields(); for (IField field : fields) { ClassNode fieldTypeClassNode = toClassNode(field.getTypeSignature()); if (GroovyUtils.isAssignable(fieldTypeClassNode, targetType)) { nameElementMap.put(field.getElementName(), field); } } // also add methods IMethod[] methods = type.getMethods(); for (IMethod method : methods) { ClassNode methodReturnTypeClassNode = toClassNode(method.getReturnType()); if (GroovyUtils.isAssignable(methodReturnTypeClassNode, targetType)) { if ((method.getParameterTypes() == null || method.getParameterTypes().length == 0) && (method.getElementName().startsWith("get") || method.getElementName().startsWith("is"))) { nameElementMap.put(method.getElementName(), method); IField field = new PropertyVariant(method); nameElementMap.put(field.getElementName(), field); } } } } private ClassNode toClassNode(String typeSignature) { int dims = Signature.getArrayCount(typeSignature); String noArray = Signature.getElementType(typeSignature); String qualifiedName = getQualifiedName(noArray); ClassNode resolved; if (typeSignature.length() == 1 + dims) { // is primitive type resolved = /*ClassHelper.getWrapper(*/ClassHelper.make(qualifiedName)/*)*/; } else { try { resolved = context.unit.getModuleInfo(false).resolver.resolve(qualifiedName); } catch (NullPointerException e) { // ignore; likely DSL support not available resolved = VariableScope.OBJECT_CLASS_NODE; } } for (int i = 0; i < dims; i += 1) { resolved = resolved.makeArray(); } return resolved; } private String getQualifiedName(String typeSignature) { String qualifier = Signature.getSignatureQualifier(typeSignature); String qualifiedName = Signature.getSignatureSimpleName(typeSignature); if (qualifier.length() > 0) { qualifiedName = qualifier + "." + qualifiedName; } return qualifiedName; } }