You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2017/12/17 14:01:24 UTC

[13/62] [abbrv] [partial] groovy git commit: Move Java source set into `src/main/java`

http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
new file mode 100644
index 0000000..895b5db
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
@@ -0,0 +1,1469 @@
+/*
+ *  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.control;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CompileUnit;
+import org.codehaus.groovy.ast.DynamicVariable;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.ImportNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+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.ConstantExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.SpreadMapExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.CatchStatement;
+import org.codehaus.groovy.ast.stmt.ForStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.ClassNodeResolver.LookupResult;
+import org.codehaus.groovy.syntax.Types;
+import org.codehaus.groovy.transform.trait.Traits;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.inSamePackage;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isDefaultVisibility;
+
+/**
+ * Visitor to resolve Types and convert VariableExpression to
+ * ClassExpressions if needed. The ResolveVisitor will try to
+ * find the Class for a ClassExpression and prints an error if
+ * it fails to do so. Constructions like C[], foo as C, (C) foo
+ * will force creation of a ClassExpression for C
+ * <p>
+ * Note: the method to start the resolving is  startResolving(ClassNode, SourceUnit).
+ */
+public class ResolveVisitor extends ClassCodeExpressionTransformer {
+    private ClassNode currentClass;
+    // note: BigInteger and BigDecimal are also imported by default
+    public static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."};
+    private final CompilationUnit compilationUnit;
+    private SourceUnit source;
+    private VariableScope currentScope;
+
+    private boolean isTopLevelProperty = true;
+    private boolean inPropertyExpression = false;
+    private boolean inClosure = false;
+
+    private Map<String, GenericsType> genericParameterNames = new HashMap<String, GenericsType>();
+    private final Set<FieldNode> fieldTypesChecked = new HashSet<FieldNode>();
+    private boolean checkingVariableTypeInDeclaration = false;
+    private ImportNode currImportNode = null;
+    private MethodNode currentMethod;
+    private ClassNodeResolver classNodeResolver;
+
+    /**
+     * A ConstructedNestedClass consists of an outer class and a name part, denoting a
+     * nested class with an unknown number of levels down. This allows resolve tests to
+     * skip this node for further inner class searches and combinations with imports, since
+     * the outer class we know is already resolved.
+     */
+    private static class ConstructedNestedClass extends ClassNode {
+        final ClassNode knownEnclosingType;
+        public ConstructedNestedClass(ClassNode outer, String inner) {
+            super(outer.getName()+"$"+(inner=replacePoints(inner)), Opcodes.ACC_PUBLIC,ClassHelper.OBJECT_TYPE);
+            this.knownEnclosingType = outer;
+            this.isPrimaryNode = false;
+        }
+        public boolean hasPackageName() {
+            if (redirect()!=this) return super.hasPackageName();
+            return knownEnclosingType.hasPackageName();
+        }
+        public String setName(String name) {
+            if (redirect()!=this) {
+                return super.setName(name);
+            } else {
+                throw new GroovyBugError("ConstructedNestedClass#setName should not be called");
+            }
+        }
+    }
+
+
+    private static String replacePoints(String name) {
+        return name.replace('.','$');
+    }
+
+    /**
+     * we use ConstructedClassWithPackage to limit the resolving the compiler
+     * does when combining package names and class names. The idea
+     * that if we use a package, then we do not want to replace the
+     * '.' with a '$' for the package part, only for the class name
+     * part. There is also the case of a imported class, so this logic
+     * can't be done in these cases...
+     */
+    private static class ConstructedClassWithPackage extends ClassNode {
+        final String prefix;
+        String className;
+        public ConstructedClassWithPackage(String pkg, String name) {
+            super(pkg+name, Opcodes.ACC_PUBLIC,ClassHelper.OBJECT_TYPE);
+            isPrimaryNode = false;
+            this.prefix = pkg;
+            this.className = name;
+        }
+        public String getName() {
+            if (redirect()!=this) return super.getName();
+            return prefix+className;
+        }
+        public boolean hasPackageName() {
+            if (redirect()!=this) return super.hasPackageName();
+            return className.indexOf('.')!=-1;
+        }
+        public String setName(String name) {
+            if (redirect()!=this) {
+                return super.setName(name);
+            } else {
+                throw new GroovyBugError("ConstructedClassWithPackage#setName should not be called");
+            }
+        }
+    }
+
+     /**
+     * we use LowerCaseClass to limit the resolving the compiler
+     * does for vanilla names starting with a lower case letter. The idea
+     * that if we use a vanilla name with a lower case letter, that this
+     * is in most cases no class. If it is a class the class needs to be
+     * imported explicitly. The effect is that in an expression like
+     * "def foo = bar" we do not have to use a loadClass call to check the
+     * name foo and bar for being classes. Instead we will ask the module
+     * for an alias for this name which is much faster.
+     */
+    private static class LowerCaseClass extends ClassNode {
+        final String className;
+        public LowerCaseClass(String name) {
+            super(name, Opcodes.ACC_PUBLIC,ClassHelper.OBJECT_TYPE);
+            isPrimaryNode = false;
+            this.className = name;
+        }
+        public String getName() {
+            if (redirect()!=this) return super.getName();
+            return className;
+        }
+        public boolean hasPackageName() {
+            if (redirect()!=this) return super.hasPackageName();
+            return false;
+        }
+        public String setName(String name) {
+            if (redirect()!=this) {
+                return super.setName(name);
+            } else {
+                throw new GroovyBugError("LowerCaseClass#setName should not be called");
+            }
+        }
+    }
+
+    public ResolveVisitor(CompilationUnit cu) {
+        compilationUnit = cu;
+        this.classNodeResolver = new ClassNodeResolver();
+    }
+
+    public void startResolving(ClassNode node, SourceUnit source) {
+        this.source = source;
+        visitClass(node);
+    }
+
+    protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
+        VariableScope oldScope = currentScope;
+        currentScope = node.getVariableScope();
+        Map<String, GenericsType> oldPNames = genericParameterNames;
+        genericParameterNames = node.isStatic()
+                ? new HashMap<String, GenericsType>()
+                : new HashMap<String, GenericsType>(genericParameterNames);
+
+        resolveGenericsHeader(node.getGenericsTypes());
+
+        Parameter[] paras = node.getParameters();
+        for (Parameter p : paras) {
+            p.setInitialExpression(transform(p.getInitialExpression()));
+            resolveOrFail(p.getType(), p.getType());
+            visitAnnotations(p);
+        }
+        ClassNode[] exceptions = node.getExceptions();
+        for (ClassNode t : exceptions) {
+            resolveOrFail(t, node);
+        }
+        resolveOrFail(node.getReturnType(), node);
+
+        MethodNode oldCurrentMethod = currentMethod;
+        currentMethod = node;
+        super.visitConstructorOrMethod(node, isConstructor);
+
+        currentMethod = oldCurrentMethod;
+        genericParameterNames = oldPNames;
+        currentScope = oldScope;
+    }
+
+    public void visitField(FieldNode node) {
+        ClassNode t = node.getType();
+        if(!fieldTypesChecked.contains(node)) {
+            resolveOrFail(t, node);
+        }
+        super.visitField(node);
+    }
+
+    public void visitProperty(PropertyNode node) {
+        Map<String, GenericsType> oldPNames = genericParameterNames;
+        if (node.isStatic()) {
+            genericParameterNames = new HashMap<String, GenericsType>();
+        }
+
+        ClassNode t = node.getType();
+        resolveOrFail(t, node);
+        super.visitProperty(node);
+        fieldTypesChecked.add(node.getField());
+
+        genericParameterNames = oldPNames;
+    }
+
+    private boolean resolveToInner (ClassNode type) {
+        // we do not do our name mangling to find an inner class
+        // if the type is a ConstructedClassWithPackage, because in this case we
+        // are resolving the name at a different place already
+        if (type instanceof ConstructedClassWithPackage) return false;
+        if (type instanceof ConstructedNestedClass) return false;
+        String name = type.getName();
+        String saved = name;
+        while (true) {
+            int len = name.lastIndexOf('.');
+            if (len == -1) break;
+            name = name.substring(0,len) + "$" + name.substring(len+1);
+            type.setName(name);
+            if (resolve(type)) return true;
+        }
+        if(resolveToNestedOfCurrent(type)) return true;
+        
+        type.setName(saved);
+        return false;
+    }
+
+    private boolean resolveToNestedOfCurrent(ClassNode type) {
+        if (type instanceof ConstructedNestedClass) return false;
+        // GROOVY-3110: It may be an inner enum defined by this class itself, in which case it does not need to be
+        // explicitly qualified by the currentClass name
+        String name = type.getName();
+        if (currentClass != type && !name.contains(".") && type.getClass().equals(ClassNode.class)) {
+            ClassNode tmp = new ConstructedNestedClass(currentClass,name);
+            if (resolve(tmp)) {
+                type.setRedirect(tmp);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void resolveOrFail(ClassNode type, String msg, ASTNode node) {
+        if (resolve(type)) return;
+        if (resolveToInner(type)) return;
+        addError("unable to resolve class " + type.getName() + " " + msg, node);
+    }
+
+    private void resolveOrFail(ClassNode type, ASTNode node, boolean prefereImports) {
+        resolveGenericsTypes(type.getGenericsTypes());
+        if (prefereImports && resolveAliasFromModule(type)) return;
+        resolveOrFail(type, node);
+    }
+
+    private void resolveOrFail(ClassNode type, ASTNode node) {
+        resolveOrFail(type, "", node);
+    }
+
+    private boolean resolve(ClassNode type) {
+        return resolve(type, true, true, true);
+    }
+
+    private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
+        resolveGenericsTypes(type.getGenericsTypes());
+        if (type.isResolved() || type.isPrimaryClassNode()) return true;
+        if (type.isArray()) {
+            ClassNode element = type.getComponentType();
+            boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
+            if (resolved) {
+                ClassNode cn = element.makeArray();
+                type.setRedirect(cn);
+            }
+            return resolved;
+        }
+
+        // test if vanilla name is current class name
+        if (currentClass == type) return true;
+
+        if (genericParameterNames.get(type.getName()) != null) {
+            GenericsType gt = genericParameterNames.get(type.getName());
+            type.setRedirect(gt.getType());
+            type.setGenericsTypes(new GenericsType[]{gt});
+            type.setGenericsPlaceHolder(true);
+            return true;
+        }
+
+        if (currentClass.getNameWithoutPackage().equals(type.getName())) {
+            type.setRedirect(currentClass);
+            return true;
+        }
+
+        return resolveNestedClass(type) ||
+                resolveFromModule(type, testModuleImports) ||
+                resolveFromCompileUnit(type) ||
+                resolveFromDefaultImports(type, testDefaultImports) ||
+                resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
+                resolveToOuter(type);
+    }
+
+    private boolean resolveNestedClass(ClassNode type) {
+        if (type instanceof ConstructedNestedClass) return false;
+        // we have for example a class name A, are in class X
+        // and there is a nested class A$X. we want to be able 
+        // to access that class directly, so A becomes a valid
+        // name in X.
+        // GROOVY-4043: Do this check up the hierarchy, if needed
+        Map<String, ClassNode> hierClasses = new LinkedHashMap<String, ClassNode>();
+        ClassNode val;
+        for(ClassNode classToCheck = currentClass; classToCheck != ClassHelper.OBJECT_TYPE; 
+            classToCheck = classToCheck.getSuperClass()) {
+            if(classToCheck == null || hierClasses.containsKey(classToCheck.getName())) break;
+            hierClasses.put(classToCheck.getName(), classToCheck);
+        }
+
+        for (ClassNode classToCheck : hierClasses.values()) {
+            val = new ConstructedNestedClass(classToCheck,type.getName());
+            if (resolveFromCompileUnit(val)) {
+                type.setRedirect(val);
+                return true;
+            }
+            // also check interfaces in case we have interfaces with nested classes
+            for (ClassNode next : classToCheck.getAllInterfaces()) {
+                if (type.getName().contains(next.getName())) continue;
+                val = new ConstructedNestedClass(next,type.getName());
+                if (resolve(val, false, false, false)) {
+                    type.setRedirect(val);
+                    return true;
+                }
+            }
+        }
+
+        // another case we want to check here is if we are in a
+        // nested class A$B$C and want to access B without
+        // qualifying it by A.B. A alone will work, since that
+        // is the qualified (minus package) name of that class
+        // anyway. 
+        
+        // That means if the current class is not an InnerClassNode
+        // there is nothing to be done.
+        if (!(currentClass instanceof InnerClassNode)) return false;
+        
+        // since we have B and want to get A we start with the most 
+        // outer class, put them together and then see if that does
+        // already exist. In case of B from within A$B we are done 
+        // after the first step already. In case of for example
+        // A.B.C.D.E.F and accessing E from F we test A$E=failed, 
+        // A$B$E=failed, A$B$C$E=fail, A$B$C$D$E=success
+        
+        LinkedList<ClassNode> outerClasses = new LinkedList<ClassNode>();
+        ClassNode outer = currentClass.getOuterClass();
+        while (outer!=null) {
+            outerClasses.addFirst(outer);
+            outer = outer.getOuterClass();
+        }
+        // most outer class is now element 0
+        for (ClassNode testNode : outerClasses) {
+            val = new ConstructedNestedClass(testNode,type.getName());
+            if (resolveFromCompileUnit(val)) {
+                type.setRedirect(val);
+                return true;
+            }
+            // also check interfaces in case we have interfaces with nested classes
+            for (ClassNode next : testNode.getAllInterfaces()) {
+                if (type.getName().contains(next.getName())) continue;
+                val = new ConstructedNestedClass(next,type.getName());
+                if (resolve(val, false, false, false)) {
+                    type.setRedirect(val);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static String replaceLastPoint(String name) {
+        int lastPoint = name.lastIndexOf('.');
+        name = new StringBuffer()
+                .append(name.substring(0, lastPoint))
+                .append("$")
+                .append(name.substring(lastPoint + 1))
+                .toString();
+        return name;
+    }
+
+    private boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) {
+        if (type instanceof ConstructedNestedClass) return false;
+
+        // a class consisting of a vanilla name can never be
+        // a static inner class, because at least one dot is
+        // required for this. Example: foo.bar -> foo$bar
+        if (type instanceof LowerCaseClass) return false;
+
+        // try to resolve a public static inner class' name
+        testStaticInnerClasses &= type.hasPackageName();
+        if (testStaticInnerClasses) {
+            if (type instanceof ConstructedClassWithPackage) {
+                // we replace '.' only in the className part
+                // with '$' to find an inner class. The case that
+                // the package is really a class is handled elsewhere
+                ConstructedClassWithPackage tmp = (ConstructedClassWithPackage) type;
+                String savedName = tmp.className;
+                tmp.className = replaceLastPoint(savedName);
+                if (resolve(tmp, false, true, true)) {
+                    type.setRedirect(tmp.redirect());
+                    return true;
+                }
+                tmp.className = savedName;
+            }   else {
+                String savedName = type.getName();
+                String replacedPointType = replaceLastPoint(savedName);
+                type.setName(replacedPointType);
+                if (resolve(type, false, true, true)) return true;
+                type.setName(savedName);
+            }
+        }
+        return false;
+    }
+
+    private boolean resolveFromDefaultImports(ClassNode type, boolean testDefaultImports) {
+        // test default imports
+        testDefaultImports &= !type.hasPackageName();
+        // we do not resolve a vanilla name starting with a lower case letter
+        // try to resolve against a default import, because we know that the
+        // default packages do not contain classes like these
+        testDefaultImports &= !(type instanceof LowerCaseClass);
+        if (testDefaultImports) {
+            for (int i = 0, size = DEFAULT_IMPORTS.length; i < size; i++) {
+                String packagePrefix = DEFAULT_IMPORTS[i];
+                String name = type.getName();
+                // We limit the inner class lookups here by using ConstructedClassWithPackage.
+                // This way only the name will change, the packagePrefix will
+                // not be included in the lookup. The case where the
+                // packagePrefix is really a class is handled elsewhere.
+                // WARNING: This code does not expect a class that has a static
+                //          inner class in DEFAULT_IMPORTS
+                ConstructedClassWithPackage tmp =  new ConstructedClassWithPackage(packagePrefix,name);
+                if (resolve(tmp, false, false, false)) {
+                    type.setRedirect(tmp.redirect());
+                    return true;
+                }
+            }
+            String name = type.getName();
+            if (name.equals("BigInteger")) {
+                type.setRedirect(ClassHelper.BigInteger_TYPE);
+                return true;
+            } else if (name.equals("BigDecimal")) {
+                type.setRedirect(ClassHelper.BigDecimal_TYPE);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean resolveFromCompileUnit(ClassNode type) {
+        // look into the compile unit if there is a class with that name
+        CompileUnit compileUnit = currentClass.getCompileUnit();
+        if (compileUnit == null) return false;
+        ClassNode cuClass = compileUnit.getClass(type.getName());
+        if (cuClass != null) {
+            if (type != cuClass) type.setRedirect(cuClass);
+            return true;
+        }
+        return false;
+    }
+
+    private void ambiguousClass(ClassNode type, ClassNode iType, String name) {
+        if (type.getName().equals(iType.getName())) {
+            addError("reference to " + name + " is ambiguous, both class " + type.getName() + " and " + iType.getName() + " match", type);
+        } else {
+            type.setRedirect(iType);
+        }
+    }
+
+    private boolean resolveAliasFromModule(ClassNode type) {
+        // In case of getting a ConstructedClassWithPackage here we do not do checks for partial
+        // matches with imported classes. The ConstructedClassWithPackage is already a constructed
+        // node and any subclass resolving will then take place elsewhere
+        if (type instanceof ConstructedClassWithPackage) return false;
+
+        ModuleNode module = currentClass.getModule();
+        if (module == null) return false;
+        String name = type.getName();
+
+        // check module node imports aliases
+        // the while loop enables a check for inner classes which are not fully imported,
+        // but visible as the surrounding class is imported and the inner class is public/protected static
+        String pname = name;
+        int index = name.length();
+        /*
+         * we have a name foo.bar and an import foo.foo. This means foo.bar is possibly
+         * foo.foo.bar rather than foo.bar. This means to cut at the dot in foo.bar and
+         * foo for import
+         */
+        while (true) {
+            pname = name.substring(0, index);
+            ClassNode aliasedNode = null;
+            ImportNode importNode = module.getImport(pname);
+            if (importNode != null && importNode != currImportNode) {
+                aliasedNode = importNode.getType();
+            }
+            if (aliasedNode == null) {
+                importNode = module.getStaticImports().get(pname);
+                if (importNode != null && importNode != currImportNode) {
+                    // static alias only for inner classes and must be at end of chain
+                    ClassNode tmp = new ConstructedNestedClass(importNode.getType(), importNode.getFieldName());
+                    if (resolve(tmp, false, false, true)) {
+                        if ((tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
+                            type.setRedirect(tmp.redirect());
+                            return true;
+                        }
+                    }
+                }
+            }
+
+            if (aliasedNode != null) {
+                if (pname.length() == name.length()) {
+                    // full match
+
+                    // We can compare here by length, because pname is always
+                    // a substring of name, so same length means they are equal.
+                    type.setRedirect(aliasedNode);
+                    return true;
+                } else {
+                    //partial match
+
+                    // At this point we know that we have a match for pname. This may
+                    // mean, that name[pname.length()..<-1] is a static inner class.
+                    // For this the rest of the name does not need any dots in its name.
+                    // It is either completely a inner static class or it is not.
+                    // Since we do not want to have useless lookups we create the name
+                    // completely and use a ConstructedClassWithPackage to prevent lookups against the package.
+                    String className = aliasedNode.getNameWithoutPackage() + '$' +
+                            name.substring(pname.length() + 1).replace('.', '$');
+                    ConstructedClassWithPackage tmp = new ConstructedClassWithPackage(aliasedNode.getPackageName()+".", className);
+                    if (resolve(tmp, true, true, false)) {
+                        type.setRedirect(tmp.redirect());
+                        return true;
+                    }
+                }
+            }
+            index = pname.lastIndexOf('.');
+            if (index == -1) break;
+        }
+        return false;
+    }
+
+    private boolean resolveFromModule(ClassNode type, boolean testModuleImports) {
+        if (type instanceof ConstructedNestedClass) return false;
+
+        // we decided if we have a vanilla name starting with a lower case
+        // letter that we will not try to resolve this name against .*
+        // imports. Instead a full import is needed for these.
+        // resolveAliasFromModule will do this check for us. This method
+        // does also check the module contains a class in the same package
+        // of this name. This check is not done for vanilla names starting
+        // with a lower case letter anymore
+        if (type instanceof LowerCaseClass) {
+            return resolveAliasFromModule(type);
+        }
+
+        String name = type.getName();
+        ModuleNode module = currentClass.getModule();
+        if (module == null) return false;
+
+        boolean newNameUsed = false;
+        // we add a package if there is none yet and the module has one. But we
+        // do not add that if the type is a ConstructedClassWithPackage. The code in ConstructedClassWithPackage
+        // hasPackageName() will return true if ConstructedClassWithPackage#className has no dots.
+        // but since the prefix may have them and the code there does ignore that
+        // fact. We check here for ConstructedClassWithPackage.
+        if (!type.hasPackageName() && module.hasPackageName() && !(type instanceof ConstructedClassWithPackage)) {
+            type.setName(module.getPackageName() + name);
+            newNameUsed = true;
+        }
+        // look into the module node if there is a class with that name
+        List<ClassNode> moduleClasses = module.getClasses();
+        for (ClassNode mClass : moduleClasses) {
+            if (mClass.getName().equals(type.getName())) {
+                if (mClass != type) type.setRedirect(mClass);
+                return true;
+            }
+        }
+        if (newNameUsed) type.setName(name);
+
+        if (testModuleImports) {
+            if (resolveAliasFromModule(type)) return true;
+
+            if (module.hasPackageName()) {
+                // check package this class is defined in. The usage of ConstructedClassWithPackage here
+                // means, that the module package will not be involved when the
+                // compiler tries to find an inner class.
+                ConstructedClassWithPackage tmp =  new ConstructedClassWithPackage(module.getPackageName(), name);
+                if (resolve(tmp, false, false, false)) {
+                    ambiguousClass(type, tmp, name);
+                    type.setRedirect(tmp.redirect());
+                    return true;
+                }
+            }
+
+            // check module static imports (for static inner classes)
+            for (ImportNode importNode : module.getStaticImports().values()) {
+                if (importNode.getFieldName().equals(name)) {
+                    ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
+                    if (resolve(tmp, false, false, true)) {
+                        if ((tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
+                            type.setRedirect(tmp.redirect());
+                            return true;
+                        }
+                    }
+                }
+            }
+
+            // check module node import packages
+            for (ImportNode importNode : module.getStarImports()) {
+                String packagePrefix = importNode.getPackageName();
+                // We limit the inner class lookups here by using ConstructedClassWithPackage.
+                // This way only the name will change, the packagePrefix will
+                // not be included in the lookup. The case where the
+                // packagePrefix is really a class is handled elsewhere.
+                ConstructedClassWithPackage tmp = new ConstructedClassWithPackage(packagePrefix, name);
+                if (resolve(tmp, false, false, true)) {
+                    ambiguousClass(type, tmp, name);
+                    type.setRedirect(tmp.redirect());
+                    return true;
+                }
+            }
+
+            // check for star imports (import static pkg.Outer.*) matching static inner classes
+            for (ImportNode importNode : module.getStaticStarImports().values()) {
+                ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
+                if (resolve(tmp, false, false, true)) {
+                    if ((tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
+                        ambiguousClass(type, tmp, name);
+                        type.setRedirect(tmp.redirect());
+                        return true;
+                    }
+                }
+
+            }
+        }
+        return false;
+    }
+
+    private boolean resolveToOuter(ClassNode type) {
+        String name = type.getName();
+
+        // We do not need to check instances of LowerCaseClass
+        // to be a Class, because unless there was an import for
+        // for this we do not lookup these cases. This was a decision
+        // made on the mailing list. To ensure we will not visit this
+        // method again we set a NO_CLASS for this name
+        if (type instanceof LowerCaseClass) {
+            classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
+            return false;
+        }
+
+        if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
+        LookupResult lr = null;
+        lr = classNodeResolver.resolveName(name, compilationUnit);
+        if (lr!=null) {
+            if (lr.isSourceUnit()) {
+                SourceUnit su = lr.getSourceUnit();
+                currentClass.getCompileUnit().addClassNodeToCompile(type, su);
+            } else {
+                type.setRedirect(lr.getClassNode());
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+    public Expression transform(Expression exp) {
+        if (exp == null) return null;
+        Expression ret = null;
+        if (exp instanceof VariableExpression) {
+            ret = transformVariableExpression((VariableExpression) exp);
+        } else if (exp.getClass() == PropertyExpression.class) {
+            ret = transformPropertyExpression((PropertyExpression) exp);
+        } else if (exp instanceof DeclarationExpression) {
+            ret = transformDeclarationExpression((DeclarationExpression) exp);
+        } else if (exp instanceof BinaryExpression) {
+            ret = transformBinaryExpression((BinaryExpression) exp);
+        } else if (exp instanceof MethodCallExpression) {
+            ret = transformMethodCallExpression((MethodCallExpression) exp);
+        } else if (exp instanceof ClosureExpression) {
+            ret = transformClosureExpression((ClosureExpression) exp);
+        } else if (exp instanceof ConstructorCallExpression) {
+            ret = transformConstructorCallExpression((ConstructorCallExpression) exp);
+        } else if (exp instanceof AnnotationConstantExpression) {
+            ret = transformAnnotationConstantExpression((AnnotationConstantExpression) exp);
+        } else {
+            resolveOrFail(exp.getType(), exp);
+            ret = exp.transformExpression(this);
+        }
+        if (ret!=null && ret!=exp) ret.setSourcePosition(exp);
+        return ret;
+    }
+
+    private static String lookupClassName(PropertyExpression pe) {
+        boolean doInitialClassTest=true;
+        String name = "";
+        // this loop builds a name from right to left each name part
+        // separated by "."
+        for (Expression it = pe; it != null; it = ((PropertyExpression) it).getObjectExpression()) {
+            if (it instanceof VariableExpression) {
+                VariableExpression ve = (VariableExpression) it;
+                // stop at super and this
+                if (ve.isSuperExpression() || ve.isThisExpression()) {
+                    return null;
+                }
+                String varName = ve.getName();
+                if (doInitialClassTest) {
+                    // we are at the first name part. This is the right most part.
+                    // If this part is in lower case, then we do not need a class
+                    // check. other parts of the property expression will be tested
+                    // by a different method call to this method, so foo.Bar.bar
+                    // can still be resolved to the class foo.Bar and the static
+                    // field bar.
+                    if (!testVanillaNameForClass(varName)) return null;
+                    doInitialClassTest = false;
+                    name = varName;
+                } else {
+                    name = varName + "." + name;
+                }
+                break;
+            }
+            // anything other than PropertyExpressions or
+            // VariableExpressions will stop resolving
+            else if (it.getClass() != PropertyExpression.class) {
+                return null;
+            } else {
+                PropertyExpression current = (PropertyExpression) it;
+                String propertyPart = current.getPropertyAsString();
+                // the class property stops resolving, dynamic property names too
+                if (propertyPart == null || propertyPart.equals("class")) {
+                    return null;
+                }
+                if (doInitialClassTest) {
+                    // we are at the first name part. This is the right most part.
+                    // If this part is in lower case, then we do not need a class
+                    // check. other parts of the property expression will be tested
+                    // by a different method call to this method, so foo.Bar.bar
+                    // can still be resolved to the class foo.Bar and the static
+                    // field bar.
+                    if (!testVanillaNameForClass(propertyPart)) return null;
+                    doInitialClassTest= false;
+                    name = propertyPart;
+                } else {
+                    name = propertyPart + "." + name;
+                }
+            }
+        }
+        if (name.length() == 0) return null;
+        return name;
+    }
+
+    // iterate from the inner most to the outer and check for classes
+    // this check will ignore a .class property, for Example Integer.class will be
+    // a PropertyExpression with the ClassExpression of Integer as objectExpression
+    // and class as property
+    private static Expression correctClassClassChain(PropertyExpression pe) {
+        LinkedList<Expression> stack = new LinkedList<Expression>();
+        ClassExpression found = null;
+        for (Expression it = pe; it != null; it = ((PropertyExpression) it).getObjectExpression()) {
+            if (it instanceof ClassExpression) {
+                found = (ClassExpression) it;
+                break;
+            } else if (!(it.getClass() == PropertyExpression.class)) {
+                return pe;
+            }
+            stack.addFirst(it);
+        }
+        if (found == null) return pe;
+
+        if (stack.isEmpty()) return pe;
+        Object stackElement = stack.removeFirst();
+        if (!(stackElement.getClass() == PropertyExpression.class)) return pe;
+        PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
+        String propertyNamePart = classPropertyExpression.getPropertyAsString();
+        if (propertyNamePart == null || !propertyNamePart.equals("class")) return pe;
+
+        found.setSourcePosition(classPropertyExpression);
+        if (stack.isEmpty()) return found;
+        stackElement = stack.removeFirst();
+        if (!(stackElement.getClass() == PropertyExpression.class)) return pe;
+        PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
+
+        classPropertyExpressionContainer.setObjectExpression(found);
+        return pe;
+    }
+
+    protected Expression transformPropertyExpression(PropertyExpression pe) {
+        boolean itlp = isTopLevelProperty;
+        boolean ipe = inPropertyExpression;
+
+        Expression objectExpression = pe.getObjectExpression();
+        inPropertyExpression = true;
+        isTopLevelProperty = (objectExpression.getClass() != PropertyExpression.class);
+        objectExpression = transform(objectExpression);
+        // we handle the property part as if it were not part of the property
+        inPropertyExpression = false;
+        Expression property = transform(pe.getProperty());
+        isTopLevelProperty = itlp;
+        inPropertyExpression = ipe;
+
+        boolean spreadSafe = pe.isSpreadSafe();
+        PropertyExpression old = pe;
+        pe = new PropertyExpression(objectExpression, property, pe.isSafe());
+        pe.setSpreadSafe(spreadSafe);
+        pe.setSourcePosition(old);
+
+        String className = lookupClassName(pe);
+        if (className != null) {
+            ClassNode type = ClassHelper.make(className);
+            if (resolve(type)) {
+                Expression ret =  new ClassExpression(type);
+                ret.setSourcePosition(pe);
+                return ret;
+            }
+        }
+        if (objectExpression instanceof ClassExpression && pe.getPropertyAsString() != null) {
+            // possibly an inner class (or inherited inner class)
+            ClassExpression ce = (ClassExpression) objectExpression;
+            ClassNode classNode = ce.getType();
+            while (classNode != null) {
+                ClassNode type = new ConstructedNestedClass(classNode, pe.getPropertyAsString());
+                if (resolve(type, false, false, false)) {
+                    if (classNode == ce.getType() || isVisibleNestedClass(type, ce.getType())) {
+                        Expression ret = new ClassExpression(type);
+                        ret.setSourcePosition(ce);
+                        return ret;
+                    }
+                }
+                classNode = classNode.getSuperClass();
+            }
+        }
+        Expression ret = pe;
+        checkThisAndSuperAsPropertyAccess(pe);
+        if (isTopLevelProperty) ret = correctClassClassChain(pe);
+        return ret;
+    }
+
+    private boolean isVisibleNestedClass(ClassNode type, ClassNode ceType) {
+        if (!type.isRedirectNode()) return false;
+        ClassNode redirect = type.redirect();
+        if (Modifier.isPublic(redirect.getModifiers()) || Modifier.isProtected(redirect.getModifiers())) return true;
+        // package local
+        return isDefaultVisibility(redirect.getModifiers()) && inSamePackage(ceType, redirect);
+    }
+
+    private boolean directlyImplementsTrait(ClassNode trait) {
+        ClassNode[] interfaces = currentClass.getInterfaces();
+        if (interfaces==null) {
+            return currentClass.getSuperClass().equals(trait);
+        }
+        for (ClassNode node : interfaces) {
+            if (node.equals(trait)) {
+                return true;
+            }
+        }
+        return currentClass.getSuperClass().equals(trait);
+    }
+
+    private void checkThisAndSuperAsPropertyAccess(PropertyExpression expression) {
+        if (expression.isImplicitThis()) return;
+        String prop = expression.getPropertyAsString();
+        if (prop == null) return;
+        if (!prop.equals("this") && !prop.equals("super")) return;
+
+        ClassNode type = expression.getObjectExpression().getType();
+        if (expression.getObjectExpression() instanceof ClassExpression) {
+            if (!(currentClass instanceof InnerClassNode) && !Traits.isTrait(type)) {
+                addError("The usage of 'Class.this' and 'Class.super' is only allowed in nested/inner classes.", expression);
+                return;
+            }
+            if (currentScope!=null && !currentScope.isInStaticContext() && Traits.isTrait(type) && "super".equals(prop) && directlyImplementsTrait(type)) {
+                return;
+            }
+            ClassNode iterType = currentClass;
+            while (iterType != null) {
+                if (iterType.equals(type)) break;
+                iterType = iterType.getOuterClass();
+            }
+            if (iterType == null) {
+                addError("The class '" + type.getName() + "' needs to be an outer class of '" +
+                        currentClass.getName() + "' when using '.this' or '.super'.", expression);
+            }
+            if ((currentClass.getModifiers() & Opcodes.ACC_STATIC) == 0) return;
+            if (currentScope != null && !currentScope.isInStaticContext()) return;
+            addError("The usage of 'Class.this' and 'Class.super' within static nested class '" +
+                    currentClass.getName() + "' is not allowed in a static context.", expression);
+        }
+    }
+
+    protected Expression transformVariableExpression(VariableExpression ve) {
+        visitAnnotations(ve);
+        Variable v = ve.getAccessedVariable();
+        
+        if(!(v instanceof DynamicVariable) && !checkingVariableTypeInDeclaration) {
+            /*
+             *  GROOVY-4009: when a normal variable is simply being used, there is no need to try to 
+             *  resolve its type. Variable type resolve should proceed only if the variable is being declared. 
+             */
+            return ve;
+        }
+        if (v instanceof DynamicVariable){
+            String name = ve.getName();
+            ClassNode t = ClassHelper.make(name);
+            // asking isResolved here allows to check if a primitive
+            // type name like "int" was used to make t. In such a case
+            // we have nothing left to do.
+            boolean isClass = t.isResolved();
+            if (!isClass) {
+                // It was no primitive type, so next we see if the name,
+                // which is a vanilla name, starts with a lower case letter.
+                // In that case we change it to a LowerCaseClass to let the
+                // compiler skip the resolving at several places in this class.
+                if (Character.isLowerCase(name.charAt(0))) {
+                  t = new LowerCaseClass(name);
+                }
+                isClass = resolve(t);
+                if(!isClass) isClass = resolveToNestedOfCurrent(t);
+            }
+            if (isClass) {
+                // the name is a type so remove it from the scoping
+                // as it is only a classvariable, it is only in
+                // referencedClassVariables, but must be removed
+                // for each parentscope too
+                for (VariableScope scope = currentScope; scope != null && !scope.isRoot(); scope = scope.getParent()) {
+                    if (scope.isRoot()) break;
+                    if (scope.removeReferencedClassVariable(ve.getName()) == null) break;
+                }
+                ClassExpression ce = new ClassExpression(t);
+                ce.setSourcePosition(ve);
+                return ce;
+            }
+        }
+        resolveOrFail(ve.getType(), ve);
+        ClassNode origin = ve.getOriginType();
+        if (origin!=ve.getType()) resolveOrFail(origin, ve);
+        return ve;
+    }
+
+    private static boolean testVanillaNameForClass(String name) {
+        if (name==null || name.length()==0) return false;
+        return !Character.isLowerCase(name.charAt(0));
+    }
+
+    private static boolean isLeftSquareBracket(int op) {
+        return op == Types.ARRAY_EXPRESSION
+                || op == Types.LEFT_SQUARE_BRACKET
+                || op == Types.SYNTH_LIST
+                || op == Types.SYNTH_MAP;
+    }
+
+    protected Expression transformBinaryExpression(BinaryExpression be) {
+        Expression left = transform(be.getLeftExpression());
+        int type = be.getOperation().getType();
+        if ((type == Types.ASSIGNMENT_OPERATOR || type == Types.EQUAL) &&
+                left instanceof ClassExpression) {
+            ClassExpression ce = (ClassExpression) left;
+            String error = "you tried to assign a value to the class '" + ce.getType().getName() + "'";
+            if (ce.getType().isScript()) {
+                error += ". Do you have a script with this name?";
+            }
+            addError(error, be.getLeftExpression());
+            return be;
+        }
+        if (left instanceof ClassExpression && isLeftSquareBracket(type)) {
+            if (be.getRightExpression() instanceof ListExpression) {
+                ListExpression list = (ListExpression) be.getRightExpression();
+                if (list.getExpressions().isEmpty()) {
+                    // we have C[] if the list is empty -> should be an array then!
+                    final ClassExpression ce = new ClassExpression(left.getType().makeArray());
+                    ce.setSourcePosition(be);
+                    return ce;
+                }
+                else {
+                    // may be we have C[k1:v1, k2:v2] -> should become (C)([k1:v1, k2:v2])
+                    boolean map = true;
+                    for (Expression expression : list.getExpressions()) {
+                        if(!(expression instanceof MapEntryExpression)) {
+                            map = false;
+                            break;
+                        }
+                    }
+
+                    if (map) {
+                        final MapExpression me = new MapExpression();
+                        for (Expression expression : list.getExpressions()) {
+                            me.addMapEntryExpression((MapEntryExpression) transform(expression));
+                        }
+                        me.setSourcePosition(list);
+                        final CastExpression ce = new CastExpression(left.getType(), me);
+                        ce.setSourcePosition(be);
+                        return ce;
+                    }
+                }
+            } else if (be.getRightExpression() instanceof SpreadMapExpression) {
+                // we have C[*:map] -> should become (C) map
+                SpreadMapExpression mapExpression = (SpreadMapExpression) be.getRightExpression();
+                Expression right = transform(mapExpression.getExpression());
+                Expression ce = new CastExpression(left.getType(), right);
+                ce.setSourcePosition(be);
+                return ce;
+            }
+
+            if (be.getRightExpression() instanceof MapEntryExpression) {
+                // may be we have C[k1:v1] -> should become (C)([k1:v1])
+                final MapExpression me = new MapExpression();
+                me.addMapEntryExpression((MapEntryExpression) transform(be.getRightExpression()));
+                me.setSourcePosition(be.getRightExpression());
+                final CastExpression ce = new CastExpression(left.getType(), me);
+                ce.setSourcePosition(be);
+                return ce;
+            }
+        }
+        Expression right = transform(be.getRightExpression());
+        be.setLeftExpression(left);
+        be.setRightExpression(right);
+        return be;
+    }
+
+    protected Expression transformClosureExpression(ClosureExpression ce) {
+        boolean oldInClosure = inClosure;
+        inClosure = true;
+        Parameter[] paras = ce.getParameters();
+        if (paras != null) {
+            for (Parameter para : paras) {
+                ClassNode t = para.getType();
+                resolveOrFail(t, ce);
+                visitAnnotations(para);
+                if (para.hasInitialExpression()) {
+                    Object initialVal = para.getInitialExpression();
+                    if (initialVal instanceof Expression) {
+                        para.setInitialExpression(transform((Expression) initialVal));
+                    }
+                }
+                visitAnnotations(para);
+            }
+        }
+        Statement code = ce.getCode();
+        if (code != null) code.visit(this);
+        inClosure = oldInClosure;
+        return ce;
+    }
+
+    protected Expression transformConstructorCallExpression(ConstructorCallExpression cce) {
+        ClassNode type = cce.getType();
+        resolveOrFail(type, cce);
+        if (Modifier.isAbstract(type.getModifiers())) {
+            addError("You cannot create an instance from the abstract " + getDescription(type) + ".", cce);
+        }
+
+        Expression ret = cce.transformExpression(this);
+        return ret;
+    }
+
+    private static String getDescription(ClassNode node) {
+        return (node.isInterface() ? "interface" : "class") + " '" + node.getName() + "'";
+    }
+    
+    protected Expression transformMethodCallExpression(MethodCallExpression mce) {
+        Expression args = transform(mce.getArguments());
+        Expression method = transform(mce.getMethod());
+        Expression object = transform(mce.getObjectExpression());
+
+        resolveGenericsTypes(mce.getGenericsTypes());
+        
+        MethodCallExpression result = new MethodCallExpression(object, method, args);
+        result.setSafe(mce.isSafe());
+        result.setImplicitThis(mce.isImplicitThis());
+        result.setSpreadSafe(mce.isSpreadSafe());
+        result.setSourcePosition(mce);
+        result.setGenericsTypes(mce.getGenericsTypes());
+        result.setMethodTarget(mce.getMethodTarget());
+        return result;
+    }
+    
+    protected Expression transformDeclarationExpression(DeclarationExpression de) {
+        visitAnnotations(de);
+        Expression oldLeft = de.getLeftExpression();
+        checkingVariableTypeInDeclaration = true;
+        Expression left = transform(oldLeft);
+        checkingVariableTypeInDeclaration = false;
+        if (left instanceof ClassExpression) {
+            ClassExpression ce = (ClassExpression) left;
+            addError("you tried to assign a value to the class " + ce.getType().getName(), oldLeft);
+            return de;
+        }
+        Expression right = transform(de.getRightExpression());
+        if (right == de.getRightExpression()) {
+            fixDeclaringClass(de);
+            return de;
+        }
+        DeclarationExpression newDeclExpr = new DeclarationExpression(left, de.getOperation(), right);
+        newDeclExpr.setDeclaringClass(de.getDeclaringClass());
+        fixDeclaringClass(newDeclExpr);
+        newDeclExpr.setSourcePosition(de);
+        newDeclExpr.addAnnotations(de.getAnnotations());
+        return newDeclExpr;
+    }
+
+    // TODO get normal resolving to set declaring class
+    private void fixDeclaringClass(DeclarationExpression newDeclExpr) {
+        if (newDeclExpr.getDeclaringClass() == null && currentMethod != null) {
+            newDeclExpr.setDeclaringClass(currentMethod.getDeclaringClass());
+        }
+    }
+
+    protected Expression transformAnnotationConstantExpression(AnnotationConstantExpression ace) {
+        AnnotationNode an = (AnnotationNode) ace.getValue();
+        ClassNode type = an.getClassNode();
+        resolveOrFail(type, ", unable to find class for annotation", an);
+        for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) {
+            member.setValue(transform(member.getValue()));
+        }
+        return ace;
+    }
+
+    public void visitAnnotations(AnnotatedNode node) {
+        List<AnnotationNode> annotations = node.getAnnotations();
+        if (annotations.isEmpty()) return;
+        Map<String, AnnotationNode> tmpAnnotations = new HashMap<String, AnnotationNode>();
+        ClassNode annType;
+        for (AnnotationNode an : annotations) {
+            // skip built-in properties
+            if (an.isBuiltIn()) continue;
+            annType = an.getClassNode();
+            resolveOrFail(annType, ",  unable to find class for annotation", an);
+            for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) {
+                Expression newValue = transform(member.getValue());
+                newValue = transformInlineConstants(newValue);
+                member.setValue(newValue);
+                checkAnnotationMemberValue(newValue);
+            }
+            if(annType.isResolved()) {
+                Class annTypeClass = annType.getTypeClass();
+                Retention retAnn = (Retention) annTypeClass.getAnnotation(Retention.class);
+                if (retAnn != null && retAnn.value().equals(RetentionPolicy.RUNTIME)) {
+                    AnnotationNode anyPrevAnnNode = tmpAnnotations.put(annTypeClass.getName(), an);
+                    if(anyPrevAnnNode != null) {
+                        addError("Cannot specify duplicate annotation on the same member : " + annType.getName(), an);
+                    }
+                }
+            }
+        }
+    }
+
+    // resolve constant-looking expressions statically (do here as gets transformed away later)
+    private Expression transformInlineConstants(Expression exp) {
+        if (exp instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) exp;
+            if (pe.getObjectExpression() instanceof ClassExpression) {
+                ClassExpression ce = (ClassExpression) pe.getObjectExpression();
+                ClassNode type = ce.getType();
+                if (type.isEnum())
+                    return exp;
+
+                FieldNode fn = type.getField(pe.getPropertyAsString());
+                if (fn != null && !fn.isEnum() && fn.isStatic() && fn.isFinal()) {
+                    if (fn.getInitialValueExpression() instanceof ConstantExpression) {
+                        return fn.getInitialValueExpression();
+                    }
+                }
+            }
+        } else if (exp instanceof ListExpression) {
+            ListExpression le = (ListExpression) exp;
+            ListExpression result = new ListExpression();
+            for (Expression e : le.getExpressions()) {
+                result.addExpression(transformInlineConstants(e));
+            }
+            return result;
+        } else if (exp instanceof AnnotationConstantExpression) {
+            ConstantExpression ce = (ConstantExpression) exp;
+            if (ce.getValue() instanceof AnnotationNode) {
+                // replicate a little bit of AnnotationVisitor here
+                // because we can't wait until later to do this
+                AnnotationNode an = (AnnotationNode) ce.getValue();
+                for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) {
+                    member.setValue(transformInlineConstants(member.getValue()));
+                }
+
+            }
+        }
+        return exp;
+    }
+
+    private void checkAnnotationMemberValue(Expression newValue) {
+        if (newValue instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) newValue;
+            if (!(pe.getObjectExpression() instanceof ClassExpression)) {
+                addError("unable to find class '" + pe.getText() + "' for annotation attribute constant", pe.getObjectExpression());
+            }
+        } else if (newValue instanceof ListExpression) {
+            ListExpression le = (ListExpression) newValue;
+            for (Expression e : le.getExpressions()) {
+                checkAnnotationMemberValue(e);
+            }
+        }
+    }
+
+    public void visitClass(ClassNode node) {
+        ClassNode oldNode = currentClass;
+
+        if (node instanceof InnerClassNode) {
+            if (Modifier.isStatic(node.getModifiers())) {
+                genericParameterNames = new HashMap<String, GenericsType>();
+            }
+        } else {
+            genericParameterNames = new HashMap<String, GenericsType>();
+        }
+        currentClass = node;
+        resolveGenericsHeader(node.getGenericsTypes());
+
+        ModuleNode module = node.getModule();
+        if (!module.hasImportsResolved()) {
+            for (ImportNode importNode : module.getImports()) {
+                currImportNode = importNode;
+                ClassNode type = importNode.getType();
+                if (resolve(type, false, false, true)) {
+                    currImportNode = null;
+                    continue;
+                }
+                currImportNode = null;
+                addError("unable to resolve class " + type.getName(), type);
+            }
+            for (ImportNode importNode : module.getStaticStarImports().values()) {
+                ClassNode type = importNode.getType();
+                if (resolve(type, false, false, true)) continue;
+                // Maybe this type belongs in the same package as the node that is doing the
+                // static import. In that case, the package may not have been explicitly specified.
+                // Try with the node's package too. If still not found, revert to original type name.
+                if (type.getPackageName() == null && node.getPackageName() != null) {
+                    String oldTypeName = type.getName();
+                    type.setName(node.getPackageName() + "." + oldTypeName);
+                    if (resolve(type, false, false, true)) continue;
+                    type.setName(oldTypeName);
+                }
+                addError("unable to resolve class " + type.getName(), type);
+            }
+            for (ImportNode importNode : module.getStaticImports().values()) {
+                ClassNode type = importNode.getType();
+                if (resolve(type, true, true, true)) continue;
+                addError("unable to resolve class " + type.getName(), type);
+            }
+            for (ImportNode importNode : module.getStaticStarImports().values()) {
+                ClassNode type = importNode.getType();
+                if (resolve(type, true, true, true)) continue;
+                addError("unable to resolve class " + type.getName(), type);
+            }
+            module.setImportsResolved(true);
+        }
+
+        ClassNode sn = node.getUnresolvedSuperClass();
+        if (sn != null) resolveOrFail(sn, node, true);
+
+        for (ClassNode anInterface : node.getInterfaces()) {
+            resolveOrFail(anInterface, node, true);
+        }
+
+        checkCyclicInheritance(node, node.getUnresolvedSuperClass(), node.getInterfaces());
+        
+        super.visitClass(node);
+
+        currentClass = oldNode;
+    }
+    
+    private void checkCyclicInheritance(ClassNode originalNode, ClassNode parentToCompare, ClassNode[] interfacesToCompare) {
+        if(!originalNode.isInterface()) {
+            if(parentToCompare == null) return;
+            if(originalNode == parentToCompare.redirect()) {
+                addError("Cyclic inheritance involving " + parentToCompare.getName() + " in class " + originalNode.getName(), originalNode);
+                return;
+            }
+            if(interfacesToCompare != null && interfacesToCompare.length > 0) {
+                for(ClassNode intfToCompare : interfacesToCompare) {
+                    if(originalNode == intfToCompare.redirect()) {
+                        addError("Cycle detected: the type " + originalNode.getName() + " cannot implement itself" , originalNode);
+                        return;
+                    }
+                }
+            }
+            if(parentToCompare == ClassHelper.OBJECT_TYPE) return;
+            checkCyclicInheritance(originalNode, parentToCompare.getUnresolvedSuperClass(), null);
+        } else {
+            if(interfacesToCompare != null && interfacesToCompare.length > 0) {
+                // check interfaces at this level first
+                for(ClassNode intfToCompare : interfacesToCompare) {
+                    if(originalNode == intfToCompare.redirect()) {
+                        addError("Cyclic inheritance involving " + intfToCompare.getName() + " in interface " + originalNode.getName(), originalNode);
+                        return;
+                    }
+                }
+                // check next level of interfaces
+                for(ClassNode intf : interfacesToCompare) {
+                    checkCyclicInheritance(originalNode, null, intf.getInterfaces());
+                }
+            } else {
+                return;
+            }
+        }
+    }
+
+    public void visitCatchStatement(CatchStatement cs) {
+        resolveOrFail(cs.getExceptionType(), cs);
+        if (cs.getExceptionType() == ClassHelper.DYNAMIC_TYPE) {
+            cs.getVariable().setType(ClassHelper.make(Exception.class));
+        }
+        super.visitCatchStatement(cs);
+    }
+
+    public void visitForLoop(ForStatement forLoop) {
+        resolveOrFail(forLoop.getVariableType(), forLoop);
+        super.visitForLoop(forLoop);
+    }
+
+    public void visitBlockStatement(BlockStatement block) {
+        VariableScope oldScope = currentScope;
+        currentScope = block.getVariableScope();
+        super.visitBlockStatement(block);
+        currentScope = oldScope;
+    }
+
+    protected SourceUnit getSourceUnit() {
+        return source;
+    }
+
+    private boolean resolveGenericsTypes(GenericsType[] types) {
+        if (types == null) return true;
+        currentClass.setUsingGenerics(true);
+        boolean resolved = true;
+        for (GenericsType type : types) {
+            // attempt resolution on all types, so don't short-circuit and stop if we've previously failed
+            resolved = resolveGenericsType(type) && resolved;
+        }
+        return resolved;
+    }
+
+    private void resolveGenericsHeader(GenericsType[] types) {
+        if (types == null) return;
+        currentClass.setUsingGenerics(true);
+        for (GenericsType type : types) {
+            ClassNode classNode = type.getType();
+            String name = type.getName();
+            ClassNode[] bounds = type.getUpperBounds();
+            if (bounds != null) {
+                boolean nameAdded = false;
+                for (ClassNode upperBound : bounds) {
+                    if (!nameAdded && upperBound != null || !resolve(classNode)) {
+                        genericParameterNames.put(name, type);
+                        type.setPlaceholder(true);
+                        classNode.setRedirect(upperBound);
+                        nameAdded = true;
+                    }
+                    resolveOrFail(upperBound, classNode);
+                }
+            } else {
+                genericParameterNames.put(name, type);
+                classNode.setRedirect(ClassHelper.OBJECT_TYPE);
+                type.setPlaceholder(true);
+            }
+        }
+    }
+
+    private boolean resolveGenericsType(GenericsType genericsType) {
+        if (genericsType.isResolved()) return true;
+        currentClass.setUsingGenerics(true);
+        ClassNode type = genericsType.getType();
+        // save name before redirect
+        String name = type.getName();
+        ClassNode[] bounds = genericsType.getUpperBounds();
+        if (!genericParameterNames.containsKey(name)) {
+            if (bounds != null) {
+                for (ClassNode upperBound : bounds) {
+                    resolveOrFail(upperBound, genericsType);
+                    type.setRedirect(upperBound);
+                    resolveGenericsTypes(upperBound.getGenericsTypes());
+                }
+            } else if (genericsType.isWildcard()) {
+                type.setRedirect(ClassHelper.OBJECT_TYPE);
+            } else {
+                resolveOrFail(type, genericsType);
+            }
+        } else {
+            GenericsType gt = genericParameterNames.get(name);
+            type.setRedirect(gt.getType());
+            genericsType.setPlaceholder(true);
+        }
+
+        if (genericsType.getLowerBound() != null) {
+            resolveOrFail(genericsType.getLowerBound(), genericsType);
+        }
+
+        if (resolveGenericsTypes(type.getGenericsTypes())) {
+            genericsType.setResolved(genericsType.getType().isResolved());
+        }
+        return genericsType.isResolved();
+
+    }
+
+    public void setClassNodeResolver(ClassNodeResolver classNodeResolver) {
+        this.classNodeResolver = classNodeResolver;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java b/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
new file mode 100644
index 0000000..a12cae1
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
@@ -0,0 +1,66 @@
+/*
+ *  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.control;
+
+import groovy.lang.GroovyRuntimeException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Looks for source file extensions in META-INF/services/org.codehaus.groovy.source.Extensions
+ */
+public class SourceExtensionHandler {
+
+    public static Set<String> getRegisteredExtensions(ClassLoader loader) {
+        Set<String> extensions = new LinkedHashSet<String>();
+        extensions.add("groovy");
+        try {
+            Enumeration<URL> globalServices = loader.getResources("META-INF/services/org.codehaus.groovy.source.Extensions");
+            while (globalServices.hasMoreElements()) {
+                BufferedReader svcIn = null;
+                URL service = globalServices.nextElement();
+                try {
+                    svcIn = new BufferedReader(new InputStreamReader(service.openStream()));
+                    String extension = svcIn.readLine();
+                    while (extension != null) {
+                        extension = extension.trim();
+                        if (!extension.startsWith("#") && extension.length() > 0) {
+                            extensions.add(extension);
+                        }
+                        extension = svcIn.readLine();
+                    }
+                } catch (IOException ex) {
+                    throw new GroovyRuntimeException("IO Exception attempting to load registered source extension " +
+                            service.toExternalForm() + ". Exception: " + ex.toString());
+                } finally {
+                    if (svcIn != null) svcIn.close();
+                }
+            }
+        } catch (IOException ex) {
+            throw new GroovyRuntimeException("IO Exception getting registered source extensions. Exception: " + ex.toString());
+        }
+        return extensions;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/SourceUnit.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/SourceUnit.java b/src/main/java/org/codehaus/groovy/control/SourceUnit.java
new file mode 100644
index 0000000..8f3ce5e
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/SourceUnit.java
@@ -0,0 +1,344 @@
+/*
+ *  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.control;
+
+import antlr.CharScanner;
+import antlr.MismatchedCharException;
+import antlr.MismatchedTokenException;
+import antlr.NoViableAltException;
+import antlr.NoViableAltForCharException;
+import groovy.lang.GroovyClassLoader;
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.control.io.FileReaderSource;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.control.io.StringReaderSource;
+import org.codehaus.groovy.control.io.URLReaderSource;
+import org.codehaus.groovy.control.messages.Message;
+import org.codehaus.groovy.control.messages.SimpleMessage;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.Reduction;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.tools.Utilities;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * Provides an anchor for a single source unit (usually a script file)
+ * as it passes through the compiler system.
+ *
+ * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
+ * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
+ */
+
+public class SourceUnit extends ProcessingUnit {
+
+    /**
+     * The pluggable parser used to generate the AST - we allow
+     * pluggability currently as we need to have Classic and JSR support
+     */
+    private ParserPlugin parserPlugin;
+
+    /**
+     * Where we can get Readers for our source unit
+     */
+    protected ReaderSource source;
+
+    /**
+     * A descriptive name of the source unit. This name shouldn't
+     * be used for controlling the SourceUnit, it is only for error
+     * messages and to determine the name of the class for
+     * a script.
+     */
+    protected String name;
+
+    /**
+     * A Concrete Syntax Tree of the source
+     */
+    protected Reduction cst;
+
+    /**
+     * The root of the Abstract Syntax Tree for the source
+     */
+    protected ModuleNode ast;
+
+    /**
+     * Initializes the SourceUnit from existing machinery.
+     */
+    public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags,
+                      GroovyClassLoader loader, ErrorCollector er) {
+        super(flags, loader, er);
+
+        this.name = name;
+        this.source = source;
+    }
+
+    /**
+     * Initializes the SourceUnit from the specified file.
+     */
+    public SourceUnit(File source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
+        this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er);
+    }
+
+    /**
+     * Initializes the SourceUnit from the specified URL.
+     */
+    public SourceUnit(URL source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
+        this(source.toExternalForm(), new URLReaderSource(source, configuration), configuration, loader, er);
+    }
+
+    /**
+     * Initializes the SourceUnit for a string of source.
+     */
+    public SourceUnit(String name, String source, CompilerConfiguration configuration,
+                      GroovyClassLoader loader, ErrorCollector er) {
+        this(name, new StringReaderSource(source, configuration), configuration, loader, er);
+    }
+
+    /**
+     * Returns the name for the SourceUnit. This name shouldn't
+     * be used for controlling the SourceUnit, it is only for error
+     * messages
+     */
+    public String getName() {
+        return name;
+    }
+
+
+    /**
+     * Returns the Concrete Syntax Tree produced during parse()ing.
+     */
+    public Reduction getCST() {
+        return this.cst;
+    }
+
+    /**
+     * Returns the Abstract Syntax Tree produced during convert()ing
+     * and expanded during later phases.
+     */
+    public ModuleNode getAST() {
+        return this.ast;
+    }
+
+
+    /**
+     * Convenience routine, primarily for use by the InteractiveShell,
+     * that returns true if parse() failed with an unexpected EOF.
+     */
+    public boolean failedWithUnexpectedEOF() {
+        // Implementation note - there are several ways for the Groovy compiler
+        // to report an unexpected EOF. Perhaps this implementation misses some.
+        // If you find another way, please add it.
+        if (getErrorCollector().hasErrors()) {
+            Message last = (Message) getErrorCollector().getLastError();
+            Throwable cause = null;
+            if (last instanceof SyntaxErrorMessage) {
+                cause = ((SyntaxErrorMessage) last).getCause().getCause();
+            }
+            if (cause != null) {
+                if (cause instanceof NoViableAltException) {
+                    return isEofToken(((NoViableAltException) cause).token);
+                } else if (cause instanceof NoViableAltForCharException) {
+                    char badChar = ((NoViableAltForCharException) cause).foundChar;
+                    return badChar == CharScanner.EOF_CHAR;
+                } else if (cause instanceof MismatchedCharException) {
+                    char badChar = (char) ((MismatchedCharException) cause).foundChar;
+                    return badChar == CharScanner.EOF_CHAR;
+                } else if (cause instanceof MismatchedTokenException) {
+                    return isEofToken(((MismatchedTokenException) cause).token);
+                }
+            }
+        }
+        return false;
+    }
+
+    protected boolean isEofToken(antlr.Token token) {
+        return token.getType() == antlr.Token.EOF_TYPE;
+    }
+
+
+    //---------------------------------------------------------------------------
+    // FACTORIES
+
+
+    /**
+     * A convenience routine to create a standalone SourceUnit on a String
+     * with defaults for almost everything that is configurable.
+     */
+    public static SourceUnit create(String name, String source) {
+        CompilerConfiguration configuration = new CompilerConfiguration();
+        configuration.setTolerance(1);
+
+        return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
+    }
+
+
+    /**
+     * A convenience routine to create a standalone SourceUnit on a String
+     * with defaults for almost everything that is configurable.
+     */
+    public static SourceUnit create(String name, String source, int tolerance) {
+        CompilerConfiguration configuration = new CompilerConfiguration();
+        configuration.setTolerance(tolerance);
+
+        return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
+    }
+
+    //---------------------------------------------------------------------------
+    // PROCESSING
+
+    /**
+     * Parses the source to a CST.  You can retrieve it with getCST().
+     */
+    public void parse() throws CompilationFailedException {
+        if (this.phase > Phases.PARSING) {
+            throw new GroovyBugError("parsing is already complete");
+        }
+
+        if (this.phase == Phases.INITIALIZATION) {
+            nextPhase();
+        }
+
+        //
+        // Create a reader on the source and run the parser.
+
+        try (Reader reader = source.getReader()) {
+            // let's recreate the parser each time as it tends to keep around state
+            parserPlugin = getConfiguration().getPluginFactory().createParserPlugin();
+
+            cst = parserPlugin.parseCST(this, reader);
+        } catch (IOException e) {
+            getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(), this));
+        }
+    }
+
+    /**
+     * Generates an AST from the CST.  You can retrieve it with getAST().
+     */
+    public void convert() throws CompilationFailedException {
+        if (this.phase == Phases.PARSING && this.phaseComplete) {
+            gotoPhase(Phases.CONVERSION);
+        }
+
+        if (this.phase != Phases.CONVERSION) {
+            throw new GroovyBugError("SourceUnit not ready for convert()");
+        }
+
+        //
+        // Build the AST
+
+        try {
+            this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst);
+            this.ast.setDescription(this.name);
+        }
+        catch (SyntaxException e) {
+            if (this.ast == null) {
+                // Create a dummy ModuleNode to represent a failed parse - in case a later phase attempts to use the ast
+                this.ast = new ModuleNode(this);
+            }
+            getErrorCollector().addError(new SyntaxErrorMessage(e, this));
+        }
+
+        String property = (String) AccessController.doPrivileged(new PrivilegedAction() {
+            public Object run() {
+                return System.getProperty("groovy.ast");
+            }
+        });
+
+        if ("xml".equals(property)) {
+            saveAsXML(name, ast);
+        }
+    }
+
+    private static void saveAsXML(String name, ModuleNode ast) {
+        XStreamUtils.serialize(name, ast);
+    }
+
+    //---------------------------------------------------------------------------    // SOURCE SAMPLING
+
+    /**
+     * Returns a sampling of the source at the specified line and column,
+     * or null if it is unavailable.
+     */
+    public String getSample(int line, int column, Janitor janitor) {
+        String sample = null;
+        String text = source.getLine(line, janitor);
+
+        if (text != null) {
+            if (column > 0) {
+                String marker = Utilities.repeatString(" ", column - 1) + "^";
+
+                if (column > 40) {
+                    int start = column - 30 - 1;
+                    int end = (column + 10 > text.length() ? text.length() : column + 10 - 1);
+                    sample = "   " + text.substring(start, end) + Utilities.eol() + "   " +
+                            marker.substring(start, marker.length());
+                } else {
+                    sample = "   " + text + Utilities.eol() + "   " + marker;
+                }
+            } else {
+                sample = text;
+            }
+        }
+
+        return sample;
+    }
+
+    /**
+     * This method adds an exception to the error collector. The Exception most likely has no line number attached to it.
+     * For this reason you should use this method sparingly. Prefer using addError for syntax errors or add an error
+     * to the {@link ErrorCollector} directly by retrieving it with getErrorCollector().
+     * @param e
+     *      the exception that occurred
+     * @throws CompilationFailedException
+     *      on error
+     */
+    public void addException(Exception e) throws CompilationFailedException {
+        getErrorCollector().addException(e, this);
+    }
+
+    /**
+     * This method adds a SyntaxException to the error collector. The exception should specify the line and column
+     * number of the error.  This method should be reserved for real errors in the syntax of the SourceUnit. If
+     * your error is not in syntax, and is a semantic error, or more general error, then use addException or use
+     * the error collector directly by retrieving it with getErrorCollector().
+     * @param se
+     *      the exception, which should have line and column information
+     * @throws CompilationFailedException
+     *      on error
+     */
+    public void addError(SyntaxException se) throws CompilationFailedException {
+        getErrorCollector().addError(se, this);
+    }
+
+    public void addErrorAndContinue(SyntaxException se) throws CompilationFailedException {
+        getErrorCollector().addErrorAndContinue(se, this);
+    }
+
+    public ReaderSource getSource() { return source; }
+
+    public void setSource(ReaderSource source) {
+        this.source = source;
+    }
+}