http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/ClassNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java new file mode 100644 index 0000000..083d5b4 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java @@ -0,0 +1,1503 @@ +/* + * 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.codehaus.groovy.ast; + +import groovy.lang.groovydoc.Groovydoc; +import groovy.lang.groovydoc.GroovydocHolder; +import org.apache.groovy.ast.tools.ClassNodeUtils; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.tools.ParameterUtils; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.transform.ASTTransformation; +import org.codehaus.groovy.transform.GroovyASTTransformation; +import org.codehaus.groovy.vmplugin.VMPluginFactory; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +/** + * Represents a class in the AST. + * <p> + * A ClassNode should be created using the methods in ClassHelper. + * This ClassNode may be used to represent a class declaration or + * any other type. This class uses a proxy mechanism allowing to + * create a class for a plain name at AST creation time. In another + * phase of the compiler the real ClassNode for the plain name may be + * found. To avoid the need of exchanging this ClassNode with an + * instance of the correct ClassNode the correct ClassNode is set as + * redirect. Most method calls are then redirected to that ClassNode. + * <p> + * There are three types of ClassNodes: + * <ol> + * <li> Primary ClassNodes:<br> + * A primary ClassNode is one where we have a source representation + * which is to be compiled by Groovy and which we have an AST for. + * The groovy compiler will output one class for each such ClassNode + * that passes through AsmBytecodeGenerator... not more, not less. + * That means for example Closures become such ClassNodes too at + * some point. + * <li> ClassNodes create through different sources (typically created + * from a java.lang.reflect.Class object):<br> + * The compiler will not output classes from these, the methods + * usually do not contain bodies. These kind of ClassNodes will be + * used in different checks, but not checks that work on the method + * bodies. For example if such a ClassNode is a super class to a primary + * ClassNode, then the abstract method test and others will be done + * with data based on these. Theoretically it is also possible to mix both + * (1 and 2) kind of classes in a hierarchy, but this probably works only + * in the newest Groovy versions. Such ClassNodes normally have to + * isResolved() returning true without having a redirect.In the Groovy + * compiler the only version of this, that exists, is a ClassNode created + * through a Class instance + * <li> Labels:<br> + * ClassNodes created through ClassHelper.makeWithoutCaching. They + * are place holders, its redirect points to the real structure, which can + * be a label too, but following all redirects it should end with a ClassNode + * from one of the other two categories. If ResolveVisitor finds such a + * node, it tries to set the redirects. Any such label created after + * ResolveVisitor has done its work needs to have a redirect pointing to + * case 1 or 2. If not the compiler may react strange... this can be considered + * as a kind of dangling pointer. + * </ol> + * <b>Note:</b> the redirect mechanism is only allowed for classes + * that are not primary ClassNodes. Typically this is done for classes + * created by name only. The redirect itself can be any type of ClassNode. + * <p> + * To describe generic type signature see {@link #getGenericsTypes()} and + * {@link #setGenericsTypes(GenericsType[])}. These methods are not proxied, + * they describe the type signature used at the point of declaration or the + * type signatures provided by the class. If the type signatures provided + * by the class are needed, then a call to {@link #redirect()} will help. + * + * @see org.codehaus.groovy.ast.ClassHelper + */ +public class ClassNode extends AnnotatedNode implements Opcodes, GroovydocHolder<ClassNode> { + + private static class MapOfLists { + private Map<Object, List<MethodNode>> map; + public List<MethodNode> get(Object key) { + return map == null ? null : map.get(key); + } + + public List<MethodNode> getNotNull(Object key) { + List<MethodNode> ret = get(key); + if (ret==null) ret = Collections.emptyList(); + return ret; + } + + public void put(Object key, MethodNode value) { + if (map == null) { + map = new HashMap<Object, List<MethodNode>>(); + } + if (map.containsKey(key)) { + get(key).add(value); + } else { + List<MethodNode> list = new ArrayList<MethodNode>(2); + list.add(value); + map.put(key, list); + } + } + + public void remove(Object key, MethodNode value) { + get(key).remove(value); + } + } + + public static final ClassNode[] EMPTY_ARRAY = new ClassNode[0]; + public static final ClassNode THIS = new ClassNode(Object.class); + public static final ClassNode SUPER = new ClassNode(Object.class); + + private String name; + private int modifiers; + private boolean syntheticPublic; + private ClassNode[] interfaces; + private MixinNode[] mixins; + private List<ConstructorNode> constructors; + private List<Statement> objectInitializers; + private MapOfLists methods; + private List<MethodNode> methodsList; + private LinkedList<FieldNode> fields; + private List<PropertyNode> properties; + private Map<String, FieldNode> fieldIndex; + private ModuleNode module; + private CompileUnit compileUnit; + private boolean staticClass = false; + private boolean scriptBody = false; + private boolean script; + private ClassNode superClass; + protected boolean isPrimaryNode; + protected List<InnerClassNode> innerClasses; + + /** + * The ASTTransformations to be applied to the Class + */ + private Map<CompilePhase, Map<Class<? extends ASTTransformation>, Set<ASTNode>>> transformInstances; + + // use this to synchronize access for the lazy init + protected final Object lazyInitLock = new Object(); + + // clazz!=null when resolved + protected Class clazz; + // only false when this classNode is constructed from a class + private volatile boolean lazyInitDone=true; + // not null if if the ClassNode is an array + private ClassNode componentType = null; + // if not null this instance is handled as proxy + // for the redirect + private ClassNode redirect=null; + // flag if the classes or its members are annotated + private boolean annotated; + + // type spec for generics + private GenericsType[] genericsTypes=null; + private boolean usesGenerics=false; + + // if set to true the name getGenericsTypes consists + // of 1 element describing the name of the placeholder + private boolean placeholder; + + /** + * Returns the ClassNode this ClassNode is redirecting to. + */ + public ClassNode redirect(){ + if (redirect==null) return this; + return redirect.redirect(); + } + + /** + * Sets this instance as proxy for the given ClassNode. + * @param cn the class to redirect to. If set to null the redirect will be removed + */ + public void setRedirect(ClassNode cn) { + if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode ("+getName()+"->"+cn.getName()+")."); + if (cn!=null) cn = cn.redirect(); + if (cn==this) return; + redirect = cn; + } + + /** + * Returns a ClassNode representing an array of the class + * represented by this ClassNode + */ + public ClassNode makeArray() { + if (redirect!=null) { + ClassNode res = redirect().makeArray(); + res.componentType = this; + return res; + } + ClassNode cn; + if (clazz!=null) { + Class ret = Array.newInstance(clazz,0).getClass(); + // don't use the ClassHelper here! + cn = new ClassNode(ret,this); + } else { + cn = new ClassNode(this); + } + return cn; + } + + /** + * @return true if this instance is a primary ClassNode + */ + public boolean isPrimaryClassNode() { + return redirect().isPrimaryNode || (componentType != null && componentType.isPrimaryClassNode()); + } + + /* + * Constructor used by makeArray() if no real class is available + */ + private ClassNode(ClassNode componentType) { + this(componentType.getName()+"[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE); + this.componentType = componentType.redirect(); + isPrimaryNode=false; + } + + /* + * Constructor used by makeArray() if a real class is available + */ + private ClassNode(Class c, ClassNode componentType) { + this(c); + this.componentType = componentType; + isPrimaryNode=false; + } + + /** + * Creates a ClassNode from a real class. The resulting + * ClassNode will not be a primary ClassNode. + */ + public ClassNode(Class c) { + this(c.getName(), c.getModifiers(), null, null ,MixinNode.EMPTY_ARRAY); + clazz=c; + lazyInitDone=false; + CompileUnit cu = getCompileUnit(); + if (cu!=null) cu.addClass(this); + isPrimaryNode=false; + } + + /** + * The complete class structure will be initialized only when really + * needed to avoid having too many objects during compilation + */ + private void lazyClassInit() { + if (lazyInitDone) return; + synchronized (lazyInitLock) { + if (redirect!=null) { + throw new GroovyBugError("lazyClassInit called on a proxy ClassNode, that must not happen."+ + "A redirect() call is missing somewhere!"); + } + if (lazyInitDone) return; + VMPluginFactory.getPlugin().configureClassNode(compileUnit,this); + lazyInitDone = true; + } + } + + // added to track the enclosing method for local inner classes + private MethodNode enclosingMethod = null; + + public MethodNode getEnclosingMethod() { + return redirect().enclosingMethod; + } + + public void setEnclosingMethod(MethodNode enclosingMethod) { + redirect().enclosingMethod = enclosingMethod; + } + + /** + * Indicates that this class has been "promoted" to public by + * Groovy when in fact there was no public modifier explicitly + * in the source code. I.e. it remembers that it has applied + * Groovy's "public classes by default" rule.This property is + * typically only of interest to AST transform writers. + * + * @return true if this class is public but had no explicit public modifier + */ + public boolean isSyntheticPublic() { + return syntheticPublic; + } + + public void setSyntheticPublic(boolean syntheticPublic) { + this.syntheticPublic = syntheticPublic; + } + + /** + * @param name is the full name of the class + * @param modifiers the modifiers, + * @param superClass the base class name - use "java.lang.Object" if no direct + * base class + * @see org.objectweb.asm.Opcodes + */ + public ClassNode(String name, int modifiers, ClassNode superClass) { + this(name, modifiers, superClass, EMPTY_ARRAY, MixinNode.EMPTY_ARRAY); + } + + /** + * @param name is the full name of the class + * @param modifiers the modifiers, + * @param superClass the base class name - use "java.lang.Object" if no direct + * base class + * @param interfaces the interfaces for this class + * @param mixins the mixins for this class + * @see org.objectweb.asm.Opcodes + */ + public ClassNode(String name, int modifiers, ClassNode superClass, ClassNode[] interfaces, MixinNode[] mixins) { + this.name = name; + this.modifiers = modifiers; + this.superClass = superClass; + this.interfaces = interfaces; + this.mixins = mixins; + isPrimaryNode = true; + if (superClass!=null) { + usesGenerics = superClass.isUsingGenerics(); + } + if (!usesGenerics && interfaces!=null) { + for (ClassNode anInterface : interfaces) { + usesGenerics = usesGenerics || anInterface.isUsingGenerics(); + if (usesGenerics) break; + } + } + this.methods = new MapOfLists(); + this.methodsList = Collections.emptyList(); + } + + /** + * Sets the superclass of this ClassNode + */ + public void setSuperClass(ClassNode superClass) { + redirect().superClass = superClass; + } + + /** + * @return the list of FieldNode's associated with this ClassNode + */ + public List<FieldNode> getFields() { + if (redirect!=null) return redirect().getFields(); + lazyClassInit(); + if (fields == null) + fields = new LinkedList<FieldNode> (); + return fields; + } + + /** + * @return the array of interfaces which this ClassNode implements + */ + public ClassNode[] getInterfaces() { + if (redirect!=null) return redirect().getInterfaces(); + lazyClassInit(); + return interfaces; + } + + public void setInterfaces(ClassNode[] interfaces) { + if (redirect!=null) { + redirect().setInterfaces(interfaces); + } else { + this.interfaces = interfaces; + } + } + + /** + * @return the array of mixins associated with this ClassNode + */ + public MixinNode[] getMixins() { + return redirect().mixins; + } + + + public void setMixins(MixinNode[] mixins) { + redirect().mixins = mixins; + } + + /** + * @return the list of methods associated with this ClassNode + */ + public List<MethodNode> getMethods() { + if (redirect!=null) return redirect().getMethods(); + lazyClassInit(); + return methodsList; + } + + /** + * @return the list of abstract methods associated with this + * ClassNode or null if there are no such methods + */ + public List<MethodNode> getAbstractMethods() { + List<MethodNode> result = new ArrayList<MethodNode>(3); + for (MethodNode method : getDeclaredMethodsMap().values()) { + if (method.isAbstract()) { + result.add(method); + } + } + + if (result.isEmpty()) { + return null; + } else { + return result; + } + + } + + public List<MethodNode> getAllDeclaredMethods() { + return new ArrayList<MethodNode>(getDeclaredMethodsMap().values()); + } + + public Set<ClassNode> getAllInterfaces () { + Set<ClassNode> res = new LinkedHashSet<ClassNode>(); + getAllInterfaces(res); + return res; + } + + private void getAllInterfaces(Set<ClassNode> res) { + if (isInterface()) + res.add(this); + + for (ClassNode anInterface : getInterfaces()) { + res.add(anInterface); + anInterface.getAllInterfaces(res); + } + } + + public Map<String, MethodNode> getDeclaredMethodsMap() { + Map<String, MethodNode> result = ClassNodeUtils.getDeclaredMethodsFromSuper(this); + ClassNodeUtils.addDeclaredMethodsFromInterfaces(this, result); + + // And add in the methods implemented in this class. + for (MethodNode method : getMethods()) { + String sig = method.getTypeDescriptor(); + result.put(sig, method); + } + return result; + } + + public String getName() { + return redirect().name; + } + + public String getUnresolvedName() { + return name; + } + + public String setName(String name) { + return redirect().name=name; + } + + public int getModifiers() { + return redirect().modifiers; + } + + public void setModifiers(int modifiers) { + redirect().modifiers = modifiers; + } + + public List<PropertyNode> getProperties() { + final ClassNode r = redirect(); + if (r.properties == null) + r.properties = new ArrayList<PropertyNode> (); + return r.properties; + } + + public List<ConstructorNode> getDeclaredConstructors() { + if (redirect != null) return redirect().getDeclaredConstructors(); + lazyClassInit(); + if (constructors == null) + constructors = new ArrayList<ConstructorNode> (); + return constructors; + } + + /** + * Finds a constructor matching the given parameters in this class. + * + * @return the constructor matching the given parameters or null + */ + public ConstructorNode getDeclaredConstructor(Parameter[] parameters) { + for (ConstructorNode method : getDeclaredConstructors()) { + if (parametersEqual(method.getParameters(), parameters)) { + return method; + } + } + return null; + } + + public void removeConstructor(ConstructorNode node) { + redirect().constructors.remove(node); + } + + public ModuleNode getModule() { + return redirect().module; + } + + public PackageNode getPackage() { + return getModule() == null ? null : getModule().getPackage(); + } + + public void setModule(ModuleNode module) { + redirect().module = module; + if (module != null) { + redirect().compileUnit = module.getUnit(); + } + } + + public void addField(FieldNode node) { + addField(node, false); + } + + public void addFieldFirst(FieldNode node) { + addField(node, true); + } + + private void addField(FieldNode node, boolean isFirst) { + final ClassNode r = redirect(); + node.setDeclaringClass(r); + node.setOwner(r); + if (r.fields == null) + r.fields = new LinkedList<>(); + if (r.fieldIndex == null) + r.fieldIndex = new HashMap<>(); + + if (isFirst) + r.fields.addFirst(node); + else + r.fields.add(node); + + r.fieldIndex.put(node.getName(), node); + } + + public Map<String, FieldNode> getFieldIndex() { + return fieldIndex; + } + + public void addProperty(PropertyNode node) { + node.setDeclaringClass(redirect()); + FieldNode field = node.getField(); + addField(field); + final ClassNode r = redirect(); + if (r.properties == null) + r.properties = new ArrayList<PropertyNode> (); + r.properties.add(node); + } + + public PropertyNode addProperty(String name, + int modifiers, + ClassNode type, + Expression initialValueExpression, + Statement getterBlock, + Statement setterBlock) { + for (PropertyNode pn : getProperties()) { + if (pn.getName().equals(name)) { + if (pn.getInitialExpression() == null && initialValueExpression != null) + pn.getField().setInitialValueExpression(initialValueExpression); + + if (pn.getGetterBlock() == null && getterBlock != null) + pn.setGetterBlock(getterBlock); + + if (pn.getSetterBlock() == null && setterBlock != null) + pn.setSetterBlock(setterBlock); + + return pn; + } + } + PropertyNode node = + new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock); + addProperty(node); + return node; + } + + public boolean hasProperty(String name) { + return getProperty(name) != null; + } + + public PropertyNode getProperty(String name) { + for (PropertyNode pn : getProperties()) { + if (pn.getName().equals(name)) return pn; + } + return null; + } + + public void addConstructor(ConstructorNode node) { + node.setDeclaringClass(this); + final ClassNode r = redirect(); + if (r.constructors == null) + r.constructors = new ArrayList<ConstructorNode> (); + r.constructors.add(node); + } + + public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) { + ConstructorNode node = new ConstructorNode(modifiers, parameters, exceptions, code); + addConstructor(node); + return node; + } + + public void addMethod(MethodNode node) { + node.setDeclaringClass(this); + ClassNode base = redirect(); + if (base.methodsList.isEmpty()) { + base.methodsList = new ArrayList<MethodNode>(); + } + base.methodsList.add(node); + base.methods.put(node.getName(), node); + } + + public void removeMethod(MethodNode node) { + ClassNode base = redirect(); + if (!base.methodsList.isEmpty()) { + base.methodsList.remove(node); + } + base.methods.remove(node.getName(), node); + } + + /** + * If a method with the given name and parameters is already defined then it is returned + * otherwise the given method is added to this node. This method is useful for + * default method adding like getProperty() or invokeMethod() where there may already + * be a method defined in a class and so the default implementations should not be added + * if already present. + */ + public MethodNode addMethod(String name, + int modifiers, + ClassNode returnType, + Parameter[] parameters, + ClassNode[] exceptions, + Statement code) { + MethodNode other = getDeclaredMethod(name, parameters); + // let's not add duplicate methods + if (other != null) { + return other; + } + MethodNode node = new MethodNode(name, modifiers, returnType, parameters, exceptions, code); + addMethod(node); + return node; + } + + /** + * @see #getDeclaredMethod(String, Parameter[]) + */ + public boolean hasDeclaredMethod(String name, Parameter[] parameters) { + MethodNode other = getDeclaredMethod(name, parameters); + return other != null; + } + + /** + * @see #getMethod(String, Parameter[]) + */ + public boolean hasMethod(String name, Parameter[] parameters) { + MethodNode other = getMethod(name, parameters); + return other != null; + } + + /** + * Adds a synthetic method as part of the compilation process + */ + public MethodNode addSyntheticMethod(String name, + int modifiers, + ClassNode returnType, + Parameter[] parameters, + ClassNode[] exceptions, + Statement code) { + MethodNode answer = addMethod(name, modifiers|ACC_SYNTHETIC, returnType, parameters, exceptions, code); + answer.setSynthetic(true); + return answer; + } + + public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) { + FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue); + addField(node); + return node; + } + + public FieldNode addFieldFirst(String name, int modifiers, ClassNode type, Expression initialValue) { + FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue); + addFieldFirst(node); + return node; + } + + public void addInterface(ClassNode type) { + // let's check if it already implements an interface + boolean skip = false; + ClassNode[] interfaces = redirect().interfaces; + for (ClassNode existing : interfaces) { + if (type.equals(existing)) { + skip = true; + break; + } + } + if (!skip) { + ClassNode[] newInterfaces = new ClassNode[interfaces.length + 1]; + System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length); + newInterfaces[interfaces.length] = type; + redirect().interfaces = newInterfaces; + } + } + + public boolean equals(Object o) { + if (redirect!=null) return redirect().equals(o); + if (!(o instanceof ClassNode)) return false; + ClassNode cn = (ClassNode) o; + return (cn.getText().equals(getText())); + } + + public int hashCode() { + if (redirect!=null) return redirect().hashCode(); + return getName().hashCode(); + } + + public void addMixin(MixinNode mixin) { + // let's check if it already uses a mixin + MixinNode[] mixins = redirect().mixins; + boolean skip = false; + for (MixinNode existing : mixins) { + if (mixin.equals(existing)) { + skip = true; + break; + } + } + if (!skip) { + MixinNode[] newMixins = new MixinNode[mixins.length + 1]; + System.arraycopy(mixins, 0, newMixins, 0, mixins.length); + newMixins[mixins.length] = mixin; + redirect().mixins = newMixins; + } + } + + /** + * Finds a field matching the given name in this class. + * + * @param name the name of the field of interest + * @return the method matching the given name and parameters or null + */ + public FieldNode getDeclaredField(String name) { + if (redirect != null) return redirect().getDeclaredField(name); + + lazyClassInit(); + return fieldIndex == null ? null : fieldIndex.get(name); + } + + /** + * Finds a field matching the given name in this class or a parent class. + * + * @param name the name of the field of interest + * @return the method matching the given name and parameters or null + */ + public FieldNode getField(String name) { + ClassNode node = this; + while (node != null) { + FieldNode fn = node.getDeclaredField(name); + if (fn != null) return fn; + node = node.getSuperClass(); + } + return null; + } + + /** + * @return the field node on the outer class or null if this is not an + * inner class + */ + public FieldNode getOuterField(String name) { + return null; + } + + /** + * Helper method to avoid casting to inner class + */ + public ClassNode getOuterClass() { + return null; + } + + /** + * Adds a statement to the object initializer. + * + * @param statements the statement to be added + */ + public void addObjectInitializerStatements(Statement statements) { + getObjectInitializerStatements().add(statements); + } + + public List<Statement> getObjectInitializerStatements() { + if (objectInitializers == null) + objectInitializers = new LinkedList<Statement> (); + return objectInitializers; + } + + private MethodNode getOrAddStaticConstructorNode() { + MethodNode method = null; + List declaredMethods = getDeclaredMethods("<clinit>"); + if (declaredMethods.isEmpty()) { + method = + addMethod("<clinit>", ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement()); + method.setSynthetic(true); + } + else { + method = (MethodNode) declaredMethods.get(0); + } + return method; + } + + public void addStaticInitializerStatements(List<Statement> staticStatements, boolean fieldInit) { + MethodNode method = getOrAddStaticConstructorNode(); + BlockStatement block = null; + Statement statement = method.getCode(); + if (statement == null) { + block = new BlockStatement(); + } + else if (statement instanceof BlockStatement) { + block = (BlockStatement) statement; + } + else { + block = new BlockStatement(); + block.addStatement(statement); + } + + // while anything inside a static initializer block is appended + // we don't want to append in the case we have a initialization + // expression of a static field. In that case we want to add + // before the other statements + if (!fieldInit) { + block.addStatements(staticStatements); + } else { + List<Statement> blockStatements = block.getStatements(); + staticStatements.addAll(blockStatements); + blockStatements.clear(); + blockStatements.addAll(staticStatements); + } + } + + public void positionStmtsAfterEnumInitStmts(List<Statement> staticFieldStatements) { + MethodNode method = getOrAddStaticConstructorNode(); + Statement statement = method.getCode(); + if (statement instanceof BlockStatement) { + BlockStatement block = (BlockStatement) statement; + // add given statements for explicitly declared static fields just after enum-special fields + // are found - the $VALUES binary expression marks the end of such fields. + List<Statement> blockStatements = block.getStatements(); + ListIterator<Statement> litr = blockStatements.listIterator(); + while (litr.hasNext()) { + Statement stmt = litr.next(); + if (stmt instanceof ExpressionStatement && + ((ExpressionStatement) stmt).getExpression() instanceof BinaryExpression) { + BinaryExpression bExp = (BinaryExpression) ((ExpressionStatement) stmt).getExpression(); + if (bExp.getLeftExpression() instanceof FieldExpression) { + FieldExpression fExp = (FieldExpression) bExp.getLeftExpression(); + if (fExp.getFieldName().equals("$VALUES")) { + for (Statement tmpStmt : staticFieldStatements) { + litr.add(tmpStmt); + } + } + } + } + } + } + } + + /** + * This methods returns a list of all methods of the given name + * defined in the current class + * @return the method list + * @see #getMethods(String) + */ + public List<MethodNode> getDeclaredMethods(String name) { + if (redirect!=null) return redirect().getDeclaredMethods(name); + lazyClassInit(); + return methods.getNotNull(name); + } + + /** + * This methods creates a list of all methods with this name of the + * current class and of all super classes + * @return the methods list + * @see #getDeclaredMethods(String) + */ + public List<MethodNode> getMethods(String name) { + List<MethodNode> answer = new ArrayList<MethodNode>(); + ClassNode node = this; + while (node != null) { + answer.addAll(node.getDeclaredMethods(name)); + node = node.getSuperClass(); + } + return answer; + } + + /** + * Finds a method matching the given name and parameters in this class. + * + * @return the method matching the given name and parameters or null + */ + public MethodNode getDeclaredMethod(String name, Parameter[] parameters) { + for (MethodNode method : getDeclaredMethods(name)) { + if (parametersEqual(method.getParameters(), parameters)) { + return method; + } + } + return null; + } + + /** + * Finds a method matching the given name and parameters in this class + * or any parent class. + * + * @return the method matching the given name and parameters or null + */ + public MethodNode getMethod(String name, Parameter[] parameters) { + for (MethodNode method : getMethods(name)) { + if (parametersEqual(method.getParameters(), parameters)) { + return method; + } + } + return null; + } + + /** + * @param type the ClassNode of interest + * @return true if this node is derived from the given ClassNode + */ + public boolean isDerivedFrom(ClassNode type) { + if (this.equals(ClassHelper.VOID_TYPE)) { + return type.equals(ClassHelper.VOID_TYPE); + } + if (type.equals(ClassHelper.OBJECT_TYPE)) return true; + ClassNode node = this; + while (node != null) { + if (type.equals(node)) { + return true; + } + node = node.getSuperClass(); + } + return false; + } + + /** + * @return true if this class is derived from a groovy object + * i.e. it implements GroovyObject + */ + public boolean isDerivedFromGroovyObject() { + return implementsInterface(ClassHelper.GROOVY_OBJECT_TYPE); + } + + /** + * @param classNode the class node for the interface + * @return true if this class or any base class implements the given interface + */ + public boolean implementsInterface(ClassNode classNode) { + ClassNode node = redirect(); + do { + if (node.declaresInterface(classNode)) { + return true; + } + node = node.getSuperClass(); + } + while (node != null); + return false; + } + + /** + * @param classNode the class node for the interface + * @return true if this class declares that it implements the given interface + * or if one of its interfaces extends directly or indirectly the interface + * + * NOTE: Doesn't consider an interface to implement itself. + * I think this is intended to be called on ClassNodes representing + * classes, not interfaces. + * + */ + public boolean declaresInterface(ClassNode classNode) { + ClassNode[] interfaces = redirect().getInterfaces(); + for (ClassNode cn : interfaces) { + if (cn.equals(classNode)) return true; + } + for (ClassNode cn : interfaces) { + if (cn.declaresInterface(classNode)) return true; + } + return false; + } + + /** + * @return the ClassNode of the super class of this type + */ + public ClassNode getSuperClass() { + if (!lazyInitDone && !isResolved()) { + throw new GroovyBugError("ClassNode#getSuperClass for "+getName()+" called before class resolving"); + } + ClassNode sn = redirect().getUnresolvedSuperClass(); + if (sn!=null) sn=sn.redirect(); + return sn; + } + + public ClassNode getUnresolvedSuperClass() { + return getUnresolvedSuperClass(true); + } + + public ClassNode getUnresolvedSuperClass(boolean useRedirect) { + if (!useRedirect) return superClass; + if (redirect != null) return redirect().getUnresolvedSuperClass(true); + lazyClassInit(); + return superClass; + } + + public void setUnresolvedSuperClass(ClassNode sn) { + superClass = sn; + } + + public ClassNode [] getUnresolvedInterfaces() { + return getUnresolvedInterfaces(true); + } + + public ClassNode [] getUnresolvedInterfaces(boolean useRedirect) { + if (!useRedirect) return interfaces; + if (redirect != null) return redirect().getUnresolvedInterfaces(true); + lazyClassInit(); + return interfaces; + } + + public CompileUnit getCompileUnit() { + if (redirect!=null) return redirect().getCompileUnit(); + if (compileUnit == null && module != null) { + compileUnit = module.getUnit(); + } + return compileUnit; + } + + protected void setCompileUnit(CompileUnit cu) { + if (redirect!=null) redirect().setCompileUnit(cu); + if (compileUnit!= null) compileUnit = cu; + } + + /** + * @return true if the two arrays are of the same size and have the same contents + */ + protected boolean parametersEqual(Parameter[] a, Parameter[] b) { + return ParameterUtils.parametersEqual(a, b); + } + + /** + * @return the package name of this class + */ + public String getPackageName() { + int idx = getName().lastIndexOf('.'); + if (idx > 0) { + return getName().substring(0, idx); + } + return null; + } + + public String getNameWithoutPackage() { + int idx = getName().lastIndexOf('.'); + if (idx > 0) { + return getName().substring(idx + 1); + } + return getName(); + } + + public void visitContents(GroovyClassVisitor visitor) { + // now let's visit the contents of the class + for (PropertyNode pn : getProperties()) { + visitor.visitProperty(pn); + } + + for (FieldNode fn : getFields()) { + visitor.visitField(fn); + } + + for (ConstructorNode cn : getDeclaredConstructors()) { + visitor.visitConstructor(cn); + } + + for (MethodNode mn : getMethods()) { + visitor.visitMethod(mn); + } + } + + public MethodNode getGetterMethod(String getterName) { + return getGetterMethod(getterName, true); + } + + public MethodNode getGetterMethod(String getterName, boolean searchSuperClasses) { + MethodNode getterMethod = null; + boolean booleanReturnOnly = getterName.startsWith("is"); + for (MethodNode method : getDeclaredMethods(getterName)) { + if (getterName.equals(method.getName()) + && ClassHelper.VOID_TYPE!=method.getReturnType() + && method.getParameters().length == 0 + && (!booleanReturnOnly || ClassHelper.Boolean_TYPE.equals(ClassHelper.getWrapper(method.getReturnType())))) { + // GROOVY-7363: There can be multiple matches for a getter returning a generic parameter type, due to + // the generation of a bridge method. The real getter is really the non-bridge, non-synthetic one as it + // has the most specific and exact return type of the two. Picking the bridge method results in loss of + // type information, as it down-casts the return type to the lower bound of the generic parameter. + if (getterMethod == null || getterMethod.isSynthetic()) { + getterMethod = method; + } + } + } + if (getterMethod != null) return getterMethod; + if (searchSuperClasses) { + ClassNode parent = getSuperClass(); + if (parent != null) return parent.getGetterMethod(getterName); + } + return null; + } + + public MethodNode getSetterMethod(String setterName) { + return getSetterMethod(setterName, true); + } + + public MethodNode getSetterMethod(String setterName, boolean voidOnly) { + for (MethodNode method : getDeclaredMethods(setterName)) { + if (setterName.equals(method.getName()) + && (!voidOnly || ClassHelper.VOID_TYPE==method.getReturnType()) + && method.getParameters().length == 1) { + return method; + } + } + ClassNode parent = getSuperClass(); + if (parent!=null) return parent.getSetterMethod(setterName, voidOnly); + return null; + } + + /** + * Is this class declared in a static method (such as a closure / inner class declared in a static method) + */ + public boolean isStaticClass() { + return redirect().staticClass; + } + + public void setStaticClass(boolean staticClass) { + redirect().staticClass = staticClass; + } + + /** + * @return Returns true if this inner class or closure was declared inside a script body + */ + public boolean isScriptBody() { + return redirect().scriptBody; + } + + public void setScriptBody(boolean scriptBody) { + redirect().scriptBody = scriptBody; + } + + public boolean isScript() { + return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE); + } + + public void setScript(boolean script) { + redirect().script = script; + } + + public String toString() { + return toString(true); + } + + public String toString(boolean showRedirect) { + if (isArray()) { + return componentType.toString(showRedirect)+"[]"; + } + StringBuilder ret = new StringBuilder(getName()); + if (placeholder) ret = new StringBuilder(getUnresolvedName()); + if (!placeholder && genericsTypes != null) { + ret.append(" <"); + for (int i = 0; i < genericsTypes.length; i++) { + if (i != 0) ret.append(", "); + GenericsType genericsType = genericsTypes[i]; + ret.append(genericTypeAsString(genericsType)); + } + ret.append(">"); + } + if (redirect != null && showRedirect) { + ret.append(" -> ").append(redirect().toString()); + } + return ret.toString(); + } + + /** + * This exists to avoid a recursive definition of toString. The default toString + * in GenericsType calls ClassNode.toString(), which calls GenericsType.toString(), etc. + * @param genericsType + * @return the string representing the generic type + */ + private String genericTypeAsString(GenericsType genericsType) { + StringBuilder ret = new StringBuilder(genericsType.getName()); + if (genericsType.getUpperBounds() != null) { + ret.append(" extends "); + for (int i = 0; i < genericsType.getUpperBounds().length; i++) { + ClassNode classNode = genericsType.getUpperBounds()[i]; + if (classNode.equals(this)) { + ret.append(classNode.getName()); + } else { + ret.append(classNode.toString(false)); + } + if (i + 1 < genericsType.getUpperBounds().length) ret.append(" & "); + } + } else if (genericsType.getLowerBound() !=null) { + ClassNode classNode = genericsType.getLowerBound(); + if (classNode.equals(this)) { + ret.append(" super ").append(classNode.getName()); + } else { + ret.append(" super ").append(classNode); + } + } + return ret.toString(); + } + + /** + * Returns true if the given method has a possibly matching instance method with the given name and arguments. + * + * @param name the name of the method of interest + * @param arguments the arguments to match against + * @return true if a matching method was found + */ + public boolean hasPossibleMethod(String name, Expression arguments) { + int count = 0; + + if (arguments instanceof TupleExpression) { + TupleExpression tuple = (TupleExpression) arguments; + // TODO this won't strictly be true when using list expansion in argument calls + count = tuple.getExpressions().size(); + } + ClassNode node = this; + do { + for (MethodNode method : getMethods(name)) { + if (method.getParameters().length == count && !method.isStatic()) { + return true; + } + } + node = node.getSuperClass(); + } + while (node != null); + return false; + } + + public MethodNode tryFindPossibleMethod(String name, Expression arguments) { + int count = 0; + + if (arguments instanceof TupleExpression) { + TupleExpression tuple = (TupleExpression) arguments; + // TODO this won't strictly be true when using list expansion in argument calls + count = tuple.getExpressions().size(); + } else + return null; + + MethodNode res = null; + ClassNode node = this; + TupleExpression args = (TupleExpression) arguments; + do { + for (MethodNode method : node.getMethods(name)) { + if (method.getParameters().length == count) { + boolean match = true; + for (int i = 0; i != count; ++i) + if (!args.getType().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(this)) + 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; + } + + /** + * Returns true if the given method has a possibly matching static method with the given name and arguments. + * + * @param name the name of the method of interest + * @param arguments the arguments to match against + * @return true if a matching method was found + */ + public boolean hasPossibleStaticMethod(String name, Expression arguments) { + return ClassNodeUtils.hasPossibleStaticMethod(this, name, arguments, false); + } + + public boolean isInterface(){ + return (getModifiers() & Opcodes.ACC_INTERFACE) > 0; + } + + public boolean isAbstract(){ + return (getModifiers() & Opcodes.ACC_ABSTRACT) > 0; + } + + public boolean isResolved() { + if (clazz != null) return true; + if (redirect != null) return redirect.isResolved(); + return componentType != null && componentType.isResolved(); + } + + public boolean isArray(){ + return componentType!=null; + } + + public ClassNode getComponentType() { + return componentType; + } + + /** + * Returns the concrete class this classnode relates to. However, this method + * is inherently unsafe as it may return null depending on the compile phase you are + * using. AST transformations should never use this method directly, but rather obtain + * a new class node using {@link #getPlainNodeReference()}. + * @return the class this classnode relates to. May return null. + */ + public Class getTypeClass(){ + if (clazz != null) return clazz; + if (redirect != null) return redirect.getTypeClass(); + + ClassNode component = redirect().componentType; + if (component!=null && component.isResolved()){ + return Array.newInstance(component.getTypeClass(), 0).getClass(); + } + throw new GroovyBugError("ClassNode#getTypeClass for "+getName()+" is called before the type class is set "); + } + + public boolean hasPackageName(){ + return redirect().name.indexOf('.')>0; + } + + /** + * Marks if the current class uses annotations or not + * @param flag + */ + public void setAnnotated(boolean flag) { + this.annotated = flag; + } + + public boolean isAnnotated() { + return this.annotated; + } + + public GenericsType[] getGenericsTypes() { + return genericsTypes; + } + + public void setGenericsTypes(GenericsType[] genericsTypes) { + usesGenerics = usesGenerics || genericsTypes!=null; + this.genericsTypes = genericsTypes; + } + + public void setGenericsPlaceHolder(boolean b) { + usesGenerics = usesGenerics || b; + placeholder = b; + } + + public boolean isGenericsPlaceHolder() { + return placeholder; + } + + public boolean isUsingGenerics() { + return usesGenerics; + } + + public void setUsingGenerics(boolean b) { + usesGenerics = b; + } + + public ClassNode getPlainNodeReference() { + if (ClassHelper.isPrimitiveType(this)) return this; + ClassNode n = new ClassNode(name, modifiers, superClass,null,null); + n.isPrimaryNode = false; + n.setRedirect(redirect()); + if (isArray()) { + n.componentType = redirect().getComponentType(); + } + return n; + } + + public boolean isAnnotationDefinition() { + return redirect().isPrimaryNode && + isInterface() && + (getModifiers() & Opcodes.ACC_ANNOTATION)!=0; + } + + public List<AnnotationNode> getAnnotations() { + if (redirect!=null) return redirect.getAnnotations(); + lazyClassInit(); + return super.getAnnotations(); + } + + public List<AnnotationNode> getAnnotations(ClassNode type) { + if (redirect!=null) return redirect.getAnnotations(type); + lazyClassInit(); + return super.getAnnotations(type); + } + + public void addTransform(Class<? extends ASTTransformation> transform, ASTNode node) { + GroovyASTTransformation annotation = transform.getAnnotation(GroovyASTTransformation.class); + if (annotation == null) return; + + Set<ASTNode> nodes = getTransformInstances().get(annotation.phase()).get(transform); + if (nodes == null) { + nodes = new LinkedHashSet<ASTNode>(); + getTransformInstances().get(annotation.phase()).put(transform, nodes); + } + nodes.add(node); + } + + public Map<Class <? extends ASTTransformation>, Set<ASTNode>> getTransforms(CompilePhase phase) { + return getTransformInstances().get(phase); + } + + public void renameField(String oldName, String newName) { + ClassNode r = redirect (); + if (r.fieldIndex == null) + r.fieldIndex = new HashMap<String,FieldNode> (); + final Map<String,FieldNode> index = r.fieldIndex; + index.put(newName, index.remove(oldName)); + } + + public void removeField(String oldName) { + ClassNode r = redirect (); + if (r.fieldIndex == null) + r.fieldIndex = new HashMap<String,FieldNode> (); + final Map<String,FieldNode> index = r.fieldIndex; + r.fields.remove(index.get(oldName)); + index.remove(oldName); + } + + public boolean isEnum() { + return (getModifiers()&Opcodes.ACC_ENUM) != 0; + } + + /** + * @return iterator of inner classes defined inside this one + */ + public Iterator<InnerClassNode> getInnerClasses() { + return (innerClasses == null ? Collections.<InnerClassNode>emptyList() : innerClasses).iterator(); + } + + private Map<CompilePhase, Map<Class<? extends ASTTransformation>, Set<ASTNode>>> getTransformInstances() { + if(transformInstances == null){ + transformInstances = new EnumMap<CompilePhase, Map<Class <? extends ASTTransformation>, Set<ASTNode>>>(CompilePhase.class); + for (CompilePhase phase : CompilePhase.values()) { + transformInstances.put(phase, new HashMap<Class <? extends ASTTransformation>, Set<ASTNode>>()); + } + } + return transformInstances; + } + + public boolean isRedirectNode() { + return redirect!=null; + } + + @Override + public String getText() { + return getName(); + } + + @Override + public Groovydoc getGroovydoc() { + return this.<Groovydoc>getNodeMetaData(DOC_COMMENT); + } + + @Override + public ClassNode getInstance() { + return this; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java b/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java new file mode 100644 index 0000000..523475e --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java @@ -0,0 +1,345 @@ +/* + * 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.codehaus.groovy.ast; + +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ArrayExpression; +import org.codehaus.groovy.ast.expr.AttributeExpression; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; +import org.codehaus.groovy.ast.expr.BooleanExpression; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ClosureListExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.ElvisOperatorExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.GStringExpression; +import org.codehaus.groovy.ast.expr.ListExpression; +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.MethodPointerExpression; +import org.codehaus.groovy.ast.expr.NotExpression; +import org.codehaus.groovy.ast.expr.PostfixExpression; +import org.codehaus.groovy.ast.expr.PrefixExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.RangeExpression; +import org.codehaus.groovy.ast.expr.SpreadExpression; +import org.codehaus.groovy.ast.expr.SpreadMapExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; +import org.codehaus.groovy.ast.expr.TernaryExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.UnaryMinusExpression; +import org.codehaus.groovy.ast.expr.UnaryPlusExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.AssertStatement; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.BreakStatement; +import org.codehaus.groovy.ast.stmt.CaseStatement; +import org.codehaus.groovy.ast.stmt.CatchStatement; +import org.codehaus.groovy.ast.stmt.ContinueStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.SwitchStatement; +import org.codehaus.groovy.ast.stmt.SynchronizedStatement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.ast.stmt.TryCatchStatement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.classgen.BytecodeExpression; + +import java.util.List; + +/** + * Abstract base class for any GroovyCodeVisitor which by default + * just walks the code and expression tree + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public abstract class CodeVisitorSupport implements GroovyCodeVisitor { + + public void visitBlockStatement(BlockStatement block) { + for (Statement statement : block.getStatements()) { + statement.visit(this); + } + } + + public void visitForLoop(ForStatement forLoop) { + forLoop.getCollectionExpression().visit(this); + forLoop.getLoopBlock().visit(this); + } + + public void visitWhileLoop(WhileStatement loop) { + loop.getBooleanExpression().visit(this); + loop.getLoopBlock().visit(this); + } + + public void visitDoWhileLoop(DoWhileStatement loop) { + loop.getLoopBlock().visit(this); + loop.getBooleanExpression().visit(this); + } + + public void visitIfElse(IfStatement ifElse) { + ifElse.getBooleanExpression().visit(this); + ifElse.getIfBlock().visit(this); + + Statement elseBlock = ifElse.getElseBlock(); + if (elseBlock instanceof EmptyStatement) { + // dispatching to EmptyStatement will not call back visitor, + // must call our visitEmptyStatement explicitly + visitEmptyStatement((EmptyStatement) elseBlock); + } else { + elseBlock.visit(this); + } + } + + public void visitExpressionStatement(ExpressionStatement statement) { + statement.getExpression().visit(this); + } + + public void visitReturnStatement(ReturnStatement statement) { + statement.getExpression().visit(this); + } + + public void visitAssertStatement(AssertStatement statement) { + statement.getBooleanExpression().visit(this); + statement.getMessageExpression().visit(this); + } + + public void visitTryCatchFinally(TryCatchStatement statement) { + statement.getTryStatement().visit(this); + for (CatchStatement catchStatement : statement.getCatchStatements()) { + catchStatement.visit(this); + } + Statement finallyStatement = statement.getFinallyStatement(); + if (finallyStatement instanceof EmptyStatement) { + // dispatching to EmptyStatement will not call back visitor, + // must call our visitEmptyStatement explicitly + visitEmptyStatement((EmptyStatement) finallyStatement); + } else { + finallyStatement.visit(this); + } + } + + protected void visitEmptyStatement(EmptyStatement statement) { + // noop + } + + public void visitSwitch(SwitchStatement statement) { + statement.getExpression().visit(this); + for (CaseStatement caseStatement : statement.getCaseStatements()) { + caseStatement.visit(this); + } + statement.getDefaultStatement().visit(this); + } + + public void visitCaseStatement(CaseStatement statement) { + statement.getExpression().visit(this); + statement.getCode().visit(this); + } + + public void visitBreakStatement(BreakStatement statement) { + } + + public void visitContinueStatement(ContinueStatement statement) { + } + + public void visitSynchronizedStatement(SynchronizedStatement statement) { + statement.getExpression().visit(this); + statement.getCode().visit(this); + } + + public void visitThrowStatement(ThrowStatement statement) { + statement.getExpression().visit(this); + } + + public void visitMethodCallExpression(MethodCallExpression call) { + call.getObjectExpression().visit(this); + call.getMethod().visit(this); + call.getArguments().visit(this); + } + + public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { + call.getArguments().visit(this); + } + + public void visitConstructorCallExpression(ConstructorCallExpression call) { + call.getArguments().visit(this); + } + + public void visitBinaryExpression(BinaryExpression expression) { + expression.getLeftExpression().visit(this); + expression.getRightExpression().visit(this); + } + + public void visitTernaryExpression(TernaryExpression expression) { + expression.getBooleanExpression().visit(this); + expression.getTrueExpression().visit(this); + expression.getFalseExpression().visit(this); + } + + public void visitShortTernaryExpression(ElvisOperatorExpression expression) { + visitTernaryExpression(expression); + } + + public void visitPostfixExpression(PostfixExpression expression) { + expression.getExpression().visit(this); + } + + public void visitPrefixExpression(PrefixExpression expression) { + expression.getExpression().visit(this); + } + + public void visitBooleanExpression(BooleanExpression expression) { + expression.getExpression().visit(this); + } + + public void visitNotExpression(NotExpression expression) { + expression.getExpression().visit(this); + } + + public void visitClosureExpression(ClosureExpression expression) { + expression.getCode().visit(this); + } + + public void visitTupleExpression(TupleExpression expression) { + visitListOfExpressions(expression.getExpressions()); + } + + public void visitListExpression(ListExpression expression) { + visitListOfExpressions(expression.getExpressions()); + } + + public void visitArrayExpression(ArrayExpression expression) { + visitListOfExpressions(expression.getExpressions()); + visitListOfExpressions(expression.getSizeExpression()); + } + + public void visitMapExpression(MapExpression expression) { + visitListOfExpressions(expression.getMapEntryExpressions()); + + } + + public void visitMapEntryExpression(MapEntryExpression expression) { + expression.getKeyExpression().visit(this); + expression.getValueExpression().visit(this); + + } + + public void visitRangeExpression(RangeExpression expression) { + expression.getFrom().visit(this); + expression.getTo().visit(this); + } + + public void visitSpreadExpression(SpreadExpression expression) { + expression.getExpression().visit(this); + } + + public void visitSpreadMapExpression(SpreadMapExpression expression) { + expression.getExpression().visit(this); + } + + public void visitMethodPointerExpression(MethodPointerExpression expression) { + expression.getExpression().visit(this); + expression.getMethodName().visit(this); + } + + public void visitUnaryMinusExpression(UnaryMinusExpression expression) { + expression.getExpression().visit(this); + } + + public void visitUnaryPlusExpression(UnaryPlusExpression expression) { + expression.getExpression().visit(this); + } + + public void visitBitwiseNegationExpression(BitwiseNegationExpression expression) { + expression.getExpression().visit(this); + } + + public void visitCastExpression(CastExpression expression) { + expression.getExpression().visit(this); + } + + public void visitConstantExpression(ConstantExpression expression) { + } + + public void visitClassExpression(ClassExpression expression) { + } + + public void visitVariableExpression(VariableExpression expression) { + } + + public void visitDeclarationExpression(DeclarationExpression expression) { + visitBinaryExpression(expression); + } + + public void visitPropertyExpression(PropertyExpression expression) { + expression.getObjectExpression().visit(this); + expression.getProperty().visit(this); + } + + public void visitAttributeExpression(AttributeExpression expression) { + expression.getObjectExpression().visit(this); + expression.getProperty().visit(this); + } + + public void visitFieldExpression(FieldExpression expression) { + } + + public void visitGStringExpression(GStringExpression expression) { + visitListOfExpressions(expression.getStrings()); + visitListOfExpressions(expression.getValues()); + } + + protected void visitListOfExpressions(List<? extends Expression> list) { + if (list == null) return; + for (Expression expression : list) { + if (expression instanceof SpreadExpression) { + Expression spread = ((SpreadExpression) expression).getExpression(); + spread.visit(this); + } else { + expression.visit(this); + } + } + } + + public void visitCatchStatement(CatchStatement statement) { + statement.getCode().visit(this); + } + + public void visitArgumentlistExpression(ArgumentListExpression ale) { + visitTupleExpression(ale); + } + + public void visitClosureListExpression(ClosureListExpression cle) { + visitListOfExpressions(cle.getExpressions()); + } + + public void visitBytecodeExpression(BytecodeExpression cle) { + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/CompileUnit.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/CompileUnit.java b/src/main/java/org/codehaus/groovy/ast/CompileUnit.java new file mode 100644 index 0000000..ea0722c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/CompileUnit.java @@ -0,0 +1,193 @@ +/* + * 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.codehaus.groovy.ast; + +import groovy.lang.GroovyClassLoader; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; + +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Represents the entire contents of a compilation step which consists of one or more + * {@link ModuleNode} instances. There's one instance of this that's shared by all modules and + * classes compiled during a single invocation of the compiler. + * <p> + * It's attached to MethodNodes and ClassNodes and is used to find fully qualified names of classes, + * resolve imports, and that sort of thing. + * + * @author <a href="mailto:[email protected]">James Strachan </a> + */ +public class CompileUnit { + + private final List<ModuleNode> modules = new ArrayList<ModuleNode>(); + private final Map<String, ClassNode> classes = new HashMap<String, ClassNode>(); + private final CompilerConfiguration config; + private final GroovyClassLoader classLoader; + private final CodeSource codeSource; + private final Map<String, ClassNode> classesToCompile = new HashMap<String, ClassNode>(); + private final Map<String, SourceUnit> classNameToSource = new HashMap<String, SourceUnit>(); + private final Map<String, InnerClassNode> generatedInnerClasses = new HashMap(); + + public CompileUnit(GroovyClassLoader classLoader, CompilerConfiguration config) { + this(classLoader, null, config); + } + + public CompileUnit(GroovyClassLoader classLoader, CodeSource codeSource, CompilerConfiguration config) { + this.classLoader = classLoader; + this.config = config; + this.codeSource = codeSource; + } + + public List<ModuleNode> getModules() { + return modules; + } + + public void addModule(ModuleNode node) { + // node==null means a compilation error prevented + // groovy from building an ast + if (node == null) return; + modules.add(node); + node.setUnit(this); + addClasses(node.getClasses()); + } + + /** + * @return the ClassNode for the given qualified name or returns null if + * the name does not exist in the current compilation unit + * (ignoring the .class files on the classpath) + */ + public ClassNode getClass(String name) { + ClassNode cn = classes.get(name); + if (cn != null) return cn; + return classesToCompile.get(name); + } + + /** + * @return a list of all the classes in each module in the compilation unit + */ + public List getClasses() { + List<ClassNode> answer = new ArrayList<ClassNode>(); + for (ModuleNode module : modules) { + answer.addAll(module.getClasses()); + } + return answer; + } + + public CompilerConfiguration getConfig() { + return config; + } + + public GroovyClassLoader getClassLoader() { + return classLoader; + } + + public CodeSource getCodeSource() { + return codeSource; + } + + /** + * Appends all of the fully qualified class names in this + * module into the given map + */ + void addClasses(List<ClassNode> classList) { + for (ClassNode node : classList) { + addClass(node); + } + } + + /** + * Adds a class to the unit. + */ + public void addClass(ClassNode node) { + node = node.redirect(); + String name = node.getName(); + ClassNode stored = classes.get(name); + if (stored != null && stored != node) { + // we have a duplicate class! + // One possibility for this is, that we declared a script and a + // class in the same file and named the class like the file + SourceUnit nodeSource = node.getModule().getContext(); + SourceUnit storedSource = stored.getModule().getContext(); + String txt = "Invalid duplicate class definition of class " + node.getName() + " : "; + if (nodeSource == storedSource) { + // same class in same source + txt += "The source " + nodeSource.getName() + " contains at least two definitions of the class " + node.getName() + ".\n"; + if (node.isScriptBody() || stored.isScriptBody()) { + txt += "One of the classes is an explicit generated class using the class statement, the other is a class generated from" + + " the script body based on the file name. Solutions are to change the file name or to change the class name.\n"; + } + } else { + txt += "The sources " + nodeSource.getName() + " and " + storedSource.getName() + " each contain a class with the name " + node.getName() + ".\n"; + } + nodeSource.getErrorCollector().addErrorAndContinue( + new SyntaxErrorMessage(new SyntaxException(txt, node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()), nodeSource) + ); + } + classes.put(name, node); + + if (classesToCompile.containsKey(name)) { + ClassNode cn = classesToCompile.get(name); + cn.setRedirect(node); + classesToCompile.remove(name); + } + } + + /** + * this method actually does not compile a class. It's only + * a marker that this type has to be compiled by the CompilationUnit + * at the end of a parse step no node should be be left. + */ + public void addClassNodeToCompile(ClassNode node, SourceUnit location) { + classesToCompile.put(node.getName(), node); + classNameToSource.put(node.getName(), location); + } + + public SourceUnit getScriptSourceLocation(String className) { + return classNameToSource.get(className); + } + + public boolean hasClassNodeToCompile() { + return !classesToCompile.isEmpty(); + } + + public Iterator<String> iterateClassNodeToCompile() { + return classesToCompile.keySet().iterator(); + } + + public InnerClassNode getGeneratedInnerClass(String name) { + return generatedInnerClasses.get(name); + } + + public void addGeneratedInnerClass(InnerClassNode icn) { + generatedInnerClasses.put(icn.getName(), icn); + } + + public Map<String, InnerClassNode> getGeneratedInnerClasses() { + return Collections.unmodifiableMap(generatedInnerClasses); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/ConstructorNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ConstructorNode.java b/src/main/java/org/codehaus/groovy/ast/ConstructorNode.java new file mode 100644 index 0000000..fe7a7da --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/ConstructorNode.java @@ -0,0 +1,59 @@ +/* + * 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.codehaus.groovy.ast; + +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.Statement; + + +/** + * Represents a constructor declaration + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class ConstructorNode extends MethodNode { + + public ConstructorNode(int modifiers, Statement code) { + this(modifiers, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, code); + } + + public ConstructorNode(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) { + super("<init>",modifiers,ClassHelper.VOID_TYPE,parameters,exceptions,code); + + // This variable scope is thrown out and replaced with a different one during semantic analysis. + VariableScope scope = new VariableScope(); + for (int i = 0; i < parameters.length; i++) { + scope.putDeclaredVariable(parameters[i]); + } + this.setVariableScope(scope); + } + + public boolean firstStatementIsSpecialConstructorCall() { + Statement code = getFirstStatement(); + if (code == null || !(code instanceof ExpressionStatement)) return false; + + Expression expression = ((ExpressionStatement) code).getExpression(); + if (!(expression instanceof ConstructorCallExpression)) return false; + ConstructorCallExpression cce = (ConstructorCallExpression) expression; + return cce.isSpecialCall(); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/DynamicVariable.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/DynamicVariable.java b/src/main/java/org/codehaus/groovy/ast/DynamicVariable.java new file mode 100644 index 0000000..f2d4550 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/DynamicVariable.java @@ -0,0 +1,76 @@ +/* + * 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.codehaus.groovy.ast; + +import org.codehaus.groovy.ast.expr.Expression; + +// An implicitly created variable, such as a variable in a script that's doesn't have an explicit +// declaration, or the "it" argument to a closure. +public class DynamicVariable implements Variable { + + private final String name; + private boolean closureShare = false; + private boolean staticContext = false; + + public DynamicVariable(String name, boolean context) { + this.name = name; + staticContext = context; + } + + public ClassNode getType() { + return ClassHelper.DYNAMIC_TYPE; + } + + public String getName() { + return name; + } + + public Expression getInitialExpression() { + return null; + } + + public boolean hasInitialExpression() { + return false; + } + + public boolean isInStaticContext() { + return staticContext; + } + + public boolean isDynamicTyped() { + return true; + } + + public boolean isClosureSharedVariable() { + return closureShare; + } + + public void setClosureSharedVariable(boolean inClosure) { + closureShare = inClosure; + } + + public int getModifiers() { + return 0; + } + + public ClassNode getOriginType() { + return getType(); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/EnumConstantClassNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/EnumConstantClassNode.java b/src/main/java/org/codehaus/groovy/ast/EnumConstantClassNode.java new file mode 100644 index 0000000..4dae4d0 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/EnumConstantClassNode.java @@ -0,0 +1,39 @@ +/* + * 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.codehaus.groovy.ast; + +/** + * Represents the anonymous inner class for an enum constant + * This subtype is needed so that EnumVisitor can differentiate between the scenarios when a InnerClassNode + * represents anonymous inner class for an enu constant and when it represents an enum class defined inside + * another class + * + * @author Roshan Dawrani + */ +public class EnumConstantClassNode extends InnerClassNode { + + /** + * @param name is the full name of the class + * @param modifiers the modifiers, @see org.objectweb.asm.Opcodes + * @param superClass the base class name - use "java.lang.Object" if no direct base class + */ + public EnumConstantClassNode(ClassNode outerClass, String name, int modifiers, ClassNode superClass) { + super(outerClass, name, modifiers, superClass); + } +}
