This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push: new 71ac104 minor refactor: properly declare local variable 71ac104 is described below commit 71ac1044be018683053723e74503a3006de01271 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 | 690 ++++++++-------- .../transform/ImmutableASTTransformation.java | 9 +- .../groovy/transform/LazyASTTransformation.java | 493 ++++++------ .../transform/SortableASTTransformation.java | 7 +- .../transform/ToStringASTTransformation.java | 2 +- .../antlr4/TryWithResourcesASTTransformation.java | 4 +- 14 files changed, 1074 insertions(+), 1054 deletions(-) diff --git a/src/main/groovy/groovy/beans/BindableASTTransformation.java b/src/main/groovy/groovy/beans/BindableASTTransformation.java index e23f4ac..037ee2f 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 70e8e18..b574ca1 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 a20017f..15f112f 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 d1651f6..6050500 100644 --- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java +++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java @@ -654,6 +654,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 fe3035c..07a090f 100644 --- a/src/main/java/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java +++ b/src/main/java/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java @@ -1,345 +1,345 @@ -/* - * 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.notIdenticalX; -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( - notIdenticalX(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( - notIdenticalX(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.notIdenticalX; +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( + notIdenticalX(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( + notIdenticalX(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 8d54071..aeaa58d 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 java.lang.ref.SoftReference; - -import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; -import static org.apache.groovy.util.BeanUtils.capitalize; -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 = 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" + 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 java.lang.ref.SoftReference; + +import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; +import static org.apache.groovy.util.BeanUtils.capitalize; +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 = 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" + 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>( diff --git a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java index b327a32..d3fc646 100644 --- a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java +++ b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java @@ -42,6 +42,7 @@ import org.objectweb.asm.Opcodes; import java.util.Collections; import java.util.List; +import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean; import static org.codehaus.groovy.syntax.Token.newSymbol; @@ -197,10 +198,11 @@ public class TryWithResourcesASTTransformation { // Throwable #primaryExc = null; String primaryExcName = this.genPrimaryExcName(); + VariableExpression primaryExcX = localVarX(primaryExcName, ClassHelper.make(Throwable.class)); ExpressionStatement primaryExcDeclarationStatement = new ExpressionStatement( new DeclarationExpression( - new VariableExpression(primaryExcName, ClassHelper.make(Throwable.class)), + primaryExcX, newSymbol(Types.ASSIGN, -1, -1), new ConstantExpression(null) )