groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cchamp...@apache.org
Subject [36/62] [abbrv] [partial] groovy git commit: Move Java source set into `src/main/java`
Date Sun, 17 Dec 2017 14:01:47 GMT
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:james@coredevelopers.net">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&lt;String&gt; 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&lt;T&gt; extends LinkedList&lt;T&gt;
+     * def list = new MyList&lt;String&gt;
+     * </code>
+     * then the parameterized superclass for MyList&lt;String&gt; is LinkedList&lt;String&gt;
+     * @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:james@coredevelopers.net">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:james@coredevelopers.net">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:james@coredevelopers.net">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.&#160;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.&#160;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.&#160;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.&#160;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:james@coredevelopers.net">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 <bsideup@gmail.com>
+ */
+
+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 <bsideup@gmail.com>
+ */
+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:james@coredevelopers.net">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:james@coredevelopers.net">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);
+    }
+}


Mime
View raw message