blob: 1ee0e8d1c762db4a35ad46ed0f088525e964f8fe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.groovy.editor.completion.inference;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.tools.GenericsUtils;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.groovy.editor.api.ASTUtils;
import org.netbeans.modules.groovy.editor.api.AstPath;
/**
*
* @author Martin Janicek
*/
public final class MethodInference {
private MethodInference() {
}
/**
* Tries to infer correct {@link ClassNode} representing type of the caller for
* the given expression. Typically the given parameter is instance of {@link MethodCallExpression}
* and in that case the return type of the method call is returned.<br/><br/>
*
* The method also handles method chain and in such case the return type of the
* last method call should be return.
*
* @param expression
* @return class type of the caller if found, {@code null} otherwise
*/
@CheckForNull
public static ClassNode findCallerType(@NonNull ASTNode expression, @NonNull AstPath path, BaseDocument baseDocument, int offset) {
// In case if the method call is chained with another method call
// For example: someInteger.toString().^
if (expression instanceof MethodCallExpression) {
MethodCallExpression methodCall = (MethodCallExpression) expression;
ClassNode callerType = findCallerType(methodCall.getObjectExpression(), path, baseDocument, offset);
if (callerType != null) {
return findReturnTypeFor(callerType, methodCall.getMethodAsString(), methodCall.getArguments(), path, false, baseDocument, offset);
}
}
// In case if the method call is directly on a variable
if (expression instanceof VariableExpression) {
int newOffset = ASTUtils.getOffset(baseDocument, expression.getLineNumber(), expression.getColumnNumber());
AstPath newPath = new AstPath(path.root(), newOffset, baseDocument);
TypeInferenceVisitor tiv = new TypeInferenceVisitor(((ModuleNode)path.root()).getContext(), newPath, baseDocument, newOffset);
tiv.collect();
return tiv.getGuessedType();
}
if (expression instanceof ConstantExpression) {
return ((ConstantExpression) expression).getType();
}
if (expression instanceof ClassExpression) {
return ClassHelper.make(((ClassExpression) expression).getType().getName());
}
if (expression instanceof StaticMethodCallExpression) {
StaticMethodCallExpression staticMethodCall = (StaticMethodCallExpression) expression;
return findReturnTypeFor(staticMethodCall.getOwnerType(), staticMethodCall.getMethod(), staticMethodCall.getArguments(), path, true, baseDocument, offset);
}
return null;
}
@CheckForNull
private static ClassNode findReturnTypeFor(
@NonNull ClassNode callerType,
@NonNull String methodName,
@NonNull Expression arguments,
@NonNull AstPath path,
@NonNull boolean isStatic,
@NonNull BaseDocument baseDocument,
@NonNull int offset
) {
List<ClassNode> paramTypes = new ArrayList<>();
if (arguments instanceof ArgumentListExpression) {
ArgumentListExpression argExpression = (ArgumentListExpression) arguments;
for (Expression e : argExpression.getExpressions()) {
if (e instanceof VariableExpression) {
ModuleNode moduleNode = (ModuleNode) path.root();
int newOffset = ASTUtils.getOffset(baseDocument, e.getLineNumber(), e.getColumnNumber());
AstPath newPath = new AstPath(moduleNode, newOffset, baseDocument);
TypeInferenceVisitor tiv = new TypeInferenceVisitor(moduleNode.getContext(), newPath, baseDocument, newOffset);
tiv.collect();
ClassNode guessedType = tiv.getGuessedType();
if (null == guessedType) {
System.out.println("Bad guessed type");
} else {
paramTypes.add(tiv.getGuessedType());
}
} else if(e instanceof ConstantExpression) {
paramTypes.add(((ConstantExpression)e).getType());
} else if (e instanceof MethodCallExpression) {
paramTypes.add(findCallerType(e, path, baseDocument, offset));
} else if (e instanceof BinaryExpression) {
BinaryExpression binExpression = (BinaryExpression) e;
paramTypes.add(binExpression.getType());
} else if (e instanceof ClassExpression) {
ClassExpression classExpression = (ClassExpression) e;
// This should be Class<classExpression.getType()>
paramTypes.add(GenericsUtils.makeClassSafeWithGenerics(Class.class, classExpression.getType()));
} else {
System.out.println(e.getClass());
}
}
}
MethodNode possibleMethod = tryFindPossibleMethod(callerType, methodName, paramTypes, isStatic);
if (possibleMethod != null) {
return possibleMethod.getReturnType();
}
return null;
}
private static MethodNode tryFindPossibleMethod(ClassNode callerType, String methodName, List<ClassNode> paramTypes, boolean isStatic) {
int count = paramTypes.size();
MethodNode res = null;
ClassNode node = callerType;
do {
for (MethodNode method : node.getMethods(methodName)) {
if (isStatic && !method.isStatic()) {
continue;
}
if (method.getParameters().length == count) {
boolean match = true;
for (int i = 0; i != count; ++i) {
if (!paramTypes.get(i).isDerivedFrom(method.getParameters()[i].getType())) {
match = false;
break;
}
}
if (match) {
if (res == null) {
res = method;
} else {
if (res.getParameters().length != count) {
return null;
}
if (node.equals(callerType)) {
return null;
}
match = true;
for (int i = 0; i != count; ++i) {
if (!res.getParameters()[i].getType().equals(method.getParameters()[i].getType())) {
match = false;
break;
}
}
if (!match) {
return null;
}
}
}
}
}
node = node.getSuperClass();
} while (node != null);
return res;
}
}