/* * 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.proposals; import static org.codehaus.groovy.eclipse.codeassist.ProposalUtils.createTypeSignature; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.completions.GroovyJavaGuessingCompletionProposal; import org.codehaus.groovy.eclipse.codeassist.completions.GroovyJavaMethodCompletionProposal; import org.codehaus.groovy.eclipse.codeassist.completions.NamedArgsMethodNode; import org.codehaus.groovy.eclipse.codeassist.processors.GroovyCompletionProposal; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation; import org.codehaus.groovy.eclipse.codeassist.requestor.MethodInfoContentAssistContext; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.CompletionFlags; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.ui.text.java.AnnotationAtttributeProposalInfo; import org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.BadLocationException; public class GroovyMethodProposal extends AbstractGroovyProposal { private final MethodNode method; private final String contributor; private ProposalFormattingOptions options; public GroovyMethodProposal(MethodNode method) { this(method, null); } public GroovyMethodProposal(MethodNode method, String contributor) { super(); this.method = method; this.contributor = contributor; } @Override public AnnotatedNode getAssociatedNode() { return method; } public MethodNode getMethod() { return method; } public ProposalFormattingOptions getProposalFormattingOptions() { if (options == null) { options = ProposalFormattingOptions.newFromOptions(); } return options; } public void setProposalFormattingOptions(ProposalFormattingOptions options) { this.options = options; } public IJavaCompletionProposal createJavaProposal(ContentAssistContext context, JavaContentAssistInvocationContext javaContext) { int kind = (context.location == ContentAssistLocation.ANNOTATION_BODY ? CompletionProposal.ANNOTATION_ATTRIBUTE_REF : CompletionProposal.METHOD_REF); GroovyCompletionProposal proposal = new GroovyCompletionProposal(kind, context.completionLocation); if (context.location == ContentAssistLocation.METHOD_CONTEXT) { // only show context information and only for methods that exactly match the name // this happens when we are at the start of an argument or an open paren MethodInfoContentAssistContext methodContext = (MethodInfoContentAssistContext) context; if (!methodContext.methodName.equals(method.getName())) { return null; } proposal.setReplaceRange(context.completionLocation, context.completionLocation); proposal.setCompletion(CharOperation.NO_CHAR); } else { // this is a normal method proposal boolean parens = (kind == CompletionProposal.ANNOTATION_ATTRIBUTE_REF ? false : !isParens(context, javaContext)); proposal.setCompletion(completionName(parens)); proposal.setReplaceRange(context.completionLocation - context.completionExpression.length(), context.completionEnd); } proposal.setDeclarationSignature(createTypeSignature(method.getDeclaringClass())); proposal.setName(method.getName().toCharArray()); if (method instanceof NamedArgsMethodNode) { fillInExtraParameters((NamedArgsMethodNode) method, proposal); } else { proposal.setParameterNames(createAllParameterNames(context.unit)); proposal.setParameterTypeNames(getParameterTypeNames(method.getParameters())); } proposal.setFlags(getModifiers()); proposal.setAdditionalFlags(CompletionFlags.Default); proposal.setSignature(createMethodSignature()); proposal.setKey(proposal.getSignature()); proposal.setRelevance(computeRelevance()); if (requiredStaticImport != null) { GroovyCompletionProposal methodImportProposal = new GroovyCompletionProposal(CompletionProposal.METHOD_IMPORT, context.completionLocation); methodImportProposal.setAdditionalFlags(CompletionFlags.StaticImport); methodImportProposal.setCompletion(("import static " + requiredStaticImport + "\n").toCharArray()); methodImportProposal.setDeclarationSignature(proposal.getDeclarationSignature()); methodImportProposal.setName(proposal.getName()); /* methodImportProposal.setDeclarationPackageName(method.declaringClass.qualifiedPackageName()); methodImportProposal.setDeclarationTypeName(method.declaringClass.qualifiedSourceName()); methodImportProposal.setFlags(method.modifiers); if (original != method) proposal.setOriginalSignature(getSignature(original)); if(parameterNames != null) methodImportProposal.setParameterNames(parameterNames); methodImportProposal.setParameterPackageNames(parameterPackageNames); methodImportProposal.setParameterTypeNames(parameterTypeNames); methodImportProposal.setPackageName(method.returnType.qualifiedPackageName()); methodImportProposal.setReplaceRange(importStart - this.offset, importEnd - this.offset); methodImportProposal.setRelevance(relevance); methodImportProposal.setSignature(getSignature(method)); methodImportProposal.setTokenRange(importStart - this.offset, importEnd - this.offset); methodImportProposal.setTypeName(method.returnType.qualifiedSourceName()); */ proposal.setRequiredProposals(new CompletionProposal[] {methodImportProposal}); } LazyJavaCompletionProposal lazyProposal = null; if (kind == CompletionProposal.ANNOTATION_ATTRIBUTE_REF) { proposal.setSignature(createTypeSignature(getMethod().getReturnType())); proposal.setFlags(getModifiers() & 0xFFFDFFFF); // clear the "default method" flag lazyProposal = new LazyJavaCompletionProposal(proposal, javaContext); lazyProposal.setProposalInfo(new AnnotationAtttributeProposalInfo(javaContext.getProject(), proposal)); } else { if (getProposalFormattingOptions().doParameterGuessing) { lazyProposal = GroovyJavaGuessingCompletionProposal.createProposal(proposal, javaContext, true, contributor, getProposalFormattingOptions()); } if (lazyProposal == null) { lazyProposal = new GroovyJavaMethodCompletionProposal(proposal, javaContext, getProposalFormattingOptions(), contributor); // if location is METHOD_CONTEXT, then the type must be MethodInfoContentAssistContext // ...but there are other times when the type is MethodInfoContentAssistContext as well if (context.location == ContentAssistLocation.METHOD_CONTEXT) { ((GroovyJavaMethodCompletionProposal) lazyProposal).contextOnly(); } } if (context.location == ContentAssistLocation.METHOD_CONTEXT) { // NOTE: I've seen STATEMENT for 'assertNu|' and EXPRESSION for 'Assert.assertNu|' // attempt to find the location immediately after the opening paren // if this is wrong, no big deal, but the context information will not be properly highlighted // assume that there is the method name, and then an opening paren (or a space) and then the arguments lazyProposal.setContextInformationPosition(((MethodInfoContentAssistContext) context).methodNameEnd + 1); } } return lazyProposal; } private void fillInExtraParameters(NamedArgsMethodNode namedArgsMethod, GroovyCompletionProposal proposal) { proposal.setParameterNames(getSpecialParameterNames(namedArgsMethod.getParameters())); proposal.setRegularParameterNames(getSpecialParameterNames(namedArgsMethod.getRegularParams())); proposal.setNamedParameterNames(getSpecialParameterNames(namedArgsMethod.getNamedParams())); proposal.setOptionalParameterNames(getSpecialParameterNames(namedArgsMethod.getOptionalParams())); proposal.setParameterTypeNames(getParameterTypeNames(namedArgsMethod.getParameters())); proposal.setRegularParameterTypeNames(getParameterTypeNames(namedArgsMethod.getRegularParams())); proposal.setNamedParameterTypeNames(getParameterTypeNames(namedArgsMethod.getNamedParams())); proposal.setOptionalParameterTypeNames(getParameterTypeNames(namedArgsMethod.getOptionalParams())); } private boolean isParens(ContentAssistContext context, JavaContentAssistInvocationContext javaContext) { if (javaContext.getDocument().getLength() > context.completionEnd) { try { return javaContext.getDocument().getChar(context.completionEnd) == '('; } catch (BadLocationException e) { GroovyContentAssist.logError("Exception during content assist", e); } } return false; } protected char[] createMethodSignature() { return ProposalUtils.createMethodSignature(method); } protected int getModifiers() { return method.getModifiers(); } protected char[] completionName(boolean includeParens) { String name = method.getName(); char[] nameArr = name.toCharArray(); if (ProposalUtils.hasWhitespace(nameArr)) { name = '"' + name + '"'; } if (includeParens) { name += "()"; } return name.toCharArray(); } protected char[][] createAllParameterNames(ICompilationUnit unit) { Parameter[] params = method.getParameters(); final int n = params == null ? 0 : params.length; // short circuit if (n == 0) { return CharOperation.NO_CHAR_CHAR; } char[][] paramNames = null; // if the MethodNode has param names filled in, then use that if (params[0].getName().equals("arg0") || params[0].getName().equals("param0")) { paramNames = calculateAllParameterNames(unit, method); } if (paramNames == null) { paramNames = new char[n][]; for (int i = 0; i < n; i += 1) { String name = params[i].getName(); if (name != null) { paramNames[i] = name.toCharArray(); } else { paramNames[i] = ("arg" + i).toCharArray(); } } } return paramNames; } protected char[][] getParameterTypeNames(Parameter[] parameters) { char[][] typeNames = new char[parameters.length][]; int i = 0; for (Parameter param : parameters) { typeNames[i] = ProposalUtils.createSimpleTypeName(param.getType()); i++; } return typeNames; } /** * FIXADE I am concerned that this takes a long time since we are doing a lookup for each method any way to cache? */ protected char[][] calculateAllParameterNames(ICompilationUnit unit, MethodNode method) { try { IType declaringType = findDeclaringType(unit, method); if (declaringType != null && declaringType.exists()) { Parameter[] params = method.getParameters(); final int n = params == null ? 0 : params.length; if (n == 0) { return CharOperation.NO_CHAR_CHAR; } String[] parameterTypeSignatures = new String[n]; for (int i = 0; i < n; i += 1) { if (declaringType.isBinary()) { parameterTypeSignatures[i] = ProposalUtils.createTypeSignatureStr(params[i].getType()); } else { parameterTypeSignatures[i] = ProposalUtils.createUnresolvedTypeSignatureStr(params[i].getType()); } } // try to find the precise method IMethod jdtMethod = findPreciseMethod(declaringType, parameterTypeSignatures); // method was found somehow...use it if (jdtMethod != null) { String[] paramNames = jdtMethod.getParameterNames(); char[][] paramNamesChar = new char[paramNames.length][]; for (int i = 0; i < paramNames.length; i += 1) { paramNamesChar[i] = paramNames[i].toCharArray(); } return paramNamesChar; } } } catch (JavaModelException e) { GroovyContentAssist.logError("Exception while looking for parameter types of " + method.getName(), e); } return null; } /** * Finds a method with the same name and parameter types. */ private IMethod findPreciseMethod(IType declaringType, String[] parameterTypeSignatures) throws JavaModelException { IMethod closestMatch = declaringType.getMethod(method.getName(), parameterTypeSignatures); if (closestMatch != null && closestMatch.exists()) { return closestMatch; } else { // try something else and be a little more lenient // look for any methods with the same name and argument types // (ignoring generic types) IMethod[] methods = declaringType.getMethods(); closestMatch = null; // prefer retrieving the method with the same arg types as // specified in the parameter. methodsIteration: for (IMethod maybeMethod : methods) { if (maybeMethod.getElementName().equals(method.getName())) { String[] maybeMethodParameters = maybeMethod.getParameterTypes(); assert maybeMethodParameters != null; if (maybeMethodParameters.length == parameterTypeSignatures.length) { closestMatch = maybeMethod; for (int i = 0; i < maybeMethodParameters.length; i++) { String maybeMethodParameterName = removeGenerics(maybeMethodParameters[i]); if (!parameterTypeSignatures[i].equals(maybeMethodParameterName)) { continue methodsIteration; } } return closestMatch; } } } } return closestMatch; } /** * @return parameter signature without generics signature */ private String removeGenerics(String maybeMethodParameterName) { int genericStart = maybeMethodParameterName.indexOf("<"); if (genericStart > 0) { maybeMethodParameterName = maybeMethodParameterName.substring(0, genericStart) + maybeMethodParameterName.substring(maybeMethodParameterName.indexOf(">") + 1, maybeMethodParameterName.length()); } return maybeMethodParameterName; } private char[][] getSpecialParameterNames(Parameter[] params) { // as opposed to getAllParameterNames, we can assume that the names are // correct as is // because these parameters were explicitly set by a script char[][] paramNames = new char[params.length][]; for (int i = 0, n = params.length; i < n; i += 1) { paramNames[i] = params[i].getName().toCharArray(); } return paramNames; } private IType findDeclaringType(ICompilationUnit unit, MethodNode method) throws JavaModelException { if (cachedDeclaringType == null) { cachedDeclaringType = unit.getJavaProject().findType(method.getDeclaringClass().getName(), new NullProgressMonitor()); } return cachedDeclaringType; } private IType cachedDeclaringType; }