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/14 21:56:52 UTC

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

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
new file mode 100644
index 0000000..5e13cd7
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
@@ -0,0 +1,342 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.PackageNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+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.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.tools.ParameterUtils;
+import org.codehaus.groovy.control.AnnotationConstantsVisitor;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.ErrorCollector;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
+
+/**
+ * A specialized Groovy AST visitor meant to perform additional verifications upon the
+ * current AST. Currently it does checks on annotated nodes and annotations itself.
+ * <p>
+ * Current limitations:
+ * - annotations on local variables are not supported
+ */
+public class ExtendedVerifier extends ClassCodeVisitorSupport {
+    public static final String JVM_ERROR_MESSAGE = "Please make sure you are running on a JVM >= 1.5";
+
+    private final SourceUnit source;
+    private ClassNode currentClass;
+
+    public ExtendedVerifier(SourceUnit sourceUnit) {
+        this.source = sourceUnit;
+    }
+
+    public void visitClass(ClassNode node) {
+        AnnotationConstantsVisitor acv = new AnnotationConstantsVisitor();
+        acv.visitClass(node, this.source);
+        this.currentClass = node;
+        if (node.isAnnotationDefinition()) {
+            visitAnnotations(node, AnnotationNode.ANNOTATION_TARGET);
+        } else {
+            visitAnnotations(node, AnnotationNode.TYPE_TARGET);
+        }
+        PackageNode packageNode = node.getPackage();
+        if (packageNode != null) {
+            visitAnnotations(packageNode, AnnotationNode.PACKAGE_TARGET);
+        }
+        node.visitContents(this);
+    }
+
+    public void visitField(FieldNode node) {
+        visitAnnotations(node, AnnotationNode.FIELD_TARGET);
+    }
+
+    @Override
+    public void visitDeclarationExpression(DeclarationExpression expression) {
+        visitAnnotations(expression, AnnotationNode.LOCAL_VARIABLE_TARGET);
+    }
+
+    public void visitConstructor(ConstructorNode node) {
+        visitConstructorOrMethod(node, AnnotationNode.CONSTRUCTOR_TARGET);
+    }
+
+    public void visitMethod(MethodNode node) {
+        visitConstructorOrMethod(node, AnnotationNode.METHOD_TARGET);
+    }
+
+    private void visitConstructorOrMethod(MethodNode node, int methodTarget) {
+        visitAnnotations(node, methodTarget);
+        for (int i = 0; i < node.getParameters().length; i++) {
+            Parameter parameter = node.getParameters()[i];
+            visitAnnotations(parameter, AnnotationNode.PARAMETER_TARGET);
+        }
+
+        if (this.currentClass.isAnnotationDefinition() && !node.isStaticConstructor()) {
+            ErrorCollector errorCollector = new ErrorCollector(this.source.getConfiguration());
+            AnnotationVisitor visitor = new AnnotationVisitor(this.source, errorCollector);
+            visitor.setReportClass(currentClass);
+            visitor.checkReturnType(node.getReturnType(), node);
+            if (node.getParameters().length > 0) {
+                addError("Annotation members may not have parameters.", node.getParameters()[0]);
+            }
+            if (node.getExceptions().length > 0) {
+                addError("Annotation members may not have a throws clause.", node.getExceptions()[0]);
+            }
+            ReturnStatement code = (ReturnStatement) node.getCode();
+            if (code != null) {
+                visitor.visitExpression(node.getName(), code.getExpression(), node.getReturnType());
+                visitor.checkCircularReference(currentClass, node.getReturnType(), code.getExpression());
+            }
+            this.source.getErrorCollector().addCollectorContents(errorCollector);
+        }
+        Statement code = node.getCode();
+        if (code != null) {
+            code.visit(this);
+        }
+
+    }
+
+    public void visitProperty(PropertyNode node) {
+    }
+
+    protected void visitAnnotations(AnnotatedNode node, int target) {
+        if (node.getAnnotations().isEmpty()) {
+            return;
+        }
+        this.currentClass.setAnnotated(true);
+        if (!isAnnotationCompatible()) {
+            addError("Annotations are not supported in the current runtime. " + JVM_ERROR_MESSAGE, node);
+            return;
+        }
+        Map<String, List<AnnotationNode>> runtimeAnnotations = new LinkedHashMap<String, List<AnnotationNode>>();
+        for (AnnotationNode unvisited : node.getAnnotations()) {
+            AnnotationNode visited = visitAnnotation(unvisited);
+            String name = visited.getClassNode().getName();
+            if (visited.hasRuntimeRetention()) {
+                List<AnnotationNode> seen = runtimeAnnotations.get(name);
+                if (seen == null) {
+                    seen = new ArrayList<AnnotationNode>();
+                }
+                seen.add(visited);
+                runtimeAnnotations.put(name, seen);
+            }
+            boolean isTargetAnnotation = name.equals("java.lang.annotation.Target");
+
+            // Check if the annotation target is correct, unless it's the target annotating an annotation definition
+            // defining on which target elements the annotation applies
+            if (!isTargetAnnotation && !visited.isTargetAllowed(target)) {
+                addError("Annotation @" + name + " is not allowed on element "
+                        + AnnotationNode.targetToName(target), visited);
+            }
+            visitDeprecation(node, visited);
+            visitOverride(node, visited);
+        }
+        checkForDuplicateAnnotations(node, runtimeAnnotations);
+    }
+
+    private void checkForDuplicateAnnotations(AnnotatedNode node, Map<String, List<AnnotationNode>> runtimeAnnotations) {
+        for (Map.Entry<String, List<AnnotationNode>> next : runtimeAnnotations.entrySet()) {
+            if (next.getValue().size() > 1) {
+                ClassNode repeatable = null;
+                AnnotationNode repeatee = next.getValue().get(0);
+                List<AnnotationNode> repeateeAnnotations = repeatee.getClassNode().getAnnotations();
+                for (AnnotationNode anno : repeateeAnnotations) {
+                    ClassNode annoClassNode = anno.getClassNode();
+                    if (annoClassNode.getName().equals("java.lang.annotation.Repeatable")) {
+                        Expression value = anno.getMember("value");
+                        if (value instanceof ClassExpression) {
+                            ClassExpression ce = (ClassExpression) value;
+                            if (ce.getType() != null && ce.getType().isAnnotationDefinition()) {
+                                repeatable = ce.getType();
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (repeatable != null) {
+                    AnnotationNode collector = new AnnotationNode(repeatable);
+                    collector.setRuntimeRetention(true); // checked earlier
+                    List<Expression> annos = new ArrayList<Expression>();
+                    for (AnnotationNode an : next.getValue()) {
+                        annos.add(new AnnotationConstantExpression(an));
+                    }
+                    collector.addMember("value", new ListExpression(annos));
+                    node.addAnnotation(collector);
+                    node.getAnnotations().removeAll(next.getValue());
+                }
+            }
+        }
+    }
+
+    private static void visitDeprecation(AnnotatedNode node, AnnotationNode visited) {
+        if (visited.getClassNode().isResolved() && visited.getClassNode().getName().equals("java.lang.Deprecated")) {
+            if (node instanceof MethodNode) {
+                MethodNode mn = (MethodNode) node;
+                mn.setModifiers(mn.getModifiers() | Opcodes.ACC_DEPRECATED);
+            } else if (node instanceof FieldNode) {
+                FieldNode fn = (FieldNode) node;
+                fn.setModifiers(fn.getModifiers() | Opcodes.ACC_DEPRECATED);
+            } else if (node instanceof ClassNode) {
+                ClassNode cn = (ClassNode) node;
+                cn.setModifiers(cn.getModifiers() | Opcodes.ACC_DEPRECATED);
+            }
+        }
+    }
+
+    // TODO GROOVY-5011 handle case of @Override on a property
+    private void visitOverride(AnnotatedNode node, AnnotationNode visited) {
+        ClassNode annotationClassNode = visited.getClassNode();
+        if (annotationClassNode.isResolved() && annotationClassNode.getName().equals("java.lang.Override")) {
+            if (node instanceof MethodNode && !Boolean.TRUE.equals(node.getNodeMetaData(Verifier.DEFAULT_PARAMETER_GENERATED))) {
+                boolean override = false;
+                MethodNode origMethod = (MethodNode) node;
+                ClassNode cNode = origMethod.getDeclaringClass();
+                if (origMethod.hasDefaultValue()) {
+                    List<MethodNode> variants = cNode.getDeclaredMethods(origMethod.getName());
+                    for (MethodNode m : variants) {
+                        if (m.getAnnotations().contains(visited) && isOverrideMethod(m)) {
+                            override = true;
+                            break;
+                        }
+                    }
+                } else {
+                    override = isOverrideMethod(origMethod);
+                }
+
+                if (!override) {
+                    addError("Method '" + origMethod.getName() + "' from class '" + cNode.getName() + "' does not override " +
+                            "method from its superclass or interfaces but is annotated with @Override.", visited);
+                }
+            }
+        }
+    }
+
+    private static boolean isOverrideMethod(MethodNode method) {
+        ClassNode cNode = method.getDeclaringClass();
+        ClassNode next = cNode;
+        outer:
+        while (next != null) {
+            Map genericsSpec = createGenericsSpec(next);
+            MethodNode mn = correctToGenericsSpec(genericsSpec, method);
+            if (next != cNode) {
+                ClassNode correctedNext = correctToGenericsSpecRecurse(genericsSpec, next);
+                MethodNode found = getDeclaredMethodCorrected(genericsSpec, mn, correctedNext);
+                if (found != null) break;
+            }
+            List<ClassNode> ifaces = new ArrayList<ClassNode>();
+            ifaces.addAll(Arrays.asList(next.getInterfaces()));
+            Map updatedGenericsSpec = new HashMap(genericsSpec);
+            while (!ifaces.isEmpty()) {
+                ClassNode origInterface = ifaces.remove(0);
+                if (!origInterface.equals(ClassHelper.OBJECT_TYPE)) {
+                    updatedGenericsSpec = createGenericsSpec(origInterface, updatedGenericsSpec);
+                    ClassNode iNode = correctToGenericsSpecRecurse(updatedGenericsSpec, origInterface);
+                    MethodNode found2 = getDeclaredMethodCorrected(updatedGenericsSpec, mn, iNode);
+                    if (found2 != null) break outer;
+                    ifaces.addAll(Arrays.asList(iNode.getInterfaces()));
+                }
+            }
+            ClassNode superClass = next.getUnresolvedSuperClass();
+            if (superClass != null) {
+                next =  correctToGenericsSpecRecurse(updatedGenericsSpec, superClass);
+            } else {
+                next = null;
+            }
+        }
+        return next != null;
+    }
+
+    private static MethodNode getDeclaredMethodCorrected(Map genericsSpec, MethodNode mn, ClassNode correctedNext) {
+        for (MethodNode orig :  correctedNext.getDeclaredMethods(mn.getName())) {
+            MethodNode method = correctToGenericsSpec(genericsSpec, orig);
+            if (ParameterUtils.parametersEqual(method.getParameters(), mn.getParameters())) {
+                return method;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Resolve metadata and details of the annotation.
+     *
+     * @param unvisited the node to visit
+     * @return the visited node
+     */
+    private AnnotationNode visitAnnotation(AnnotationNode unvisited) {
+        ErrorCollector errorCollector = new ErrorCollector(this.source.getConfiguration());
+        AnnotationVisitor visitor = new AnnotationVisitor(this.source, errorCollector);
+        AnnotationNode visited = visitor.visit(unvisited);
+        this.source.getErrorCollector().addCollectorContents(errorCollector);
+        return visited;
+    }
+
+    /**
+     * Check if the current runtime allows Annotation usage.
+     *
+     * @return true if running on a 1.5+ runtime
+     */
+    protected boolean isAnnotationCompatible() {
+        return CompilerConfiguration.isPostJDK5(this.source.getConfiguration().getTargetBytecode());
+    }
+
+    public void addError(String msg, ASTNode expr) {
+        this.source.getErrorCollector().addErrorAndContinue(
+                new SyntaxErrorMessage(
+                        new SyntaxException(msg + '\n', expr.getLineNumber(), expr.getColumnNumber(), expr.getLastLineNumber(), expr.getLastColumnNumber()), this.source)
+        );
+    }
+
+    @Override
+    protected SourceUnit getSourceUnit() {
+        return source;
+    }
+
+    // TODO use it or lose it
+    public void visitGenericType(GenericsType genericsType) {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java b/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java
new file mode 100644
index 0000000..76c4e1f
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java
@@ -0,0 +1,363 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.EmptyExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.PostfixExpression;
+import org.codehaus.groovy.ast.expr.PrefixExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+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.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.stmt.TryCatchStatement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
+
+import java.lang.reflect.Modifier;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+public class FinalVariableAnalyzer extends ClassCodeVisitorSupport {
+
+    private final SourceUnit sourceUnit;
+    private final VariableNotFinalCallback callback;
+
+    private Set<VariableExpression> declaredFinalVariables = null;
+    private boolean inAssignment = false;
+
+    private enum VariableState {
+        is_uninitialized(false),
+        is_final(true),
+        is_var(false);
+
+        private final boolean isFinal;
+
+        VariableState(final boolean isFinal) {
+            this.isFinal = isFinal;
+        }
+
+        public VariableState getNext() {
+            switch (this) {
+                case is_uninitialized:
+                    return is_final;
+                default:
+                    return is_var;
+            }
+        }
+
+        public boolean isFinal() {
+            return isFinal;
+        }
+    }
+
+    private final Deque<Map<Variable, VariableState>> assignmentTracker = new LinkedList<Map<Variable, VariableState>>();
+
+    public FinalVariableAnalyzer(final SourceUnit sourceUnit) {
+        this(sourceUnit, null);
+    }
+
+    public FinalVariableAnalyzer(final SourceUnit sourceUnit, final VariableNotFinalCallback callback) {
+        this.callback = callback;
+        this.sourceUnit = sourceUnit;
+        pushState();
+    }
+
+    private Map<Variable, VariableState> pushState() {
+        Map<Variable, VariableState> state = new StateMap();
+        assignmentTracker.add(state);
+        return state;
+    }
+
+    private static Variable getTarget(Variable v) {
+        if (v instanceof VariableExpression) {
+            Variable t = ((VariableExpression) v).getAccessedVariable();
+            if (t == v) return t;
+            return getTarget(t);
+        }
+        return v;
+    }
+
+    private Map<Variable, VariableState> popState() {
+        return assignmentTracker.removeLast();
+    }
+
+    private Map<Variable, VariableState> getState() {
+        return assignmentTracker.getLast();
+    }
+
+    @Override
+    protected SourceUnit getSourceUnit() {
+        return sourceUnit;
+    }
+
+    public boolean isEffectivelyFinal(Variable v) {
+        VariableState state = getState().get(v);
+        return (v instanceof Parameter && state == null)
+                || (state != null && state.isFinal());
+    }
+
+    @Override
+    public void visitBlockStatement(final BlockStatement block) {
+        Set<VariableExpression> old = declaredFinalVariables;
+        declaredFinalVariables = new HashSet<VariableExpression>();
+        super.visitBlockStatement(block);
+        if (callback != null) {
+            Map<Variable, VariableState> state = getState();
+            for (VariableExpression declaredFinalVariable : declaredFinalVariables) {
+                VariableState variableState = state.get(declaredFinalVariable.getAccessedVariable());
+                if (variableState == null || variableState != VariableState.is_final) {
+                    callback.variableNotAlwaysInitialized(declaredFinalVariable);
+                }
+            }
+        }
+        declaredFinalVariables = old;
+    }
+
+    @Override
+    public void visitBinaryExpression(final BinaryExpression expression) {
+        boolean assignment = StaticTypeCheckingSupport.isAssignment(expression.getOperation().getType());
+        boolean isDeclaration = expression instanceof DeclarationExpression;
+        Expression leftExpression = expression.getLeftExpression();
+        Expression rightExpression = expression.getRightExpression();
+        if (isDeclaration && leftExpression instanceof VariableExpression) {
+            VariableExpression var = (VariableExpression) leftExpression;
+            if (Modifier.isFinal(var.getModifiers())) {
+                declaredFinalVariables.add(var);
+            }
+        }
+        leftExpression.visit(this);
+        inAssignment = assignment;
+        rightExpression.visit(this);
+        inAssignment = false;
+        if (assignment) {
+            if (leftExpression instanceof Variable) {
+                boolean uninitialized =
+                        isDeclaration && rightExpression == EmptyExpression.INSTANCE;
+                recordAssignment((Variable) leftExpression, isDeclaration, uninitialized, false, expression);
+            } else if (leftExpression instanceof TupleExpression) {
+                TupleExpression te = (TupleExpression) leftExpression;
+                for (Expression next : te.getExpressions()) {
+                    if (next instanceof Variable) {
+                        recordAssignment((Variable) next, isDeclaration, false, false, next);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visitClosureExpression(final ClosureExpression expression) {
+        boolean old = inAssignment;
+        inAssignment = false;
+        super.visitClosureExpression(expression);
+        inAssignment = old;
+    }
+
+    @Override
+    public void visitPrefixExpression(final PrefixExpression expression) {
+        inAssignment = expression.getExpression() instanceof VariableExpression;
+        super.visitPrefixExpression(expression);
+        inAssignment = false;
+        checkPrePostfixOperation(expression.getExpression(), expression);
+    }
+
+    @Override
+    public void visitPostfixExpression(final PostfixExpression expression) {
+        inAssignment = expression.getExpression() instanceof VariableExpression;
+        super.visitPostfixExpression(expression);
+        inAssignment = false;
+        checkPrePostfixOperation(expression.getExpression(), expression);
+    }
+
+    private void checkPrePostfixOperation(final Expression variable, final Expression originalExpression) {
+        if (variable instanceof Variable) {
+            recordAssignment((Variable) variable, false, false, true, originalExpression);
+            if (variable instanceof VariableExpression) {
+                Variable accessed = ((VariableExpression) variable).getAccessedVariable();
+                if (accessed != variable) {
+                    recordAssignment(accessed, false, false, true, originalExpression);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visitVariableExpression(final VariableExpression expression) {
+        super.visitVariableExpression(expression);
+        if (inAssignment) {
+            Map<Variable, VariableState> state = getState();
+            Variable key = expression.getAccessedVariable();
+            VariableState variableState = state.get(key);
+            if (variableState == VariableState.is_uninitialized) {
+                variableState = VariableState.is_var;
+                state.put(key, variableState);
+            }
+        }
+    }
+
+    @Override
+    public void visitIfElse(final IfStatement ifElse) {
+        visitStatement(ifElse);
+        ifElse.getBooleanExpression().visit(this);
+        Map<Variable, VariableState> ifState = pushState();
+        ifElse.getIfBlock().visit(this);
+        popState();
+        Statement elseBlock = ifElse.getElseBlock();
+        Map<Variable, VariableState> elseState = pushState();
+        if (elseBlock instanceof EmptyStatement) {
+            // dispatching to EmptyStatement will not call back visitor,
+            // must call our visitEmptyStatement explicitly
+            visitEmptyStatement((EmptyStatement) elseBlock);
+        } else {
+            elseBlock.visit(this);
+        }
+        popState();
+
+        // merge if/else branches
+        Map<Variable, VariableState> curState = getState();
+        Set<Variable> allVars = new HashSet<Variable>();
+        allVars.addAll(curState.keySet());
+        allVars.addAll(ifState.keySet());
+        allVars.addAll(elseState.keySet());
+        for (Variable var : allVars) {
+            VariableState beforeValue = curState.get(var);
+            VariableState ifValue = ifState.get(var);
+            VariableState elseValue = elseState.get(var);
+            // merge if and else values
+            VariableState mergedIfElse;
+            mergedIfElse = ifValue != null && ifValue.isFinal
+                    && elseValue != null && elseValue.isFinal ? VariableState.is_final : VariableState.is_var;
+            if (beforeValue == null) {
+                curState.put(var, mergedIfElse);
+            } else {
+                if (beforeValue == VariableState.is_uninitialized) {
+                    curState.put(var, mergedIfElse);
+                } else if (ifValue != null || elseValue != null) {
+                    curState.put(var, VariableState.is_var);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visitTryCatchFinally(final TryCatchStatement statement) {
+        visitStatement(statement);
+        Map<Variable, VariableState> beforeTryCatch = new HashMap<Variable, VariableState>(getState());
+        statement.getTryStatement().visit(this);
+        for (CatchStatement catchStatement : statement.getCatchStatements()) {
+            catchStatement.visit(this);
+        }
+        Statement finallyStatement = statement.getFinallyStatement();
+        // we need to recall which final variables are unassigned so cloning the current state
+        Map<Variable, VariableState> afterTryCatchState = new HashMap<Variable, VariableState>(getState());
+        if (finallyStatement instanceof EmptyStatement) {
+            // dispatching to EmptyStatement will not call back visitor,
+            // must call our visitEmptyStatement explicitly
+            visitEmptyStatement((EmptyStatement) finallyStatement);
+        } else {
+            finallyStatement.visit(this);
+        }
+        // and now we must reset to uninitialized state variables which were only initialized during try/catch
+        Map<Variable, VariableState> afterFinally = new HashMap<Variable, VariableState>(getState());
+        for (Map.Entry<Variable, VariableState> entry : afterFinally.entrySet()) {
+            Variable var = entry.getKey();
+            VariableState afterFinallyState = entry.getValue();
+            VariableState beforeTryCatchState = beforeTryCatch.get(var);
+            if (afterFinallyState == VariableState.is_final
+                    && beforeTryCatchState != VariableState.is_final
+                    && afterTryCatchState.get(var) != beforeTryCatchState) {
+                getState().put(var, beforeTryCatchState == null ? VariableState.is_uninitialized : beforeTryCatchState);
+            }
+        }
+    }
+
+    private void recordAssignment(
+            Variable var,
+            boolean isDeclaration,
+            boolean uninitialized,
+            boolean forceVariable,
+            Expression expression) {
+        if (var == null) {
+            return;
+        }
+        if (!isDeclaration && var.isClosureSharedVariable()) {
+            getState().put(var, VariableState.is_var);
+        }
+        VariableState variableState = getState().get(var);
+        if (variableState == null) {
+            variableState = uninitialized ? VariableState.is_uninitialized : VariableState.is_final;
+            if (getTarget(var) instanceof Parameter) {
+                variableState = VariableState.is_var;
+            }
+        } else {
+            variableState = variableState.getNext();
+        }
+        if (forceVariable) {
+            variableState = VariableState.is_var;
+        }
+        getState().put(var, variableState);
+        if (variableState == VariableState.is_var && callback != null) {
+            callback.variableNotFinal(var, expression);
+        }
+    }
+
+    public interface VariableNotFinalCallback {
+        /**
+         * Callback called whenever an assignment transforms an effectively final variable into a non final variable
+         * (aka, breaks the "final" modifier contract)
+         *
+         * @param var  the variable detected as not final
+         * @param bexp the expression responsible for the contract to be broken
+         */
+        void variableNotFinal(Variable var, Expression bexp);
+
+        /**
+         * Callback used whenever a variable is declared as final, but can remain in an uninitialized state
+         *
+         * @param var the variable detected as potentially uninitialized
+         */
+        void variableNotAlwaysInitialized(VariableExpression var);
+    }
+
+    private static class StateMap extends HashMap<Variable, VariableState> {
+        @Override
+        public VariableState get(final Object key) {
+            return super.get(getTarget((Variable) key));
+        }
+
+        @Override
+        public VariableState put(final Variable key, final VariableState value) {
+            return super.put(getTarget(key), value);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java b/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java
new file mode 100644
index 0000000..8b8a510
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java
@@ -0,0 +1,113 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CompileUnit;
+import org.codehaus.groovy.ast.MethodNode;
+
+
+/**
+ * A context shared across generations of a class and its inner classes
+ * 
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public class GeneratorContext {
+
+    private int innerClassIdx = 1;
+    private int closureClassIdx = 1;
+    private final CompileUnit compileUnit;
+    
+    public GeneratorContext(CompileUnit compileUnit) {
+        this.compileUnit = compileUnit;
+    }
+
+    public GeneratorContext(CompileUnit compileUnit, int innerClassOffset) {
+        this.compileUnit = compileUnit;
+        this.innerClassIdx = innerClassOffset;
+    }
+
+    public int getNextInnerClassIdx() {
+        return innerClassIdx++;
+    }
+
+    public CompileUnit getCompileUnit() {
+        return compileUnit;
+    }
+
+    public String getNextClosureInnerName(ClassNode owner, ClassNode enclosingClass, MethodNode enclosingMethod) {
+        String methodName = "";
+        if (enclosingMethod != null) {
+            methodName = enclosingMethod.getName();
+
+            if (enclosingClass.isDerivedFrom(ClassHelper.CLOSURE_TYPE)) {
+                methodName = "";
+            } else {
+                methodName = "_"+encodeAsValidClassName(methodName);
+            }
+        }
+        return methodName + "_closure" + closureClassIdx++;
+    }
+
+
+    private static final int MIN_ENCODING = ' ';
+    private static final int MAX_ENCODING = ']';
+    private static final boolean[] CHARACTERS_TO_ENCODE = new boolean[MAX_ENCODING-MIN_ENCODING+1];
+    static {
+        CHARACTERS_TO_ENCODE[' '-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['!'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['/'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['.'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE[';'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['$'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['<'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['>'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['['-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE[']'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE[':'-MIN_ENCODING] = true;
+        CHARACTERS_TO_ENCODE['\\'-MIN_ENCODING] = true;
+    }
+
+    public static String encodeAsValidClassName(String name) {
+        final int l = name.length();
+        StringBuilder b = null;
+        int lastEscape = -1;
+        for(int i = 0; i < l; ++i) {
+            final int encodeIndex = name.charAt(i) - MIN_ENCODING;
+            if (encodeIndex >= 0 && encodeIndex < CHARACTERS_TO_ENCODE.length) {
+                if (CHARACTERS_TO_ENCODE[encodeIndex]) {
+                    if(b == null) {
+                        b = new StringBuilder(name.length() + 3);
+                        b.append(name, 0, i);
+                    } else {
+                        b.append(name, lastEscape + 1, i);
+                    }
+                    b.append('_');
+                    lastEscape = i;
+                }
+            }
+        }
+        if(b == null) return name.toString();
+        if (lastEscape == -1) throw new GroovyBugError("unexpected escape char control flow in "+name);
+        b.append(name, lastEscape + 1, l);
+        return b.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java
new file mode 100644
index 0000000..a8d84c8
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java
@@ -0,0 +1,454 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.SourceUnit;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
+
+public class InnerClassCompletionVisitor extends InnerClassVisitorHelper implements Opcodes {
+
+    private final SourceUnit sourceUnit;
+    private ClassNode classNode;
+    private FieldNode thisField = null;
+
+    private static final String
+            CLOSURE_INTERNAL_NAME   = BytecodeHelper.getClassInternalName(CLOSURE_TYPE),
+            CLOSURE_DESCRIPTOR      = BytecodeHelper.getTypeDescription(CLOSURE_TYPE);
+
+    public InnerClassCompletionVisitor(CompilationUnit cu, SourceUnit su) {
+        sourceUnit = su;
+    }
+
+    @Override
+    protected SourceUnit getSourceUnit() {
+        return sourceUnit;
+    }
+
+    @Override
+    public void visitClass(ClassNode node) {
+        this.classNode = node;
+        thisField = null;
+        InnerClassNode innerClass = null;
+        if (!node.isEnum() && !node.isInterface() && node instanceof InnerClassNode) {
+            innerClass = (InnerClassNode) node;
+            thisField = innerClass.getField("this$0");
+            if (innerClass.getVariableScope() == null && innerClass.getDeclaredConstructors().isEmpty()) {
+                // add dummy constructor
+                innerClass.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY, null, null);
+            }
+        }
+        if (node.isEnum() || node.isInterface()) return;
+        // use Iterator.hasNext() to check for available inner classes
+        if (node.getInnerClasses().hasNext()) addDispatcherMethods(node);
+        if (innerClass == null) return;
+        super.visitClass(node);
+        addDefaultMethods(innerClass);
+    }
+
+    @Override
+    public void visitConstructor(ConstructorNode node) {
+        addThisReference(node);
+        super.visitConstructor(node);
+    }
+
+    private static String getTypeDescriptor(ClassNode node, boolean isStatic) {
+        return BytecodeHelper.getTypeDescription(getClassNode(node, isStatic));
+    }
+
+    private static String getInternalName(ClassNode node, boolean isStatic) {
+        return BytecodeHelper.getClassInternalName(getClassNode(node, isStatic));
+    }
+
+    private static void addDispatcherMethods(ClassNode classNode) {
+        final int objectDistance = getObjectDistance(classNode);
+
+        // since we added an anonymous inner class we should also
+        // add the dispatcher methods
+
+        // add method dispatcher
+        Parameter[] parameters = new Parameter[]{
+                new Parameter(ClassHelper.STRING_TYPE, "name"),
+                new Parameter(ClassHelper.OBJECT_TYPE, "args")
+        };
+        MethodNode method = classNode.addSyntheticMethod(
+                "this$dist$invoke$" + objectDistance,
+                ACC_PUBLIC + ACC_SYNTHETIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        BlockStatement block = new BlockStatement();
+        setMethodDispatcherCode(block, VariableExpression.THIS_EXPRESSION, parameters);
+        method.setCode(block);
+
+        // add property setter
+        parameters = new Parameter[]{
+                new Parameter(ClassHelper.STRING_TYPE, "name"),
+                new Parameter(ClassHelper.OBJECT_TYPE, "value")
+        };
+        method = classNode.addSyntheticMethod(
+                "this$dist$set$" + objectDistance,
+                ACC_PUBLIC + ACC_SYNTHETIC,
+                ClassHelper.VOID_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        setPropertySetterDispatcher(block, VariableExpression.THIS_EXPRESSION, parameters);
+        method.setCode(block);
+
+        // add property getter
+        parameters = new Parameter[]{
+                new Parameter(ClassHelper.STRING_TYPE, "name")
+        };
+        method = classNode.addSyntheticMethod(
+                "this$dist$get$" + objectDistance,
+                ACC_PUBLIC + ACC_SYNTHETIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        setPropertyGetterDispatcher(block, VariableExpression.THIS_EXPRESSION, parameters);
+        method.setCode(block);
+    }
+
+    private void getThis(MethodVisitor mv, String classInternalName, String outerClassDescriptor, String innerClassInternalName) {
+        mv.visitVarInsn(ALOAD, 0);
+        if (CLOSURE_TYPE.equals(thisField.getType())) {
+            mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", CLOSURE_DESCRIPTOR);
+            mv.visitMethodInsn(INVOKEVIRTUAL, CLOSURE_INTERNAL_NAME, "getThisObject", "()Ljava/lang/Object;", false);
+            mv.visitTypeInsn(CHECKCAST, innerClassInternalName);
+        } else {
+            mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", outerClassDescriptor);
+        }
+    }
+    
+    private void addDefaultMethods(InnerClassNode node) {
+        final boolean isStatic = isStatic(node);
+
+        ClassNode outerClass = node.getOuterClass();
+        final String classInternalName = org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName(node);
+        final String outerClassInternalName = getInternalName(outerClass, isStatic);
+        final String outerClassDescriptor = getTypeDescriptor(outerClass, isStatic);
+        final int objectDistance = getObjectDistance(outerClass);
+
+        // add missing method dispatcher
+        Parameter[] parameters = new Parameter[]{
+                new Parameter(ClassHelper.STRING_TYPE, "name"),
+                new Parameter(ClassHelper.OBJECT_TYPE, "args")
+        };
+
+        String methodName = "methodMissing";
+        if (isStatic)
+            addCompilationErrorOnCustomMethodNode(node, methodName, parameters);
+
+        MethodNode method = node.addSyntheticMethod(
+                methodName,
+                Opcodes.ACC_PUBLIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        BlockStatement block = new BlockStatement();
+        if (isStatic) {
+            setMethodDispatcherCode(block, new ClassExpression(outerClass), parameters);
+        } else {
+            block.addStatement(
+                    new BytecodeSequence(new BytecodeInstruction() {
+                        public void visit(MethodVisitor mv) {
+                            getThis(mv,classInternalName,outerClassDescriptor,outerClassInternalName);
+                            mv.visitVarInsn(ALOAD, 1);
+                            mv.visitVarInsn(ALOAD, 2);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$invoke$" + objectDistance, "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", false);
+                            mv.visitInsn(ARETURN);
+                        }
+                    })
+            );
+        }
+        method.setCode(block);
+
+        // add static missing method dispatcher
+        methodName = "$static_methodMissing";
+        if (isStatic)
+            addCompilationErrorOnCustomMethodNode(node, methodName, parameters);
+
+        method = node.addSyntheticMethod(
+                methodName,
+                Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        setMethodDispatcherCode(block, new ClassExpression(outerClass), parameters);
+        method.setCode(block);
+
+        // add property setter dispatcher
+        parameters = new Parameter[]{
+                new Parameter(ClassHelper.STRING_TYPE, "name"),
+                new Parameter(ClassHelper.OBJECT_TYPE, "val")
+        };
+
+        methodName = "propertyMissing";
+        if (isStatic)
+            addCompilationErrorOnCustomMethodNode(node, methodName, parameters);
+
+        method = node.addSyntheticMethod(
+                methodName,
+                Opcodes.ACC_PUBLIC,
+                ClassHelper.VOID_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        if (isStatic) {
+            setPropertySetterDispatcher(block, new ClassExpression(outerClass), parameters);
+        } else {
+            block.addStatement(
+                    new BytecodeSequence(new BytecodeInstruction() {
+                        public void visit(MethodVisitor mv) {
+                            getThis(mv,classInternalName,outerClassDescriptor,outerClassInternalName);
+                            mv.visitVarInsn(ALOAD, 1);
+                            mv.visitVarInsn(ALOAD, 2);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$set$" + objectDistance, "(Ljava/lang/String;Ljava/lang/Object;)V", false);
+                            mv.visitInsn(RETURN);
+                        }
+                    })
+            );
+        }
+        method.setCode(block);
+
+        // add static property missing setter dispatcher
+        methodName = "$static_propertyMissing";
+        if (isStatic)
+            addCompilationErrorOnCustomMethodNode(node, methodName, parameters);
+
+        method = node.addSyntheticMethod(
+                methodName,
+                Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+                ClassHelper.VOID_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        setPropertySetterDispatcher(block, new ClassExpression(outerClass), parameters);
+        method.setCode(block);
+
+        // add property getter dispatcher
+        parameters = new Parameter[]{
+                new Parameter(ClassHelper.STRING_TYPE, "name")
+        };
+
+        methodName = "propertyMissing";
+        if (isStatic)
+            addCompilationErrorOnCustomMethodNode(node, methodName, parameters);
+
+        method = node.addSyntheticMethod(
+                methodName,
+                Opcodes.ACC_PUBLIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        if (isStatic) {
+            setPropertyGetterDispatcher(block, new ClassExpression(outerClass), parameters);
+        } else {
+            block.addStatement(
+                    new BytecodeSequence(new BytecodeInstruction() {
+                        public void visit(MethodVisitor mv) {
+                            getThis(mv,classInternalName,outerClassDescriptor,outerClassInternalName);
+                            mv.visitVarInsn(ALOAD, 1);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$get$" + objectDistance, "(Ljava/lang/String;)Ljava/lang/Object;", false);
+                            mv.visitInsn(ARETURN);
+                        }
+                    })
+            );
+        }
+        method.setCode(block);
+
+        // add static property missing getter dispatcher
+        methodName = "$static_propertyMissing";
+        if (isStatic)
+            addCompilationErrorOnCustomMethodNode(node, methodName, parameters);
+
+        method = node.addSyntheticMethod(
+                methodName,
+                Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                null
+        );
+
+        block = new BlockStatement();
+        setPropertyGetterDispatcher(block, new ClassExpression(outerClass), parameters);
+        method.setCode(block);
+    }
+
+    /**
+     * Adds a compilation error if a {@link MethodNode} with the given <tt>methodName</tt> and
+     * <tt>parameters</tt> exists in the {@link InnerClassNode}.
+     */
+    private void addCompilationErrorOnCustomMethodNode(InnerClassNode node, String methodName, Parameter[] parameters) {
+        MethodNode existingMethodNode = node.getMethod(methodName, parameters);
+        // if there is a user-defined methodNode, add compiler error msg and continue
+        if (existingMethodNode != null && !existingMethodNode.isSynthetic())  {
+            addError("\"" +methodName + "\" implementations are not supported on static inner classes as " +
+                    "a synthetic version of \"" + methodName + "\" is added during compilation for the purpose " +
+                    "of outer class delegation.",
+                    existingMethodNode);
+        }
+    }
+
+    private void addThisReference(ConstructorNode node) {
+        if (!shouldHandleImplicitThisForInnerClass(classNode)) return;
+        Statement code = node.getCode();
+
+        // add "this$0" field init
+
+        //add this parameter to node
+        Parameter[] params = node.getParameters();
+        Parameter[] newParams = new Parameter[params.length + 1];
+        System.arraycopy(params, 0, newParams, 1, params.length);
+        String name = getUniqueName(params, node);
+
+        Parameter thisPara = new Parameter(classNode.getOuterClass().getPlainNodeReference(), name);
+        newParams[0] = thisPara;
+        node.setParameters(newParams);
+
+        BlockStatement block = null;
+        if (code == null) {
+            block = new BlockStatement();
+        } else if (!(code instanceof BlockStatement)) {
+            block = new BlockStatement();
+            block.addStatement(code);
+        } else {
+            block = (BlockStatement) code;
+        }
+        BlockStatement newCode = new BlockStatement();
+        addFieldInit(thisPara, thisField, newCode);
+        ConstructorCallExpression cce = getFirstIfSpecialConstructorCall(block);
+        if (cce == null) {
+            cce = new ConstructorCallExpression(ClassNode.SUPER, new TupleExpression());
+            block.getStatements().add(0, new ExpressionStatement(cce));
+        }
+        if (shouldImplicitlyPassThisPara(cce)) {
+            // add thisPara to this(...)
+            TupleExpression args = (TupleExpression) cce.getArguments();
+            List<Expression> expressions = args.getExpressions();
+            VariableExpression ve = new VariableExpression(thisPara.getName());
+            ve.setAccessedVariable(thisPara);
+            expressions.add(0, ve);
+        }
+        if (cce.isSuperCall()) {
+            // we have a call to super here, so we need to add
+            // our code after that
+            block.getStatements().add(1, newCode);
+        }
+        node.setCode(block);
+    }
+
+    private boolean shouldImplicitlyPassThisPara(ConstructorCallExpression cce) {
+        boolean pass = false;
+        ClassNode superCN = classNode.getSuperClass();
+        if (cce.isThisCall()) {
+            pass = true;
+        } else if (cce.isSuperCall()) {
+            // if the super class is another non-static inner class in the same outer class hierarchy, implicit this
+            // needs to be passed
+            if (!superCN.isEnum() && !superCN.isInterface() && superCN instanceof InnerClassNode) {
+                InnerClassNode superInnerCN = (InnerClassNode) superCN;
+                if (!isStatic(superInnerCN) && classNode.getOuterClass().isDerivedFrom(superCN.getOuterClass())) {
+                    pass = true;
+                }
+            }
+        }
+        return pass;
+    }
+
+    private String getUniqueName(Parameter[] params, ConstructorNode node) {
+        String namePrefix = "$p";
+        outer:
+        for (int i = 0; i < 100; i++) {
+            namePrefix = namePrefix + "$";
+            for (Parameter p : params) {
+                if (p.getName().equals(namePrefix)) continue outer;
+            }
+            return namePrefix;
+        }
+        addError("unable to find a unique prefix name for synthetic this reference in inner class constructor", node);
+        return namePrefix;
+    }
+
+    private static ConstructorCallExpression getFirstIfSpecialConstructorCall(BlockStatement code) {
+        if (code == null) return null;
+
+        final List<Statement> statementList = code.getStatements();
+        if (statementList.isEmpty()) return null;
+
+        final Statement statement = statementList.get(0);
+        if (!(statement instanceof ExpressionStatement)) return null;
+
+        Expression expression = ((ExpressionStatement) statement).getExpression();
+        if (!(expression instanceof ConstructorCallExpression)) return null;
+        ConstructorCallExpression cce = (ConstructorCallExpression) expression;
+        if (cce.isSpecialCall()) return cce;
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java
new file mode 100644
index 0000000..87815a8
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java
@@ -0,0 +1,288 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.SourceUnit;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcodes {
+
+    private final SourceUnit sourceUnit;
+    private ClassNode classNode;
+    private static final int PUBLIC_SYNTHETIC = Opcodes.ACC_PUBLIC + Opcodes.ACC_SYNTHETIC;
+    private FieldNode thisField = null;
+    private MethodNode currentMethod;
+    private FieldNode currentField;
+    private boolean processingObjInitStatements = false;
+    private boolean inClosure = false;
+
+    public InnerClassVisitor(CompilationUnit cu, SourceUnit su) {
+        sourceUnit = su;
+    }
+
+    @Override
+    protected SourceUnit getSourceUnit() {
+        return sourceUnit;
+    }
+
+    @Override
+    public void visitClass(ClassNode node) {
+        this.classNode = node;
+        thisField = null;
+        InnerClassNode innerClass = null;
+        if (!node.isEnum() && !node.isInterface() && node instanceof InnerClassNode) {
+            innerClass = (InnerClassNode) node;
+            if (!isStatic(innerClass) && innerClass.getVariableScope() == null) {
+                thisField = innerClass.addField("this$0", PUBLIC_SYNTHETIC, node.getOuterClass().getPlainNodeReference(), null);
+            }
+        }
+
+        super.visitClass(node);
+
+        if (node.isEnum() || node.isInterface()) return;
+        if (innerClass == null) return;
+
+        if (node.getSuperClass().isInterface()) {
+            node.addInterface(node.getUnresolvedSuperClass());
+            node.setUnresolvedSuperClass(ClassHelper.OBJECT_TYPE);
+        }
+    }
+    
+    @Override
+    public void visitClosureExpression(ClosureExpression expression) {
+        boolean inClosureOld = inClosure;
+        inClosure = true;
+        super.visitClosureExpression(expression);
+        inClosure = inClosureOld;
+    }
+
+    @Override
+    protected void visitObjectInitializerStatements(ClassNode node) {
+        processingObjInitStatements = true;
+        super.visitObjectInitializerStatements(node);
+        processingObjInitStatements = false;
+    }
+
+    @Override
+    protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
+        this.currentMethod = node;
+        visitAnnotations(node);
+        visitClassCodeContainer(node.getCode());
+        // GROOVY-5681: initial expressions should be visited too!
+        for (Parameter param : node.getParameters()) {
+            if (param.hasInitialExpression()) {
+                param.getInitialExpression().visit(this);
+            }
+            visitAnnotations(param);
+        }
+        this.currentMethod = null;
+    }
+
+    @Override
+    public void visitField(FieldNode node) {
+        this.currentField = node;
+        super.visitField(node);
+        this.currentField = null;
+    }
+
+    @Override
+    public void visitProperty(PropertyNode node) {
+        final FieldNode field = node.getField();
+        final Expression init = field.getInitialExpression();
+        field.setInitialValueExpression(null);
+        super.visitProperty(node);
+        field.setInitialValueExpression(init);
+    }
+
+    @Override
+    public void visitConstructorCallExpression(ConstructorCallExpression call) {
+        super.visitConstructorCallExpression(call);
+        if (!call.isUsingAnonymousInnerClass()) {
+            passThisReference(call);
+            return;
+        }
+
+        InnerClassNode innerClass = (InnerClassNode) call.getType();
+        ClassNode outerClass = innerClass.getOuterClass();
+        ClassNode superClass = innerClass.getSuperClass();
+        if (superClass instanceof InnerClassNode
+                && !superClass.isInterface()
+                && !(superClass.isStaticClass()||((superClass.getModifiers()&ACC_STATIC)==ACC_STATIC))) {
+            insertThis0ToSuperCall(call, innerClass);
+        }
+        if (!innerClass.getDeclaredConstructors().isEmpty()) return;
+        if ((innerClass.getModifiers() & ACC_STATIC) != 0) return;
+
+        VariableScope scope = innerClass.getVariableScope();
+        if (scope == null) return;
+
+        // expressions = constructor call arguments
+        List<Expression> expressions = ((TupleExpression) call.getArguments()).getExpressions();
+        // block = init code for the constructor we produce
+        BlockStatement block = new BlockStatement();
+        // parameters = parameters of the constructor
+        final int additionalParamCount = 1 + scope.getReferencedLocalVariablesCount();
+        List<Parameter> parameters = new ArrayList<Parameter>(expressions.size() + additionalParamCount);
+        // superCallArguments = arguments for the super call == the constructor call arguments
+        List<Expression> superCallArguments = new ArrayList<Expression>(expressions.size());
+
+        // first we add a super() call for all expressions given in the 
+        // constructor call expression
+        int pCount = additionalParamCount;
+        for (Expression expr : expressions) {
+            pCount++;
+            // add one parameter for each expression in the
+            // constructor call
+            Parameter param = new Parameter(ClassHelper.OBJECT_TYPE, "p" + pCount);
+            parameters.add(param);
+            // add to super call
+            superCallArguments.add(new VariableExpression(param));
+        }
+
+        // add the super call
+        ConstructorCallExpression cce = new ConstructorCallExpression(
+                ClassNode.SUPER,
+                new TupleExpression(superCallArguments)
+        );
+
+        block.addStatement(new ExpressionStatement(cce));
+
+        // we need to add "this" to access unknown methods/properties
+        // this is saved in a field named this$0
+        pCount = 0;
+        expressions.add(pCount, VariableExpression.THIS_EXPRESSION);
+        boolean isStatic = isStaticThis(innerClass,scope);
+        ClassNode outerClassType = getClassNode(outerClass, isStatic);
+        if (!isStatic && inClosure) outerClassType = ClassHelper.CLOSURE_TYPE;
+        outerClassType = outerClassType.getPlainNodeReference();
+        Parameter thisParameter = new Parameter(outerClassType, "p" + pCount);
+        parameters.add(pCount, thisParameter);
+
+        thisField = innerClass.addField("this$0", PUBLIC_SYNTHETIC, outerClassType, null);
+        addFieldInit(thisParameter, thisField, block);
+
+        // for each shared variable we add a reference and save it as field
+        for (Iterator it = scope.getReferencedLocalVariablesIterator(); it.hasNext();) {
+            pCount++;
+            org.codehaus.groovy.ast.Variable var = (org.codehaus.groovy.ast.Variable) it.next();
+            VariableExpression ve = new VariableExpression(var);
+            ve.setClosureSharedVariable(true);
+            ve.setUseReferenceDirectly(true);
+            expressions.add(pCount, ve);
+
+            ClassNode rawReferenceType = ClassHelper.REFERENCE_TYPE.getPlainNodeReference();
+            Parameter p = new Parameter(rawReferenceType, "p" + pCount);
+            parameters.add(pCount, p);
+            p.setOriginType(var.getOriginType());
+            final VariableExpression initial = new VariableExpression(p);
+            initial.setSynthetic(true);
+            initial.setUseReferenceDirectly(true);
+            final FieldNode pField = innerClass.addFieldFirst(ve.getName(), PUBLIC_SYNTHETIC,rawReferenceType, initial);
+            pField.setHolder(true);
+            pField.setOriginType(ClassHelper.getWrapper(var.getOriginType()));
+        }
+
+        innerClass.addConstructor(ACC_SYNTHETIC, parameters.toArray(new Parameter[parameters.size()]), ClassNode.EMPTY_ARRAY, block);
+    }
+
+    private boolean isStaticThis(InnerClassNode innerClass, VariableScope scope) {
+        if (inClosure) return false;
+        boolean ret = innerClass.isStaticClass();
+        if (    innerClass.getEnclosingMethod()!=null) {
+            ret = ret || innerClass.getEnclosingMethod().isStatic();
+        } else if (currentField!=null) {
+            ret = ret || currentField.isStatic();
+        } else if (currentMethod!=null && "<clinit>".equals(currentMethod.getName())) {
+            ret = true;
+        }
+        return ret;
+    }
+
+    // this is the counterpart of addThisReference(). To non-static inner classes, outer this should be
+    // passed as the first argument implicitly.
+    private void passThisReference(ConstructorCallExpression call) {
+        ClassNode cn = call.getType().redirect();
+        if (!shouldHandleImplicitThisForInnerClass(cn)) return;
+
+        boolean isInStaticContext = true;
+        if (currentMethod != null)
+            isInStaticContext = currentMethod.getVariableScope().isInStaticContext();
+        else if (currentField != null)
+            isInStaticContext = currentField.isStatic();
+        else if (processingObjInitStatements)
+            isInStaticContext = false;
+
+        // if constructor call is not in static context, return
+        if (isInStaticContext) {
+            // constructor call is in static context and the inner class is non-static - 1st arg is supposed to be 
+            // passed as enclosing "this" instance
+            //
+            Expression args = call.getArguments();
+            if (args instanceof TupleExpression && ((TupleExpression) args).getExpressions().isEmpty()) {
+                addError("No enclosing instance passed in constructor call of a non-static inner class", call);
+            }
+            return;
+        }
+        insertThis0ToSuperCall(call, cn);
+
+    }
+
+    private void insertThis0ToSuperCall(final ConstructorCallExpression call, final ClassNode cn) {
+        // calculate outer class which we need for this$0
+        ClassNode parent = classNode;
+        int level = 0;
+        for (; parent != null && parent != cn.getOuterClass(); parent = parent.getOuterClass()) {
+            level++;
+        }
+
+        // if constructor call is not in outer class, don't pass 'this' implicitly. Return.
+        if (parent == null) return;
+
+        //add this parameter to node
+        Expression argsExp = call.getArguments();
+        if (argsExp instanceof TupleExpression) {
+            TupleExpression argsListExp = (TupleExpression) argsExp;
+            Expression this0 = VariableExpression.THIS_EXPRESSION;
+            for (int i = 0; i != level; ++i)
+                this0 = new PropertyExpression(this0, "this$0");
+            argsListExp.getExpressions().add(0, this0);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java
new file mode 100644
index 0000000..7ed0629
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java
@@ -0,0 +1,143 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.FieldExpression;
+import org.codehaus.groovy.ast.expr.GStringExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.SpreadExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.syntax.Token;
+import org.codehaus.groovy.syntax.Types;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class InnerClassVisitorHelper extends ClassCodeVisitorSupport {
+    protected static void setPropertyGetterDispatcher(BlockStatement block, Expression thiz, Parameter[] parameters) {
+        List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>();
+        gStringStrings.add(new ConstantExpression(""));
+        gStringStrings.add(new ConstantExpression(""));
+        List<Expression> gStringValues = new ArrayList<Expression>();
+        gStringValues.add(new VariableExpression(parameters[0]));
+        block.addStatement(
+                new ReturnStatement(
+                        new PropertyExpression(
+                                thiz,
+                                new GStringExpression("$name", gStringStrings, gStringValues)
+                        )
+                )
+        );
+    }
+
+    protected static void setPropertySetterDispatcher(BlockStatement block, Expression thiz, Parameter[] parameters) {
+        List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>();
+        gStringStrings.add(new ConstantExpression(""));
+        gStringStrings.add(new ConstantExpression(""));
+        List<Expression> gStringValues = new ArrayList<Expression>();
+        gStringValues.add(new VariableExpression(parameters[0]));
+        block.addStatement(
+                new ExpressionStatement(
+                        new BinaryExpression(
+                                new PropertyExpression(
+                                        thiz,
+                                        new GStringExpression("$name", gStringStrings, gStringValues)
+                                ),
+                                Token.newSymbol(Types.ASSIGN, -1, -1),
+                                new VariableExpression(parameters[1])
+                        )
+                )
+        );
+    }
+
+    protected static void setMethodDispatcherCode(BlockStatement block, Expression thiz, Parameter[] parameters) {
+        List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>();
+        gStringStrings.add(new ConstantExpression(""));
+        gStringStrings.add(new ConstantExpression(""));
+        List<Expression> gStringValues = new ArrayList<Expression>();
+        gStringValues.add(new VariableExpression(parameters[0]));
+        block.addStatement(
+                new ReturnStatement(
+                        new MethodCallExpression(
+                                thiz,
+                                new GStringExpression("$name", gStringStrings, gStringValues),
+                                new ArgumentListExpression(
+                                        new SpreadExpression(new VariableExpression(parameters[1]))
+                                )
+                        )
+                )
+        );
+    }
+
+    protected static boolean isStatic(InnerClassNode node) {
+        VariableScope scope = node.getVariableScope();
+        if (scope != null) return scope.isInStaticContext();
+        return (node.getModifiers() & Opcodes.ACC_STATIC) != 0;
+    }
+
+    protected static ClassNode getClassNode(ClassNode node, boolean isStatic) {
+        if (isStatic) node = ClassHelper.CLASS_Type;
+        return node;
+    }
+
+    protected static int getObjectDistance(ClassNode node) {
+        int count = 0;
+        while (node != null && node != ClassHelper.OBJECT_TYPE) {
+            count++;
+            node = node.getSuperClass();
+        }
+        return count;
+    }
+
+    protected static void addFieldInit(Parameter p, FieldNode fn, BlockStatement block) {
+        VariableExpression ve = new VariableExpression(p);
+        FieldExpression fe = new FieldExpression(fn);
+        block.addStatement(new ExpressionStatement(
+                new BinaryExpression(fe, Token.newSymbol(Types.ASSIGN, -1, -1), ve)
+        ));
+    }
+
+    protected static boolean shouldHandleImplicitThisForInnerClass(ClassNode cn) {
+        if (cn.isEnum() || cn.isInterface()) return false;
+        if ((cn.getModifiers() & Opcodes.ACC_STATIC) != 0) return false;
+
+        if (!(cn instanceof InnerClassNode)) return false;
+        InnerClassNode innerClass = (InnerClassNode) cn;
+        // scope != null means aic, we don't handle that here
+        if (innerClass.getVariableScope() != null) return false;
+        // static inner classes don't need this$0
+        return (innerClass.getModifiers() & Opcodes.ACC_STATIC) == 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java b/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java
new file mode 100644
index 0000000..22f3059
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java
@@ -0,0 +1,279 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+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.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.stmt.SwitchStatement;
+import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
+import org.codehaus.groovy.ast.stmt.ThrowStatement;
+import org.codehaus.groovy.ast.stmt.TryCatchStatement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to add return statements.
+ * Extracted from Verifier as it can be useful for some AST transformations
+ */
+public class ReturnAdder {
+
+    private static final ReturnStatementListener DEFAULT_LISTENER = new ReturnStatementListener() {
+        public void returnStatementAdded(final ReturnStatement returnStatement) {
+        }
+    };
+
+    /**
+     * If set to 'true', then returns are effectively added. This is useful whenever you just want
+     * to check what returns are produced without eventually adding them.
+     */
+    private final boolean doAdd;
+
+    private final ReturnStatementListener listener;
+
+    public ReturnAdder() {
+        doAdd = true;
+        listener = DEFAULT_LISTENER;
+    }
+
+    public ReturnAdder(ReturnStatementListener listener) {
+        this.listener = listener;
+        this.doAdd = false;
+    }
+
+    /**
+     * Adds return statements in method code whenever an implicit return is detected.
+     * @param node the method node where to add return statements
+     * @deprecated Use {@link #visitMethod(org.codehaus.groovy.ast.MethodNode)} instead
+     */
+    @Deprecated
+    public static void addReturnIfNeeded(MethodNode node) {
+        ReturnAdder adder = new ReturnAdder();
+        adder.visitMethod(node);
+    }
+
+    public void visitMethod(MethodNode node) {
+        Statement statement = node.getCode();
+        if (!node.isVoidMethod()) {
+            if (statement != null) // it happens with @interface methods
+            {
+                final Statement code = addReturnsIfNeeded(statement, node.getVariableScope());
+                if (doAdd) node.setCode(code);
+            }
+        } else if (!node.isAbstract() && node.getReturnType().redirect()!=ClassHelper.VOID_TYPE) {
+            if (!(statement instanceof BytecodeSequence)) {
+                BlockStatement newBlock = new BlockStatement();
+                Statement code = node.getCode();
+                if (code instanceof BlockStatement) {
+                    newBlock.setVariableScope(((BlockStatement) code).getVariableScope());
+                }
+                if (statement instanceof BlockStatement) {
+                    newBlock.addStatements(((BlockStatement)statement).getStatements());
+                } else {
+                    newBlock.addStatement(statement);
+                }
+                final ReturnStatement returnStatement = ReturnStatement.RETURN_NULL_OR_VOID;
+                listener.returnStatementAdded(returnStatement);
+                newBlock.addStatement(returnStatement);
+                newBlock.setSourcePosition(statement);
+                if (doAdd) node.setCode(newBlock);
+            }
+        }
+    }
+
+    private Statement addReturnsIfNeeded(Statement statement, VariableScope scope) {
+        if (  statement instanceof ReturnStatement
+           || statement instanceof BytecodeSequence
+           || statement instanceof ThrowStatement)
+        {
+            return statement;
+        }
+
+        if (statement instanceof EmptyStatement) {
+            final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL);
+            listener.returnStatementAdded(returnStatement);
+            return returnStatement;
+        }
+
+        if (statement instanceof ExpressionStatement) {
+            ExpressionStatement expStmt = (ExpressionStatement) statement;
+            Expression expr = expStmt.getExpression();
+            ReturnStatement ret = new ReturnStatement(expr);
+            ret.setSourcePosition(expr);
+            ret.setStatementLabel(statement.getStatementLabel());
+            listener.returnStatementAdded(ret);
+            return ret;
+        }
+
+        if (statement instanceof SynchronizedStatement) {
+            SynchronizedStatement sync = (SynchronizedStatement) statement;
+            final Statement code = addReturnsIfNeeded(sync.getCode(), scope);
+            if (doAdd) sync.setCode(code);
+            return sync;
+        }
+
+        if (statement instanceof IfStatement) {
+            IfStatement ifs = (IfStatement) statement;
+            final Statement ifBlock = addReturnsIfNeeded(ifs.getIfBlock(), scope);
+            final Statement elseBlock = addReturnsIfNeeded(ifs.getElseBlock(), scope);
+            if (doAdd) {
+                ifs.setIfBlock(ifBlock);
+                ifs.setElseBlock(elseBlock);
+            }
+            return ifs;
+        }
+
+        if (statement instanceof SwitchStatement) {
+            SwitchStatement swi = (SwitchStatement) statement;
+            for (CaseStatement caseStatement : swi.getCaseStatements()) {
+                final Statement code = adjustSwitchCaseCode(caseStatement.getCode(), scope, false);
+                if (doAdd) caseStatement.setCode(code);
+            }
+            final Statement defaultStatement = adjustSwitchCaseCode(swi.getDefaultStatement(), scope, true);
+            if (doAdd) swi.setDefaultStatement(defaultStatement);
+            return swi;
+        }
+
+        if (statement instanceof TryCatchStatement) {
+            TryCatchStatement trys = (TryCatchStatement) statement;
+            final boolean[] missesReturn = new boolean[1];
+            new ReturnAdder(new ReturnStatementListener() {
+                @Override
+                public void returnStatementAdded(ReturnStatement returnStatement) {
+                    missesReturn[0] = true;
+                }
+            }).addReturnsIfNeeded(trys.getFinallyStatement(), scope);
+            boolean hasFinally = !(trys.getFinallyStatement() instanceof EmptyStatement);
+
+            // if there is no missing return in the finally block and the block exists
+            // there is nothing to do
+            if (hasFinally && !missesReturn[0]) return trys;
+
+            // add returns to try and catch blocks
+            final Statement tryStatement = addReturnsIfNeeded(trys.getTryStatement(), scope);
+            if (doAdd) trys.setTryStatement(tryStatement);
+            final int len = trys.getCatchStatements().size();
+            for (int i = 0; i != len; ++i) {
+                final CatchStatement catchStatement = trys.getCatchStatement(i);
+                final Statement code = addReturnsIfNeeded(catchStatement.getCode(), scope);
+                if (doAdd) catchStatement.setCode(code);
+            }
+            return trys;
+        }
+
+        if (statement instanceof BlockStatement) {
+            BlockStatement block = (BlockStatement) statement;
+
+            final List list = block.getStatements();
+            if (!list.isEmpty()) {
+                int idx = list.size() - 1;
+                Statement last = addReturnsIfNeeded((Statement) list.get(idx), block.getVariableScope());
+                if (doAdd) list.set(idx, last);
+                if (!statementReturns(last)) {
+                    final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL);
+                    listener.returnStatementAdded(returnStatement);
+                    if (doAdd) list.add(returnStatement);
+                }
+            } else {
+                ReturnStatement ret = new ReturnStatement(ConstantExpression.NULL);
+                ret.setSourcePosition(block);
+                listener.returnStatementAdded(ret);
+                return ret;
+            }
+
+            BlockStatement newBlock = new BlockStatement(list, block.getVariableScope());
+            newBlock.setSourcePosition(block);
+            return newBlock;
+        }
+
+        if (statement == null) {
+            final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL);
+            listener.returnStatementAdded(returnStatement);
+            return returnStatement;
+        } else {
+            final List list = new ArrayList();
+            list.add(statement);
+            final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL);
+            listener.returnStatementAdded(returnStatement);
+            list.add(returnStatement);
+
+            BlockStatement newBlock = new BlockStatement(list, new VariableScope(scope));
+            newBlock.setSourcePosition(statement);
+            return newBlock;
+        }
+    }
+
+    private Statement adjustSwitchCaseCode(Statement statement, VariableScope scope, boolean defaultCase) {
+        if(statement instanceof BlockStatement) {
+            final List list = ((BlockStatement)statement).getStatements();
+            if (!list.isEmpty()) {
+                int idx = list.size() - 1;
+                Statement last = (Statement) list.get(idx);
+                if(last instanceof BreakStatement) {
+                    if (doAdd) {
+                        list.remove(idx);
+                        return addReturnsIfNeeded(statement, scope);
+                    } else {
+                        BlockStatement newStmt = new BlockStatement();
+                        for (int i=0;i<idx; i++) {
+                            newStmt.addStatement((Statement) list.get(i));
+                        }
+                        return addReturnsIfNeeded(newStmt, scope);
+                    }
+                } else if(defaultCase) {
+                    return addReturnsIfNeeded(statement, scope);
+                }
+            }
+        }
+        return statement;
+    }
+
+    private static boolean statementReturns(Statement last) {
+        return (
+                last instanceof ReturnStatement ||
+                last instanceof BlockStatement ||
+                last instanceof IfStatement ||
+                last instanceof ExpressionStatement ||
+                last instanceof EmptyStatement ||
+                last instanceof TryCatchStatement ||
+                last instanceof BytecodeSequence ||
+                last instanceof ThrowStatement ||
+                last instanceof SynchronizedStatement
+                );
+    }
+
+    /**
+     * Implement this method in order to be notified whenever a return statement is generated.
+     */
+    public interface ReturnStatementListener {
+        void returnStatementAdded(ReturnStatement returnStatement);
+    }
+}