/*
* 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.dsl.contributions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import groovyjarjarasm.asm.Opcodes;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.eclipse.codeassist.ProposalUtils;
import org.codehaus.groovy.eclipse.codeassist.completions.NamedArgsMethodNode;
import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyMethodProposal;
import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyNamedArgumentProposal;
import org.codehaus.groovy.eclipse.codeassist.proposals.IGroovyProposal;
import org.codehaus.groovy.eclipse.codeassist.proposals.ProposalFormattingOptions;
import org.codehaus.groovy.eclipse.dsl.lookup.ResolverCache;
import org.eclipse.jdt.groovy.search.AbstractSimplifiedTypeLookup.TypeAndDeclaration;
import org.eclipse.jdt.groovy.search.VariableScope;
public class MethodContributionElement implements IContributionElement {
private static final BlockStatement EMPTY_BLOCK = new BlockStatement();
private static final ClassNode[] NO_EXCEPTIONS = ClassNode.EMPTY_ARRAY;
private static final ParameterContribution[] NO_PARAMETER_CONTRIBUTION = new ParameterContribution[0];
private static final ClassNode UNKNOWN_TYPE = ClassHelper.DYNAMIC_TYPE;
private final String methodName;
private final ParameterContribution[] params;
private final ParameterContribution[] namedParams;
private final ParameterContribution[] optionalParams;
private final String returnType;
private final String declaringType;
private final boolean isStatic;
private final boolean useNamedArgs;
private final String provider;
private final String doc;
private ClassNode cachedDeclaringType;
private ClassNode cachedReturnType;
private Parameter[] cachedRegularParameters;
private Parameter[] cachedNamedParameters;
private Parameter[] cachedOptionalParameters;
private final int relevanceMultiplier;
private final boolean isDeprecated;
private final boolean noParens;
public MethodContributionElement(String methodName, ParameterContribution[] params, String returnType, String declaringType, boolean isStatic, String provider, String doc, boolean useNamedArgs, boolean isDeprecated, int relevanceMultiplier) {
this(methodName, params, NO_PARAMETER_CONTRIBUTION, NO_PARAMETER_CONTRIBUTION, returnType, declaringType, isStatic, provider, doc, useNamedArgs, false, isDeprecated, relevanceMultiplier);
}
public MethodContributionElement(String methodName, ParameterContribution[] params, ParameterContribution[] namedParams,
ParameterContribution[] optionalParams, String returnType, String declaringType, boolean isStatic, String provider,
String doc, boolean useNamedArgs, boolean noParens, boolean isDeprecated, int relevanceMultiplier) {
this.methodName = methodName;
this.params = params;
this.namedParams = namedParams;
this.optionalParams = optionalParams;
this.returnType = returnType;
this.isStatic = isStatic;
this.declaringType = declaringType;
this.useNamedArgs = useNamedArgs;
this.noParens = noParens;
this.isDeprecated = isDeprecated;
this.relevanceMultiplier = relevanceMultiplier;
this.provider = provider == null ? GROOVY_DSL_PROVIDER : provider;
this.doc = doc == null ? NO_DOC + this.provider : doc;
}
public TypeAndDeclaration lookupType(String name, ClassNode declaringType, ResolverCache resolver) {
if (name.equals(methodName))
return new TypeAndDeclaration(ensureReturnType(resolver), toMethod(declaringType, resolver),
ensureDeclaringType(declaringType, resolver), doc);
else
return null;
}
public IGroovyProposal toProposal(ClassNode declaringType, ResolverCache resolver) {
GroovyMethodProposal proposal = new GroovyMethodProposal(toMethod(declaringType.redirect(), resolver), provider);
proposal.setProposalFormattingOptions(ProposalFormattingOptions.newFromOptions().newFromExisting(useNamedArgs, noParens, proposal.getMethod()));
proposal.setRelevanceMultiplier(relevanceMultiplier);
return proposal;
}
public List<IGroovyProposal> extraProposals(ClassNode declaringType, ResolverCache resolver, Expression expression) {
// first find the arguments that are possible
Map<String, ClassNode> availableParams = findAvailableParameters(resolver);
if (availableParams.isEmpty()) {
return ProposalUtils.NO_PROPOSALS;
}
removeUsedParameters(expression, availableParams);
List<IGroovyProposal> extraProposals = new ArrayList<IGroovyProposal>(availableParams.size());
for (Entry<String, ClassNode> available : availableParams.entrySet()) {
extraProposals.add(new GroovyNamedArgumentProposal(available.getKey(), available.getValue(), toMethod(declaringType.redirect(), resolver), provider));
}
return extraProposals;
}
private void removeUsedParameters(Expression expression, Map<String, ClassNode> availableParams) {
if (expression instanceof MethodCallExpression) {
// next find out if there are any existing named args
MethodCallExpression call = (MethodCallExpression) expression;
Expression arguments = call.getArguments();
if (arguments instanceof TupleExpression) {
for (Expression maybeArg : ((TupleExpression) arguments).getExpressions()) {
if (maybeArg instanceof MapExpression) {
arguments = maybeArg;
break;
}
}
}
// now remove the arguments that are already written
if (arguments instanceof MapExpression) {
// Do extra filtering to determine what parameters are still available
MapExpression enclosingCallArgs = (MapExpression) arguments;
for (MapEntryExpression entry : enclosingCallArgs .getMapEntryExpressions()) {
String paramName = entry.getKeyExpression().getText();
availableParams.remove(paramName);
}
}
}
}
private Map<String, ClassNode> findAvailableParameters(ResolverCache resolver) {
Map<String, ClassNode> available = new HashMap<String, ClassNode>(params.length);
if (useNamedArgs) {
for (ParameterContribution param : params) {
available.put(param.name, param.toParameter(resolver).getType());
}
}
for (ParameterContribution param : namedParams) {
available.put(param.name, param.toParameter(resolver).getType());
}
for (ParameterContribution param : optionalParams) {
available.put(param.name, param.toParameter(resolver).getType());
}
return available;
}
private MethodNode toMethod(ClassNode declaringType, ResolverCache resolver) {
if (cachedRegularParameters == null) {
cachedRegularParameters = initParams(params, resolver);
cachedOptionalParameters = initParams(optionalParams, resolver);
cachedNamedParameters = initParams(namedParams, resolver);
if (cachedReturnType == null) {
if (resolver != null) {
cachedReturnType = resolver.resolve(returnType);
} else {
cachedReturnType = VariableScope.OBJECT_CLASS_NODE;
}
}
}
MethodNode meth = new NamedArgsMethodNode(methodName, opcode(), cachedReturnType, cachedRegularParameters, cachedNamedParameters, cachedOptionalParameters, NO_EXCEPTIONS, EMPTY_BLOCK);
meth.setDeclaringClass(ensureDeclaringType(declaringType, resolver));
return meth;
}
private Parameter[] initParams(ParameterContribution[] pcs, ResolverCache resolver) {
Parameter[] ps;
if (pcs == null) {
ps = Parameter.EMPTY_ARRAY;
} else {
ps = new Parameter[pcs.length];
for (int i = 0; i < pcs.length; i++) {
ps[i] = pcs[i].toParameter(resolver);
}
}
return ps;
}
protected ClassNode ensureReturnType(ResolverCache resolver) {
if (cachedReturnType == null) {
cachedReturnType = resolver.resolve(returnType);
}
return cachedReturnType == null ? UNKNOWN_TYPE : cachedReturnType;
}
protected ClassNode ensureDeclaringType(ClassNode lexicalDeclaringType, ResolverCache resolver) {
if (declaringType != null && cachedDeclaringType == null) {
cachedDeclaringType = resolver.resolve(declaringType);
}
return cachedDeclaringType == null ? lexicalDeclaringType : cachedDeclaringType;
}
protected int opcode() {
int modifiers = isStatic ? Opcodes.ACC_STATIC : Opcodes.ACC_PUBLIC;
modifiers |= isDeprecated ? Opcodes.ACC_DEPRECATED : 0;
return modifiers;
}
public String contributionName() {
return methodName;
}
public String description() {
return "Method: " + declaringType + "." + methodName + "(..)";
}
public String getDeclaringTypeName() {
return declaringType;
}
@Override
public String toString() {
return "public " + (isStatic ? "static " : "") + (isDeprecated ? "deprecated " : "")
+ (useNamedArgs ? "useNamedArgs " : "") + returnType + " " + declaringType + "." + methodName + "("
+ Arrays.toString(params) + ") (" + provider + ")";
}
}