/*
* 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.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import groovy.lang.Tuple;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.ImmutableClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.runtime.DateGroovyMethods;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods;
import org.codehaus.groovy.runtime.EncodingGroovyMethods;
import org.codehaus.groovy.runtime.ProcessGroovyMethods;
import org.codehaus.groovy.runtime.SwingGroovyMethods;
import org.codehaus.groovy.runtime.XmlGroovyMethods;
import org.codehaus.jdt.groovy.internal.compiler.ast.JDTMethodNode;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.groovy.core.util.GroovyUtils;
import org.eclipse.jdt.groovy.core.util.ReflectionUtils;
/**
* Maps variable names to types in a hierarchy.
*/
public class VariableScope implements Iterable<VariableScope.VariableInfo> {
public static final ClassNode OBJECT_CLASS_NODE = ClassHelper.OBJECT_TYPE;
public static final ClassNode GROOVY_OBJECT_CLASS_NODE = ClassHelper.GROOVY_OBJECT_TYPE;
public static final ClassNode NULL_TYPE = new ImmutableClassNode(Object.class);
public static final ClassNode VOID_CLASS_NODE = ClassHelper.make(void.class);
public static final ClassNode VOID_WRAPPER_CLASS_NODE = ClassHelper.void_WRAPPER_TYPE;
public static final ClassNode LIST_CLASS_NODE = ClassHelper.LIST_TYPE;
public static final ClassNode MAP_CLASS_NODE = ClassHelper.MAP_TYPE;
public static final ClassNode RANGE_CLASS_NODE = ClassHelper.RANGE_TYPE;
public static final ClassNode TUPLE_CLASS_NODE = ClassHelper.make(Tuple.class);
public static final ClassNode PATTERN_CLASS_NODE = ClassHelper.PATTERN_TYPE;
public static final ClassNode MATCHER_CLASS_NODE = ClassHelper.make(Matcher.class);
public static final ClassNode STRING_CLASS_NODE = ClassHelper.STRING_TYPE;
public static final ClassNode GSTRING_CLASS_NODE = ClassHelper.GSTRING_TYPE;
public static final ClassNode CLOSURE_CLASS_NODE = ClassHelper.CLOSURE_TYPE;
public static final ClassNode NUMBER_CLASS_NODE = ClassHelper.make(Number.class);
public static final ClassNode ITERATOR_CLASS = ClassHelper.make(Iterator.class);
public static final ClassNode ENUMERATION_CLASS = ClassHelper.make(Enumeration.class);
public static final ClassNode INPUT_STREAM_CLASS = ClassHelper.make(InputStream.class);
public static final ClassNode OUTPUT_STREAM_CLASS = ClassHelper.make(OutputStream.class);
public static final ClassNode DATA_INPUT_STREAM_CLASS = ClassHelper.make(DataInputStream.class);
public static final ClassNode DATA_OUTPUT_STREAM_CLASS = ClassHelper.make(DataOutputStream.class);
public static final ClassNode OBJECT_OUTPUT_STREAM = ClassHelper.make(ObjectOutputStream.class);
public static final ClassNode OBJECT_INPUT_STREAM = ClassHelper.make(ObjectInputStream.class);
public static final ClassNode FILE_CLASS_NODE = ClassHelper.make(File.class);
public static final ClassNode BUFFERED_READER_CLASS_NODE = ClassHelper.make(BufferedReader.class);
public static final ClassNode BUFFERED_WRITER_CLASS_NODE = ClassHelper.make(BufferedWriter.class);
public static final ClassNode PRINT_WRITER_CLASS_NODE = ClassHelper.make(PrintWriter.class);
// standard category classes
public static final ClassNode DGM_CLASS_NODE = ClassHelper.make(DefaultGroovyMethods.class);
public static final ClassNode EGM_CLASS_NODE = ClassHelper.make(EncodingGroovyMethods.class);
public static final ClassNode PGM_CLASS_NODE = ClassHelper.make(ProcessGroovyMethods.class);
public static final ClassNode SGM_CLASS_NODE = ClassHelper.make(SwingGroovyMethods.class);
public static final ClassNode XGM_CLASS_NODE = ClassHelper.make(XmlGroovyMethods.class);
public static final ClassNode DGSM_CLASS_NODE = ClassHelper.make(DefaultGroovyStaticMethods.class);
public static final ClassNode DATE_GM_CLASS_NODE = ClassHelper.make(DateGroovyMethods.class);
// only exists on Groovy 2.0
public static ClassNode RESOURCE_GROOVY_METHODS;
public static ClassNode STRING_GROOVY_METHODS;
public static ClassNode IO_GROOVY_METHODS;
static {
try {
RESOURCE_GROOVY_METHODS = ClassHelper.make(Class.forName("org.codehaus.groovy.runtime.ResourceGroovyMethods"));
STRING_GROOVY_METHODS = ClassHelper.make(Class.forName("org.codehaus.groovy.runtime.StringGroovyMethods"));
IO_GROOVY_METHODS = ClassHelper.make(Class.forName("org.codehaus.groovy.runtime.IOGroovyMethods"));
} catch (ClassNotFoundException e) {
RESOURCE_GROOVY_METHODS = null;
STRING_GROOVY_METHODS = null;
IO_GROOVY_METHODS = null;
}
}
// only exists on 2.1 and later
public static ClassNode DELEGATES_TO;
static {
try {
DELEGATES_TO = ClassHelper.make(Class.forName("groovy.lang.DelegatesTo"));
} catch (ClassNotFoundException e) {
DELEGATES_TO = null;
}
}
public static Set<ClassNode> ALL_DEFAULT_CATEGORIES;
static {
// add all of the known DGM classes. Order counts since we look up earlier in the list before later and need to
// ensure we don't accidentally place deprecated elements early in the list
List<ClassNode> dgm_classes = new ArrayList<ClassNode>(10);
if (STRING_GROOVY_METHODS != null) {
dgm_classes.add(STRING_GROOVY_METHODS);
}
if (RESOURCE_GROOVY_METHODS != null) {
dgm_classes.add(RESOURCE_GROOVY_METHODS);
}
if (IO_GROOVY_METHODS != null) {
dgm_classes.add(IO_GROOVY_METHODS);
}
dgm_classes.add(EGM_CLASS_NODE);
dgm_classes.add(PGM_CLASS_NODE);
dgm_classes.add(SGM_CLASS_NODE);
dgm_classes.add(XGM_CLASS_NODE);
dgm_classes.add(DATE_GM_CLASS_NODE);
dgm_classes.add(DGSM_CLASS_NODE);
dgm_classes.add(DGM_CLASS_NODE);
ALL_DEFAULT_CATEGORIES = Collections.unmodifiableSet(new LinkedHashSet<ClassNode>(dgm_classes));
}
// don't cache because we have to add properties
public static final ClassNode CLASS_CLASS_NODE = ClassHelper.makeWithoutCaching(Class.class);
static {
initializeProperties(CLASS_CLASS_NODE);
}
public static final ClassNode CLASS_ARRAY_CLASS_NODE = CLASS_CLASS_NODE.makeArray();
// primitive wrapper classes
public static final ClassNode BOOLEAN_CLASS_NODE = ClassHelper.Boolean_TYPE;
public static final ClassNode CHARACTER_CLASS_NODE = ClassHelper.Character_TYPE;
public static final ClassNode BYTE_CLASS_NODE = ClassHelper.Byte_TYPE;
public static final ClassNode INTEGER_CLASS_NODE = ClassHelper.Integer_TYPE;
public static final ClassNode SHORT_CLASS_NODE = ClassHelper.Short_TYPE;
public static final ClassNode LONG_CLASS_NODE = ClassHelper.Long_TYPE;
public static final ClassNode FLOAT_CLASS_NODE = ClassHelper.Float_TYPE;
public static final ClassNode DOUBLE_CLASS_NODE = ClassHelper.Double_TYPE;
//--------------------------------------------------------------------------
public static class VariableInfo {
public ASTNode scopeNode;
public final String name;
public final ClassNode type;
public final ClassNode declaringType;
public VariableInfo(String name, ClassNode type, ClassNode declaringType) {
this.name = name;
this.type = type;
this.declaringType = declaringType;
}
private VariableInfo(VariableInfo info, ASTNode node) {
this(info.name, info.type, info.declaringType);
this.scopeNode = node;
}
public String getTypeSignature() {
return GroovyUtils.getTypeSignature(type, /*fully-qualified:*/ true, false);
}
}
public static class CallAndType {
public CallAndType(MethodCallExpression call, ClassNode declaringType, ASTNode declaration) {
this.call = call;
this.declaringType = declaringType;
this.declaration = declaration;
// the @DelegatesTo Groovy 2.1 annotation
if (DELEGATES_TO != null && declaration instanceof MethodNode) {
MethodNode methodDecl = (MethodNode) declaration;
if (methodDecl.getParameters() != null) {
Expression argsExpr = call.getArguments();
List<Expression> args = null;
if (argsExpr instanceof TupleExpression) {
args = ((TupleExpression) argsExpr).getExpressions();
}
if (args != null) {
Parameter[] parameters = methodDecl.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter p = parameters[i];
List<AnnotationNode> annotations = p.getAnnotations();
if (annotations != null) {
for (AnnotationNode annotation : annotations) {
if (annotation.getClassNode().getName().equals(DELEGATES_TO.getName()) &&
args.size() > i && args.get(i) instanceof ClosureExpression &&
annotation.getMember("value") instanceof ClassExpression) {
delegatesToClosures = Collections.singletonMap(
(ClosureExpression) args.get(i), annotation.getMember("value").getType());
}
}
}
}
}
}
}
if (delegatesToClosures == null) {
delegatesToClosures = Collections.emptyMap();
}
}
public final ASTNode declaration;
public final MethodCallExpression call;
public final ClassNode declaringType;
public Map<ClosureExpression, ClassNode> delegatesToClosures;
}
/**
* Contains state that is shared amongst {@link VariableScope}s
*/
private class SharedState {
/**
* this field stores values that need to get passed between parts of the file to another
*/
final Map<String, Object> wormhole = new HashMap<String, Object>();
/**
* the enclosing method call is the one where there are the current node is part of an argument list
*/
final LinkedList<CallAndType> enclosingCallStack = new LinkedList<VariableScope.CallAndType>();
/**
* Node currently being evaluated, or null if none
*/
final LinkedList<ASTNode> nodeStack = new LinkedList<ASTNode>();
/**
* true iff current scope is implicit run method of script
*/
boolean isRunMethod;
}
/**
* Null for the top level scope
*/
private VariableScope parent;
/**
* Shared with parent scopes
*/
private SharedState shared;
/**
* AST node for this scope, typically, a block, closure, or body declaration
*/
private ASTNode scopeNode;
/**
* number of parameters of current method call or -1 if not a method call
*/
private boolean isPrimaryNode;
private final boolean isStaticScope;
/**
* Category that will be declared in the next scope
*/
private ClassNode categoryBeingDeclared;
private List<ClassNode> methodCallArgumentTypes;
private GenericsType[] methodCallGenericsTypes;
private final Map<String, VariableInfo> nameVariableMap = new HashMap<String, VariableInfo>();
//--------------------------------------------------------------------------
public VariableScope(VariableScope parent, ASTNode enclosingNode, boolean isStatic) {
this.parent = parent;
this.scopeNode = enclosingNode;
this.shared = parent != null ? parent.shared : new SharedState();
this.isStaticScope = (isStatic || (parent != null && parent.isStaticScope)) &&
(getEnclosingClosure() == null); // if in a closure, items may be found on delegate or owner
// keep track of whether or not in a script body; also, try not to recalculate each time
if (enclosingNode instanceof MethodNode) {
this.shared.isRunMethod = ((MethodNode) enclosingNode).isScriptBody();
} else if (enclosingNode instanceof FieldNode || enclosingNode instanceof ClassNode) {
this.shared.isRunMethod = false;
}
}
/**
* Back door for storing and retrieving objects between lookup locations.
*/
public Map<String, Object> getWormhole() {
return shared.wormhole;
}
public ASTNode getEnclosingNode() {
if (shared.nodeStack.size() > 1) {
ASTNode current = shared.nodeStack.removeLast();
ASTNode enclosing = shared.nodeStack.getLast();
shared.nodeStack.add(current);
return enclosing;
} else {
return null;
}
}
public void setPrimaryNode(boolean isPrimaryNode) {
this.isPrimaryNode = isPrimaryNode;
}
public void setCurrentNode(ASTNode currentNode) {
shared.nodeStack.add(currentNode);
}
public void forgetCurrentNode() {
if (!shared.nodeStack.isEmpty()) {
shared.nodeStack.removeLast();
}
}
public ASTNode getCurrentNode() {
if (!shared.nodeStack.isEmpty()) {
return shared.nodeStack.getLast();
} else {
return null;
}
}
/**
* The name of all categories in scope.
*/
public Set<ClassNode> getCategoryNames() {
if (parent != null) {
Set<ClassNode> categories = parent.getCategoryNames();
// don't look at this scope's category, but the parent scope's
// category. This is because although current scope knows that it
// is a category scope, the category type is only available from parent
// scope
if (parent.isCategoryBeingDeclared()) {
categories.add(parent.categoryBeingDeclared);
}
return categories;
} else {
return new LinkedHashSet<ClassNode>(ALL_DEFAULT_CATEGORIES);
}
}
private boolean isCategoryBeingDeclared() {
return categoryBeingDeclared != null;
}
public void setCategoryBeingDeclared(ClassNode categoryBeingDeclared) {
this.categoryBeingDeclared = categoryBeingDeclared;
}
/**
* Finds the variable in the current scope or parent scopes.
*
* @return the variable info or null if not found
*/
public VariableInfo lookupName(String name) {
if ("super".equals(name)) {
ClassNode type = getDelegateOrThis();
if (type != null) {
ClassNode superType = type.getSuperClass();
superType = superType == null ? VariableScope.OBJECT_CLASS_NODE : superType;
return new VariableInfo(name, superType, superType);
}
}
VariableInfo var = lookupNameInCurrentScope(name);
if (var == null && parent != null) {
var = parent.lookupName(name);
}
return var;
}
/**
* Finds the name in the current scope. Does not recur up to parent scopes.
*/
public VariableInfo lookupNameInCurrentScope(String name) {
VariableInfo info = nameVariableMap.get(name);
if (info != null) {
info = new VariableInfo(info, scopeNode);
}
return info;
}
public ClassNode getThis() {
VariableInfo thiz = lookupName("this");
return thiz != null ? thiz.type : null;
}
public ClassNode getDelegate() {
VariableInfo delegate = lookupName("delegate");
return delegate != null ? delegate.type : null;
}
/**
* @return the current delegate type if exists, or this type if exists, or
* Object. Returns null if in top level scope (i.e. in import statement).
*/
public VariableInfo getDelegateOrThisInfo() {
VariableInfo info = lookupName("delegate");
if (info != null) {
return info;
}
info = lookupName("this");
// might be null if in imports
return info;
}
public ClassNode getDelegateOrThis() {
VariableInfo info = getDelegateOrThisInfo();
return info != null ? info.type : null;
}
public void addVariable(String name, ClassNode type, ClassNode declaringType) {
nameVariableMap.put(name, new VariableInfo(name, type, declaringType != null ? declaringType : OBJECT_CLASS_NODE));
}
public void addVariable(Variable var) {
addVariable(var.getName(), var.getType(), var.getOriginType());
}
public ModuleNode getEnclosingModuleNode() {
if (scopeNode instanceof ModuleNode) {
return (ModuleNode) scopeNode;
} else if (parent != null) {
return parent.getEnclosingModuleNode();
} else {
return null;
}
}
public ClassNode getEnclosingTypeDeclaration() {
if (scopeNode instanceof ClassNode) {
return (ClassNode) scopeNode;
} else if (parent != null) {
return parent.getEnclosingTypeDeclaration();
} else {
return null;
}
}
public FieldNode getEnclosingFieldDeclaration() {
if (scopeNode instanceof FieldNode) {
return (FieldNode) scopeNode;
} else if (parent != null) {
return parent.getEnclosingFieldDeclaration();
} else {
return null;
}
}
public MethodNode getEnclosingMethodDeclaration() {
if (scopeNode instanceof MethodNode) {
return (MethodNode) scopeNode;
} else if (parent != null) {
return parent.getEnclosingMethodDeclaration();
} else {
return null;
}
}
public ClosureExpression getEnclosingClosure() {
if (scopeNode instanceof ClosureExpression) {
return (ClosureExpression) scopeNode;
}
if (parent != null) {
return parent.getEnclosingClosure();
}
return null;
}
private static PropertyNode createPropertyNodeForMethodNode(MethodNode methodNode) {
ClassNode propertyType = methodNode.getReturnType();
String methodName = methodNode.getName();
StringBuffer propertyName = new StringBuffer();
propertyName.append(Character.toLowerCase(methodName.charAt(3)));
if (methodName.length() > 4) {
propertyName.append(methodName.substring(4));
}
int mods = methodNode.getModifiers();
ClassNode declaringClass = methodNode.getDeclaringClass();
PropertyNode property = new PropertyNode(propertyName.toString(), mods, propertyType, declaringClass, null, null, null);
property.setDeclaringClass(declaringClass);
property.getField().setDeclaringClass(declaringClass);
return property;
}
private static void initializeProperties(ClassNode node) {
// getX methods
for (MethodNode methodNode : node.getMethods()) {
if (AccessorSupport.isGetter(methodNode)) {
node.addProperty(createPropertyNodeForMethodNode(methodNode));
}
}
}
/**
* Updates the type info of this variable if it already exists in scope, or just adds it if it doesn't
*/
public void updateOrAddVariable(String name, ClassNode type, ClassNode declaringType) {
if (!internalUpdateVariable(name, type, declaringType)) {
addVariable(name, type, declaringType);
}
}
/**
* Updates the identifier if it exists in this scope or a parent scope. Otherwise does nothing
*
* @param name identifier to update
* @param type type of identifier
* @param declaringType declaring type of identifier
* @return true iff the variable exists in scope and was updated
*/
public boolean updateVariable(String name, ClassNode type, ClassNode declaringType) {
return internalUpdateVariable(name, type, declaringType);
}
/**
* Return true if the type has been udpated, false otherwise
*/
private boolean internalUpdateVariable(String name, ClassNode type, ClassNode declaringType) {
VariableInfo info = lookupNameInCurrentScope(name);
if (info != null) {
nameVariableMap.put(name, new VariableInfo(name, type, declaringType == null ? info.declaringType : declaringType));
return true;
} else if (parent != null) {
return parent.internalUpdateVariable(name, type, declaringType);
} else {
return false;
}
}
public static ClassNode resolveTypeParameterization(GenericsMapper mapper, ClassNode type) {
if (mapper.hasGenerics()) {
GenericsType[] parameterizedTypes = GroovyUtils.getGenericsTypes(type);
if (parameterizedTypes.length > 0) {
for (int i = 0, n = parameterizedTypes.length; i < n; i += 1) {
GenericsType parameterizedType = parameterizedTypes[i];
ClassNode maybe = resolveTypeParameterization(mapper, parameterizedType, type);
if (maybe != type) {
assert n == 1;
type = maybe;
break;
}
}
}
}
return type;
}
public static ClassNode resolveTypeParameterization(GenericsMapper mapper, GenericsType generic, ClassNode unresolved) {
if (!generic.isWildcard()) {
resolveTypeParameterization(mapper, generic.getType()); // TODO: capture return value?
String toParameterizeName = generic.getName();
ClassNode resolved = mapper.findParameter(toParameterizeName, generic.getType());
// there are three known possibilities for resolved:
// 1. it is the resolution of a type parameter itself (eg- E --> String)
// 2. it is the resolved type parameter of a generic type (eg- Iterator<E> --> Iterator<String>)
// 3. it is a substitution of one type parameter for another (eg- List<T> --> List<E>, where T comes from the declaring type)
if (typeParameterExistsInRedirected(unresolved, toParameterizeName)) {
Assert.isLegal(unresolved.redirect() != unresolved, "Error: trying to resolve type parameters of a type declaration: " + unresolved);
// Iterator<E> --> Iterator<String>
generic.setLowerBound(null);
generic.setUpperBounds(null);
generic.setPlaceholder(false); // before setType to prevent mutation of resolved
generic.setWildcard(false);
generic.setResolved(true);
generic.setType(resolved);
generic.setName(generic.getType().getName());
} else {
// E --> String
// E[] --> String[]
while (unresolved.isArray()) {
unresolved = unresolved.getComponentType();
resolved = resolved.makeArray();
}
return resolved;
}
} else if (generic.getLowerBound() != null) {
// List<? super E> --> List<? super String>
ClassNode resolved = resolveTypeParameterization(mapper, generic.getLowerBound());
generic.setLowerBound(resolved);
generic.setResolved(true);
} else if (generic.getUpperBounds() != null) {
// List<? extends E> --> List<? extends String>
ClassNode[] parameterizedTypeUpperBounds = generic.getUpperBounds();
for (int j = 0, k = parameterizedTypeUpperBounds.length; j < k; j += 1) {
ClassNode resolved = resolveTypeParameterization(mapper, parameterizedTypeUpperBounds[j]);
parameterizedTypeUpperBounds[j] = resolved;
}
generic.setResolved(true);
}
return unresolved;
}
public static MethodNode resolveTypeParameterization(GenericsMapper mapper, MethodNode method) {
if (mapper.hasGenerics() && (GroovyUtils.getGenericsTypes(method).length > 0 ||
GroovyUtils.getGenericsTypes(method.getDeclaringClass()).length > 0)) {
ClassNode returnType = resolveTypeParameterization(mapper, clone(method.getReturnType()));
Parameter[] parameters = method.getParameters();
if (parameters != null && parameters.length > 0) {
int n = parameters.length;
parameters = new Parameter[n];
for (int i = 0; i < n; i += 1) {
Parameter original = method.getParameters()[i];
ClassNode parameterType = resolveTypeParameterization(mapper, clone(original.getType()));
parameters[i] = new Parameter(parameterType, original.getName(), original.getInitialExpression());
parameters[i].addAnnotations(original.getAnnotations());
parameters[i].setClosureSharedVariable(original.isClosureSharedVariable());
parameters[i].setDeclaringClass(original.getDeclaringClass()); // TODO: resolve?
parameters[i].setHasNoRealSourcePosition(original.hasNoRealSourcePosition());
parameters[i].setInStaticContext(original.isInStaticContext());
parameters[i].setModifiers(original.getModifiers());
parameters[i].copyNodeMetaData(original);
parameters[i].setOriginType(original.getOriginType());
parameters[i].setSourcePosition(original);
parameters[i].setSynthetic(original.isSynthetic());
}
}
MethodNode resolved;
if (method instanceof JDTMethodNode) {
resolved = new JDTMethodNode(
((JDTMethodNode) method).getMethodBinding(), ((JDTMethodNode) method).getResolver(),
method.getName(), method.getModifiers(), returnType, parameters, method.getExceptions(), method.getCode());
} else {
resolved = new MethodNode(
method.getName(), method.getModifiers(), returnType, parameters, method.getExceptions(), method.getCode());
resolved.addAnnotations(method.getAnnotations());
}
resolved.setAnnotationDefault(method.hasAnnotationDefault());
resolved.setDeclaringClass(resolveTypeParameterization(mapper, clone(method.getDeclaringClass())));
resolved.setGenericsTypes(method.getGenericsTypes()); // TODO: resolve?
resolved.setHasNoRealSourcePosition(method.hasNoRealSourcePosition());
resolved.copyNodeMetaData(method);
resolved.setOriginal(method.getOriginal());
resolved.setSourcePosition(method);
resolved.setSynthetic(method.isSynthetic());
resolved.setSyntheticPublic(method.isSyntheticPublic());
resolved.setVariableScope(method.getVariableScope());
method = resolved;
}
return method;
}
private static boolean typeParameterExistsInRedirected(ClassNode type, String toParameterizeName) {
ClassNode redirect = type.redirect();
GenericsType[] genericsTypes = redirect.getGenericsTypes();
if (genericsTypes != null) {
// I don't *think* we need to check here. if any type parameter exists in the redirect, then we are parameterizing
return true;
}
return false;
}
/**
* Create a copy of this class, taking into account generics and redirects
*/
public static ClassNode clone(ClassNode type) {
return cloneInternal(type, 0);
}
public static ClassNode clonedMap() {
ClassNode clone = clone(MAP_CLASS_NODE);
cleanGenerics(clone.getGenericsTypes()[0]);
cleanGenerics(clone.getGenericsTypes()[1]);
return clone;
}
public static ClassNode clonedList() {
ClassNode clone = clone(LIST_CLASS_NODE);
cleanGenerics(clone.getGenericsTypes()[0]);
return clone;
}
public static ClassNode clonedRange() {
ClassNode clone = clone(RANGE_CLASS_NODE);
cleanGenerics(clone.getGenericsTypes()[0]);
return clone;
}
private static void cleanGenerics(GenericsType gt) {
gt.getType().setGenericsTypes(null);
gt.setName("java.lang.Object");
gt.setPlaceholder(false);
gt.setWildcard(false);
gt.setResolved(true);
gt.setUpperBounds(null);
gt.setLowerBound(null);
}
/**
* Internal variant of clone that ensures recursion never gets too deep.
*
* @param type class node to clone
* @param depth stack overflow prevention
*/
private static ClassNode cloneInternal(ClassNode type, int depth) {
if (type == null || type.isPrimitive()) {
return type;
}
ClassNode newType = type.getPlainNodeReference();
newType.setSourcePosition(type);
newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder());
ReflectionUtils.setPrivateField(ClassNode.class, "componentType", newType,
cloneInternal(type.getComponentType(), depth + 1)
);
// GRECLIPSE-1024: set an arbitrary depth to return from
// ensures that improperly set up generics do not lead to infinite recursion
if (depth < 11) {
GenericsType[] generics = type.getGenericsTypes();
if (generics != null) {
int n = generics.length;
GenericsType[] clones = new GenericsType[n];
for (int i = 0; i < n; i += 1) {
clones[i] = clone(generics[i], depth);
}
newType.setGenericsTypes(clones);
}
}
return newType;
}
/**
* Create a copy of this {@link GenericsType}
*
* @param origgt the original {@link GenericsType} to copy
* @param depth prevent infinite recursion on bad generics
*/
public static GenericsType clone(GenericsType origgt, int depth) {
GenericsType newgt = new GenericsType();
newgt.setType(cloneInternal(origgt.getType(), depth + 1));
newgt.setLowerBound(cloneInternal(origgt.getLowerBound(), depth + 1));
ClassNode[] oldUpperBounds = origgt.getUpperBounds();
if (oldUpperBounds != null) {
int n = oldUpperBounds.length;
ClassNode[] newUpperBounds = new ClassNode[n];
for (int i = 0; i < n; i += 1) {
newUpperBounds[i] = cloneInternal(oldUpperBounds[i], depth + 1);
}
newgt.setUpperBounds(newUpperBounds);
}
newgt.setName(origgt.getName());
newgt.setPlaceholder(origgt.isPlaceholder());
newgt.setWildcard(origgt.isWildcard());
newgt.setResolved(origgt.isResolved());
newgt.setSourcePosition(origgt);
return newgt;
}
/**
* @return true iff this is a static stack frame
*/
public boolean isStatic() {
return isStaticScope;
}
/**
* @return true iff the current node is not the RHS of a dotted expression
*/
public boolean isPrimaryNode() {
return isPrimaryNode;
}
/**
* @return the enclosing method call expression if one exists, or null otherwise.
* For example, when visiting the following closure, the enclosing method call is 'run'
* <pre>
* def runner = new Runner()
* runner.run {
* print "hello!"
* }
* </pre>
*/
public List<CallAndType> getAllEnclosingMethodCallExpressions() {
return shared.enclosingCallStack;
}
public CallAndType getEnclosingMethodCallExpression() {
if (shared.enclosingCallStack.isEmpty()) {
return null;
} else {
return shared.enclosingCallStack.getLast();
}
}
public void addEnclosingMethodCall(CallAndType enclosingMethodCall) {
shared.enclosingCallStack.add(enclosingMethodCall);
}
public void forgetEnclosingMethodCall() {
shared.enclosingCallStack.removeLast();
}
public boolean isTopLevel() {
return parent == null;
}
/**
* Does the following name exist in this scope (does not recur up to parent scopes).
*
* @return {@code true} iff in the {@link #nameVariableMap}
*/
public boolean containsInThisScope(String name) {
return nameVariableMap.containsKey(name);
}
void setMethodCallArgumentTypes(List<ClassNode> methodCallArgumentTypes) {
this.methodCallArgumentTypes = methodCallArgumentTypes;
}
public List<ClassNode> getMethodCallArgumentTypes() {
return methodCallArgumentTypes;
}
void setMethodCallGenericsTypes(GenericsType[] methodCallGenericsTypes) {
this.methodCallGenericsTypes = methodCallGenericsTypes;
}
public GenericsType[] getMethodCallGenericsTypes() {
return methodCallGenericsTypes;
}
/**
* If visiting the identifier of a method call expression, this field will
* be equal to the number of arguments to the method call.
*/
int getMethodCallNumberOfArguments() {
return isMethodCall() ? methodCallArgumentTypes.size() : 0;
}
public boolean isMethodCall() {
return methodCallArgumentTypes != null;
}
public Iterator<VariableInfo> iterator() {
return new Iterator<VariableInfo>() {
VariableScope currentScope = VariableScope.this;
Iterator<VariableInfo> currentIter = currentScope.nameVariableMap.values().iterator();
public boolean hasNext() {
if (currentIter == null) {
return false;
}
if (!currentIter.hasNext()) {
currentScope = currentScope.parent;
currentIter = currentScope == null ? null : currentScope.nameVariableMap.values().iterator();
}
return currentIter != null && currentIter.hasNext();
}
public VariableInfo next() {
return new VariableInfo(currentIter.next(), currentScope.scopeNode);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Finds all interfaces implemented by {@code type} (including itself, if it
* is an interface). The ordering is that the interfaces closest to type are
* first (in declared order) and then interfaces declared on super interfaces
* occur (if they are not duplicates).
*
* @param allInterfaces an accumulator set that will ensure that each interface exists at most once and in a predictible order
* @param useResolved whether or not to use the resolved interfaces
*/
public static void findAllInterfaces(ClassNode type, LinkedHashSet<ClassNode> allInterfaces, boolean useResolved) {
if (!useResolved) type = type.redirect();
boolean isInterface = type.isInterface();
if (!isInterface || !allInterfaces.contains(type)) {
if (isInterface) {
allInterfaces.add(type);
}
// Urrrgh...I don't like this.
// Groovy compiler has a different notion of 'resolved' than we do here.
// Groovy compiler considers a resolved ClassNode one that has no redirect.
// However, we consider a ClassNode to be resolved if its type parameters are resolved.
ClassNode[] faces = !useResolved ? type.getInterfaces() : type.getUnresolvedInterfaces();
if (faces != null) {
for (ClassNode face : faces) {
findAllInterfaces(face, allInterfaces, useResolved);
}
}
if (!isInterface) {
ClassNode superType = type.getSuperClass();
if (superType != null && !OBJECT_CLASS_NODE.equals(superType)) {
findAllInterfaces(superType, allInterfaces, useResolved);
}
}
}
}
/**
* Creates a type hierarchy for the <code>clazz</code>>, including self.
* Classes come first and then interfaces.
* <p>
* FIXADE: The ordering of super interfaces will not be the same as in {@link VariableScope#findAllInterfaces(ClassNode, LinkedHashSet, boolean)}. Should we make it the same?
*/
public static void createTypeHierarchy(ClassNode type, LinkedHashSet<ClassNode> allClasses, boolean useResolved) {
if (!useResolved) {
type = type.redirect();
}
if (!allClasses.contains(type)) {
if (!type.isInterface()) {
allClasses.add(type);
ClassNode superClass;
// Urrrgh...I don't like this.
// Groovy compiler has a different notion of 'resolved' than we do here.
// Groovy compiler considers a resolved ClassNode one that has no redirect.
// however, we consider a ClassNode to be resolved if its type parameters are resolved.
// that is why we call getUnresolvedSuperClass if useResolved is true (and vice versa).
if (useResolved) {
superClass = type.getUnresolvedSuperClass();
} else {
superClass = type.getSuperClass();
}
if (superClass != null) {
createTypeHierarchy(superClass, allClasses, useResolved);
}
}
// interfaces will be added from the top-most type first
findAllInterfaces(type, allClasses, useResolved);
}
}
/**
* Extracts an element type from a collection
*
* @param collectionType a collection object, or an object that is iterable
*/
public static ClassNode extractElementType(ClassNode collectionType) {
// if array, then use the component type
if (collectionType.isArray()) {
return collectionType.getComponentType();
}
// check to see if this type has an iterator method
// if so, then resolve the type parameters
MethodNode iterator = collectionType.getMethod("iterator", Parameter.EMPTY_ARRAY);
ClassNode typeToResolve = null;
if (iterator == null && collectionType.isInterface()) {
// could be a type that implements List
if (collectionType.implementsInterface(VariableScope.LIST_CLASS_NODE) && collectionType.getGenericsTypes() != null
&& collectionType.getGenericsTypes().length == 1) {
typeToResolve = collectionType;
} else if (collectionType.declaresInterface(ITERATOR_CLASS) || collectionType.equals(ITERATOR_CLASS)
|| collectionType.declaresInterface(ENUMERATION_CLASS) || collectionType.equals(ENUMERATION_CLASS)) {
// if the type is an iterator or an enumeration, then resolve the type parameter
typeToResolve = collectionType;
} else if (collectionType.declaresInterface(MAP_CLASS_NODE) || collectionType.equals(MAP_CLASS_NODE)) {
// if the type is a map, then resolve the entrySet
MethodNode entrySetMethod = collectionType.getMethod("entrySet", Parameter.EMPTY_ARRAY);
if (entrySetMethod != null) {
typeToResolve = entrySetMethod.getReturnType();
}
}
} else if (iterator != null) {
typeToResolve = iterator.getReturnType();
}
if (typeToResolve != null) {
typeToResolve = clone(typeToResolve);
ClassNode unresolvedCollectionType = collectionType.redirect();
GenericsMapper mapper = GenericsMapper.gatherGenerics(collectionType, unresolvedCollectionType);
ClassNode resolved = resolveTypeParameterization(mapper, typeToResolve);
// the first type parameter of resolvedReturn should be what we want
GenericsType[] resolvedReturnGenerics = resolved.getGenericsTypes();
if (resolvedReturnGenerics != null && resolvedReturnGenerics.length > 0) {
return resolvedReturnGenerics[0].getType();
}
}
// this is hardcoded from DGM
if (collectionType.declaresInterface(INPUT_STREAM_CLASS) || collectionType.declaresInterface(DATA_INPUT_STREAM_CLASS)
|| collectionType.equals(INPUT_STREAM_CLASS) || collectionType.equals(DATA_INPUT_STREAM_CLASS)) {
return BYTE_CLASS_NODE;
}
// else assume collection of size 1 (itself)
return collectionType;
}
/**
* @return true iff the current scope is the implicit run method of a script
*/
public boolean inScriptRunMethod() {
return shared.isRunMethod;
}
public static boolean isPlainClosure(ClassNode type) {
return CLOSURE_CLASS_NODE.equals(type) && !type.isUsingGenerics();
}
public static boolean isParameterizedClosure(ClassNode type) {
return CLOSURE_CLASS_NODE.equals(type) && type.isUsingGenerics();
}
public static boolean isThisOrSuper(Variable var) {
return var.getName().equals("this") || var.getName().equals("super");
}
public static boolean isVoidOrObject(ClassNode type) {
return type != null && (type.getName().equals(VOID_CLASS_NODE.getName()) ||
type.getName().equals(VOID_WRAPPER_CLASS_NODE.getName()) ||
type.getName().equals(OBJECT_CLASS_NODE.getName()));
}
}