/*
* 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.eclipse.jdt.groovy.search;
import java.util.Collections;
import java.util.List;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.syntax.Types;
/**
* Records types for variables in the scope based on assignment statements.
*
* There are several possibilities here:
* <ul>
* <li>If {@link DeclarationExpression}, and the declared type isn't Object, use that type</li>
* <li>If {@link DeclarationExpression}, and the declared type is Object, and there is an object
* expression, use the type of the object expression; add to VariableScope, don't replace</li>
* <li>If {@link BinaryExpression}, use the type of the objectExpression; replace in VariableScope, don't add</li>
* </ul>
*
* @author Andrew Eisenberg
*/
public class AssignmentStorer {
/**
* Store the result of the current assignment statement in the given scope.
*
* @param assign assignment statement to look at
* @param scope scope to store result in
* @param rhsType type of the right hand side
*/
public void storeAssignment(BinaryExpression assign, VariableScope scope, ClassNode rhsType) {
if (assign instanceof DeclarationExpression) {
DeclarationExpression decl = (DeclarationExpression) assign;
if (decl.isMultipleAssignmentDeclaration()) {
TupleExpression tuple = (TupleExpression) decl.getLeftExpression();
handleMultiAssignment(scope, rhsType, decl, tuple);
} else {
VariableExpression var = decl.getVariableExpression();
scope.addVariable(var.getName(), findVariableType(var, rhsType), null);
}
} else if (isInterestingOperation(assign)) {
Expression lhs = assign.getLeftExpression();
if (lhs instanceof TupleExpression) {
TupleExpression tuple = (TupleExpression) lhs;
handleMultiAssignment(scope, rhsType, assign, tuple);
} else {
handleSingleAssignment(lhs, scope, rhsType);
}
}
}
public void storeField(FieldNode node, VariableScope scope) {
Expression init = node.getInitialExpression();
if (!isObjectType(init)) {
scope.addVariable(node.getName(), init.getType(), node.getDeclaringClass());
}
}
public void storeImport(ImportNode node, VariableScope scope) {
// if this is a static import, then add to the top level scope
ClassNode type = node.getType();
if (node.isStar() && type != null) {
// importing all static fields in the class
List<FieldNode> fields = type.getFields();
for (FieldNode field : fields) {
if (field.isStatic()) {
scope.addVariable(field.getName(), field.getType(), type);
}
}
List<MethodNode> methods = node.getType().getMethods();
for (MethodNode method : methods) {
if (method.isStatic()) {
scope.addVariable(method.getName(), method.getReturnType(), type);
}
}
} else {
String fieldName = node.getFieldName();
if (node.isStatic() && type != null && fieldName != null) {
String alias;
if (node.getAlias() != null) {
alias = node.getAlias();
} else {
alias = fieldName;
}
FieldNode field = type.getField(fieldName);
if (field != null) {
scope.addVariable(alias, field.getType(), type);
}
List<MethodNode> methods = type.getDeclaredMethods(fieldName);
if (methods != null) {
for (MethodNode method : methods) {
scope.addVariable(alias, method.getReturnType(), type);
}
}
}
}
}
private void handleMultiAssignment(VariableScope scope, ClassNode objectExpressionType, BinaryExpression binaryExpr, TupleExpression tuple) {
// the type to use if rhs is not a List literal expression
ClassNode maybeType = findComponentType(objectExpressionType);
// try to associate the individual tuple expression elements with something on the rhs
ListExpression rhs = binaryExpr.getRightExpression() instanceof ListExpression ? (ListExpression) binaryExpr.getRightExpression() : null;
List<Expression> lhsExprs = (tuple == null ? Collections.EMPTY_LIST : tuple.getExpressions());
List<Expression> rhsExprs = (rhs == null ? Collections.EMPTY_LIST : rhs.getExpressions());
for (int i = 0, lhsSize = lhsExprs.size(), rhsSize = rhsExprs.size(); i < lhsSize; i += 1) {
Expression lhsExpr = lhsExprs.get(i);
ClassNode rhsType = i < rhsSize ? rhsExprs.get(i).getType() : maybeType;
if (lhsExpr instanceof VariableExpression) {
VariableExpression var = (VariableExpression) lhsExpr;
scope.addVariable(var.getName(), findVariableType(var, rhsType), null);
}
}
}
protected void handleSingleAssignment(Expression lhs, VariableScope scope, ClassNode rhsType) {
if (lhs instanceof PropertyExpression) {
lhs = ((PropertyExpression) lhs).getProperty();
handleSingleAssignment(lhs, scope, rhsType);
} else if (lhs instanceof VariableExpression) {
VariableExpression var = (VariableExpression) lhs;
if (scope.inScriptRunMethod() || scope.getEnclosingClosure() != null) {
// undeclared variables are allowed in scripts and may resolve to something in closures
scope.updateOrAddVariable(var.getName(), findVariableType(var, rhsType), findDeclaringType(var));
} else {
// undeclared variables are not allowed, so don't add just update
scope.updateVariable(var.getName(), findVariableType(var, rhsType), findDeclaringType(var));
}
scope.getWormhole().put("lhs", lhs);
} else if (lhs instanceof ConstantExpression) {
// not a variable, but save ref to help find accessor
scope.getWormhole().put("lhs", lhs);
}/* else {
System.err.println("AssignmentStorer.storeAssignment: LHS is " + lhs.getClass().getSimpleName());
}*/
}
/**
* This method is a placeholder for supporting more than just assignments.
*/
private boolean isInterestingOperation(BinaryExpression assign) {
switch (assign.getOperation().getType()) {
case Types.EQUALS:
// should we handle other cases too?
// case Types.PLUS_EQUAL:
// case Types.MINUS_EQUAL:
// case Types.LEFT_SHIFT:
// case Types.BITWISE_AND_EQUAL:
// case Types.BITWISE_OR_EQUAL:
// case Types.BITWISE_XOR_EQUAL:
// case Types.DIVIDE_EQUAL:
// case Types.LOGICAL_AND_EQUAL:
// case Types.LOGICAL_OR_EQUAL:
return true;
default:
return false;
}
}
/**
* @return {@code true} if init is of type java.lang.Object, or there is no init
*/
private boolean isObjectType(Expression init) {
return init == null || VariableScope.OBJECT_CLASS_NODE.equals(init.getType());
}
private ClassNode findComponentType(ClassNode objectExpressionType) {
if (objectExpressionType == null) {
return VariableScope.OBJECT_CLASS_NODE;
} else {
return VariableScope.extractElementType(objectExpressionType);
}
}
/**
* Finds the declaring type of the accessed variable.
* Will be {@code null} if this is a local variable.
*/
private ClassNode findDeclaringType(VariableExpression var) {
if (var.getAccessedVariable() instanceof AnnotatedNode) {
return ((AnnotatedNode) var.getAccessedVariable()).getDeclaringClass();
}
return null;
}
private ClassNode findVariableType(VariableExpression var, ClassNode rhsType) {
ClassNode varType = var.getOriginType();
if (varType == null) {
varType = var.getType();
}
if (varType != null && !VariableScope.isVoidOrObject(varType)) {
return varType;
}
if (rhsType != null && !VariableScope.isVoidOrObject(rhsType)) {
return rhsType;
}
return VariableScope.OBJECT_CLASS_NODE;
}
}