/* * 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.processors; import static org.codehaus.groovy.eclipse.codeassist.ProposalUtils.createDisplayString; import static org.codehaus.groovy.eclipse.codeassist.ProposalUtils.createMethodSignatureStr; import static org.codehaus.groovy.eclipse.codeassist.ProposalUtils.createTypeSignature; import static org.codehaus.groovy.eclipse.codeassist.ProposalUtils.getImage; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import groovyjarjarasm.asm.Opcodes; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; import org.codehaus.groovy.eclipse.codeassist.relevance.Relevance; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.groovy.search.GenericsMapper; import org.eclipse.jdt.groovy.search.VariableScope; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.core.SearchableEnvironment; import org.eclipse.jdt.internal.ui.text.java.OverrideCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.contentassist.ICompletionProposal; /** * Completion processor that determines methods to be overridden or implemented. */ public class NewMethodCompletionProcessor extends AbstractGroovyCompletionProcessor { public NewMethodCompletionProcessor(ContentAssistContext context, JavaContentAssistInvocationContext javaContext, SearchableEnvironment nameEnvironment) { super(context, javaContext, nameEnvironment); } public List<ICompletionProposal> generateProposals(IProgressMonitor monitor) { List<MethodNode> unimplementedMethods = getAllUnimplementedMethods(getClassNode()); List<ICompletionProposal> proposals = new LinkedList<ICompletionProposal>(); ContentAssistContext context = getContext(); IType enclosingType = context.getEnclosingType(); if (enclosingType != null) { for (MethodNode method : unimplementedMethods) { proposals.add(createProposal(method, context, enclosingType)); } } // now add proposals from relevant proposal providers try { List<IProposalProvider> providers = ProposalProviderRegistry.getRegistry().getProvidersFor(context.unit); for (IProposalProvider provider : providers) { List<MethodNode> newProposals = provider.getNewMethodProposals(context); if (newProposals != null) { for (MethodNode methodNode : newProposals) { proposals.add(createProposal(methodNode, context, enclosingType)); } } } } catch (CoreException e) { GroovyContentAssist.logError("Exception looking for proposal providers in " + context.unit.getElementName(), e); } return proposals; } private ClassNode getClassNode() { // if the current completion is inside a script, then the containing code block will be a Block object, not a ClassNode // Must get class node in a different way. return getContext().containingCodeBlock instanceof ClassNode ? (ClassNode) getContext().containingCodeBlock : getScript(); } private ClassNode getScript() { ModuleNode module = getContext().unit.getModuleNode(); for (ClassNode clazz : (Iterable<ClassNode>) module.getClasses()) { if (clazz.isScript()) { return clazz; } } throw new IllegalArgumentException("Expecting script in current module: " + module.getPackageName()); } private ICompletionProposal createProposal(MethodNode method, ContentAssistContext context, IType enclosingType) { int relevance = Relevance.VERY_HIGH.getRelavance(); GroovyCompletionProposal proposal = createProposal(CompletionProposal.METHOD_DECLARATION, context.completionLocation); String methodSignature = createMethodSignatureStr(method); proposal.setSignature(methodSignature.toCharArray()); proposal.setDeclarationSignature(createTypeSignature(method.getDeclaringClass())); proposal.setName(method.getName().toCharArray()); proposal.setDeclarationTypeName(method.getDeclaringClass().getName().toCharArray()); proposal.setTypeName(method.getReturnType().getName().toCharArray()); proposal.setParameterNames(getParameterNames(method)); String[] parameterTypeNamesStr = getParameterTypeNames(method); char[][] parameterTypeNames = new char[parameterTypeNamesStr.length][]; for (int i = 0; i < parameterTypeNames.length; i++) { parameterTypeNames[i] = parameterTypeNamesStr[i].toCharArray(); } proposal.setParameterTypeNames(parameterTypeNames); StringBuffer completion = new StringBuffer(); createMethod(method, completion); proposal.setCompletion(completion.toString().toCharArray()); proposal.setDeclarationKey(method.getDeclaringClass().getName().toCharArray()); proposal.setReplaceRange(context.completionLocation - context.completionExpression.length(), context.completionEnd); proposal.setFlags(method.getModifiers()); proposal.setRelevance(relevance); OverrideCompletionProposal override = new OverrideCompletionProposal(context.unit.getJavaProject(), context.unit, method.getName(), parameterTypeNamesStr, context.completionLocation, context.completionExpression.length(), createDisplayString(proposal), String.valueOf(proposal.getCompletion())); override.setImage(getImage(proposal)); override.setRelevance(relevance); override.setReplacementOffset(context.completionLocation - context.completionExpression.length()); override.setReplacementLength(context.completionExpression.length()); override.setRelevance(proposal.getRelevance()); return override; } private char[][] getParameterNames(MethodNode method) { Parameter[] parameters = method.getParameters(); char[][] paramNames = new char[parameters.length][]; for (int i = 0; i < paramNames.length; i++) { paramNames[i] = parameters[i].getName().toCharArray(); } return paramNames; } private Map<ClassNode, GenericsMapper> mappers = new HashMap<ClassNode, GenericsMapper>(); private String[] getParameterTypeNames(MethodNode method) { // need to keep track of generic types GenericsMapper mapper = null; ClassNode declaringClass = method.getDeclaringClass(); if (declaringClass.getGenericsTypes() != null && declaringClass.getGenericsTypes().length > 0) { if (!mappers.containsKey(declaringClass)) { ClassNode thiz = getClassNode(); mapper = GenericsMapper.gatherGenerics(findResolvedType(thiz, declaringClass), declaringClass); } else { mapper = mappers.get(declaringClass); } } Parameter[] parameters = method.getParameters(); String[] paramTypeNames = new String[parameters.length]; for (int i = 0; i < paramTypeNames.length; i++) { ClassNode paramType = parameters[i].getType(); if (mapper != null && paramType.getGenericsTypes() != null && paramType.getGenericsTypes().length > 0) { paramType = VariableScope.resolveTypeParameterization(mapper, VariableScope.clone(paramType)); } paramTypeNames[i] = paramType.getName(); if (paramTypeNames[i].startsWith("[")) { int cnt = Signature.getArrayCount(paramTypeNames[i]); String sig = Signature.getElementType(paramTypeNames[i]); String qualifier = Signature.getSignatureQualifier(sig); String simple = Signature.getSignatureSimpleName(sig); StringBuilder sb = new StringBuilder(); if (qualifier.length() > 0) { sb.append(qualifier).append("."); } sb.append(simple); for (int j = 0; j < cnt; j++) { sb.append("[]"); } paramTypeNames[i] = sb.toString(); } } return paramTypeNames; } private ClassNode findResolvedType(ClassNode target, ClassNode toResolve) { if (target != null) { if (target.equals(toResolve)) { return target; } ClassNode result = findResolvedType(target.getUnresolvedSuperClass(false), toResolve); if (result != null) { return result; } for (ClassNode inter : target.getUnresolvedInterfaces(false)) { result = findResolvedType(inter, toResolve); if (result != null) { return result; } } ClassNode redirect = target.redirect(); if (redirect != target) { return findResolvedType(redirect, toResolve); } } return null; } private List<MethodNode> getAllUnimplementedMethods(ClassNode declaring) { List<MethodNode> allMethods = declaring.getAllDeclaredMethods(); List<MethodNode> thisClassMethods = declaring.getMethods(); List<MethodNode> unimplementedMethods = new ArrayList<MethodNode>(allMethods.size()-thisClassMethods.size()); // uggh n^2 loop. Can be made more efficient by doing declaring.getMethods(allMethodNode.getName()) for (MethodNode allMethodNode : allMethods) { if (allMethodNode.getName().startsWith(getContext().completionExpression)) { if (isOverridableMethod(allMethodNode)) { boolean found = false; inner: for (MethodNode thisClassMethod : thisClassMethods) { if (allMethodNode.getParameters().length == thisClassMethod.getParameters().length && allMethodNode.getName().equals(thisClassMethod.getName())) { // now check param types Parameter[] allMethodParams = allMethodNode.getParameters(); Parameter[] thisClassParams = thisClassMethod.getParameters(); for (int i = 0; i < thisClassParams.length; i++) { if (! allMethodParams[i].getType().getName().equals(thisClassParams[i].getType().getName())) { continue inner; } } found = true; break inner; } } if (!found) { unimplementedMethods.add(allMethodNode); } found = false; } } } return unimplementedMethods; } /** * @param allMethodNode * @return */ private boolean isOverridableMethod(MethodNode methodNode) { String name = methodNode.getName(); return !name.contains("$") && !name.contains("<") && !methodNode.isPrivate() && !methodNode.isStatic() && (methodNode.getModifiers() & Opcodes.ACC_FINAL) == 0 ; } private void createMethod(MethodNode method, StringBuffer completion) { //// Modifiers // flush uninteresting modifiers int insertedModifiers = method.getModifiers() & ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC); ASTNode.printModifiers(insertedModifiers, completion); //// Type parameters // ignore too difficult and not really needed for Groovy // GenericsType[] typeVariableBindings = method.getGenericsTypes(); // if(typeVariableBindings != null && typeVariableBindings.length != 0) { // completion.append('<'); // for (int i = 0; i < typeVariableBindings.length; i++) { // if(i != 0) { // completion.append(','); // completion.append(' '); // } // createTypeVariable(typeVariableBindings[i], completion); // } // completion.append('>'); // completion.append(' '); // } //// Return type createType(method.getReturnType(), completion, false); completion.append(' '); //// Selector completion.append(method.getName()); completion.append('('); ////Parameters Parameter[] parameters = method.getParameters(); int length = parameters.length; for (int i = 0; i < length; i++) { if(i != 0) { completion.append(','); completion.append(' '); } createType(parameters[i].getType(), completion, true); completion.append(' '); completion.append(parameters[i].getName()); } completion.append(')'); //// Exceptions ClassNode[] exceptions = method.getExceptions(); if (exceptions != null && exceptions.length > 0){ completion.append(' '); completion.append("throws"); completion.append(' '); for(int i = 0; i < exceptions.length ; i++){ if(i != 0) { completion.append(' '); completion.append(','); } createType(exceptions[i], completion, false); } } } // ignore. Too difficult and not really needed for groovy. // private void createTypeVariable(GenericsType typeVariable, StringBuffer completion) { // completion.append(typeVariable.getName()); // // if (typeVariable.getUpperBounds() != null && typeVariable.getUpperBounds().length > 0) { // for (int i = 0; i < typeVariable.getUpperBounds().length; i++) { // if (i > 0) { // } // completion.append(' '); // completion.append("extends"); // completion.append(' '); // createType(typeVariable.getUpperBounds()[0], completion); // // } // } // if (typeVariable.get != null && typeVariable.superInterfaces != Binding.NO_SUPERINTERFACES) { // if (typeVariable.firstBound != typeVariable.superclass) { // completion.append(' '); // completion.append("extends"); // completion.append(' '); // } // for (int i = 0, length = typeVariable.superInterfaces.length; i < length; i++) { // if (i > 0 || typeVariable.firstBound == typeVariable.superclass) { // completion.append(' '); // completion.append(EXTENDS); // completion.append(' '); // } // createType(typeVariable.superInterfaces[i], scope, completion); // } // } // } private void createType(ClassNode type, StringBuffer completion, boolean isParameter) { int arrayCount = 0; while (type.getComponentType() != null) { arrayCount++; type = type.getComponentType(); } if (type.getName().equals("java.lang.Object") && arrayCount == 0) { if (!isParameter) { completion.append("def"); } } else { completion.append(type.getNameWithoutPackage()); for (int i = 0; i < arrayCount; i++) { completion.append("[]"); } } } }