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:38 UTC
[27/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/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/0edfcde9/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/0edfcde9/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/0edfcde9/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/0edfcde9/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/0edfcde9/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/0edfcde9/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);
+ }
+}