You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2019/02/25 02:23:08 UTC
[groovy] branch GROOVY_2_5_X updated: minor refactor: properly
declare local variable
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_2_5_X by this push:
new 2a41cab minor refactor: properly declare local variable
2a41cab is described below
commit 2a41cabce6e02038560f91adc8980a57256df653
Author: Paul King <pa...@asert.com.au>
AuthorDate: Mon Feb 25 09:20:01 2019 +1000
minor refactor: properly declare local variable
---
.../groovy/beans/BindableASTTransformation.java | 5 +-
.../beans/ListenerListASTTransformation.groovy | 8 +-
.../groovy/beans/VetoableASTTransformation.java | 5 +-
.../groovy/transform/builder/DefaultStrategy.java | 3 +-
.../groovy/transform/builder/ExternalStrategy.java | 3 +-
.../groovy/transform/tailrec/AstHelper.groovy | 3 +-
.../codehaus/groovy/ast/tools/GeneralUtils.java | 6 +
.../org/codehaus/groovy/classgen/EnumVisitor.java | 890 +++++++++++----------
.../EqualsAndHashCodeASTTransformation.java | 688 ++++++++--------
.../transform/ImmutableASTTransformation.java | 9 +-
.../groovy/transform/LazyASTTransformation.java | 493 ++++++------
.../transform/SortableASTTransformation.java | 7 +-
.../transform/ToStringASTTransformation.java | 2 +-
13 files changed, 1070 insertions(+), 1052 deletions(-)
diff --git a/src/main/groovy/groovy/beans/BindableASTTransformation.java b/src/main/groovy/groovy/beans/BindableASTTransformation.java
index c17be72..19d66ba 100644
--- a/src/main/groovy/groovy/beans/BindableASTTransformation.java
+++ b/src/main/groovy/groovy/beans/BindableASTTransformation.java
@@ -52,6 +52,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
@@ -188,8 +189,8 @@ public class BindableASTTransformation implements ASTTransformation, Opcodes {
// Get the existing code block
Statement code = setter.getCode();
- Expression oldValue = varX("$oldValue");
- Expression newValue = varX("$newValue");
+ Expression oldValue = localVarX("$oldValue");
+ Expression newValue = localVarX("$newValue");
BlockStatement block = new BlockStatement();
// create a local variable to hold the old value from the getter
diff --git a/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy b/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy
index ef61a78..3dc0b57 100644
--- a/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy
+++ b/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy
@@ -54,6 +54,8 @@ import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.objectweb.asm.Opcodes
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX
+
/**
* Handles generation of code for the {@code @ListenerList} annotation.
* <p>
@@ -68,7 +70,7 @@ class ListenerListASTTransformation implements ASTTransformation, Opcodes {
private static final Class MY_CLASS = groovy.beans.ListenerList.class
private static final ClassNode COLLECTION_TYPE = ClassHelper.make(Collection)
- public void visit(ASTNode[] nodes, SourceUnit source) {
+ void visit(ASTNode[] nodes, SourceUnit source) {
if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
throw new RuntimeException("Internal error: wrong types: ${node.class} / ${parent.class}")
}
@@ -277,7 +279,7 @@ class ListenerListASTTransformation implements ASTTransformation, Opcodes {
block.addStatements([
new ExpressionStatement(
new DeclarationExpression(
- new VariableExpression("__result", ClassHelper.DYNAMIC_TYPE),
+ localVarX("__result", ClassHelper.DYNAMIC_TYPE),
Token.newSymbol(Types.EQUALS, 0, 0),
new ListExpression()
)),
@@ -345,7 +347,7 @@ class ListenerListASTTransformation implements ASTTransformation, Opcodes {
new BlockStatement([
new ExpressionStatement(
new DeclarationExpression(
- new VariableExpression('__list', listenerListType),
+ localVarX('__list', listenerListType),
Token.newSymbol(Types.EQUALS, 0, 0),
new ConstructorCallExpression(listenerListType, new ArgumentListExpression(
new VariableExpression(field.name)
diff --git a/src/main/groovy/groovy/beans/VetoableASTTransformation.java b/src/main/groovy/groovy/beans/VetoableASTTransformation.java
index aa2f0b3..44e1da7 100644
--- a/src/main/groovy/groovy/beans/VetoableASTTransformation.java
+++ b/src/main/groovy/groovy/beans/VetoableASTTransformation.java
@@ -52,6 +52,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
@@ -172,8 +173,8 @@ public class VetoableASTTransformation extends BindableASTTransformation {
// Get the existing code block
Statement code = setter.getCode();
- Expression oldValue = varX("$oldValue");
- Expression newValue = varX("$newValue");
+ Expression oldValue = localVarX("$oldValue");
+ Expression newValue = localVarX("$newValue");
Expression proposedValue = varX(setter.getParameters()[0].getName());
BlockStatement block = new BlockStatement();
diff --git a/src/main/groovy/groovy/transform/builder/DefaultStrategy.java b/src/main/groovy/groovy/transform/builder/DefaultStrategy.java
index b0dfae4..e3cd82a 100644
--- a/src/main/groovy/groovy/transform/builder/DefaultStrategy.java
+++ b/src/main/groovy/groovy/transform/builder/DefaultStrategy.java
@@ -43,6 +43,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
@@ -284,7 +285,7 @@ public class DefaultStrategy extends BuilderASTTransformation.AbstractBuilderStr
}
private static Expression initializeInstance(ClassNode buildee, List<PropertyInfo> props, BlockStatement body) {
- Expression instance = varX("_the" + buildee.getNameWithoutPackage(), buildee);
+ Expression instance = localVarX("_the" + buildee.getNameWithoutPackage(), buildee);
body.addStatement(declS(instance, ctorX(buildee)));
for (PropertyInfo pi : props) {
body.addStatement(stmt(assignX(propX(instance, pi.getName()), varX(pi.getName(), pi.getType()))));
diff --git a/src/main/groovy/groovy/transform/builder/ExternalStrategy.java b/src/main/groovy/groovy/transform/builder/ExternalStrategy.java
index cba3b8b..fafc4fe 100644
--- a/src/main/groovy/groovy/transform/builder/ExternalStrategy.java
+++ b/src/main/groovy/groovy/transform/builder/ExternalStrategy.java
@@ -36,6 +36,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
@@ -148,7 +149,7 @@ public class ExternalStrategy extends BuilderASTTransformation.AbstractBuilderSt
}
private static Expression initializeInstance(ClassNode sourceClass, List<PropertyInfo> props, BlockStatement body) {
- Expression instance = varX("_the" + sourceClass.getNameWithoutPackage(), sourceClass);
+ Expression instance = localVarX("_the" + sourceClass.getNameWithoutPackage(), sourceClass);
body.addStatement(declS(instance, ctorX(sourceClass)));
for (PropertyInfo prop : props) {
body.addStatement(stmt(assignX(propX(instance, prop.getName()), varX(prop.getName().equals("class") ? "clazz" : prop.getName(), newClass(prop.getType())))));
diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy
index 3f7740d..fbfad98 100644
--- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy
+++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy
@@ -31,6 +31,7 @@ import java.lang.reflect.Modifier
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX
@@ -40,7 +41,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX
@CompileStatic
class AstHelper {
static ExpressionStatement createVariableDefinition(String variableName, ClassNode variableType, Expression value, boolean variableShouldBeFinal = false ) {
- def newVariable = varX(variableName, variableType)
+ def newVariable = localVarX(variableName, variableType)
if (variableShouldBeFinal)
newVariable.setModifiers(Modifier.FINAL)
(ExpressionStatement) declS(newVariable, value)
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
index d9abc76..2acdef6 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -647,6 +647,12 @@ public class GeneralUtils {
return result;
}
+ public static VariableExpression localVarX(String name, ClassNode type) {
+ VariableExpression result = new VariableExpression(name, type);
+ result.setAccessedVariable(result);
+ return result;
+ }
+
public static BinaryExpression ltX(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, LT, rhv);
}
diff --git a/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java b/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java
index 743801f..208a34e 100644
--- a/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java
+++ b/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java
@@ -1,444 +1,446 @@
-/*
- * 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.AnnotatedNode;
-import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
-import org.codehaus.groovy.ast.ClassHelper;
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.EnumConstantClassNode;
-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.ArgumentListExpression;
-import org.codehaus.groovy.ast.expr.ArrayExpression;
-import org.codehaus.groovy.ast.expr.BinaryExpression;
-import org.codehaus.groovy.ast.expr.BooleanExpression;
-import org.codehaus.groovy.ast.expr.ClassExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
-import org.codehaus.groovy.ast.expr.DeclarationExpression;
-import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.FieldExpression;
-import org.codehaus.groovy.ast.expr.ListExpression;
-import org.codehaus.groovy.ast.expr.MapEntryExpression;
-import org.codehaus.groovy.ast.expr.MapExpression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.ast.expr.SpreadExpression;
-import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
-import org.codehaus.groovy.ast.expr.VariableExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
-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.control.CompilationUnit;
-import org.codehaus.groovy.control.SourceUnit;
-import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
-import org.codehaus.groovy.syntax.SyntaxException;
-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 class EnumVisitor extends ClassCodeVisitorSupport {
- // some constants for modifiers
- private static final int FS = Opcodes.ACC_FINAL | Opcodes.ACC_STATIC;
- private static final int PS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
- private static final int PUBLIC_FS = Opcodes.ACC_PUBLIC | FS;
- private static final int PRIVATE_FS = Opcodes.ACC_PRIVATE | FS;
-
- private final SourceUnit sourceUnit;
-
-
- public EnumVisitor(CompilationUnit cu, SourceUnit su) {
- sourceUnit = su;
- }
-
- public void visitClass(ClassNode node) {
- if (!node.isEnum()) return;
- completeEnum(node);
- }
-
- protected SourceUnit getSourceUnit() {
- return sourceUnit;
- }
-
- private void completeEnum(ClassNode enumClass) {
- boolean isAic = isAnonymousInnerClass(enumClass);
- // create MIN_VALUE and MAX_VALUE fields
- FieldNode minValue = null, maxValue = null, values = null;
-
- if (!isAic) {
- ClassNode enumRef = enumClass.getPlainNodeReference();
-
- // create values field
- values = new FieldNode("$VALUES", PRIVATE_FS | Opcodes.ACC_SYNTHETIC, enumRef.makeArray(), enumClass, null);
- values.setSynthetic(true);
-
- addMethods(enumClass, values);
- checkForAbstractMethods(enumClass);
-
- // create MIN_VALUE and MAX_VALUE fields
- minValue = new FieldNode("MIN_VALUE", PUBLIC_FS, enumRef, enumClass, null);
- maxValue = new FieldNode("MAX_VALUE", PUBLIC_FS, enumRef, enumClass, null);
- }
- addInit(enumClass, minValue, maxValue, values, isAic);
- }
-
- private static void checkForAbstractMethods(ClassNode enumClass) {
- List<MethodNode> methods = enumClass.getMethods();
- for (MethodNode m : methods) {
- if (m.isAbstract()) {
- // make the class abstract also see Effective Java p.152
- enumClass.setModifiers(enumClass.getModifiers() | Opcodes.ACC_ABSTRACT);
- break;
- }
- }
- }
-
- private static void addMethods(ClassNode enumClass, FieldNode values) {
- List<MethodNode> methods = enumClass.getMethods();
-
- boolean hasNext = false;
- boolean hasPrevious = false;
- for (MethodNode m : methods) {
- if (m.getName().equals("next") && m.getParameters().length == 0) hasNext = true;
- if (m.getName().equals("previous") && m.getParameters().length == 0) hasPrevious = true;
- if (hasNext && hasPrevious) break;
- }
-
- ClassNode enumRef = enumClass.getPlainNodeReference();
-
- {
- // create values() method
- MethodNode valuesMethod = new MethodNode("values", PUBLIC_FS, enumRef.makeArray(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
- valuesMethod.setSynthetic(true);
- BlockStatement code = new BlockStatement();
- MethodCallExpression cloneCall = new MethodCallExpression(new FieldExpression(values), "clone", MethodCallExpression.NO_ARGUMENTS);
- cloneCall.setMethodTarget(values.getType().getMethod("clone", Parameter.EMPTY_ARRAY));
- code.addStatement(new ReturnStatement(cloneCall));
- valuesMethod.setCode(code);
- enumClass.addMethod(valuesMethod);
- }
-
- if (!hasNext) {
- // create next() method, code:
- // Day next() {
- // int ordinal = ordinal().next()
- // if (ordinal >= values().size()) ordinal = 0
- // return values()[ordinal]
- // }
- Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
- Token ge = Token.newSymbol(Types.COMPARE_GREATER_THAN_EQUAL, -1, -1);
- MethodNode nextMethod = new MethodNode("next", Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
- nextMethod.setSynthetic(true);
- BlockStatement code = new BlockStatement();
- BlockStatement ifStatement = new BlockStatement();
- ifStatement.addStatement(
- new ExpressionStatement(
- new BinaryExpression(new VariableExpression("ordinal"), assign, new ConstantExpression(0))
- )
- );
-
- code.addStatement(
- new ExpressionStatement(
- new DeclarationExpression(
- new VariableExpression("ordinal"),
- assign,
- new MethodCallExpression(
- new MethodCallExpression(
- VariableExpression.THIS_EXPRESSION,
- "ordinal",
- MethodCallExpression.NO_ARGUMENTS),
- "next",
- MethodCallExpression.NO_ARGUMENTS
- )
- )
- )
- );
- code.addStatement(
- new IfStatement(
- new BooleanExpression(new BinaryExpression(
- new VariableExpression("ordinal"),
- ge,
- new MethodCallExpression(
- new FieldExpression(values),
- "size",
- MethodCallExpression.NO_ARGUMENTS
- )
- )),
- ifStatement,
- EmptyStatement.INSTANCE
- )
- );
- code.addStatement(
- new ReturnStatement(
- new MethodCallExpression(new FieldExpression(values), "getAt", new VariableExpression("ordinal"))
- )
- );
- nextMethod.setCode(code);
- enumClass.addMethod(nextMethod);
- }
-
- if (!hasPrevious) {
- // create previous() method, code:
- // Day previous() {
- // int ordinal = ordinal().previous()
- // if (ordinal < 0) ordinal = values().size() - 1
- // return values()[ordinal]
- // }
- Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
- Token lt = Token.newSymbol(Types.COMPARE_LESS_THAN, -1, -1);
- MethodNode nextMethod = new MethodNode("previous", Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
- nextMethod.setSynthetic(true);
- BlockStatement code = new BlockStatement();
- BlockStatement ifStatement = new BlockStatement();
- ifStatement.addStatement(
- new ExpressionStatement(
- new BinaryExpression(new VariableExpression("ordinal"), assign,
- new MethodCallExpression(
- new MethodCallExpression(
- new FieldExpression(values),
- "size",
- MethodCallExpression.NO_ARGUMENTS
- ),
- "minus",
- new ConstantExpression(1)
- )
- )
- )
- );
-
- code.addStatement(
- new ExpressionStatement(
- new DeclarationExpression(
- new VariableExpression("ordinal"),
- assign,
- new MethodCallExpression(
- new MethodCallExpression(
- VariableExpression.THIS_EXPRESSION,
- "ordinal",
- MethodCallExpression.NO_ARGUMENTS),
- "previous",
- MethodCallExpression.NO_ARGUMENTS
- )
- )
- )
- );
- code.addStatement(
- new IfStatement(
- new BooleanExpression(new BinaryExpression(
- new VariableExpression("ordinal"),
- lt,
- new ConstantExpression(0)
- )),
- ifStatement,
- EmptyStatement.INSTANCE
- )
- );
- code.addStatement(
- new ReturnStatement(
- new MethodCallExpression(new FieldExpression(values), "getAt", new VariableExpression("ordinal"))
- )
- );
- nextMethod.setCode(code);
- enumClass.addMethod(nextMethod);
- }
-
- {
- // create valueOf
- Parameter stringParameter = new Parameter(ClassHelper.STRING_TYPE, "name");
- MethodNode valueOfMethod = new MethodNode("valueOf", PS, enumRef, new Parameter[]{stringParameter}, ClassNode.EMPTY_ARRAY, null);
- ArgumentListExpression callArguments = new ArgumentListExpression();
- callArguments.addExpression(new ClassExpression(enumClass));
- callArguments.addExpression(new VariableExpression("name"));
-
- BlockStatement code = new BlockStatement();
- code.addStatement(
- new ReturnStatement(
- new MethodCallExpression(new ClassExpression(ClassHelper.Enum_Type), "valueOf", callArguments)
- )
- );
- valueOfMethod.setCode(code);
- valueOfMethod.setSynthetic(true);
- enumClass.addMethod(valueOfMethod);
- }
- }
-
- private void addInit(ClassNode enumClass, FieldNode minValue,
- FieldNode maxValue, FieldNode values,
- boolean isAic) {
- // constructor helper
- // This method is used instead of calling the constructor as
- // calling the constructor may require a table with MetaClass
- // selecting the constructor for each enum value. So instead we
- // use this method to have a central point for constructor selection
- // and only one table. The whole construction is needed because
- // Reflection forbids access to the enum constructor.
- // code:
- // def $INIT(Object[] para) {
- // return this(*para)
- // }
- ClassNode enumRef = enumClass.getPlainNodeReference();
- Parameter[] parameter = new Parameter[]{new Parameter(ClassHelper.OBJECT_TYPE.makeArray(), "para")};
- MethodNode initMethod = new MethodNode("$INIT", PUBLIC_FS | Opcodes.ACC_SYNTHETIC, enumRef, parameter, ClassNode.EMPTY_ARRAY, null);
- initMethod.setSynthetic(true);
- ConstructorCallExpression cce = new ConstructorCallExpression(
- ClassNode.THIS,
- new ArgumentListExpression(
- new SpreadExpression(new VariableExpression("para"))
- )
- );
- BlockStatement code = new BlockStatement();
- code.addStatement(new ReturnStatement(cce));
- initMethod.setCode(code);
- enumClass.addMethod(initMethod);
-
- // static init
- List<FieldNode> fields = enumClass.getFields();
- List<Expression> arrayInit = new ArrayList<Expression>();
- int value = -1;
- Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
- List<Statement> block = new ArrayList<Statement>();
- FieldNode tempMin = null;
- FieldNode tempMax = null;
- for (FieldNode field : fields) {
- if ((field.getModifiers() & Opcodes.ACC_ENUM) == 0) continue;
- value++;
- if (tempMin == null) tempMin = field;
- tempMax = field;
-
- ClassNode enumBase = enumClass;
- ArgumentListExpression args = new ArgumentListExpression();
- args.addExpression(new ConstantExpression(field.getName()));
- args.addExpression(new ConstantExpression(value));
- if (field.getInitialExpression() == null) {
- if ((enumClass.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
- addError(field, "The enum constant " + field.getName() + " must override abstract methods from " + enumBase.getName() + ".");
- continue;
- }
- } else {
- ListExpression oldArgs = (ListExpression) field.getInitialExpression();
- List<MapEntryExpression> savedMapEntries = new ArrayList<MapEntryExpression>();
- for (Expression exp : oldArgs.getExpressions()) {
- if (exp instanceof MapEntryExpression) {
- savedMapEntries.add((MapEntryExpression) exp);
- continue;
- }
-
- InnerClassNode inner = null;
- if (exp instanceof ClassExpression) {
- ClassExpression clazzExp = (ClassExpression) exp;
- ClassNode ref = clazzExp.getType();
- if (ref instanceof EnumConstantClassNode) {
- inner = (InnerClassNode) ref;
- }
- }
- if (inner != null) {
- List<MethodNode> baseMethods = enumBase.getMethods();
- for (MethodNode methodNode : baseMethods) {
- if (!methodNode.isAbstract()) continue;
- MethodNode enumConstMethod = inner.getMethod(methodNode.getName(), methodNode.getParameters());
- if (enumConstMethod == null || (enumConstMethod.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
- addError(field, "Can't have an abstract method in enum constant " + field.getName() + ". Implement method '" + methodNode.getTypeDescriptor() + "'.");
- }
- }
- if (inner.getVariableScope() == null) {
- enumBase = inner;
- /*
- * GROOVY-3985: Remove the final modifier from $INIT method in this case
- * so that subclasses of enum generated for enum constants (aic) can provide
- * their own $INIT method
- */
- initMethod.setModifiers(initMethod.getModifiers() & ~Opcodes.ACC_FINAL);
- continue;
- }
- }
- args.addExpression(exp);
- }
- if (!savedMapEntries.isEmpty()) {
- args.getExpressions().add(2, new MapExpression(savedMapEntries));
- }
- }
- field.setInitialValueExpression(null);
- block.add(
- new ExpressionStatement(
- new BinaryExpression(
- new FieldExpression(field),
- assign,
- new StaticMethodCallExpression(enumBase, "$INIT", args)
- )
- )
- );
- arrayInit.add(new FieldExpression(field));
- }
-
- if (!isAic) {
- if (tempMin != null) {
- block.add(
- new ExpressionStatement(
- new BinaryExpression(
- new FieldExpression(minValue),
- assign,
- new FieldExpression(tempMin)
- )
- )
- );
- block.add(
- new ExpressionStatement(
- new BinaryExpression(
- new FieldExpression(maxValue),
- assign,
- new FieldExpression(tempMax)
- )
- )
- );
- enumClass.addField(minValue);
- enumClass.addField(maxValue);
- }
-
- block.add(
- new ExpressionStatement(
- new BinaryExpression(new FieldExpression(values), assign, new ArrayExpression(enumClass, arrayInit))
- )
- );
- enumClass.addField(values);
- }
- enumClass.addStaticInitializerStatements(block, true);
- }
-
- private void addError(AnnotatedNode exp, String msg) {
- sourceUnit.getErrorCollector().addErrorAndContinue(
- new SyntaxErrorMessage(
- new SyntaxException(msg + '\n', exp.getLineNumber(), exp.getColumnNumber(), exp.getLastLineNumber(), exp.getLastColumnNumber()), sourceUnit)
- );
- }
-
- private static boolean isAnonymousInnerClass(ClassNode enumClass) {
- if (!(enumClass instanceof EnumConstantClassNode)) return false;
- InnerClassNode ic = (InnerClassNode) enumClass;
- return ic.getVariableScope() == null;
- }
-
-}
+/*
+ * 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.AnnotatedNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.EnumConstantClassNode;
+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.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ArrayExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.FieldExpression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.SpreadExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+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.control.CompilationUnit;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+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;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+
+public class EnumVisitor extends ClassCodeVisitorSupport {
+ // some constants for modifiers
+ private static final int FS = Opcodes.ACC_FINAL | Opcodes.ACC_STATIC;
+ private static final int PS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
+ private static final int PUBLIC_FS = Opcodes.ACC_PUBLIC | FS;
+ private static final int PRIVATE_FS = Opcodes.ACC_PRIVATE | FS;
+
+ private final SourceUnit sourceUnit;
+
+
+ public EnumVisitor(CompilationUnit cu, SourceUnit su) {
+ sourceUnit = su;
+ }
+
+ public void visitClass(ClassNode node) {
+ if (!node.isEnum()) return;
+ completeEnum(node);
+ }
+
+ protected SourceUnit getSourceUnit() {
+ return sourceUnit;
+ }
+
+ private void completeEnum(ClassNode enumClass) {
+ boolean isAic = isAnonymousInnerClass(enumClass);
+ // create MIN_VALUE and MAX_VALUE fields
+ FieldNode minValue = null, maxValue = null, values = null;
+
+ if (!isAic) {
+ ClassNode enumRef = enumClass.getPlainNodeReference();
+
+ // create values field
+ values = new FieldNode("$VALUES", PRIVATE_FS | Opcodes.ACC_SYNTHETIC, enumRef.makeArray(), enumClass, null);
+ values.setSynthetic(true);
+
+ addMethods(enumClass, values);
+ checkForAbstractMethods(enumClass);
+
+ // create MIN_VALUE and MAX_VALUE fields
+ minValue = new FieldNode("MIN_VALUE", PUBLIC_FS, enumRef, enumClass, null);
+ maxValue = new FieldNode("MAX_VALUE", PUBLIC_FS, enumRef, enumClass, null);
+ }
+ addInit(enumClass, minValue, maxValue, values, isAic);
+ }
+
+ private static void checkForAbstractMethods(ClassNode enumClass) {
+ List<MethodNode> methods = enumClass.getMethods();
+ for (MethodNode m : methods) {
+ if (m.isAbstract()) {
+ // make the class abstract also see Effective Java p.152
+ enumClass.setModifiers(enumClass.getModifiers() | Opcodes.ACC_ABSTRACT);
+ break;
+ }
+ }
+ }
+
+ private static void addMethods(ClassNode enumClass, FieldNode values) {
+ List<MethodNode> methods = enumClass.getMethods();
+
+ boolean hasNext = false;
+ boolean hasPrevious = false;
+ for (MethodNode m : methods) {
+ if (m.getName().equals("next") && m.getParameters().length == 0) hasNext = true;
+ if (m.getName().equals("previous") && m.getParameters().length == 0) hasPrevious = true;
+ if (hasNext && hasPrevious) break;
+ }
+
+ ClassNode enumRef = enumClass.getPlainNodeReference();
+
+ {
+ // create values() method
+ MethodNode valuesMethod = new MethodNode("values", PUBLIC_FS, enumRef.makeArray(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
+ valuesMethod.setSynthetic(true);
+ BlockStatement code = new BlockStatement();
+ MethodCallExpression cloneCall = new MethodCallExpression(new FieldExpression(values), "clone", MethodCallExpression.NO_ARGUMENTS);
+ cloneCall.setMethodTarget(values.getType().getMethod("clone", Parameter.EMPTY_ARRAY));
+ code.addStatement(new ReturnStatement(cloneCall));
+ valuesMethod.setCode(code);
+ enumClass.addMethod(valuesMethod);
+ }
+
+ if (!hasNext) {
+ // create next() method, code:
+ // Day next() {
+ // int ordinal = ordinal().next()
+ // if (ordinal >= values().size()) ordinal = 0
+ // return values()[ordinal]
+ // }
+ Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
+ Token ge = Token.newSymbol(Types.COMPARE_GREATER_THAN_EQUAL, -1, -1);
+ MethodNode nextMethod = new MethodNode("next", Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
+ nextMethod.setSynthetic(true);
+ BlockStatement code = new BlockStatement();
+ BlockStatement ifStatement = new BlockStatement();
+ ifStatement.addStatement(
+ new ExpressionStatement(
+ new BinaryExpression(new VariableExpression("ordinal"), assign, new ConstantExpression(0))
+ )
+ );
+
+ code.addStatement(
+ new ExpressionStatement(
+ new DeclarationExpression(
+ localVarX("ordinal"),
+ assign,
+ new MethodCallExpression(
+ new MethodCallExpression(
+ VariableExpression.THIS_EXPRESSION,
+ "ordinal",
+ MethodCallExpression.NO_ARGUMENTS),
+ "next",
+ MethodCallExpression.NO_ARGUMENTS
+ )
+ )
+ )
+ );
+ code.addStatement(
+ new IfStatement(
+ new BooleanExpression(new BinaryExpression(
+ new VariableExpression("ordinal"),
+ ge,
+ new MethodCallExpression(
+ new FieldExpression(values),
+ "size",
+ MethodCallExpression.NO_ARGUMENTS
+ )
+ )),
+ ifStatement,
+ EmptyStatement.INSTANCE
+ )
+ );
+ code.addStatement(
+ new ReturnStatement(
+ new MethodCallExpression(new FieldExpression(values), "getAt", new VariableExpression("ordinal"))
+ )
+ );
+ nextMethod.setCode(code);
+ enumClass.addMethod(nextMethod);
+ }
+
+ if (!hasPrevious) {
+ // create previous() method, code:
+ // Day previous() {
+ // int ordinal = ordinal().previous()
+ // if (ordinal < 0) ordinal = values().size() - 1
+ // return values()[ordinal]
+ // }
+ Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
+ Token lt = Token.newSymbol(Types.COMPARE_LESS_THAN, -1, -1);
+ MethodNode nextMethod = new MethodNode("previous", Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
+ nextMethod.setSynthetic(true);
+ BlockStatement code = new BlockStatement();
+ BlockStatement ifStatement = new BlockStatement();
+ ifStatement.addStatement(
+ new ExpressionStatement(
+ new BinaryExpression(new VariableExpression("ordinal"), assign,
+ new MethodCallExpression(
+ new MethodCallExpression(
+ new FieldExpression(values),
+ "size",
+ MethodCallExpression.NO_ARGUMENTS
+ ),
+ "minus",
+ new ConstantExpression(1)
+ )
+ )
+ )
+ );
+
+ code.addStatement(
+ new ExpressionStatement(
+ new DeclarationExpression(
+ localVarX("ordinal"),
+ assign,
+ new MethodCallExpression(
+ new MethodCallExpression(
+ VariableExpression.THIS_EXPRESSION,
+ "ordinal",
+ MethodCallExpression.NO_ARGUMENTS),
+ "previous",
+ MethodCallExpression.NO_ARGUMENTS
+ )
+ )
+ )
+ );
+ code.addStatement(
+ new IfStatement(
+ new BooleanExpression(new BinaryExpression(
+ new VariableExpression("ordinal"),
+ lt,
+ new ConstantExpression(0)
+ )),
+ ifStatement,
+ EmptyStatement.INSTANCE
+ )
+ );
+ code.addStatement(
+ new ReturnStatement(
+ new MethodCallExpression(new FieldExpression(values), "getAt", new VariableExpression("ordinal"))
+ )
+ );
+ nextMethod.setCode(code);
+ enumClass.addMethod(nextMethod);
+ }
+
+ {
+ // create valueOf
+ Parameter stringParameter = new Parameter(ClassHelper.STRING_TYPE, "name");
+ MethodNode valueOfMethod = new MethodNode("valueOf", PS, enumRef, new Parameter[]{stringParameter}, ClassNode.EMPTY_ARRAY, null);
+ ArgumentListExpression callArguments = new ArgumentListExpression();
+ callArguments.addExpression(new ClassExpression(enumClass));
+ callArguments.addExpression(new VariableExpression("name"));
+
+ BlockStatement code = new BlockStatement();
+ code.addStatement(
+ new ReturnStatement(
+ new MethodCallExpression(new ClassExpression(ClassHelper.Enum_Type), "valueOf", callArguments)
+ )
+ );
+ valueOfMethod.setCode(code);
+ valueOfMethod.setSynthetic(true);
+ enumClass.addMethod(valueOfMethod);
+ }
+ }
+
+ private void addInit(ClassNode enumClass, FieldNode minValue,
+ FieldNode maxValue, FieldNode values,
+ boolean isAic) {
+ // constructor helper
+ // This method is used instead of calling the constructor as
+ // calling the constructor may require a table with MetaClass
+ // selecting the constructor for each enum value. So instead we
+ // use this method to have a central point for constructor selection
+ // and only one table. The whole construction is needed because
+ // Reflection forbids access to the enum constructor.
+ // code:
+ // def $INIT(Object[] para) {
+ // return this(*para)
+ // }
+ ClassNode enumRef = enumClass.getPlainNodeReference();
+ Parameter[] parameter = new Parameter[]{new Parameter(ClassHelper.OBJECT_TYPE.makeArray(), "para")};
+ MethodNode initMethod = new MethodNode("$INIT", PUBLIC_FS | Opcodes.ACC_SYNTHETIC, enumRef, parameter, ClassNode.EMPTY_ARRAY, null);
+ initMethod.setSynthetic(true);
+ ConstructorCallExpression cce = new ConstructorCallExpression(
+ ClassNode.THIS,
+ new ArgumentListExpression(
+ new SpreadExpression(new VariableExpression("para"))
+ )
+ );
+ BlockStatement code = new BlockStatement();
+ code.addStatement(new ReturnStatement(cce));
+ initMethod.setCode(code);
+ enumClass.addMethod(initMethod);
+
+ // static init
+ List<FieldNode> fields = enumClass.getFields();
+ List<Expression> arrayInit = new ArrayList<Expression>();
+ int value = -1;
+ Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
+ List<Statement> block = new ArrayList<Statement>();
+ FieldNode tempMin = null;
+ FieldNode tempMax = null;
+ for (FieldNode field : fields) {
+ if ((field.getModifiers() & Opcodes.ACC_ENUM) == 0) continue;
+ value++;
+ if (tempMin == null) tempMin = field;
+ tempMax = field;
+
+ ClassNode enumBase = enumClass;
+ ArgumentListExpression args = new ArgumentListExpression();
+ args.addExpression(new ConstantExpression(field.getName()));
+ args.addExpression(new ConstantExpression(value));
+ if (field.getInitialExpression() == null) {
+ if ((enumClass.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
+ addError(field, "The enum constant " + field.getName() + " must override abstract methods from " + enumBase.getName() + ".");
+ continue;
+ }
+ } else {
+ ListExpression oldArgs = (ListExpression) field.getInitialExpression();
+ List<MapEntryExpression> savedMapEntries = new ArrayList<MapEntryExpression>();
+ for (Expression exp : oldArgs.getExpressions()) {
+ if (exp instanceof MapEntryExpression) {
+ savedMapEntries.add((MapEntryExpression) exp);
+ continue;
+ }
+
+ InnerClassNode inner = null;
+ if (exp instanceof ClassExpression) {
+ ClassExpression clazzExp = (ClassExpression) exp;
+ ClassNode ref = clazzExp.getType();
+ if (ref instanceof EnumConstantClassNode) {
+ inner = (InnerClassNode) ref;
+ }
+ }
+ if (inner != null) {
+ List<MethodNode> baseMethods = enumBase.getMethods();
+ for (MethodNode methodNode : baseMethods) {
+ if (!methodNode.isAbstract()) continue;
+ MethodNode enumConstMethod = inner.getMethod(methodNode.getName(), methodNode.getParameters());
+ if (enumConstMethod == null || (enumConstMethod.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
+ addError(field, "Can't have an abstract method in enum constant " + field.getName() + ". Implement method '" + methodNode.getTypeDescriptor() + "'.");
+ }
+ }
+ if (inner.getVariableScope() == null) {
+ enumBase = inner;
+ /*
+ * GROOVY-3985: Remove the final modifier from $INIT method in this case
+ * so that subclasses of enum generated for enum constants (aic) can provide
+ * their own $INIT method
+ */
+ initMethod.setModifiers(initMethod.getModifiers() & ~Opcodes.ACC_FINAL);
+ continue;
+ }
+ }
+ args.addExpression(exp);
+ }
+ if (!savedMapEntries.isEmpty()) {
+ args.getExpressions().add(2, new MapExpression(savedMapEntries));
+ }
+ }
+ field.setInitialValueExpression(null);
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(
+ new FieldExpression(field),
+ assign,
+ new StaticMethodCallExpression(enumBase, "$INIT", args)
+ )
+ )
+ );
+ arrayInit.add(new FieldExpression(field));
+ }
+
+ if (!isAic) {
+ if (tempMin != null) {
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(
+ new FieldExpression(minValue),
+ assign,
+ new FieldExpression(tempMin)
+ )
+ )
+ );
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(
+ new FieldExpression(maxValue),
+ assign,
+ new FieldExpression(tempMax)
+ )
+ )
+ );
+ enumClass.addField(minValue);
+ enumClass.addField(maxValue);
+ }
+
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(new FieldExpression(values), assign, new ArrayExpression(enumClass, arrayInit))
+ )
+ );
+ enumClass.addField(values);
+ }
+ enumClass.addStaticInitializerStatements(block, true);
+ }
+
+ private void addError(AnnotatedNode exp, String msg) {
+ sourceUnit.getErrorCollector().addErrorAndContinue(
+ new SyntaxErrorMessage(
+ new SyntaxException(msg + '\n', exp.getLineNumber(), exp.getColumnNumber(), exp.getLastLineNumber(), exp.getLastColumnNumber()), sourceUnit)
+ );
+ }
+
+ private static boolean isAnonymousInnerClass(ClassNode enumClass) {
+ if (!(enumClass instanceof EnumConstantClassNode)) return false;
+ InnerClassNode ic = (InnerClassNode) enumClass;
+ return ic.getVariableScope() == null;
+ }
+
+}
diff --git a/src/main/java/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java
index ca4b660..7e7d0c7 100644
--- a/src/main/java/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java
@@ -1,344 +1,344 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.codehaus.groovy.transform;
-
-import groovy.transform.EqualsAndHashCode;
-import org.codehaus.groovy.ast.ASTNode;
-import org.codehaus.groovy.ast.AnnotatedNode;
-import org.codehaus.groovy.ast.AnnotationNode;
-import org.codehaus.groovy.ast.ClassHelper;
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.FieldNode;
-import org.codehaus.groovy.ast.Parameter;
-import org.codehaus.groovy.ast.PropertyNode;
-import org.codehaus.groovy.ast.expr.BinaryExpression;
-import org.codehaus.groovy.ast.expr.CastExpression;
-import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.VariableExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.ast.tools.GenericsUtils;
-import org.codehaus.groovy.control.CompilePhase;
-import org.codehaus.groovy.control.SourceUnit;
-import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
-import org.codehaus.groovy.util.HashCodeHelper;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
-import static org.codehaus.groovy.ast.ClassHelper.make;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.callSuperX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.getAllProperties;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceNonPropertyFields;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.getterThisX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.hasClassX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.hasDeclaredMethod;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.hasEqualFieldX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.hasEqualPropertyX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.hasSameFieldX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.hasSamePropertyX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.isInstanceOfX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.isZeroX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.orX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.sameX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
-import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe;
-
-@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
-public class EqualsAndHashCodeASTTransformation extends AbstractASTTransformation {
- static final Class MY_CLASS = EqualsAndHashCode.class;
- static final ClassNode MY_TYPE = make(MY_CLASS);
- static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
- private static final ClassNode HASHUTIL_TYPE = make(HashCodeHelper.class);
- private static final ClassNode OBJECT_TYPE = makeClassSafe(Object.class);
-
- public void visit(ASTNode[] nodes, SourceUnit source) {
- init(nodes, source);
- AnnotatedNode parent = (AnnotatedNode) nodes[1];
- AnnotationNode anno = (AnnotationNode) nodes[0];
- if (!MY_TYPE.equals(anno.getClassNode())) return;
-
- if (parent instanceof ClassNode) {
- ClassNode cNode = (ClassNode) parent;
- if (!checkNotInterface(cNode, MY_TYPE_NAME)) return;
- boolean callSuper = memberHasValue(anno, "callSuper", true);
- boolean cacheHashCode = memberHasValue(anno, "cache", true);
- boolean useCanEqual = !memberHasValue(anno, "useCanEqual", false);
- if (callSuper && cNode.getSuperClass().getName().equals("java.lang.Object")) {
- addError("Error during " + MY_TYPE_NAME + " processing: callSuper=true but '" + cNode.getName() + "' has no super class.", anno);
- }
- boolean includeFields = memberHasValue(anno, "includeFields", true);
- List<String> excludes = getMemberStringList(anno, "excludes");
- List<String> includes = getMemberStringList(anno, "includes");
- final boolean allNames = memberHasValue(anno, "allNames", true);
- final boolean allProperties = memberHasValue(anno, "allProperties", true);
- if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, MY_TYPE_NAME)) return;
- if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields)) return;
- if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields)) return;
- createHashCode(cNode, cacheHashCode, includeFields, callSuper, excludes, includes, allNames, allProperties);
- createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes, allNames, allProperties);
- }
- }
-
- public static void createHashCode(ClassNode cNode, boolean cacheResult, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes) {
- createHashCode(cNode, cacheResult, includeFields, callSuper, excludes, includes, false);
- }
-
- public static void createHashCode(ClassNode cNode, boolean cacheResult, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes, boolean allNames) {
- createHashCode(cNode, cacheResult, includeFields, callSuper, excludes, includes, allNames,false);
- }
-
- public static void createHashCode(ClassNode cNode, boolean cacheResult, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes, boolean allNames, boolean allProperties) {
- // make a public method if none exists otherwise try a private method with leading underscore
- boolean hasExistingHashCode = hasDeclaredMethod(cNode, "hashCode", 0);
- if (hasExistingHashCode && hasDeclaredMethod(cNode, "_hashCode", 0)) return;
-
- final BlockStatement body = new BlockStatement();
- // TODO use pList and fList
- if (cacheResult) {
- final FieldNode hashField = cNode.addField("$hash$code", ACC_PRIVATE | ACC_SYNTHETIC, ClassHelper.int_TYPE, null);
- final Expression hash = varX(hashField);
- body.addStatement(ifS(
- isZeroX(hash),
- calculateHashStatements(cNode, hash, includeFields, callSuper, excludes, includes, allNames, allProperties)
- ));
- body.addStatement(returnS(hash));
- } else {
- body.addStatement(calculateHashStatements(cNode, null, includeFields, callSuper, excludes, includes, allNames, allProperties));
- }
-
- addGeneratedMethod(cNode,
- hasExistingHashCode ? "_hashCode" : "hashCode",
- hasExistingHashCode ? ACC_PRIVATE : ACC_PUBLIC,
- ClassHelper.int_TYPE,
- Parameter.EMPTY_ARRAY,
- ClassNode.EMPTY_ARRAY,
- body);
- }
-
- private static Statement calculateHashStatements(ClassNode cNode, Expression hash, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes, boolean allNames, boolean allProperties) {
- final Set<String> names = new HashSet<String>();
- final List<PropertyNode> pList = getAllProperties(names, cNode, true, false, allProperties, false, false, false);
- final List<FieldNode> fList = new ArrayList<FieldNode>();
- if (includeFields) {
- fList.addAll(getInstanceNonPropertyFields(cNode));
- }
- final BlockStatement body = new BlockStatement();
- // def _result = HashCodeHelper.initHash()
- final Expression result = localVarX("_result");
- body.addStatement(declS(result, callX(HASHUTIL_TYPE, "initHash")));
-
- for (PropertyNode pNode : pList) {
- if (shouldSkipUndefinedAware(pNode.getName(), excludes, includes, allNames)) continue;
- // _result = HashCodeHelper.updateHash(_result, getProperty()) // plus self-reference checking
- Expression getter = getterThisX(cNode, pNode);
- final Expression current = callX(HASHUTIL_TYPE, "updateHash", args(result, getter));
- body.addStatement(ifS(
- notX(sameX(getter, varX("this"))),
- assignS(result, current)));
-
- }
- for (FieldNode fNode : fList) {
- if (shouldSkipUndefinedAware(fNode.getName(), excludes, includes, allNames)) continue;
- // _result = HashCodeHelper.updateHash(_result, field) // plus self-reference checking
- final Expression fieldExpr = varX(fNode);
- final Expression current = callX(HASHUTIL_TYPE, "updateHash", args(result, fieldExpr));
- body.addStatement(ifS(
- notX(sameX(fieldExpr, varX("this"))),
- assignS(result, current)));
- }
- if (callSuper) {
- // _result = HashCodeHelper.updateHash(_result, super.hashCode())
- final Expression current = callX(HASHUTIL_TYPE, "updateHash", args(result, callSuperX("hashCode")));
- body.addStatement(assignS(result, current));
- }
- // $hash$code = _result
- if (hash != null) {
- body.addStatement(assignS(hash, result));
- } else {
- body.addStatement(returnS(result));
- }
- return body;
- }
-
- private static void createCanEqual(ClassNode cNode) {
- boolean hasExistingCanEqual = hasDeclaredMethod(cNode, "canEqual", 1);
- if (hasExistingCanEqual && hasDeclaredMethod(cNode, "_canEqual", 1)) return;
-
- final BlockStatement body = new BlockStatement();
- VariableExpression other = varX("other");
- body.addStatement(returnS(isInstanceOfX(other, GenericsUtils.nonGeneric(cNode))));
- addGeneratedMethod(cNode,
- hasExistingCanEqual ? "_canEqual" : "canEqual",
- hasExistingCanEqual ? ACC_PRIVATE : ACC_PUBLIC,
- ClassHelper.boolean_TYPE,
- params(param(OBJECT_TYPE, other.getName())),
- ClassNode.EMPTY_ARRAY,
- body);
- }
-
- public static void createEquals(ClassNode cNode, boolean includeFields, boolean callSuper, boolean useCanEqual, List<String> excludes, List<String> includes) {
- createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes, false);
- }
-
- public static void createEquals(ClassNode cNode, boolean includeFields, boolean callSuper, boolean useCanEqual, List<String> excludes, List<String> includes, boolean allNames) {
- createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes, allNames,false);
- }
-
- public static void createEquals(ClassNode cNode, boolean includeFields, boolean callSuper, boolean useCanEqual, List<String> excludes, List<String> includes, boolean allNames, boolean allProperties) {
- if (useCanEqual) createCanEqual(cNode);
- // make a public method if none exists otherwise try a private method with leading underscore
- boolean hasExistingEquals = hasDeclaredMethod(cNode, "equals", 1);
- if (hasExistingEquals && hasDeclaredMethod(cNode, "_equals", 1)) return;
-
- final BlockStatement body = new BlockStatement();
- VariableExpression other = varX("other");
-
- // some short circuit cases for efficiency
- body.addStatement(ifS(equalsNullX(other), returnS(constX(Boolean.FALSE, true))));
- body.addStatement(ifS(sameX(varX("this"), other), returnS(constX(Boolean.TRUE, true))));
-
- if (useCanEqual) {
- body.addStatement(ifS(notX(isInstanceOfX(other, GenericsUtils.nonGeneric(cNode))), returnS(constX(Boolean.FALSE,true))));
- } else {
- body.addStatement(ifS(notX(hasClassX(other, GenericsUtils.nonGeneric(cNode))), returnS(constX(Boolean.FALSE,true))));
- }
-
- VariableExpression otherTyped = varX("otherTyped", GenericsUtils.nonGeneric(cNode));
- CastExpression castExpression = new CastExpression(GenericsUtils.nonGeneric(cNode), other);
- castExpression.setStrict(true);
- body.addStatement(declS(otherTyped, castExpression));
-
- if (useCanEqual) {
- body.addStatement(ifS(notX(callX(otherTyped, "canEqual", varX("this"))), returnS(constX(Boolean.FALSE,true))));
- }
-
- final Set<String> names = new HashSet<String>();
- final List<PropertyNode> pList = getAllProperties(names, cNode, true, includeFields, allProperties, false, false, false);
- for (PropertyNode pNode : pList) {
- if (shouldSkipUndefinedAware(pNode.getName(), excludes, includes, allNames)) continue;
- boolean canBeSelf = StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(
- pNode.getOriginType(), cNode
- );
- if (!canBeSelf) {
- body.addStatement(ifS(notX(hasEqualPropertyX(otherTyped.getOriginType(), pNode, otherTyped)), returnS(constX(Boolean.FALSE, true))));
- } else {
- body.addStatement(
- ifS(notX(hasSamePropertyX(pNode, otherTyped)),
- ifElseS(differentSelfRecursivePropertyX(pNode, otherTyped),
- returnS(constX(Boolean.FALSE, true)),
- ifS(notX(bothSelfRecursivePropertyX(pNode, otherTyped)),
- ifS(notX(hasEqualPropertyX(otherTyped.getOriginType(), pNode, otherTyped)), returnS(constX(Boolean.FALSE, true))))
- )
- )
- );
- }
- }
- List<FieldNode> fList = new ArrayList<FieldNode>();
- if (includeFields) {
- fList.addAll(getInstanceNonPropertyFields(cNode));
- }
- for (FieldNode fNode : fList) {
- if (shouldSkipUndefinedAware(fNode.getName(), excludes, includes, allNames)) continue;
- body.addStatement(
- ifS(notX(hasSameFieldX(fNode, otherTyped)),
- ifElseS(differentSelfRecursiveFieldX(fNode, otherTyped),
- returnS(constX(Boolean.FALSE,true)),
- ifS(notX(bothSelfRecursiveFieldX(fNode, otherTyped)),
- ifS(notX(hasEqualFieldX(fNode, otherTyped)), returnS(constX(Boolean.FALSE,true)))))
- ));
- }
- if (callSuper) {
- body.addStatement(ifS(
- notX(isTrueX(callSuperX("equals", other))),
- returnS(constX(Boolean.FALSE,true))
- ));
- }
-
- // default
- body.addStatement(returnS(constX(Boolean.TRUE,true)));
-
- addGeneratedMethod(cNode,
- hasExistingEquals ? "_equals" : "equals",
- hasExistingEquals ? ACC_PRIVATE : ACC_PUBLIC,
- ClassHelper.boolean_TYPE,
- params(param(OBJECT_TYPE, other.getName())),
- ClassNode.EMPTY_ARRAY,
- body);
- }
-
- private static BinaryExpression differentSelfRecursivePropertyX(PropertyNode pNode, Expression other) {
- String getterName = getGetterName(pNode);
- Expression selfGetter = callThisX(getterName);
- Expression otherGetter = callX(other, getterName);
- return orX(
- andX(sameX(selfGetter, varX("this")), notX(sameX(otherGetter, other))),
- andX(notX(sameX(selfGetter, varX("this"))), sameX(otherGetter, other))
- );
- }
-
- private static BinaryExpression bothSelfRecursivePropertyX(PropertyNode pNode, Expression other) {
- String getterName = getGetterName(pNode);
- Expression selfGetter = callThisX(getterName);
- Expression otherGetter = callX(other, getterName);
- return andX(
- sameX(selfGetter, varX("this")),
- sameX(otherGetter, other)
- );
- }
-
- private static BinaryExpression differentSelfRecursiveFieldX(FieldNode fNode, Expression other) {
- final Expression fieldExpr = varX(fNode);
- final Expression otherExpr = propX(other, fNode.getName());
- return orX(
- andX(sameX(fieldExpr, varX("this")), notX(sameX(otherExpr, other))),
- andX(notX(sameX(fieldExpr, varX("this"))), sameX(otherExpr, other))
- );
- }
-
- private static BinaryExpression bothSelfRecursiveFieldX(FieldNode fNode, Expression other) {
- final Expression fieldExpr = varX(fNode);
- final Expression otherExpr = propX(other, fNode.getName());
- return andX(
- sameX(fieldExpr, varX("this")),
- sameX(otherExpr, other)
- );
- }
-}
+/*
+ * 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.transform;
+
+import groovy.transform.EqualsAndHashCode;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.CastExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.tools.GenericsUtils;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
+import org.codehaus.groovy.util.HashCodeHelper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
+import static org.codehaus.groovy.ast.ClassHelper.make;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callSuperX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.getAllProperties;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceNonPropertyFields;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.getterThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.hasClassX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.hasDeclaredMethod;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.hasEqualFieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.hasEqualPropertyX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.hasSameFieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.hasSamePropertyX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isInstanceOfX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isZeroX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.orX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.sameX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe;
+
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class EqualsAndHashCodeASTTransformation extends AbstractASTTransformation {
+ static final Class MY_CLASS = EqualsAndHashCode.class;
+ static final ClassNode MY_TYPE = make(MY_CLASS);
+ static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
+ private static final ClassNode HASHUTIL_TYPE = make(HashCodeHelper.class);
+ private static final ClassNode OBJECT_TYPE = makeClassSafe(Object.class);
+
+ public void visit(ASTNode[] nodes, SourceUnit source) {
+ init(nodes, source);
+ AnnotatedNode parent = (AnnotatedNode) nodes[1];
+ AnnotationNode anno = (AnnotationNode) nodes[0];
+ if (!MY_TYPE.equals(anno.getClassNode())) return;
+
+ if (parent instanceof ClassNode) {
+ ClassNode cNode = (ClassNode) parent;
+ if (!checkNotInterface(cNode, MY_TYPE_NAME)) return;
+ boolean callSuper = memberHasValue(anno, "callSuper", true);
+ boolean cacheHashCode = memberHasValue(anno, "cache", true);
+ boolean useCanEqual = !memberHasValue(anno, "useCanEqual", false);
+ if (callSuper && cNode.getSuperClass().getName().equals("java.lang.Object")) {
+ addError("Error during " + MY_TYPE_NAME + " processing: callSuper=true but '" + cNode.getName() + "' has no super class.", anno);
+ }
+ boolean includeFields = memberHasValue(anno, "includeFields", true);
+ List<String> excludes = getMemberStringList(anno, "excludes");
+ List<String> includes = getMemberStringList(anno, "includes");
+ final boolean allNames = memberHasValue(anno, "allNames", true);
+ final boolean allProperties = memberHasValue(anno, "allProperties", true);
+ if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, MY_TYPE_NAME)) return;
+ if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields)) return;
+ if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields)) return;
+ createHashCode(cNode, cacheHashCode, includeFields, callSuper, excludes, includes, allNames, allProperties);
+ createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes, allNames, allProperties);
+ }
+ }
+
+ public static void createHashCode(ClassNode cNode, boolean cacheResult, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes) {
+ createHashCode(cNode, cacheResult, includeFields, callSuper, excludes, includes, false);
+ }
+
+ public static void createHashCode(ClassNode cNode, boolean cacheResult, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes, boolean allNames) {
+ createHashCode(cNode, cacheResult, includeFields, callSuper, excludes, includes, allNames,false);
+ }
+
+ public static void createHashCode(ClassNode cNode, boolean cacheResult, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes, boolean allNames, boolean allProperties) {
+ // make a public method if none exists otherwise try a private method with leading underscore
+ boolean hasExistingHashCode = hasDeclaredMethod(cNode, "hashCode", 0);
+ if (hasExistingHashCode && hasDeclaredMethod(cNode, "_hashCode", 0)) return;
+
+ final BlockStatement body = new BlockStatement();
+ // TODO use pList and fList
+ if (cacheResult) {
+ final FieldNode hashField = cNode.addField("$hash$code", ACC_PRIVATE | ACC_SYNTHETIC, ClassHelper.int_TYPE, null);
+ final Expression hash = varX(hashField);
+ body.addStatement(ifS(
+ isZeroX(hash),
+ calculateHashStatements(cNode, hash, includeFields, callSuper, excludes, includes, allNames, allProperties)
+ ));
+ body.addStatement(returnS(hash));
+ } else {
+ body.addStatement(calculateHashStatements(cNode, null, includeFields, callSuper, excludes, includes, allNames, allProperties));
+ }
+
+ addGeneratedMethod(cNode,
+ hasExistingHashCode ? "_hashCode" : "hashCode",
+ hasExistingHashCode ? ACC_PRIVATE : ACC_PUBLIC,
+ ClassHelper.int_TYPE,
+ Parameter.EMPTY_ARRAY,
+ ClassNode.EMPTY_ARRAY,
+ body);
+ }
+
+ private static Statement calculateHashStatements(ClassNode cNode, Expression hash, boolean includeFields, boolean callSuper, List<String> excludes, List<String> includes, boolean allNames, boolean allProperties) {
+ final Set<String> names = new HashSet<String>();
+ final List<PropertyNode> pList = getAllProperties(names, cNode, true, false, allProperties, false, false, false);
+ final List<FieldNode> fList = new ArrayList<FieldNode>();
+ if (includeFields) {
+ fList.addAll(getInstanceNonPropertyFields(cNode));
+ }
+ final BlockStatement body = new BlockStatement();
+ // def _result = HashCodeHelper.initHash()
+ final Expression result = localVarX("_result");
+ body.addStatement(declS(result, callX(HASHUTIL_TYPE, "initHash")));
+
+ for (PropertyNode pNode : pList) {
+ if (shouldSkipUndefinedAware(pNode.getName(), excludes, includes, allNames)) continue;
+ // _result = HashCodeHelper.updateHash(_result, getProperty()) // plus self-reference checking
+ Expression getter = getterThisX(cNode, pNode);
+ final Expression current = callX(HASHUTIL_TYPE, "updateHash", args(result, getter));
+ body.addStatement(ifS(
+ notX(sameX(getter, varX("this"))),
+ assignS(result, current)));
+
+ }
+ for (FieldNode fNode : fList) {
+ if (shouldSkipUndefinedAware(fNode.getName(), excludes, includes, allNames)) continue;
+ // _result = HashCodeHelper.updateHash(_result, field) // plus self-reference checking
+ final Expression fieldExpr = varX(fNode);
+ final Expression current = callX(HASHUTIL_TYPE, "updateHash", args(result, fieldExpr));
+ body.addStatement(ifS(
+ notX(sameX(fieldExpr, varX("this"))),
+ assignS(result, current)));
+ }
+ if (callSuper) {
+ // _result = HashCodeHelper.updateHash(_result, super.hashCode())
+ final Expression current = callX(HASHUTIL_TYPE, "updateHash", args(result, callSuperX("hashCode")));
+ body.addStatement(assignS(result, current));
+ }
+ // $hash$code = _result
+ if (hash != null) {
+ body.addStatement(assignS(hash, result));
+ } else {
+ body.addStatement(returnS(result));
+ }
+ return body;
+ }
+
+ private static void createCanEqual(ClassNode cNode) {
+ boolean hasExistingCanEqual = hasDeclaredMethod(cNode, "canEqual", 1);
+ if (hasExistingCanEqual && hasDeclaredMethod(cNode, "_canEqual", 1)) return;
+
+ final BlockStatement body = new BlockStatement();
+ VariableExpression other = varX("other");
+ body.addStatement(returnS(isInstanceOfX(other, GenericsUtils.nonGeneric(cNode))));
+ addGeneratedMethod(cNode,
+ hasExistingCanEqual ? "_canEqual" : "canEqual",
+ hasExistingCanEqual ? ACC_PRIVATE : ACC_PUBLIC,
+ ClassHelper.boolean_TYPE,
+ params(param(OBJECT_TYPE, other.getName())),
+ ClassNode.EMPTY_ARRAY,
+ body);
+ }
+
+ public static void createEquals(ClassNode cNode, boolean includeFields, boolean callSuper, boolean useCanEqual, List<String> excludes, List<String> includes) {
+ createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes, false);
+ }
+
+ public static void createEquals(ClassNode cNode, boolean includeFields, boolean callSuper, boolean useCanEqual, List<String> excludes, List<String> includes, boolean allNames) {
+ createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes, allNames,false);
+ }
+
+ public static void createEquals(ClassNode cNode, boolean includeFields, boolean callSuper, boolean useCanEqual, List<String> excludes, List<String> includes, boolean allNames, boolean allProperties) {
+ if (useCanEqual) createCanEqual(cNode);
+ // make a public method if none exists otherwise try a private method with leading underscore
+ boolean hasExistingEquals = hasDeclaredMethod(cNode, "equals", 1);
+ if (hasExistingEquals && hasDeclaredMethod(cNode, "_equals", 1)) return;
+
+ final BlockStatement body = new BlockStatement();
+ VariableExpression other = varX("other");
+
+ // some short circuit cases for efficiency
+ body.addStatement(ifS(equalsNullX(other), returnS(constX(Boolean.FALSE, true))));
+ body.addStatement(ifS(sameX(varX("this"), other), returnS(constX(Boolean.TRUE, true))));
+
+ if (useCanEqual) {
+ body.addStatement(ifS(notX(isInstanceOfX(other, GenericsUtils.nonGeneric(cNode))), returnS(constX(Boolean.FALSE,true))));
+ } else {
+ body.addStatement(ifS(notX(hasClassX(other, GenericsUtils.nonGeneric(cNode))), returnS(constX(Boolean.FALSE,true))));
+ }
+
+ VariableExpression otherTyped = localVarX("otherTyped", GenericsUtils.nonGeneric(cNode));
+ CastExpression castExpression = new CastExpression(GenericsUtils.nonGeneric(cNode), other);
+ castExpression.setStrict(true);
+ body.addStatement(declS(otherTyped, castExpression));
+
+ if (useCanEqual) {
+ body.addStatement(ifS(notX(callX(otherTyped, "canEqual", varX("this"))), returnS(constX(Boolean.FALSE,true))));
+ }
+
+ final Set<String> names = new HashSet<String>();
+ final List<PropertyNode> pList = getAllProperties(names, cNode, true, includeFields, allProperties, false, false, false);
+ for (PropertyNode pNode : pList) {
+ if (shouldSkipUndefinedAware(pNode.getName(), excludes, includes, allNames)) continue;
+ boolean canBeSelf = StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(
+ pNode.getOriginType(), cNode
+ );
+ if (!canBeSelf) {
+ body.addStatement(ifS(notX(hasEqualPropertyX(otherTyped.getOriginType(), pNode, otherTyped)), returnS(constX(Boolean.FALSE, true))));
+ } else {
+ body.addStatement(
+ ifS(notX(hasSamePropertyX(pNode, otherTyped)),
+ ifElseS(differentSelfRecursivePropertyX(pNode, otherTyped),
+ returnS(constX(Boolean.FALSE, true)),
+ ifS(notX(bothSelfRecursivePropertyX(pNode, otherTyped)),
+ ifS(notX(hasEqualPropertyX(otherTyped.getOriginType(), pNode, otherTyped)), returnS(constX(Boolean.FALSE, true))))
+ )
+ )
+ );
+ }
+ }
+ List<FieldNode> fList = new ArrayList<FieldNode>();
+ if (includeFields) {
+ fList.addAll(getInstanceNonPropertyFields(cNode));
+ }
+ for (FieldNode fNode : fList) {
+ if (shouldSkipUndefinedAware(fNode.getName(), excludes, includes, allNames)) continue;
+ body.addStatement(
+ ifS(notX(hasSameFieldX(fNode, otherTyped)),
+ ifElseS(differentSelfRecursiveFieldX(fNode, otherTyped),
+ returnS(constX(Boolean.FALSE,true)),
+ ifS(notX(bothSelfRecursiveFieldX(fNode, otherTyped)),
+ ifS(notX(hasEqualFieldX(fNode, otherTyped)), returnS(constX(Boolean.FALSE,true)))))
+ ));
+ }
+ if (callSuper) {
+ body.addStatement(ifS(
+ notX(isTrueX(callSuperX("equals", other))),
+ returnS(constX(Boolean.FALSE,true))
+ ));
+ }
+
+ // default
+ body.addStatement(returnS(constX(Boolean.TRUE,true)));
+
+ addGeneratedMethod(cNode,
+ hasExistingEquals ? "_equals" : "equals",
+ hasExistingEquals ? ACC_PRIVATE : ACC_PUBLIC,
+ ClassHelper.boolean_TYPE,
+ params(param(OBJECT_TYPE, other.getName())),
+ ClassNode.EMPTY_ARRAY,
+ body);
+ }
+
+ private static BinaryExpression differentSelfRecursivePropertyX(PropertyNode pNode, Expression other) {
+ String getterName = getGetterName(pNode);
+ Expression selfGetter = callThisX(getterName);
+ Expression otherGetter = callX(other, getterName);
+ return orX(
+ andX(sameX(selfGetter, varX("this")), notX(sameX(otherGetter, other))),
+ andX(notX(sameX(selfGetter, varX("this"))), sameX(otherGetter, other))
+ );
+ }
+
+ private static BinaryExpression bothSelfRecursivePropertyX(PropertyNode pNode, Expression other) {
+ String getterName = getGetterName(pNode);
+ Expression selfGetter = callThisX(getterName);
+ Expression otherGetter = callX(other, getterName);
+ return andX(
+ sameX(selfGetter, varX("this")),
+ sameX(otherGetter, other)
+ );
+ }
+
+ private static BinaryExpression differentSelfRecursiveFieldX(FieldNode fNode, Expression other) {
+ final Expression fieldExpr = varX(fNode);
+ final Expression otherExpr = propX(other, fNode.getName());
+ return orX(
+ andX(sameX(fieldExpr, varX("this")), notX(sameX(otherExpr, other))),
+ andX(notX(sameX(fieldExpr, varX("this"))), sameX(otherExpr, other))
+ );
+ }
+
+ private static BinaryExpression bothSelfRecursiveFieldX(FieldNode fNode, Expression other) {
+ final Expression fieldExpr = varX(fNode);
+ final Expression otherExpr = propX(other, fNode.getName());
+ return andX(
+ sameX(fieldExpr, varX("this")),
+ sameX(otherExpr, other)
+ );
+ }
+}
diff --git a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
index 500e3b1..b81046f 100644
--- a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
@@ -72,6 +72,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.hasDeclaredMethod;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.neX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.orX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
@@ -239,7 +240,7 @@ public class ImmutableASTTransformation extends AbstractASTTransformation implem
block(
new VariableScope(),
declS(
- varX("newValue", ClassHelper.OBJECT_TYPE),
+ localVarX("newValue", ClassHelper.OBJECT_TYPE),
callX(
varX("map", HMAP_TYPE),
"get",
@@ -247,7 +248,7 @@ public class ImmutableASTTransformation extends AbstractASTTransformation implem
)
),
declS(
- varX("oldValue", ClassHelper.OBJECT_TYPE),
+ localVarX("oldValue", ClassHelper.OBJECT_TYPE),
callThisX(getGetterName(pNode))
),
ifS(
@@ -298,8 +299,8 @@ public class ImmutableASTTransformation extends AbstractASTTransformation implem
),
returnS(varX("this", cNode))
));
- body.addStatement(declS(varX("dirty", ClassHelper.boolean_TYPE), ConstantExpression.PRIM_FALSE));
- body.addStatement(declS(varX("construct", HMAP_TYPE), ctorX(HMAP_TYPE)));
+ body.addStatement(declS(localVarX("dirty", ClassHelper.boolean_TYPE), ConstantExpression.PRIM_FALSE));
+ body.addStatement(declS(localVarX("construct", HMAP_TYPE), ctorX(HMAP_TYPE)));
// Check for each property
for (final PropertyNode pNode : pList) {
diff --git a/src/main/java/org/codehaus/groovy/transform/LazyASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/LazyASTTransformation.java
index ce21481..06ec761 100644
--- a/src/main/java/org/codehaus/groovy/transform/LazyASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/LazyASTTransformation.java
@@ -1,246 +1,247 @@
-/*
- * 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.transform;
-
-import org.codehaus.groovy.ast.ASTNode;
-import org.codehaus.groovy.ast.AnnotatedNode;
-import org.codehaus.groovy.ast.AnnotationNode;
-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.PropertyNode;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.expr.EmptyExpression;
-import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.ast.expr.VariableExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
-import org.codehaus.groovy.control.CompilePhase;
-import org.codehaus.groovy.control.SourceUnit;
-import org.codehaus.groovy.runtime.MetaClassHelper;
-
-import java.lang.ref.SoftReference;
-
-import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
-import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.notNullX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
-
-/**
- * Handles generation of code for the @Lazy annotation
- */
-@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
-public class LazyASTTransformation extends AbstractASTTransformation {
-
- private static final ClassNode SOFT_REF = makeWithoutCaching(SoftReference.class, false);
- private static final Expression NULL_EXPR = ConstantExpression.NULL;
-
- public void visit(ASTNode[] nodes, SourceUnit source) {
- init(nodes, source);
- AnnotatedNode parent = (AnnotatedNode) nodes[1];
- AnnotationNode node = (AnnotationNode) nodes[0];
-
- if (parent instanceof FieldNode) {
- final FieldNode fieldNode = (FieldNode) parent;
- visitField(this, node, fieldNode);
- }
- }
-
- static void visitField(ErrorCollecting xform, AnnotationNode node, FieldNode fieldNode) {
- final Expression soft = node.getMember("soft");
- final Expression init = getInitExpr(xform, fieldNode);
-
- String backingFieldName = "$" + fieldNode.getName();
- fieldNode.rename(backingFieldName);
- fieldNode.setModifiers(ACC_PRIVATE | (fieldNode.getModifiers() & (~(ACC_PUBLIC | ACC_PROTECTED))));
- PropertyNode pNode = fieldNode.getDeclaringClass().getProperty(backingFieldName);
- if (pNode != null) {
- fieldNode.getDeclaringClass().getProperties().remove(pNode);
- }
-
- if (soft instanceof ConstantExpression && ((ConstantExpression) soft).getValue().equals(true)) {
- createSoft(fieldNode, init);
- } else {
- create(fieldNode, init);
- // @Lazy not meaningful with primitive so convert to wrapper if needed
- if (ClassHelper.isPrimitiveType(fieldNode.getType())) {
- fieldNode.setType(ClassHelper.getWrapper(fieldNode.getType()));
- }
- }
- }
-
- private static void create(FieldNode fieldNode, final Expression initExpr) {
- final BlockStatement body = new BlockStatement();
- if (fieldNode.isStatic()) {
- addHolderClassIdiomBody(body, fieldNode, initExpr);
- } else if (fieldNode.isVolatile()) {
- addDoubleCheckedLockingBody(body, fieldNode, initExpr);
- } else {
- addNonThreadSafeBody(body, fieldNode, initExpr);
- }
- addMethod(fieldNode, body, fieldNode.getType());
- }
-
- private static void addHolderClassIdiomBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
- final ClassNode declaringClass = fieldNode.getDeclaringClass();
- final ClassNode fieldType = fieldNode.getType();
- final int visibility = ACC_PRIVATE | ACC_STATIC;
- final String fullName = declaringClass.getName() + "$" + fieldType.getNameWithoutPackage() + "Holder_" + fieldNode.getName().substring(1);
- final InnerClassNode holderClass = new InnerClassNode(declaringClass, fullName, visibility, ClassHelper.OBJECT_TYPE);
- final String innerFieldName = "INSTANCE";
-
- // we have two options:
- // (1) embed initExpr within holder class but redirect field access/method calls to declaring class members
- // (2) keep initExpr within a declaring class method that is only called by the holder class
- // currently we have gone with (2) for simplicity with only a slight memory footprint increase in the declaring class
- final String initializeMethodName = (fullName + "_initExpr").replace('.', '_');
- addGeneratedMethod(declaringClass, initializeMethodName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType,
- Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, returnS(initExpr));
- holderClass.addField(innerFieldName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType,
- callX(declaringClass, initializeMethodName));
-
- final Expression innerField = propX(classX(holderClass), innerFieldName);
- declaringClass.getModule().addClass(holderClass);
- body.addStatement(returnS(innerField));
- }
-
- private static void addDoubleCheckedLockingBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
- final Expression fieldExpr = varX(fieldNode);
- final VariableExpression localVar = varX(fieldNode.getName() + "_local");
- body.addStatement(declS(localVar, fieldExpr));
- body.addStatement(ifElseS(
- notNullX(localVar),
- returnS(localVar),
- new SynchronizedStatement(
- syncTarget(fieldNode),
- ifElseS(
- notNullX(fieldExpr),
- returnS(fieldExpr),
- returnS(assignX(fieldExpr, initExpr))
- )
- )
- ));
- }
-
- private static void addNonThreadSafeBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
- final Expression fieldExpr = varX(fieldNode);
- body.addStatement(ifElseS(notNullX(fieldExpr), stmt(fieldExpr), assignS(fieldExpr, initExpr)));
- }
-
- private static void addMethod(FieldNode fieldNode, BlockStatement body, ClassNode type) {
- int visibility = ACC_PUBLIC;
- if (fieldNode.isStatic()) visibility |= ACC_STATIC;
- String propName = MetaClassHelper.capitalize(fieldNode.getName().substring(1));
- ClassNode declaringClass = fieldNode.getDeclaringClass();
- addGeneratedMethod(declaringClass, "get" + propName, visibility, type, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body);
- if (ClassHelper.boolean_TYPE.equals(type)) {
- addGeneratedMethod(declaringClass, "is" + propName, visibility, type,
- Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, stmt(callThisX("get" + propName)));
- }
- }
-
- private static void createSoft(FieldNode fieldNode, Expression initExpr) {
- final ClassNode type = fieldNode.getType();
- fieldNode.setType(SOFT_REF);
- createSoftGetter(fieldNode, initExpr, type);
- createSoftSetter(fieldNode, type);
- }
-
- private static void createSoftGetter(FieldNode fieldNode, Expression initExpr, ClassNode type) {
- final BlockStatement body = new BlockStatement();
- final Expression fieldExpr = varX(fieldNode);
- final Expression resExpr = varX("res", type);
- final MethodCallExpression callExpression = callX(fieldExpr, "get");
- callExpression.setSafe(true);
- body.addStatement(declS(resExpr, callExpression));
-
- final Statement mainIf = ifElseS(notNullX(resExpr), stmt(resExpr), block(
- assignS(resExpr, initExpr),
- assignS(fieldExpr, ctorX(SOFT_REF, resExpr)),
- stmt(resExpr)));
-
- if (fieldNode.isVolatile()) {
- body.addStatement(ifElseS(
- notNullX(resExpr),
- stmt(resExpr),
- new SynchronizedStatement(syncTarget(fieldNode), block(
- assignS(resExpr, callExpression),
- mainIf)
- )
- ));
- } else {
- body.addStatement(mainIf);
- }
- addMethod(fieldNode, body, type);
- }
-
- private static void createSoftSetter(FieldNode fieldNode, ClassNode type) {
- final BlockStatement body = new BlockStatement();
- final Expression fieldExpr = varX(fieldNode);
- final String name = "set" + MetaClassHelper.capitalize(fieldNode.getName().substring(1));
- final Parameter parameter = param(type, "value");
- final Expression paramExpr = varX(parameter);
- body.addStatement(ifElseS(
- notNullX(paramExpr),
- assignS(fieldExpr, ctorX(SOFT_REF, paramExpr)),
- assignS(fieldExpr, NULL_EXPR)
- ));
- int visibility = ACC_PUBLIC;
- if (fieldNode.isStatic()) visibility |= ACC_STATIC;
- ClassNode declaringClass = fieldNode.getDeclaringClass();
- addGeneratedMethod(declaringClass, name, visibility, ClassHelper.VOID_TYPE, params(parameter), ClassNode.EMPTY_ARRAY, body);
- }
-
- private static Expression syncTarget(FieldNode fieldNode) {
- return fieldNode.isStatic() ? classX(fieldNode.getDeclaringClass()) : varX("this");
- }
-
- private static Expression getInitExpr(ErrorCollecting xform, FieldNode fieldNode) {
- Expression initExpr = fieldNode.getInitialValueExpression();
- fieldNode.setInitialValueExpression(null);
-
- if (initExpr == null || initExpr instanceof EmptyExpression) {
- if (fieldNode.getType().isAbstract()) {
- xform.addError("You cannot lazily initialize '" + fieldNode.getName() + "' from the abstract class '" +
- fieldNode.getType().getName() + "'", fieldNode);
- }
- initExpr = ctorX(fieldNode.getType());
- }
-
- return initExpr;
- }
-}
+/*
+ * 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.transform;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+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.PropertyNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.EmptyExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.runtime.MetaClassHelper;
+
+import java.lang.ref.SoftReference;
+
+import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
+import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.notNullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * Handles generation of code for the @Lazy annotation
+ */
+@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
+public class LazyASTTransformation extends AbstractASTTransformation {
+
+ private static final ClassNode SOFT_REF = makeWithoutCaching(SoftReference.class, false);
+ private static final Expression NULL_EXPR = ConstantExpression.NULL;
+
+ public void visit(ASTNode[] nodes, SourceUnit source) {
+ init(nodes, source);
+ AnnotatedNode parent = (AnnotatedNode) nodes[1];
+ AnnotationNode node = (AnnotationNode) nodes[0];
+
+ if (parent instanceof FieldNode) {
+ final FieldNode fieldNode = (FieldNode) parent;
+ visitField(this, node, fieldNode);
+ }
+ }
+
+ static void visitField(ErrorCollecting xform, AnnotationNode node, FieldNode fieldNode) {
+ final Expression soft = node.getMember("soft");
+ final Expression init = getInitExpr(xform, fieldNode);
+
+ String backingFieldName = "$" + fieldNode.getName();
+ fieldNode.rename(backingFieldName);
+ fieldNode.setModifiers(ACC_PRIVATE | (fieldNode.getModifiers() & (~(ACC_PUBLIC | ACC_PROTECTED))));
+ PropertyNode pNode = fieldNode.getDeclaringClass().getProperty(backingFieldName);
+ if (pNode != null) {
+ fieldNode.getDeclaringClass().getProperties().remove(pNode);
+ }
+
+ if (soft instanceof ConstantExpression && ((ConstantExpression) soft).getValue().equals(true)) {
+ createSoft(fieldNode, init);
+ } else {
+ create(fieldNode, init);
+ // @Lazy not meaningful with primitive so convert to wrapper if needed
+ if (ClassHelper.isPrimitiveType(fieldNode.getType())) {
+ fieldNode.setType(ClassHelper.getWrapper(fieldNode.getType()));
+ }
+ }
+ }
+
+ private static void create(FieldNode fieldNode, final Expression initExpr) {
+ final BlockStatement body = new BlockStatement();
+ if (fieldNode.isStatic()) {
+ addHolderClassIdiomBody(body, fieldNode, initExpr);
+ } else if (fieldNode.isVolatile()) {
+ addDoubleCheckedLockingBody(body, fieldNode, initExpr);
+ } else {
+ addNonThreadSafeBody(body, fieldNode, initExpr);
+ }
+ addMethod(fieldNode, body, fieldNode.getType());
+ }
+
+ private static void addHolderClassIdiomBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
+ final ClassNode declaringClass = fieldNode.getDeclaringClass();
+ final ClassNode fieldType = fieldNode.getType();
+ final int visibility = ACC_PRIVATE | ACC_STATIC;
+ final String fullName = declaringClass.getName() + "$" + fieldType.getNameWithoutPackage() + "Holder_" + fieldNode.getName().substring(1);
+ final InnerClassNode holderClass = new InnerClassNode(declaringClass, fullName, visibility, ClassHelper.OBJECT_TYPE);
+ final String innerFieldName = "INSTANCE";
+
+ // we have two options:
+ // (1) embed initExpr within holder class but redirect field access/method calls to declaring class members
+ // (2) keep initExpr within a declaring class method that is only called by the holder class
+ // currently we have gone with (2) for simplicity with only a slight memory footprint increase in the declaring class
+ final String initializeMethodName = (fullName + "_initExpr").replace('.', '_');
+ addGeneratedMethod(declaringClass, initializeMethodName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType,
+ Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, returnS(initExpr));
+ holderClass.addField(innerFieldName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType,
+ callX(declaringClass, initializeMethodName));
+
+ final Expression innerField = propX(classX(holderClass), innerFieldName);
+ declaringClass.getModule().addClass(holderClass);
+ body.addStatement(returnS(innerField));
+ }
+
+ private static void addDoubleCheckedLockingBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
+ final Expression fieldExpr = varX(fieldNode);
+ final VariableExpression localVar = localVarX(fieldNode.getName() + "_local");
+ body.addStatement(declS(localVar, fieldExpr));
+ body.addStatement(ifElseS(
+ notNullX(localVar),
+ returnS(localVar),
+ new SynchronizedStatement(
+ syncTarget(fieldNode),
+ ifElseS(
+ notNullX(fieldExpr),
+ returnS(fieldExpr),
+ returnS(assignX(fieldExpr, initExpr))
+ )
+ )
+ ));
+ }
+
+ private static void addNonThreadSafeBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
+ final Expression fieldExpr = varX(fieldNode);
+ body.addStatement(ifElseS(notNullX(fieldExpr), stmt(fieldExpr), assignS(fieldExpr, initExpr)));
+ }
+
+ private static void addMethod(FieldNode fieldNode, BlockStatement body, ClassNode type) {
+ int visibility = ACC_PUBLIC;
+ if (fieldNode.isStatic()) visibility |= ACC_STATIC;
+ String propName = MetaClassHelper.capitalize(fieldNode.getName().substring(1));
+ ClassNode declaringClass = fieldNode.getDeclaringClass();
+ addGeneratedMethod(declaringClass, "get" + propName, visibility, type, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body);
+ if (ClassHelper.boolean_TYPE.equals(type)) {
+ addGeneratedMethod(declaringClass, "is" + propName, visibility, type,
+ Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, stmt(callThisX("get" + propName)));
+ }
+ }
+
+ private static void createSoft(FieldNode fieldNode, Expression initExpr) {
+ final ClassNode type = fieldNode.getType();
+ fieldNode.setType(SOFT_REF);
+ createSoftGetter(fieldNode, initExpr, type);
+ createSoftSetter(fieldNode, type);
+ }
+
+ private static void createSoftGetter(FieldNode fieldNode, Expression initExpr, ClassNode type) {
+ final BlockStatement body = new BlockStatement();
+ final Expression fieldExpr = varX(fieldNode);
+ final Expression resExpr = localVarX("_result", type);
+ final MethodCallExpression callExpression = callX(fieldExpr, "get");
+ callExpression.setSafe(true);
+ body.addStatement(declS(resExpr, callExpression));
+
+ final Statement mainIf = ifElseS(notNullX(resExpr), stmt(resExpr), block(
+ assignS(resExpr, initExpr),
+ assignS(fieldExpr, ctorX(SOFT_REF, resExpr)),
+ stmt(resExpr)));
+
+ if (fieldNode.isVolatile()) {
+ body.addStatement(ifElseS(
+ notNullX(resExpr),
+ stmt(resExpr),
+ new SynchronizedStatement(syncTarget(fieldNode), block(
+ assignS(resExpr, callExpression),
+ mainIf)
+ )
+ ));
+ } else {
+ body.addStatement(mainIf);
+ }
+ addMethod(fieldNode, body, type);
+ }
+
+ private static void createSoftSetter(FieldNode fieldNode, ClassNode type) {
+ final BlockStatement body = new BlockStatement();
+ final Expression fieldExpr = varX(fieldNode);
+ final String name = "set" + MetaClassHelper.capitalize(fieldNode.getName().substring(1));
+ final Parameter parameter = param(type, "value");
+ final Expression paramExpr = varX(parameter);
+ body.addStatement(ifElseS(
+ notNullX(paramExpr),
+ assignS(fieldExpr, ctorX(SOFT_REF, paramExpr)),
+ assignS(fieldExpr, NULL_EXPR)
+ ));
+ int visibility = ACC_PUBLIC;
+ if (fieldNode.isStatic()) visibility |= ACC_STATIC;
+ ClassNode declaringClass = fieldNode.getDeclaringClass();
+ addGeneratedMethod(declaringClass, name, visibility, ClassHelper.VOID_TYPE, params(parameter), ClassNode.EMPTY_ARRAY, body);
+ }
+
+ private static Expression syncTarget(FieldNode fieldNode) {
+ return fieldNode.isStatic() ? classX(fieldNode.getDeclaringClass()) : varX("this");
+ }
+
+ private static Expression getInitExpr(ErrorCollecting xform, FieldNode fieldNode) {
+ Expression initExpr = fieldNode.getInitialValueExpression();
+ fieldNode.setInitialValueExpression(null);
+
+ if (initExpr == null || initExpr instanceof EmptyExpression) {
+ if (fieldNode.getType().isAbstract()) {
+ xform.addError("You cannot lazily initialize '" + fieldNode.getName() + "' from the abstract class '" +
+ fieldNode.getType().getName() + "'", fieldNode);
+ }
+ initExpr = ctorX(fieldNode.getType());
+ }
+
+ return initExpr;
+ }
+}
diff --git a/src/main/java/org/codehaus/groovy/transform/SortableASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/SortableASTTransformation.java
index 4a1304a..9886d20 100644
--- a/src/main/java/org/codehaus/groovy/transform/SortableASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/SortableASTTransformation.java
@@ -63,6 +63,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getAllProperties;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.neX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.notNullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
@@ -147,12 +148,12 @@ public class SortableASTTransformation extends AbstractASTTransformation {
if (properties.isEmpty()) {
// perhaps overkill but let compareTo be based on hashes for commutativity
// return this.hashCode() <=> other.hashCode()
- statements.add(declS(varX(THIS_HASH, ClassHelper.Integer_TYPE), callX(varX("this"), "hashCode")));
- statements.add(declS(varX(OTHER_HASH, ClassHelper.Integer_TYPE), callX(varX(OTHER), "hashCode")));
+ statements.add(declS(localVarX(THIS_HASH, ClassHelper.Integer_TYPE), callX(varX("this"), "hashCode")));
+ statements.add(declS(localVarX(OTHER_HASH, ClassHelper.Integer_TYPE), callX(varX(OTHER), "hashCode")));
statements.add(returnS(compareExpr(varX(THIS_HASH), varX(OTHER_HASH), reversed)));
} else {
// int value = 0;
- statements.add(declS(varX(VALUE, ClassHelper.int_TYPE), constX(0)));
+ statements.add(declS(localVarX(VALUE, ClassHelper.int_TYPE), constX(0)));
for (PropertyNode property : properties) {
String propName = property.getName();
// value = this.prop <=> other.prop;
diff --git a/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
index b0a0570..eff7aa6 100644
--- a/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
@@ -181,7 +181,7 @@ public class ToStringASTTransformation extends AbstractASTTransformation {
List<ToStringElement> elements = new ArrayList<ToStringElement>();
// def $toStringFirst = true
- final VariableExpression first = varX("$toStringFirst");
+ final VariableExpression first = localVarX("$toStringFirst");
body.addStatement(declS(first, constX(Boolean.TRUE)));
// <class_name>(