http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/FieldNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/FieldNode.java b/src/main/java/org/codehaus/groovy/ast/FieldNode.java new file mode 100644 index 0000000..d38f4f4 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/FieldNode.java @@ -0,0 +1,205 @@ +/* + * 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.codehaus.groovy.ast.expr.Expression; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Field; + +/** + * Represents a field (member variable) + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class FieldNode extends AnnotatedNode implements Opcodes, Variable, GroovydocHolder<FieldNode> { + + private String name; + private int modifiers; + private ClassNode type; + private ClassNode owner; + private Expression initialValueExpression; + private boolean dynamicTyped; + private boolean holder; + private ClassNode originType = ClassHelper.DYNAMIC_TYPE; + + public static FieldNode newStatic(Class theClass, String name) throws SecurityException, NoSuchFieldException { + Field field = theClass.getField(name); + ClassNode fldType = ClassHelper.make(field.getType()); + return new FieldNode(name, ACC_PUBLIC | ACC_STATIC, fldType, ClassHelper.make(theClass), null); + } + + public FieldNode(String name, int modifiers, ClassNode type, ClassNode owner, Expression initialValueExpression) { + this.name = name; + this.modifiers = modifiers; + this.type = type; + if (this.type == ClassHelper.DYNAMIC_TYPE && initialValueExpression != null) + this.setType(initialValueExpression.getType()); + this.setType(type); + this.originType = type; + this.owner = owner; + this.initialValueExpression = initialValueExpression; + } + + public Expression getInitialExpression() { + return initialValueExpression; + } + + public int getModifiers() { + return modifiers; + } + + public String getName() { + return name; + } + + public ClassNode getType() { + return type; + } + + public void setType(ClassNode type) { + this.type = type; + this.originType = type; + dynamicTyped |= type == ClassHelper.DYNAMIC_TYPE; + } + + public ClassNode getOwner() { + return owner; + } + + public boolean isHolder() { + return holder; + } + + public void setHolder(boolean holder) { + this.holder = holder; + } + + public boolean isDynamicTyped() { + return dynamicTyped; + } + + public void setModifiers(int modifiers) { + this.modifiers = modifiers; + } + + /** + * @return true if the field is static + */ + public boolean isStatic() { + return (modifiers & ACC_STATIC) != 0; + } + + /** + * @return true if the field is an enum + */ + public boolean isEnum() { + return (modifiers & ACC_ENUM) != 0; + } + + /** + * @return true if the field is final + */ + public boolean isFinal() { + return (modifiers & ACC_FINAL) != 0; + } + + /** + * @return true if the field is volatile + */ + public boolean isVolatile() { + return (modifiers & ACC_VOLATILE) != 0; + } + + /** + * @return true if the field is public + */ + public boolean isPublic() { + return (modifiers & ACC_PUBLIC) != 0; + } + + /** + * @return true if the field is protected + */ + public boolean isProtected() { + return (modifiers & ACC_PROTECTED) != 0; + } + + /** + * @param owner The owner to set. + */ + public void setOwner(ClassNode owner) { + this.owner = owner; + } + + public boolean hasInitialExpression() { + return initialValueExpression != null; + } + + public boolean isInStaticContext() { + return isStatic(); + } + + public Expression getInitialValueExpression() { + return initialValueExpression; + } + + public void setInitialValueExpression(Expression initialValueExpression) { + this.initialValueExpression = initialValueExpression; + } + + /** + * @deprecated + */ + @Deprecated + public boolean isClosureSharedVariable() { + return false; + } + /** + * @deprecated + */ + @Deprecated + public void setClosureSharedVariable(boolean inClosure) { + } + + public ClassNode getOriginType() { + return originType; + } + + public void setOriginType(ClassNode cn) { + originType = cn; + } + + public void rename(String name) { + declaringClass.renameField(this.name, name); + this.name = name; + } + + @Override + public Groovydoc getGroovydoc() { + return this.<Groovydoc>getNodeMetaData(DOC_COMMENT); + } + + @Override + public FieldNode getInstance() { + return this; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/GenericsType.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/GenericsType.java b/src/main/java/org/codehaus/groovy/ast/GenericsType.java new file mode 100644 index 0000000..57d4919 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/GenericsType.java @@ -0,0 +1,500 @@ +/* + * 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.tools.GenericsUtils; +import org.codehaus.groovy.ast.tools.WideningCategories; + +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.codehaus.groovy.ast.ClassHelper.GROOVY_OBJECT_TYPE; + +/** + * This class is used to describe generic type signatures for ClassNodes. + * + * @author Jochen Theodorou + * @see ClassNode + */ +public class GenericsType extends ASTNode { + public static final GenericsType[] EMPTY_ARRAY = new GenericsType[0]; + + private final ClassNode[] upperBounds; + private final ClassNode lowerBound; + private ClassNode type; + private String name; + private boolean placeholder; + private boolean resolved; + private boolean wildcard; + + public GenericsType(ClassNode type, ClassNode[] upperBounds, ClassNode lowerBound) { + this.type = type; + this.name = type.isGenericsPlaceHolder() ? type.getUnresolvedName() : type.getName(); + this.upperBounds = upperBounds; + this.lowerBound = lowerBound; + placeholder = type.isGenericsPlaceHolder(); + resolved = false; + } + + public GenericsType(ClassNode basicType) { + this(basicType, null, null); + } + + public ClassNode getType() { + return type; + } + + public void setType(ClassNode type) { + this.type = type; + } + + public String toString() { + Set<String> visited = new HashSet<String>(); + return toString(visited); + } + + private String toString(Set<String> visited) { + if (placeholder) visited.add(name); + StringBuilder ret = new StringBuilder(wildcard ? "?" : ((type == null || placeholder) ? name : genericsBounds(type, visited))); + if (upperBounds != null) { + if (placeholder && upperBounds.length==1 && !upperBounds[0].isGenericsPlaceHolder() && upperBounds[0].getName().equals("java.lang.Object")) { + // T extends Object should just be printed as T + } else { + ret.append(" extends "); + for (int i = 0; i < upperBounds.length; i++) { + ret.append(genericsBounds(upperBounds[i], visited)); + if (i + 1 < upperBounds.length) ret.append(" & "); + } + } + } else if (lowerBound != null) { + ret.append(" super ").append(genericsBounds(lowerBound, visited)); + } + return ret.toString(); + } + + private String nameOf(ClassNode theType) { + StringBuilder ret = new StringBuilder(); + if (theType.isArray()) { + ret.append(nameOf(theType.getComponentType())); + ret.append("[]"); + } else { + ret.append(theType.getName()); + } + return ret.toString(); + } + + private String genericsBounds(ClassNode theType, Set<String> visited) { + + StringBuilder ret = new StringBuilder(); + + if (theType.isArray()) { + ret.append(nameOf(theType)); + } else if (theType.redirect() instanceof InnerClassNode) { + InnerClassNode innerClassNode = (InnerClassNode) theType.redirect(); + String parentClassNodeName = innerClassNode.getOuterClass().getName(); + if (Modifier.isStatic(innerClassNode.getModifiers()) || innerClassNode.isInterface()) { + ret.append(innerClassNode.getOuterClass().getName()); + } else { + ret.append(genericsBounds(innerClassNode.getOuterClass(), new HashSet<String>())); + } + ret.append("."); + String typeName = theType.getName(); + ret.append(typeName.substring(parentClassNodeName.length() + 1)); + } else { + ret.append(theType.getName()); + } + + GenericsType[] genericsTypes = theType.getGenericsTypes(); + if (genericsTypes == null || genericsTypes.length == 0) + return ret.toString(); + + // TODO instead of catching Object<T> here stop it from being placed into type in first place + if (genericsTypes.length == 1 && genericsTypes[0].isPlaceholder() && theType.getName().equals("java.lang.Object")) { + return genericsTypes[0].getName(); + } + + ret.append("<"); + for (int i = 0; i < genericsTypes.length; i++) { + if (i != 0) ret.append(", "); + + GenericsType type = genericsTypes[i]; + if (type.isPlaceholder() && visited.contains(type.getName())) { + ret.append(type.getName()); + } + else { + ret.append(type.toString(visited)); + } + } + ret.append(">"); + + return ret.toString(); + } + + public ClassNode[] getUpperBounds() { + return upperBounds; + } + + public String getName() { + return name; + } + + public boolean isPlaceholder() { + return placeholder; + } + + public void setPlaceholder(boolean placeholder) { + this.placeholder = placeholder; + type.setGenericsPlaceHolder(placeholder); + } + + public boolean isResolved() { + return resolved || placeholder; + } + + public void setResolved(boolean res) { + resolved = res; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isWildcard() { + return wildcard; + } + + public void setWildcard(boolean wildcard) { + this.wildcard = wildcard; + } + + public ClassNode getLowerBound() { + return lowerBound; + } + + /** + * Tells if the provided class node is compatible with this generic type definition + * @param classNode the class node to be checked + * @return true if the class node is compatible with this generics type definition + */ + public boolean isCompatibleWith(ClassNode classNode) { + return new GenericsTypeMatcher().matches(classNode); + } + + /** + * Implements generics type comparison. + */ + private class GenericsTypeMatcher { + + public boolean implementsInterfaceOrIsSubclassOf(ClassNode type, ClassNode superOrInterface) { + boolean result = type.equals(superOrInterface) + || type.isDerivedFrom(superOrInterface) + || type.implementsInterface(superOrInterface); + if (result) { + return true; + } + if (GROOVY_OBJECT_TYPE.equals(superOrInterface) && type.getCompileUnit()!=null) { + // type is being compiled so it will implement GroovyObject later + return true; + } + if (superOrInterface instanceof WideningCategories.LowestUpperBoundClassNode) { + WideningCategories.LowestUpperBoundClassNode cn = (WideningCategories.LowestUpperBoundClassNode) superOrInterface; + result = implementsInterfaceOrIsSubclassOf(type, cn.getSuperClass()); + if (result) { + for (ClassNode interfaceNode : cn.getInterfaces()) { + result = implementsInterfaceOrIsSubclassOf(type,interfaceNode); + if (!result) break; + } + } + if (result) return true; + } + if (type.isArray() && superOrInterface.isArray()) { + return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType()); + } + return false; + } + + /** + * Compares this generics type with the one represented by the provided class node. If the provided + * classnode is compatible with the generics specification, returns true. Otherwise, returns false. + * The check is complete, meaning that we also check "nested" generics. + * @param classNode the classnode to be checked + * @return true iff the classnode is compatible with this generics specification + */ + public boolean matches(ClassNode classNode) { + GenericsType[] genericsTypes = classNode.getGenericsTypes(); + // diamond always matches + if (genericsTypes!=null && genericsTypes.length==0) return true; + if (classNode.isGenericsPlaceHolder()) { + // if the classnode we compare to is a generics placeholder (like <E>) then we + // only need to check that the names are equal + if (genericsTypes==null) return true; + if (isWildcard()) { + if (lowerBound!=null) return genericsTypes[0].getName().equals(lowerBound.getUnresolvedName()); + if (upperBounds!=null) { + for (ClassNode upperBound : upperBounds) { + String name = upperBound.getGenericsTypes()[0].getName(); + if (genericsTypes[0].getName().equals(name)) return true; + } + return false; + } + } + return genericsTypes[0].getName().equals(name); + } + if (wildcard || placeholder) { + // if the current generics spec is a wildcard spec or a placeholder spec + // then we must check upper and lower bounds + if (upperBounds != null) { + // check that the provided classnode is a subclass of all provided upper bounds + boolean upIsOk = true; + for (int i = 0, upperBoundsLength = upperBounds.length; i < upperBoundsLength && upIsOk; i++) { + final ClassNode upperBound = upperBounds[i]; + upIsOk = implementsInterfaceOrIsSubclassOf(classNode, upperBound); + } + // if the provided classnode is a subclass of the upper bound + // then check that the generic types supplied by the class node are compatible with + // this generics specification + // for example, we could have the spec saying List<String> but provided classnode + // saying List<Integer> + upIsOk = upIsOk && checkGenerics(classNode); + return upIsOk; + } + if (lowerBound != null) { + // if a lower bound is declared, then we must perform the same checks that for an upper bound + // but with reversed arguments + return implementsInterfaceOrIsSubclassOf(lowerBound, classNode) && checkGenerics(classNode); + } + // If there are no bounds, the generic type is basically Object, and everything is compatible. + return true; + } + // if this is not a generics placeholder, first compare that types represent the same type + if ((type!=null && !type.equals(classNode))) { + return false; + } + // last, we could have the spec saying List<String> and a classnode saying List<Integer> so + // we must check that generics are compatible. + // The null check is normally not required but done to prevent from NPEs + return type == null || compareGenericsWithBound(classNode, type); + } + + /** + * Iterates over each generics bound of this generics specification, and checks + * that the generics defined by the bound are compatible with the generics specified + * by the type. + * @param classNode the classnode the bounds should be compared with + * @return true if generics from bounds are compatible + */ + private boolean checkGenerics(final ClassNode classNode) { + if (upperBounds!=null) { + for (ClassNode upperBound : upperBounds) { + if (!compareGenericsWithBound(classNode, upperBound)) return false; + } + } + if (lowerBound!=null) { + if (!lowerBound.redirect().isUsingGenerics()) { + if (!compareGenericsWithBound(classNode, lowerBound)) return false; + } + } + return true; + } + + /** + * Given a parameterized type (List<String> for example), checks that its + * generic types are compatible with those from a bound. + * @param classNode the classnode from which we will compare generics types + * @param bound the bound to which the types will be compared + * @return true if generics are compatible + */ + private boolean compareGenericsWithBound(final ClassNode classNode, final ClassNode bound) { + if (classNode==null) return false; + if (!bound.isUsingGenerics() || (classNode.getGenericsTypes()==null && classNode.redirect().getGenericsTypes()!=null)) { + // if the bound is not using generics, there's nothing to compare with + return true; + } + if (!classNode.equals(bound)) { + // the class nodes are on different types + // in this situation, we must choose the correct execution path : either the bound + // is an interface and we must find the implementing interface from the classnode + // to compare their parameterized generics, or the bound is a regular class and we + // must compare the bound with a superclass + if (bound.isInterface()) { + Set<ClassNode> interfaces = classNode.getAllInterfaces(); + // iterate over all interfaces to check if any corresponds to the bound we are + // comparing to + for (ClassNode anInterface : interfaces) { + if (anInterface.equals(bound)) { + // when we obtain an interface, the types represented by the interface + // class node are not parameterized. This means that we must create a + // new class node with the parameterized types that the current class node + // has defined. + ClassNode node = GenericsUtils.parameterizeType(classNode, anInterface); + return compareGenericsWithBound(node, bound); + } + } + } + if (bound instanceof WideningCategories.LowestUpperBoundClassNode) { + // another special case here, where the bound is a "virtual" type + // we must then check the superclass and the interfaces + boolean success = compareGenericsWithBound(classNode, bound.getSuperClass()); + if (success) { + ClassNode[] interfaces = bound.getInterfaces(); + for (ClassNode anInterface : interfaces) { + success &= compareGenericsWithBound(classNode, anInterface); + if (!success) break; + } + if (success) return true; + } + } + return compareGenericsWithBound(getParameterizedSuperClass(classNode), bound); + } + GenericsType[] cnTypes = classNode.getGenericsTypes(); + if (cnTypes==null && classNode.isRedirectNode()) cnTypes=classNode.redirect().getGenericsTypes(); + if (cnTypes==null) { + // may happen if generic type is Foo<T extends Foo> and classnode is Foo -> Foo + return true; + } + GenericsType[] redirectBoundGenericTypes = bound.redirect().getGenericsTypes(); + Map<String, GenericsType> classNodePlaceholders = GenericsUtils.extractPlaceholders(classNode); + Map<String, GenericsType> boundPlaceHolders = GenericsUtils.extractPlaceholders(bound); + boolean match = true; + for (int i = 0; redirectBoundGenericTypes!=null && i < redirectBoundGenericTypes.length && match; i++) { + GenericsType redirectBoundType = redirectBoundGenericTypes[i]; + GenericsType classNodeType = cnTypes[i]; + if (classNodeType.isPlaceholder()) { + String name = classNodeType.getName(); + if (redirectBoundType.isPlaceholder()) { + match = name.equals(redirectBoundType.getName()); + if (!match) { + GenericsType genericsType = boundPlaceHolders.get(redirectBoundType.getName()); + match = false; + if (genericsType!=null) { + if (genericsType.isPlaceholder()) { + match = true; + } else if (genericsType.isWildcard()) { + if (genericsType.getUpperBounds()!=null) { + for (ClassNode up : genericsType.getUpperBounds()) { + match |= redirectBoundType.isCompatibleWith(up); + } + if (genericsType.getLowerBound()!=null) { + match |= redirectBoundType.isCompatibleWith(genericsType.getLowerBound()); + } + } + } + } + } + } else { + if (classNodePlaceholders.containsKey(name)) classNodeType=classNodePlaceholders.get(name); + match = classNodeType.isCompatibleWith(redirectBoundType.getType()); + } + } else { + if (redirectBoundType.isPlaceholder()) { + if (classNodeType.isPlaceholder()) { + match = classNodeType.getName().equals(redirectBoundType.getName()); + } else { + String name = redirectBoundType.getName(); + if (boundPlaceHolders.containsKey(name)) { + redirectBoundType = boundPlaceHolders.get(name); + boolean wildcard = redirectBoundType.isWildcard(); + boolean placeholder = redirectBoundType.isPlaceholder(); + if (placeholder || wildcard) { + // placeholder aliases, like Map<U,V> -> Map<K,V> +// redirectBoundType = classNodePlaceholders.get(name); + if (wildcard) { + // ex: Comparable<Integer> <=> Comparable<? super T> + if (redirectBoundType.lowerBound!=null) { + GenericsType gt = new GenericsType(redirectBoundType.lowerBound); + if (gt.isPlaceholder()) { + // check for recursive generic typedef, like in + // <T extends Comparable<? super T>> + if (classNodePlaceholders.containsKey(gt.getName())) { + gt = classNodePlaceholders.get(gt.getName()); + } + } + match = implementsInterfaceOrIsSubclassOf(gt.getType(), classNodeType.getType()); + } + if (match && redirectBoundType.upperBounds!=null) { + for (ClassNode upperBound : redirectBoundType.upperBounds) { + GenericsType gt = new GenericsType(upperBound); + if (gt.isPlaceholder()) { + // check for recursive generic typedef, like in + // <T extends Comparable<? super T>> + if (classNodePlaceholders.containsKey(gt.getName())) { + gt = classNodePlaceholders.get(gt.getName()); + } + } + match = implementsInterfaceOrIsSubclassOf(classNodeType.getType(), gt.getType()) + || classNodeType.isCompatibleWith(gt.getType()); // workaround for GROOVY-6095 + if (!match) break; + } + } + return match; + } else if (classNodePlaceholders.containsKey(name)) { + redirectBoundType = classNodePlaceholders.get(name); + } + } + } + match = redirectBoundType.isCompatibleWith(classNodeType.getType()); + } + } else { + // todo: the check for isWildcard should be replaced with a more complete check + match = redirectBoundType.isWildcard() || classNodeType.isCompatibleWith(redirectBoundType.getType()); + } + } + } + return match; + } + } + + /** + * If you have a class which extends a class using generics, returns the superclass with parameterized types. For + * example, if you have: + * <code>class MyList<T> extends LinkedList<T> + * def list = new MyList<String> + * </code> + * then the parameterized superclass for MyList<String> is LinkedList<String> + * @param classNode the class for which we want to return the parameterized superclass + * @return the parameterized superclass + */ + private static ClassNode getParameterizedSuperClass(ClassNode classNode) { + if (ClassHelper.OBJECT_TYPE.equals(classNode)) return null; + ClassNode superClass = classNode.getUnresolvedSuperClass(); + if (superClass==null) { + return ClassHelper.OBJECT_TYPE; + } + if (!classNode.isUsingGenerics() || !superClass.isUsingGenerics()) return superClass; + GenericsType[] genericsTypes = classNode.getGenericsTypes(); + GenericsType[] redirectGenericTypes = classNode.redirect().getGenericsTypes(); + superClass = superClass.getPlainNodeReference(); + if (genericsTypes==null || redirectGenericTypes==null || superClass.getGenericsTypes()==null) return superClass; + for (int i = 0, genericsTypesLength = genericsTypes.length; i < genericsTypesLength; i++) { + if (redirectGenericTypes[i].isPlaceholder()) { + final GenericsType genericsType = genericsTypes[i]; + GenericsType[] superGenericTypes = superClass.getGenericsTypes(); + for (int j = 0, superGenericTypesLength = superGenericTypes.length; j < superGenericTypesLength; j++) { + final GenericsType superGenericType = superGenericTypes[j]; + if (superGenericType.isPlaceholder() && superGenericType.getName().equals(redirectGenericTypes[i].getName())) { + superGenericTypes[j] = genericsType; + } + } + } + } + return superClass; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/GroovyClassVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/GroovyClassVisitor.java b/src/main/java/org/codehaus/groovy/ast/GroovyClassVisitor.java new file mode 100644 index 0000000..e5dff83 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/GroovyClassVisitor.java @@ -0,0 +1,56 @@ +/* + * 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; + +/** + * A special visitor for working with the structure of a class. In general, your + * will want to use the Abstract class based on this class {@link ClassCodeVisitorSupport}. + * + * @see org.codehaus.groovy.ast.ClassNode + * @see org.codehaus.groovy.ast.ClassCodeVisitorSupport + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public interface GroovyClassVisitor { + + /** + * Visit a ClassNode. + */ + void visitClass(ClassNode node); + + /** + * Visit a ConstructorNode. + */ + void visitConstructor(ConstructorNode node); + + /** + * Visit a MethodNode. + */ + void visitMethod(MethodNode node); + + /** + * Visit a FieldNode. + */ + void visitField(FieldNode node); + + /** + * Visit a PropertyNode. + */ + void visitProperty(PropertyNode node); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java b/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java new file mode 100644 index 0000000..12787c0 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java @@ -0,0 +1,191 @@ +/* + * 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.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.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.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; + +/** + * An implementation of the visitor pattern for working with ASTNodes + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ + +public interface GroovyCodeVisitor { + + // statements + + //------------------------------------------------------------------------- + + void visitBlockStatement(BlockStatement statement); + + void visitForLoop(ForStatement forLoop); + + void visitWhileLoop(WhileStatement loop); + + void visitDoWhileLoop(DoWhileStatement loop); + + void visitIfElse(IfStatement ifElse); + + void visitExpressionStatement(ExpressionStatement statement); + + void visitReturnStatement(ReturnStatement statement); + + void visitAssertStatement(AssertStatement statement); + + void visitTryCatchFinally(TryCatchStatement finally1); + + void visitSwitch(SwitchStatement statement); + + void visitCaseStatement(CaseStatement statement); + + void visitBreakStatement(BreakStatement statement); + + void visitContinueStatement(ContinueStatement statement); + + void visitThrowStatement(ThrowStatement statement); + + void visitSynchronizedStatement(SynchronizedStatement statement); + + void visitCatchStatement(CatchStatement statement); + + // expressions + + //------------------------------------------------------------------------- + + void visitMethodCallExpression(MethodCallExpression call); + + void visitStaticMethodCallExpression(StaticMethodCallExpression expression); + + void visitConstructorCallExpression(ConstructorCallExpression expression); + + void visitTernaryExpression(TernaryExpression expression); + + void visitShortTernaryExpression(ElvisOperatorExpression expression); + + void visitBinaryExpression(BinaryExpression expression); + + void visitPrefixExpression(PrefixExpression expression); + + void visitPostfixExpression(PostfixExpression expression); + + void visitBooleanExpression(BooleanExpression expression); + + void visitClosureExpression(ClosureExpression expression); + + void visitTupleExpression(TupleExpression expression); + + void visitMapExpression(MapExpression expression); + + void visitMapEntryExpression(MapEntryExpression expression); + + void visitListExpression(ListExpression expression); + + void visitRangeExpression(RangeExpression expression); + + void visitPropertyExpression(PropertyExpression expression); + + void visitAttributeExpression(AttributeExpression attributeExpression); + + void visitFieldExpression(FieldExpression expression); + + void visitMethodPointerExpression(MethodPointerExpression expression); + + void visitConstantExpression(ConstantExpression expression); + + void visitClassExpression(ClassExpression expression); + + void visitVariableExpression(VariableExpression expression); + + void visitDeclarationExpression(DeclarationExpression expression); + + void visitGStringExpression(GStringExpression expression); + + void visitArrayExpression(ArrayExpression expression); + + void visitSpreadExpression(SpreadExpression expression); + + void visitSpreadMapExpression(SpreadMapExpression expression); + + void visitNotExpression(NotExpression expression); + + void visitUnaryMinusExpression(UnaryMinusExpression expression); + + void visitUnaryPlusExpression(UnaryPlusExpression expression); + + void visitBitwiseNegationExpression(BitwiseNegationExpression expression); + + void visitCastExpression(CastExpression expression); + + void visitArgumentlistExpression(ArgumentListExpression expression); + + void visitClosureListExpression(ClosureListExpression closureListExpression); + + void visitBytecodeExpression(BytecodeExpression expression); +} + http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/ImportNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ImportNode.java b/src/main/java/org/codehaus/groovy/ast/ImportNode.java new file mode 100644 index 0000000..5f01494 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/ImportNode.java @@ -0,0 +1,153 @@ +/* + * 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.objectweb.asm.Opcodes; + +/** + * Represents an import statement of a single class + * + * @author Jochen Theodorou + * @author Paul King + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class ImportNode extends AnnotatedNode implements Opcodes { + + private final ClassNode type; + private final String alias; + private final String fieldName; + // TODO use PackageNode instead here? + private final String packageName; + private final boolean isStar; + private final boolean isStatic; + + /** + * Represent an import of an entire package, i.e. import package.Classname + * + * @param type the referenced class + * @param alias optional alias + */ + public ImportNode(ClassNode type, String alias) { + this.type = type; + this.alias = alias; + this.isStar = false; + this.isStatic = false; + this.packageName = null; + this.fieldName = null; + } + + /** + * Represent an import of an entire package, i.e. import package.* + * + * @param packageName the name of the package + */ + public ImportNode(String packageName) { + this.type = null; + this.alias = null; + this.isStar = true; + this.isStatic = false; + this.packageName = packageName; + this.fieldName = null; + } + + /** + * Represent a static import of a Class, i.e. import static package.Classname.* + * + * @param type the referenced class + */ + public ImportNode(ClassNode type) { + this.type = type; + this.alias = null; + this.isStar = true; + this.isStatic = true; + this.packageName = null; + this.fieldName = null; + } + + /** + * Represent a static import of a field or method, i.e. import static package.Classname.name + * + * @param type the referenced class + * @param fieldName the field name + * @param alias optional alias + */ + public ImportNode(ClassNode type, String fieldName, String alias) { + this.type = type; + this.alias = alias; + this.isStar = false; + this.isStatic = true; + this.packageName = null; + this.fieldName = fieldName; + } + + /** + * @return the text display of this import + */ + public String getText() { + String typeName = getClassName(); + if (isStar && !isStatic) { + return "import " + packageName + "*"; + } + if (isStar) { + return "import static " + typeName + ".*"; + } + if (isStatic) { + if (alias != null && alias.length() != 0 && !alias.equals(fieldName)) { + return "import static " + typeName + "." + fieldName + " as " + alias; + } + return "import static " + typeName + "." + fieldName; + } + if (alias == null || alias.length() == 0) { + return "import " + typeName; + } + return "import " + typeName + " as " + alias; + } + + public String getPackageName() { + return packageName; + } + + public String getFieldName() { + return fieldName; + } + + public boolean isStar() { + return isStar; + } + + public boolean isStatic() { + return isStatic; + } + + public String getAlias() { + return alias; + } + + public ClassNode getType() { + return type; + } + + public String getClassName() { + return type == null ? null : type.getName(); + } + + public void visit(GroovyCodeVisitor visitor) { + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/InnerClassNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/InnerClassNode.java b/src/main/java/org/codehaus/groovy/ast/InnerClassNode.java new file mode 100644 index 0000000..f20c457 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/InnerClassNode.java @@ -0,0 +1,103 @@ +/* + * 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.stmt.Statement; + +import java.util.LinkedList; + +/** + * Represents an inner class declaration + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class InnerClassNode extends ClassNode { + + private final ClassNode outerClass; + private VariableScope scope; + private boolean anonymous; + + /** + * @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 InnerClassNode(ClassNode outerClass, String name, int modifiers, ClassNode superClass) { + this(outerClass, name, modifiers, superClass, ClassHelper.EMPTY_TYPE_ARRAY, MixinNode.EMPTY_ARRAY); + } + + /** + * @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 InnerClassNode(ClassNode outerClass, String name, int modifiers, ClassNode superClass, ClassNode[] interfaces, MixinNode[] mixins) { + super(name, modifiers, superClass, interfaces, mixins); + this.outerClass = outerClass; + + if (outerClass.innerClasses == null) + outerClass.innerClasses = new LinkedList<InnerClassNode> (); + outerClass.innerClasses.add(this); + } + + public ClassNode getOuterClass() { + return outerClass; + } + + public ClassNode getOuterMostClass() { + ClassNode outerClass = getOuterClass(); + while (outerClass instanceof InnerClassNode) { + outerClass = outerClass.getOuterClass(); + } + return outerClass; + } + + /** + * @return the field node on the outer class or null if this is not an inner class + */ + public FieldNode getOuterField(String name) { + return outerClass.getDeclaredField(name); + } + + public VariableScope getVariableScope() { + return scope; + } + + public void setVariableScope(VariableScope scope) { + this.scope = scope; + } + + public boolean isAnonymous() { + return anonymous; + } + + public void setAnonymous(boolean anonymous) { + this.anonymous = anonymous; + } + + @Override + public void addConstructor(final ConstructorNode node) { + super.addConstructor(node); + } + + @Override + public ConstructorNode addConstructor(final int modifiers, final Parameter[] parameters, final ClassNode[] exceptions, final Statement code) { + return super.addConstructor(modifiers, parameters, exceptions, code); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/InterfaceHelperClassNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/InterfaceHelperClassNode.java b/src/main/java/org/codehaus/groovy/ast/InterfaceHelperClassNode.java new file mode 100644 index 0000000..d2e952f --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/InterfaceHelperClassNode.java @@ -0,0 +1,51 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +/** + * Represents an inner class defined as helper for an interface + * + * @author Roshan Dawrani + */ +public class InterfaceHelperClassNode extends InnerClassNode { + + private List callSites = new ArrayList(); + + /** + * @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 + * @param callSites list of callsites used in the interface + */ + public InterfaceHelperClassNode(ClassNode outerClass, String name, int modifiers, ClassNode superClass, List<String> callSites) { + super(outerClass, name, modifiers, superClass, ClassHelper.EMPTY_TYPE_ARRAY, MixinNode.EMPTY_ARRAY); + setCallSites(callSites); + } + + public void setCallSites(List<String> cs) { + callSites = (cs != null) ? cs : new ArrayList<String>(); + } + + public List<String> getCallSites() { + return callSites; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/MethodCallTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/MethodCallTransformation.java b/src/main/java/org/codehaus/groovy/ast/MethodCallTransformation.java new file mode 100644 index 0000000..69ab6c0 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/MethodCallTransformation.java @@ -0,0 +1,116 @@ +/* + * 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.MissingPropertyException; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.ASTTransformation; + +/** + * + * @author Hamlet D'Arcy + * @author Sergei Egorov <[email protected]> + */ + +public abstract class MethodCallTransformation implements ASTTransformation { + + public void visit(ASTNode[] nodes, SourceUnit sourceUnit) { + + GroovyCodeVisitor transformer = getTransformer(nodes, sourceUnit); + + if (nodes != null) { + for (ASTNode it : nodes) { + if (!(it instanceof AnnotationNode) && !(it instanceof ClassNode)) { + it.visit(transformer); + } + } + } + if (sourceUnit.getAST() != null) { + sourceUnit.getAST().visit(transformer); + if (sourceUnit.getAST().getStatementBlock() != null) { + sourceUnit.getAST().getStatementBlock().visit(transformer); + } + if (sourceUnit.getAST().getClasses() != null) { + for (ClassNode classNode : sourceUnit.getAST().getClasses()) { + if (classNode.getMethods() != null) { + for (MethodNode node : classNode.getMethods()) { + if (node != null && node.getCode() != null) { + node.getCode().visit(transformer); + } + } + } + + try { + if (classNode.getDeclaredConstructors() != null) { + for (MethodNode node : classNode.getDeclaredConstructors()) { + if (node != null && node.getCode() != null) { + node.getCode().visit(transformer); + } + } + } + } catch (MissingPropertyException ignored) { + // todo: inner class nodes don't have a constructors field available + } + + // all properties are also always fields + if (classNode.getFields() != null) { + for (FieldNode node : classNode.getFields()) { + if (node.getInitialValueExpression() != null) { + node.getInitialValueExpression().visit(transformer); + } + } + } + + try { + if (classNode.getObjectInitializerStatements() != null) { + for (Statement node : classNode.getObjectInitializerStatements()) { + if (node != null) { + node.visit(transformer); + } + } + } + } catch (MissingPropertyException ignored) { + // todo: inner class nodes don't have an objectInitializers field available + } + + // todo: is there anything to do with the module ??? + } + } + if (sourceUnit.getAST().getMethods() != null) { + for (MethodNode node : sourceUnit.getAST().getMethods()) { + if (node != null) { + if (node.getParameters() != null) { + for (Parameter parameter : node.getParameters()) { + if (parameter != null && parameter.getInitialExpression() != null) { + parameter.getInitialExpression().visit(transformer); + } + } + } + if (node.getCode() != null) { + node.getCode().visit(transformer); + } + } + } + } + } + } + + protected abstract GroovyCodeVisitor getTransformer(ASTNode[] nodes, SourceUnit sourceUnit); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/MethodInvocationTrap.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/MethodInvocationTrap.java b/src/main/java/org/codehaus/groovy/ast/MethodInvocationTrap.java new file mode 100644 index 0000000..9801c60 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/MethodInvocationTrap.java @@ -0,0 +1,97 @@ +/* + * 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.ClosureExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.tools.ClosureUtils; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.io.ReaderSource; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; + +/** + * + * @author Hamlet D'Arcy + * @author Sergei Egorov <[email protected]> + */ +public abstract class MethodInvocationTrap extends CodeVisitorSupport { + + protected final ReaderSource source; + protected final SourceUnit sourceUnit; + + public MethodInvocationTrap(ReaderSource source, SourceUnit sourceUnit) { + if (source == null) throw new IllegalArgumentException("Null: source"); + if (sourceUnit == null) throw new IllegalArgumentException("Null: sourceUnit"); + this.source = source; + this.sourceUnit = sourceUnit; + } + + /** + * Attempts to find AstBuilder 'from code' invocations. When found, converts them into calls + * to the 'from string' approach. + * + * @param call the method call expression that may or may not be an AstBuilder 'from code' invocation. + */ + public void visitMethodCallExpression(MethodCallExpression call) { + boolean shouldContinueWalking = true; + + if (isBuildInvocation(call)) { + shouldContinueWalking = handleTargetMethodCallExpression(call); + } + + if(shouldContinueWalking) { + // continue normal tree walking + call.getObjectExpression().visit(this); + call.getMethod().visit(this); + call.getArguments().visit(this); + } + } + + /** + * Reports an error back to the source unit. + * + * @param msg the error message + * @param expr the expression that caused the error message. + */ + protected void addError(String msg, ASTNode expr) { + sourceUnit.getErrorCollector().addErrorAndContinue( + new SyntaxErrorMessage(new SyntaxException(msg + '\n', expr.getLineNumber(), expr.getColumnNumber(), expr.getLastLineNumber(), expr.getLastColumnNumber()), sourceUnit) + ); + } + + /** + * Converts a ClosureExpression into the String source. + * + * @param expression a closure + * @return the source the closure was created from + */ + protected String convertClosureToSource(ClosureExpression expression) { + try { + return ClosureUtils.convertClosureToSource(source, expression); + } catch(Exception e) { + addError(e.getMessage(), expression); + } + return null; + } + + protected abstract boolean handleTargetMethodCallExpression(MethodCallExpression call); + + protected abstract boolean isBuildInvocation(MethodCallExpression call); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/MethodNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/MethodNode.java b/src/main/java/org/codehaus/groovy/ast/MethodNode.java new file mode 100644 index 0000000..a43286f --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/MethodNode.java @@ -0,0 +1,284 @@ +/* + * 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.MethodNodeUtils; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.objectweb.asm.Opcodes; + +import java.util.List; + +/** + * Represents a method declaration + * + * @author <a href="mailto:[email protected]">James Strachan</a> + * @author Hamlet D'Arcy + */ +public class MethodNode extends AnnotatedNode implements Opcodes, GroovydocHolder<MethodNode> { + + public static final String SCRIPT_BODY_METHOD_KEY = "org.codehaus.groovy.ast.MethodNode.isScriptBody"; + private final String name; + private int modifiers; + private boolean syntheticPublic; + private ClassNode returnType; + private Parameter[] parameters; + private boolean hasDefaultValue = false; + private Statement code; + private boolean dynamicReturnType; + private VariableScope variableScope; + private final ClassNode[] exceptions; + private final boolean staticConstructor; + + // type spec for generics + private GenericsType[] genericsTypes = null; + private boolean hasDefault; + + // cached data + String typeDescriptor; + + public MethodNode(String name, int modifiers, ClassNode returnType, Parameter[] parameters, ClassNode[] exceptions, Statement code) { + this.name = name; + this.modifiers = modifiers; + this.code = code; + setReturnType(returnType); + VariableScope scope = new VariableScope(); + setVariableScope(scope); + setParameters(parameters); + this.hasDefault = false; + this.exceptions = exceptions; + this.staticConstructor = (name != null && name.equals("<clinit>")); + } + + /** + * The type descriptor for a method node is a string containing the name of the method, its return type, + * and its parameter types in a canonical form. For simplicity, we use the format of a Java declaration + * without parameter names or generics. + * + * @return the type descriptor + */ + public String getTypeDescriptor() { + if (typeDescriptor == null) { + typeDescriptor = MethodNodeUtils.methodDescriptor(this); + } + return typeDescriptor; + } + + private void invalidateCachedData() { + typeDescriptor = null; + } + + public boolean isVoidMethod() { + return returnType == ClassHelper.VOID_TYPE; + } + + public Statement getCode() { + return code; + } + + public void setCode(Statement code) { + this.code = code; + } + + public int getModifiers() { + return modifiers; + } + + public void setModifiers(int modifiers) { + invalidateCachedData(); + this.modifiers = modifiers; + } + + public String getName() { + return name; + } + + public Parameter[] getParameters() { + return parameters; + } + + public void setParameters(Parameter[] parameters) { + invalidateCachedData(); + VariableScope scope = new VariableScope(); + this.parameters = parameters; + if (parameters != null && parameters.length > 0) { + for (Parameter para : parameters) { + if (para.hasInitialExpression()) { + this.hasDefaultValue = true; + } + para.setInStaticContext(isStatic()); + scope.putDeclaredVariable(para); + } + } + setVariableScope(scope); + } + + public ClassNode getReturnType() { + return returnType; + } + + public VariableScope getVariableScope() { + return variableScope; + } + + public void setVariableScope(VariableScope variableScope) { + this.variableScope = variableScope; + variableScope.setInStaticContext(isStatic()); + } + + public boolean isDynamicReturnType() { + return dynamicReturnType; + } + + public boolean isAbstract() { + return (modifiers & ACC_ABSTRACT) != 0; + } + + public boolean isStatic() { + return (modifiers & ACC_STATIC) != 0; + } + + public boolean isPublic() { + return (modifiers & ACC_PUBLIC) != 0; + } + + public boolean isPrivate() { + return (modifiers & ACC_PRIVATE) != 0; + } + + public boolean isFinal() { + return (modifiers & ACC_FINAL) != 0; + } + + public boolean isProtected() { + return (modifiers & ACC_PROTECTED) != 0; + } + + public boolean hasDefaultValue() { + return this.hasDefaultValue; + } + + /** + * @return true if this method is the run method from a script + */ + public boolean isScriptBody() { + return getNodeMetaData(SCRIPT_BODY_METHOD_KEY) != null; + } + + /** + * Set the metadata flag for this method to indicate that it is a script body implementation. + * @see ModuleNode createStatementsClass(). + */ + public void setIsScriptBody() { + setNodeMetaData(SCRIPT_BODY_METHOD_KEY, true); + } + + public String toString() { + return "MethodNode@" + hashCode() + "[" + getDeclaringClass().getName() + "#" + getTypeDescriptor() + "]"; + } + + public void setReturnType(ClassNode returnType) { + invalidateCachedData(); + dynamicReturnType |= ClassHelper.DYNAMIC_TYPE == returnType; + this.returnType = returnType; + if (returnType == null) this.returnType = ClassHelper.OBJECT_TYPE; + } + + public ClassNode[] getExceptions() { + return exceptions; + } + + public Statement getFirstStatement() { + if (code == null) return null; + Statement first = code; + while (first instanceof BlockStatement) { + List<Statement> list = ((BlockStatement) first).getStatements(); + if (list.isEmpty()) { + first = null; + } else { + first = list.get(0); + } + } + return first; + } + + public GenericsType[] getGenericsTypes() { + return genericsTypes; + } + + public void setGenericsTypes(GenericsType[] genericsTypes) { + invalidateCachedData(); + this.genericsTypes = genericsTypes; + } + + public void setAnnotationDefault(boolean b) { + this.hasDefault = b; + } + + public boolean hasAnnotationDefault() { + return hasDefault; + } + + public boolean isStaticConstructor() { + return staticConstructor; + } + + /** + * Indicates that this method 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 methods 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; + } + + /** + * Provides a nicely formatted string of the method definition. For simplicity, generic types on some of the elements + * are not displayed. + * @return + * string form of node with some generic elements suppressed + */ + @Override + public String getText() { + String retType = AstToTextHelper.getClassText(returnType); + String exceptionTypes = AstToTextHelper.getThrowsClauseText(exceptions); + String parms = AstToTextHelper.getParametersText(parameters); + return AstToTextHelper.getModifiersText(modifiers) + " " + retType + " " + name + "(" + parms + ") " + exceptionTypes + " { ... }"; + } + + @Override + public Groovydoc getGroovydoc() { + return this.<Groovydoc>getNodeMetaData(DOC_COMMENT); + } + + @Override + public MethodNode getInstance() { + return this; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/MixinASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/MixinASTTransformation.java b/src/main/java/org/codehaus/groovy/ast/MixinASTTransformation.java new file mode 100644 index 0000000..21bccce --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/MixinASTTransformation.java @@ -0,0 +1,88 @@ +/* + * 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.Mixin; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.AbstractASTTransformation; +import org.codehaus.groovy.transform.GroovyASTTransformation; + +import static org.codehaus.groovy.ast.ClassHelper.make; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; + +/** + * @deprecated static mixins have been deprecated in favour of traits (trait keyword). + */ +@Deprecated +@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) +public class MixinASTTransformation extends AbstractASTTransformation { + private static final ClassNode MY_TYPE = make(Mixin.class); + + public void visit(ASTNode nodes[], SourceUnit source) { + init(nodes, source); + AnnotationNode node = (AnnotationNode) nodes[0]; + AnnotatedNode parent = (AnnotatedNode) nodes[1]; + if (!MY_TYPE.equals(node.getClassNode())) + return; + + final Expression expr = node.getMember("value"); + if (expr == null) { + return; + } + + Expression useClasses = null; + if (expr instanceof ClassExpression) { + useClasses = expr; + } else if (expr instanceof ListExpression) { + ListExpression listExpression = (ListExpression) expr; + for (Expression ex : listExpression.getExpressions()) { + if (!(ex instanceof ClassExpression)) + return; + } + useClasses = expr; + } + + if (useClasses == null) + return; + + if (parent instanceof ClassNode) { + ClassNode annotatedClass = (ClassNode) parent; + + final Parameter[] noparams = Parameter.EMPTY_ARRAY; + MethodNode clinit = annotatedClass.getDeclaredMethod("<clinit>", noparams); + if (clinit == null) { + clinit = annotatedClass.addMethod("<clinit>", ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, ClassHelper.VOID_TYPE, noparams, null, new BlockStatement()); + clinit.setSynthetic(true); + } + + final BlockStatement code = (BlockStatement) clinit.getCode(); + code.addStatement( + stmt(callX(propX(classX(annotatedClass), "metaClass"), "mixin", useClasses)) + ); + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/MixinNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/MixinNode.java b/src/main/java/org/codehaus/groovy/ast/MixinNode.java new file mode 100644 index 0000000..08dee7c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/MixinNode.java @@ -0,0 +1,47 @@ +/* + * 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 a mixin which can be applied to any ClassNode to implement mixins + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class MixinNode extends ClassNode { + + public static final MixinNode[] EMPTY_ARRAY = {}; + + /** + * @param name is the full name of the class + * @param modifiers the modifiers, @see org.objectweb.asm.Opcodes + * @param superType the base class name - use "java.lang.Object" if no direct base class + */ + public MixinNode(String name, int modifiers, ClassNode superType) { + this(name, modifiers, superType, ClassHelper.EMPTY_TYPE_ARRAY); + } + + /** + * @param name is the full name of the class + * @param modifiers the modifiers, @see org.objectweb.asm.Opcodes + * @param superType the base class name - use "java.lang.Object" if no direct base class + */ + public MixinNode(String name, int modifiers, ClassNode superType, ClassNode[] interfaces) { + super(name, modifiers, superType, interfaces, MixinNode.EMPTY_ARRAY); + } +}
