GROOVY-8477: @Immutable-related transformations should be more configurable
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/ba811fdf Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/ba811fdf Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/ba811fdf Branch: refs/heads/GROOVY_2_6_X Commit: ba811fdfd53b861f796d1d056b93dada273e2532 Parents: 6e3e62c Author: paulk <[email protected]> Authored: Tue Feb 13 15:06:07 2018 +1000 Committer: paulk <[email protected]> Committed: Thu Feb 15 12:49:36 2018 +1000 ---------------------------------------------------------------------- .../groovy/groovy/transform/Immutable.groovy | 6 +- .../groovy/groovy/transform/MapConstructor.java | 26 +- .../groovy/transform/TupleConstructor.java | 9 + .../construction/DefaultPropertyHandler.java | 103 +++++ .../construction/ImmutablePropertyHandler.java | 295 ++++++++++++ .../LegacyHashMapPropertyHandler.java | 99 ++++ .../transform/construction/PropertyHandler.java | 76 ++++ .../ast/tools/ImmutablePropertyUtils.java | 233 ++++++++++ .../codehaus/groovy/ast/tools/GeneralUtils.java | 18 +- .../transform/AbstractASTTransformation.java | 15 +- .../transform/ImmutableASTTransformation.java | 449 +------------------ .../MapConstructorASTTransformation.java | 91 ++-- .../transform/ToStringASTTransformation.java | 4 +- .../TupleConstructorASTTransformation.java | 104 ++--- .../transform/ImmutableTransformTest.groovy | 6 +- 15 files changed, 979 insertions(+), 555 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/Immutable.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/Immutable.groovy b/src/main/groovy/groovy/transform/Immutable.groovy index 3fa518a..cfbcc30 100644 --- a/src/main/groovy/groovy/transform/Immutable.groovy +++ b/src/main/groovy/groovy/transform/Immutable.groovy @@ -18,6 +18,8 @@ */ package groovy.transform +import groovy.transform.construction.ImmutablePropertyHandler + /** * Meta annotation used when defining immutable classes. * <p> @@ -174,8 +176,8 @@ package groovy.transform @ToString(cache = true, includeSuperProperties = true) @EqualsAndHashCode(cache = true) @ImmutableBase -@TupleConstructor(defaults = false) -@MapConstructor(noArg = true, includeSuperProperties = true) +@TupleConstructor(defaults = false, propertyHandler = ImmutablePropertyHandler) +@MapConstructor(noArg = true, includeSuperProperties = true, includeFields = true, propertyHandler = ImmutablePropertyHandler) @KnownImmutable @AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED) @interface Immutable { } http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/MapConstructor.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/MapConstructor.java b/src/main/groovy/groovy/transform/MapConstructor.java index e59ce75..2cdbf94 100644 --- a/src/main/groovy/groovy/transform/MapConstructor.java +++ b/src/main/groovy/groovy/transform/MapConstructor.java @@ -18,6 +18,8 @@ */ package groovy.transform; +import groovy.transform.construction.DefaultPropertyHandler; +import groovy.transform.construction.PropertyHandler; import org.codehaus.groovy.transform.GroovyASTTransformationClass; import java.lang.annotation.ElementType; @@ -74,7 +76,12 @@ import java.lang.annotation.Target; * is included and the type of that property (or field) is Object, AbstractMap, Map or HashMap. * In this case, the generated constructor will be of type {@code LinkedHashMap}. * This allows the possibility of also adding a tuple constructor without conflict, although - * no such constructor is added automatically. + * no such constructor is added automatically. You can disable this behavior by setting + * the specialNamedArgHandling annotation attribute to false. This means that for the special + * case mentioned above, you will not be able to also add a tuple constructor with a single Map + * argument but you can supply any kind of map as your argument. We'd also recommend not + * having both a map constructor and a tuple constructor with a single Object, AbstractMap or + * HashMap since it can cause confusion as to which will be called. * </li> * </ul> * @@ -143,6 +150,11 @@ public @interface MapConstructor { boolean useSetters() default false; /** + * Whether to include static properties in the constructor. + */ + boolean includeStatic() default false; + + /** * Whether to include all fields and/or properties within the constructor, including those with names that are considered internal. */ boolean allNames() default false; @@ -153,6 +165,18 @@ public @interface MapConstructor { boolean noArg() default false; /** + * A class defining the property handler + */ + Class<? extends PropertyHandler> propertyHandler() default DefaultPropertyHandler.class; + + /** + * If true, change the type of the map constructor argument from Map to LinkedHashMap only for the case where + * the class has a single property (or field) with a Map-like type. This allows both a map and a tuple constructor + * to be used side-by-side so long as care is taken about the types used when calling. + */ + boolean specialNamedArgHandling() default true; + + /** * A Closure containing statements which will be prepended to the generated constructor. The first statement within the Closure may be "super(someArgs)" in which case the no-arg super constructor won't be called. */ Class pre() default Undefined.CLASS.class; http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/TupleConstructor.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/TupleConstructor.java b/src/main/groovy/groovy/transform/TupleConstructor.java index e611cb5..a162f0f 100644 --- a/src/main/groovy/groovy/transform/TupleConstructor.java +++ b/src/main/groovy/groovy/transform/TupleConstructor.java @@ -18,6 +18,8 @@ */ package groovy.transform; +import groovy.transform.construction.DefaultPropertyHandler; +import groovy.transform.construction.PropertyHandler; import org.codehaus.groovy.transform.GroovyASTTransformationClass; import java.lang.annotation.ElementType; @@ -298,6 +300,13 @@ public @interface TupleConstructor { boolean allProperties() default false; /** + * A class defining the property handler + * + * @since 2.5.0 + */ + Class<? extends PropertyHandler> propertyHandler() default DefaultPropertyHandler.class; + + /** * A Closure containing statements which will be prepended to the generated constructor. The first statement * within the Closure may be {@code super(someArgs)} in which case the no-arg super constructor won't be called. * http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java b/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java new file mode 100644 index 0000000..198e69d --- /dev/null +++ b/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java @@ -0,0 +1,103 @@ +/* + * 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 groovy.transform.construction; + +import org.codehaus.groovy.ast.AnnotationNode; +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.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MapExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.transform.AbstractASTTransformation; +import org.codehaus.groovy.transform.ImmutableASTTransformation; +import org.codehaus.groovy.transform.MapConstructorASTTransformation; + +import java.util.List; + +import static org.codehaus.groovy.ast.ClassHelper.make; +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.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.equalsNullX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +public class DefaultPropertyHandler extends PropertyHandler { + private static final ClassNode IMMUTABLE_XFORM_TYPE = make(ImmutableASTTransformation.class); + + @Override + public boolean validateAttributes(AbstractASTTransformation xform, AnnotationNode anno) { + boolean success = true; + success |= isValidAttribute(xform, anno, ""); + return success; + } + + @Override + public boolean validateProperties(AbstractASTTransformation xform, BlockStatement body, ClassNode cNode, List<PropertyNode> props) { + if (xform instanceof MapConstructorASTTransformation) { + body.addStatement(ifS(equalsNullX(varX("args")), assignS(varX("args"), new MapExpression()))); + body.addStatement(stmt(callX(IMMUTABLE_XFORM_TYPE, "checkPropNames", args("this", "args")))); + } + return super.validateProperties(xform, body, cNode, props); + } + + @Override + public void createStatement(AbstractASTTransformation xform, AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) { + String name = pNode.getName(); + FieldNode fNode = pNode.getField(); + boolean useSetters = xform.memberHasValue(anno, "useSetters", true); + boolean hasSetter = cNode.getProperty(name) != null && !fNode.isFinal(); + if (namedArgsMap != null) { + assignField(useSetters, namedArgsMap, body, name); + } else { + Expression var = varX(name); + if (useSetters && hasSetter) { + body.addStatement(setViaSetter(name, var)); + } else { + body.addStatement(assignToField(name, var)); + } + } + + } + + private static Statement assignToField(String name, Expression var) { + return assignS(propX(varX("this"), name), var); + } + + private static Statement setViaSetter(String name, Expression var) { + return stmt(callThisX(getSetterName(name), var)); + } + + private static void assignField(boolean useSetters, Parameter map, BlockStatement body, String name) { + ArgumentListExpression nameArg = args(constX(name)); + Expression var = callX(varX(map), "get", nameArg); + body.addStatement(ifS(callX(varX(map), "containsKey", nameArg), useSetters ? + setViaSetter(name, var) : + assignToField(name, var))); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java b/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java new file mode 100644 index 0000000..09e9126 --- /dev/null +++ b/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java @@ -0,0 +1,295 @@ +/* + * 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 groovy.transform.construction; + +import groovy.lang.ReadOnlyPropertyException; +import org.apache.groovy.ast.tools.ImmutablePropertyUtils; +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.ClassExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.expr.MapExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.transform.AbstractASTTransformation; +import org.codehaus.groovy.transform.ImmutableASTTransformation; +import org.codehaus.groovy.transform.MapConstructorASTTransformation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.cloneArrayOrCloneableExpr; +import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.cloneDateExpr; +import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.derivesFromDate; +import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.implementsCloneable; +import static org.codehaus.groovy.ast.ClassHelper.make; +import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching; +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.callThisX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.classList2args; +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.equalsNullX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.findArg; +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.isOrImplements; +import static org.codehaus.groovy.ast.tools.GeneralUtils.list2args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.notX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ternaryX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +public class ImmutablePropertyHandler extends PropertyHandler { + private static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = "knownImmutableClasses"; + private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class); + private static final ClassNode COLLECTION_TYPE = makeWithoutCaching(Collection.class, false); + private static final ClassNode DGM_TYPE = make(DefaultGroovyMethods.class); + private static final ClassNode SELF_TYPE = make(ImmutableASTTransformation.class); + private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, false); + private static final ClassNode SORTEDSET_CLASSNODE = make(SortedSet.class); + private static final ClassNode SORTEDMAP_CLASSNODE = make(SortedMap.class); + private static final ClassNode SET_CLASSNODE = make(Set.class); + private static final ClassNode MAP_CLASSNODE = make(Map.class); + private static final ClassNode READONLYEXCEPTION_TYPE = make(ReadOnlyPropertyException.class); + private static final ClassNode IMMUTABLE_XFORM_TYPE = make(ImmutableASTTransformation.class); + + private static List<String> getKnownImmutableClasses(AbstractASTTransformation xform, AnnotationNode node) { + final List<String> immutableClasses = new ArrayList<String>(); + + if (node == null) return immutableClasses; + final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLE_CLASSES); + if (expression == null) return immutableClasses; + + if (!(expression instanceof ListExpression)) { + xform.addError("Use the Groovy list notation [el1, el2] to specify known immutable classes via \"" + MEMBER_KNOWN_IMMUTABLE_CLASSES + "\"", node); + return immutableClasses; + } + + final ListExpression listExpression = (ListExpression) expression; + for (Expression listItemExpression : listExpression.getExpressions()) { + if (listItemExpression instanceof ClassExpression) { + immutableClasses.add(listItemExpression.getType().getName()); + } + } + + return immutableClasses; + } + + protected Expression cloneCollectionExpr(Expression fieldExpr, ClassNode type) { + return castX(type, createIfInstanceOfAsImmutableS(fieldExpr, SORTEDSET_CLASSNODE, + createIfInstanceOfAsImmutableS(fieldExpr, SORTEDMAP_CLASSNODE, + createIfInstanceOfAsImmutableS(fieldExpr, SET_CLASSNODE, + createIfInstanceOfAsImmutableS(fieldExpr, MAP_CLASSNODE, + createIfInstanceOfAsImmutableS(fieldExpr, ClassHelper.LIST_TYPE, + createAsImmutableX(fieldExpr, COLLECTION_TYPE)) + ) + ) + ) + )); + } + + private Expression createIfInstanceOfAsImmutableS(Expression expr, ClassNode type, Expression elseStatement) { + return ternaryX(isInstanceOfX(expr, type), createAsImmutableX(expr, type), elseStatement); + } + + protected Expression createAsImmutableX(final Expression expr, final ClassNode type) { + return callX(DGM_TYPE, "asImmutable", castX(type, expr)); + } + + protected Statement createConstructorStatement(AbstractASTTransformation xform, ClassNode cNode, PropertyNode pNode, boolean namedArgs) { + List<AnnotationNode> annotations = cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_BASE_TYPE); + AnnotationNode annoImmutable = annotations.isEmpty() ? null : annotations.get(0); + final List<String> knownImmutableClasses = getKnownImmutableClasses(xform, annoImmutable); + final List<String> knownImmutables = ImmutablePropertyUtils.getKnownImmutables(xform, annoImmutable); + FieldNode fNode = pNode.getField(); + final ClassNode fType = fNode.getType(); + Statement statement; + if (ImmutablePropertyUtils.isKnownImmutableType(fType, knownImmutableClasses) || isKnownImmutable(pNode.getName(), knownImmutables)) { + statement = createConstructorStatementDefault(fNode, namedArgs); + } else if (fType.isArray() || implementsCloneable(fType)) { + statement = createConstructorStatementArrayOrCloneable(fNode, namedArgs); + } else if (derivesFromDate(fType)) { + statement = createConstructorStatementDate(fNode, namedArgs); + } else if (isOrImplements(fType, COLLECTION_TYPE) || fType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fType, MAP_TYPE) || fType.isDerivedFrom(MAP_TYPE)) { + statement = createConstructorStatementCollection(fNode, namedArgs); + } else if (fType.isResolved()) { + xform.addError(ImmutablePropertyUtils.createErrorMessage(cNode.getName(), fNode.getName(), fType.getName(), "compiling"), fNode); + statement = EmptyStatement.INSTANCE; + } else { + statement = createConstructorStatementGuarded(cNode, fNode, namedArgs, knownImmutables, knownImmutableClasses); + } + return statement; + } + + private static Statement createConstructorStatementDefault(FieldNode fNode, boolean namedArgs) { + final ClassNode fType = fNode.getType(); + final Expression fieldExpr = propX(varX("this"), fNode.getName()); + Expression initExpr = fNode.getInitialValueExpression(); + Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression)initExpr).isNullExpression())) { + if (ClassHelper.isPrimitiveType(fType)) { + assignInit = EmptyStatement.INSTANCE; + } else { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } + } else { + assignInit = assignS(fieldExpr, initExpr); + } + fNode.setInitialValueExpression(null); + Expression param = getParam(fNode, namedArgs); + Statement assignStmt = assignS(fieldExpr, castX(fType, param)); + return assignWithDefault(namedArgs, assignInit, param, assignStmt); + } + + private static Statement assignWithDefault(boolean namedArgs, Statement assignInit, Expression param, Statement assignStmt) { + if (!namedArgs) { + return assignStmt; + } + return ifElseS(equalsNullX(param), assignInit, assignStmt); + } + + private static Statement createConstructorStatementGuarded(ClassNode cNode, FieldNode fNode, boolean namedArgs, List<String> knownImmutables, List<String> knownImmutableClasses) { + final Expression fieldExpr = propX(varX("this"), fNode.getName()); + Expression initExpr = fNode.getInitialValueExpression(); + final Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } else { + assignInit = assignS(fieldExpr, checkUnresolved(fNode, initExpr, knownImmutables, knownImmutableClasses)); + } + Expression param = getParam(fNode, namedArgs); + Statement assignStmt = assignS(fieldExpr, checkUnresolved(fNode, param, knownImmutables, knownImmutableClasses)); + return assignWithDefault(namedArgs, assignInit, param, assignStmt); + } + + private static Expression checkUnresolved(FieldNode fNode, Expression value, List<String> knownImmutables, List<String> knownImmutableClasses) { + Expression args = args(callThisX("getClass"), constX(fNode.getName()), value, list2args(knownImmutables), classList2args(knownImmutableClasses)); + return callX(SELF_TYPE, "checkImmutable", args); + } + + private Statement createConstructorStatementCollection(FieldNode fNode, boolean namedArgs) { + final Expression fieldExpr = propX(varX("this"), fNode.getName()); + ClassNode fieldType = fieldExpr.getType(); + Expression initExpr = fNode.getInitialValueExpression(); + final Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } else { + assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, fieldType)); + } + Expression param = getParam(fNode, namedArgs); + Statement assignStmt = ifElseS( + isInstanceOfX(param, CLONEABLE_TYPE), + assignS(fieldExpr, cloneCollectionExpr(cloneArrayOrCloneableExpr(param, fieldType), fieldType)), + assignS(fieldExpr, cloneCollectionExpr(param, fieldType))); + return assignWithDefault(namedArgs, assignInit, param, assignStmt); + } + + private static Statement createConstructorStatementArrayOrCloneable(FieldNode fNode, boolean namedArgs) { + final Expression fieldExpr = propX(varX("this"), fNode.getName()); + final Expression initExpr = fNode.getInitialValueExpression(); + final ClassNode fieldType = fNode.getType(); + final Expression param = getParam(fNode, namedArgs); + final Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } else { + assignInit = assignS(fieldExpr, cloneArrayOrCloneableExpr(initExpr, fieldType)); + } + Statement assignStmt = assignS(fieldExpr, cloneArrayOrCloneableExpr(param, fieldType)); + return assignWithDefault(namedArgs, assignInit, param, assignStmt); + } + + private static Expression getParam(FieldNode fNode, boolean namedArgs) { + return namedArgs ? findArg(fNode.getName()) : varX(fNode.getName(), fNode.getType()); + } + + private static Statement createConstructorStatementDate(FieldNode fNode, boolean namedArgs) { + final Expression fieldExpr = propX(varX("this"), fNode.getName()); + Expression initExpr = fNode.getInitialValueExpression(); + final Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } else { + assignInit = assignS(fieldExpr, cloneDateExpr(initExpr)); + } + final Expression param = getParam(fNode, namedArgs); + Statement assignStmt = assignS(fieldExpr, cloneDateExpr(param)); + return assignWithDefault(namedArgs, assignInit, param, assignStmt); + } + + private static boolean isKnownImmutable(String fieldName, List<String> knownImmutables) { + return knownImmutables.contains(fieldName); + } + + protected Statement checkFinalArgNotOverridden(ClassNode cNode, FieldNode fNode) { + final String name = fNode.getName(); + Expression value = findArg(name); + return ifS( + notX(equalsNullX(value)), + throwS(ctorX(READONLYEXCEPTION_TYPE, + args(constX(name), constX(cNode.getName())) + ))); + } + + @Override + public boolean validateAttributes(AbstractASTTransformation xform, AnnotationNode anno) { + boolean success = isValidAttribute(xform, anno, "useSuper"); + return success; + } + + @Override + public boolean validateProperties(AbstractASTTransformation xform, BlockStatement body, ClassNode cNode, List<PropertyNode> props) { + if (xform instanceof MapConstructorASTTransformation) { + body.addStatement(ifS(equalsNullX(varX("args")), assignS(varX("args"), new MapExpression()))); + body.addStatement(stmt(callX(IMMUTABLE_XFORM_TYPE, "checkPropNames", args("this", "args")))); + } + return super.validateProperties(xform, body, cNode, props); + } + + @Override + public void createStatement(AbstractASTTransformation xform, AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) { + FieldNode fNode = pNode.getField(); + if (fNode.isFinal() && fNode.isStatic()) return; + if (fNode.isFinal() && fNode.getInitialExpression() != null) { + body.addStatement(checkFinalArgNotOverridden(cNode, fNode)); + } + body.addStatement(createConstructorStatement(xform, cNode, pNode, namedArgsMap != null)); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java b/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java new file mode 100644 index 0000000..5991f68 --- /dev/null +++ b/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java @@ -0,0 +1,99 @@ +/* + * 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 groovy.transform.construction; + +import org.codehaus.groovy.ast.AnnotationNode; +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.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.transform.AbstractASTTransformation; +import org.codehaus.groovy.transform.TupleConstructorASTTransformation; + +import java.util.HashMap; +import java.util.List; + +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.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.findArg; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.isOneX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +public class LegacyHashMapPropertyHandler extends ImmutablePropertyHandler { + private static final ClassNode HMAP_TYPE = makeWithoutCaching(HashMap.class, false); + + private Statement createLegacyConstructorStatementMapSpecial(FieldNode fNode) { + final Expression fieldExpr = varX(fNode); + final ClassNode fieldType = fieldExpr.getType(); + final Expression initExpr = fNode.getInitialValueExpression(); + final Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } else { + assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, fieldType)); + } + Expression namedArgs = findArg(fNode.getName()); + Expression baseArgs = varX("args"); + Statement assignStmt = ifElseS( + equalsNullX(namedArgs), + ifElseS( + isTrueX(callX(baseArgs, "containsKey", constX(fNode.getName()))), + assignS(fieldExpr, namedArgs), + assignS(fieldExpr, cloneCollectionExpr(baseArgs, fieldType))), + ifElseS( + isOneX(callX(baseArgs, "size")), + assignS(fieldExpr, cloneCollectionExpr(namedArgs, fieldType)), + assignS(fieldExpr, cloneCollectionExpr(baseArgs, fieldType))) + ); + return ifElseS(equalsNullX(baseArgs), assignInit, assignStmt); + } + + @Override + public boolean validateAttributes(AbstractASTTransformation xform, AnnotationNode anno) { + return !(xform instanceof TupleConstructorASTTransformation) && super.validateAttributes(xform, anno); + } + + @Override + public boolean validateProperties(AbstractASTTransformation xform, BlockStatement body, ClassNode cNode, List<PropertyNode> props) { + if (!(props.size() == 1 && props.get(0).getType().equals(HMAP_TYPE))) { + xform.addError("Error during " + xform.getAnnotationName() + " processing. Property handler " + getClass().getName() + " only accepts a single HashMap property", props.size() == 1 ? props.get(0) : cNode); + return false; + } + return true; + } + + @Override + public void createStatement(AbstractASTTransformation xform, AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) { + FieldNode fNode = pNode.getField(); + if (fNode.isFinal() && fNode.isStatic()) return; + if (fNode.isFinal() && fNode.getInitialExpression() != null) { + body.addStatement(checkFinalArgNotOverridden(cNode, fNode)); + } + body.addStatement(createLegacyConstructorStatementMapSpecial(fNode)); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/groovy/groovy/transform/construction/PropertyHandler.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/construction/PropertyHandler.java b/src/main/groovy/groovy/transform/construction/PropertyHandler.java new file mode 100644 index 0000000..f3170c2 --- /dev/null +++ b/src/main/groovy/groovy/transform/construction/PropertyHandler.java @@ -0,0 +1,76 @@ +/* + * 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 groovy.transform.construction; + +import groovy.lang.GroovyClassLoader; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.transform.AbstractASTTransformation; + +import java.util.List; + +public abstract class PropertyHandler { + public abstract boolean validateAttributes(AbstractASTTransformation xform, AnnotationNode anno); + + public boolean validateProperties(AbstractASTTransformation xform, BlockStatement body, ClassNode cNode, List<PropertyNode> props) { + return true; + } + + public abstract void createStatement(AbstractASTTransformation xform, AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, Parameter namedArgMap); + + protected boolean isValidAttribute(AbstractASTTransformation xform, AnnotationNode anno, String memberName) { + if (xform.getMemberValue(anno, memberName) != null) { + xform.addError("Error during " + xform.getAnnotationName() + " processing: Annotation attribute '" + memberName + + "' not supported for property handler " + getClass().getSimpleName(), anno); + return false; + } + return true; + } + + public static PropertyHandler createPropertyHandler(AbstractASTTransformation xform, AnnotationNode anno, GroovyClassLoader loader) { + ClassNode handlerClass = xform.getMemberClassValue(anno, "propertyHandler", ClassHelper.make(DefaultPropertyHandler.class)); + + if (handlerClass == null) { + xform.addError("Couldn't determine propertyHandler class", anno); + return null; + } + + String className = handlerClass.getName(); + try { + Object instance = loader.loadClass(className).newInstance(); + if (instance == null) { + xform.addError("Can't load propertyHandler '" + className + "'", anno); + return null; + } + if (!PropertyHandler.class.isAssignableFrom(instance.getClass())) { + xform.addError("The propertyHandler class '" + handlerClass.getName() + "' on " + xform.getAnnotationName() + " is not a propertyHandler", anno); + return null; + } + + return (PropertyHandler) instance; + } catch (Exception e) { + xform.addError("Can't load propertyHandler '" + className + "' " + e, anno); + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java b/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java new file mode 100644 index 0000000..e263f33 --- /dev/null +++ b/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java @@ -0,0 +1,233 @@ +/* + * 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.apache.groovy.ast.tools; + +import groovy.transform.ImmutableBase; +import groovy.transform.KnownImmutable; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.expr.ArrayExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.runtime.ReflectionMethodInvoker; +import org.codehaus.groovy.transform.AbstractASTTransformation; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.codehaus.groovy.ast.ClassHelper.make; +import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; +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.isOrImplements; + +public class ImmutablePropertyUtils { + private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class); + private static final ClassNode DATE_TYPE = make(Date.class); + private static final ClassNode REFLECTION_INVOKER_TYPE = make(ReflectionMethodInvoker.class); + private static final String KNOWN_IMMUTABLE_NAME = KnownImmutable.class.getName(); + private static final Class<? extends Annotation> MY_CLASS = ImmutableBase.class; + public static final ClassNode IMMUTABLE_BASE_TYPE = makeWithoutCaching(MY_CLASS, false); + private static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables"; + /* + Currently leaving BigInteger and BigDecimal in list but see: + http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370 + + Also, Color is not final so while not normally used with child + classes, it isn't strictly immutable. Use at your own risk. + + This list can by extended by providing "known immutable" classes + via Immutable.knownImmutableClasses + */ + private static Set<String> builtinImmutables = new HashSet<String>(Arrays.asList( + "java.lang.Class", + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.Character", + "java.lang.Double", + "java.lang.Float", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Short", + "java.lang.String", + "java.math.BigInteger", + "java.math.BigDecimal", + "java.awt.Color", + "java.net.URI", + "java.util.UUID", + "java.time.DayOfWeek", + "java.time.Duration", + "java.time.Instant", + "java.time.LocalDate", + "java.time.LocalDateTime", + "java.time.LocalTime", + "java.time.Month", + "java.time.MonthDay", + "java.time.OffsetDateTime", + "java.time.OffsetTime", + "java.time.Period", + "java.time.Year", + "java.time.YearMonth", + "java.time.ZonedDateTime", + "java.time.ZoneOffset", + "java.time.ZoneRegion", + "java.time.chrono.ChronoLocalDate", + "java.time.chrono.ChronoLocalDateTime", + "java.time.chrono.Chronology", + "java.time.chrono.ChronoPeriod", + "java.time.chrono.ChronoZonedDateTime", + "java.time.chrono.Era", + "java.time.format.DecimalStyle", + "java.time.format.FormatStyle", + "java.time.format.ResolverStyle", + "java.time.format.SignStyle", + "java.time.format.TextStyle", + "java.time.temporal.IsoFields", + "java.time.temporal.JulianFields", + "java.time.temporal.ValueRange", + "java.time.temporal.WeekFields" + )); + + private ImmutablePropertyUtils() { } + + public static Expression cloneArrayOrCloneableExpr(Expression fieldExpr, ClassNode type) { + Expression smce = callX( + REFLECTION_INVOKER_TYPE, + "invoke", + args( + fieldExpr, + constX("clone"), + new ArrayExpression(ClassHelper.OBJECT_TYPE.makeArray(), Collections.<Expression>emptyList()) + ) + ); + return castX(type, smce); + } + + public static boolean implementsCloneable(ClassNode fieldType) { + return isOrImplements(fieldType, CLONEABLE_TYPE); + } + + public static Expression cloneDateExpr(Expression origDate) { + return ctorX(DATE_TYPE, callX(origDate, "getTime")); + } + + public static boolean derivesFromDate(ClassNode fieldType) { + return fieldType.isDerivedFrom(DATE_TYPE); + } + + public static String createErrorMessage(String className, String fieldName, String typeName, String mode) { + return "Unsupported type (" + prettyTypeName(typeName) + ") found for field '" + fieldName + "' while " + mode + " immutable class " + className + ".\n" + + "Immutable classes only support properties with effectively immutable types including:\n" + + "- Strings, primitive types, wrapper types, Class, BigInteger and BigDecimal, enums\n" + + "- classes annotated with @KnownImmutable and known immutables (java.awt.Color, java.net.URI)\n" + + "- Cloneable classes, collections, maps and arrays, and other classes with special handling\n" + + " (java.util.Date and various java.time.* classes and interfaces)\n" + + "Other restrictions apply, please see the groovydoc for " + IMMUTABLE_BASE_TYPE.getNameWithoutPackage() + " for further details"; + } + + private static String prettyTypeName(String name) { + return name.equals("java.lang.Object") ? name + " or def" : name; + } + + public static boolean isKnownImmutableType(ClassNode fieldType, List<String> knownImmutableClasses) { + if (builtinOrDeemedType(fieldType, knownImmutableClasses)) + return true; + if (!fieldType.isResolved()) + return false; + if ("java.util.Optional".equals(fieldType.getName()) && fieldType.getGenericsTypes() != null && fieldType.getGenericsTypes().length == 1) { + GenericsType optionalType = fieldType.getGenericsTypes()[0]; + if (optionalType.isResolved() && !optionalType.isPlaceholder() && !optionalType.isWildcard()) { + ClassNode valueType = optionalType.getType(); + if (builtinOrDeemedType(valueType, knownImmutableClasses)) return true; + if (valueType.isEnum()) return true; + } + } + return fieldType.isEnum() || + ClassHelper.isPrimitiveType(fieldType) || + hasImmutableAnnotation(fieldType); + } + + private static boolean builtinOrDeemedType(ClassNode fieldType, List<String> knownImmutableClasses) { + return isBuiltinImmutable(fieldType.getName()) || knownImmutableClasses.contains(fieldType.getName()) || hasImmutableAnnotation(fieldType); + } + + private static boolean hasImmutableAnnotation(ClassNode type) { + List<AnnotationNode> annotations = type.getAnnotations(); + for (AnnotationNode next : annotations) { + String name = next.getClassNode().getName(); + if (matchingMarkerName(name)) return true; + } + return false; + } + + private static boolean matchingMarkerName(String name) { + return name.equals("groovy.transform.Immutable") || name.equals(KNOWN_IMMUTABLE_NAME); + } + + public static boolean isBuiltinImmutable(String typeName) { + return builtinImmutables.contains(typeName); + } + + private static boolean hasImmutableAnnotation(Class clazz) { + Annotation[] annotations = clazz.getAnnotations(); + for (Annotation next : annotations) { + String name = next.annotationType().getName(); + if (matchingMarkerName(name)) return true; + } + return false; + } + + public static boolean builtinOrMarkedImmutableClass(Class<?> clazz) { + return isBuiltinImmutable(clazz.getName()) || hasImmutableAnnotation(clazz); + } + + public static List<String> getKnownImmutables(AbstractASTTransformation xform, AnnotationNode node) { + final List<String> immutables = new ArrayList<String>(); + + if (node == null) return immutables; + final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLES); + if (expression == null) return immutables; + + if (!(expression instanceof ListExpression)) { + xform.addError("Use the Groovy list notation [el1, el2] to specify known immutable property names via \"" + MEMBER_KNOWN_IMMUTABLES + "\"", node); + return immutables; + } + + final ListExpression listExpression = (ListExpression) expression; + for (Expression listItemExpression : listExpression.getExpressions()) { + if (listItemExpression instanceof ConstantExpression) { + immutables.add((String) ((ConstantExpression) listItemExpression).getValue()); + } + } + + return immutables; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java ---------------------------------------------------------------------- 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 e4ea892..875cc48 100644 --- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java +++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java @@ -472,30 +472,38 @@ public class GeneralUtils { } public static List<PropertyNode> getAllProperties(Set<String> names, ClassNode origType, ClassNode cNode, boolean includeProperties, boolean includeFields, boolean includePseudoGetters, boolean includePseudoSetters, boolean traverseSuperClasses, boolean skipReadonly) { - return getAllProperties(names, origType, cNode, includeProperties, includeFields, includePseudoGetters, includePseudoSetters, traverseSuperClasses, skipReadonly, false); + return getAllProperties(names, origType, cNode, includeProperties, includeFields, includePseudoGetters, includePseudoSetters, traverseSuperClasses, skipReadonly, false, false, false); } - public static List<PropertyNode> getAllProperties(Set<String> names, ClassNode origType, ClassNode cNode, boolean includeProperties, boolean includeFields, boolean includePseudoGetters, boolean includePseudoSetters, boolean traverseSuperClasses, boolean skipReadonly, boolean reverse) { + public static List<PropertyNode> getAllProperties(Set<String> names, ClassNode origType, ClassNode cNode, boolean includeProperties, + boolean includeFields, boolean includePseudoGetters, boolean includePseudoSetters, + boolean traverseSuperClasses, boolean skipReadonly, boolean reverse, boolean allNames, boolean includeStatic) { final List<PropertyNode> result = new ArrayList<PropertyNode>(); if (cNode != ClassHelper.OBJECT_TYPE && traverseSuperClasses && !reverse) { result.addAll(getAllProperties(names, origType, cNode.getSuperClass(), includeProperties, includeFields, includePseudoGetters, includePseudoSetters, true, skipReadonly)); } if (includeProperties) { for (PropertyNode pNode : cNode.getProperties()) { - if (!pNode.isStatic() && !names.contains(pNode.getName())) { + if ((!pNode.isStatic() || includeStatic) && !names.contains(pNode.getName())) { result.add(pNode); names.add(pNode.getName()); } } if (includePseudoGetters || includePseudoSetters) { - BeanUtils.addPseudoProperties(origType, cNode, result, names, false, includePseudoGetters, includePseudoSetters); + BeanUtils.addPseudoProperties(origType, cNode, result, names, includeStatic, includePseudoGetters, includePseudoSetters); } } if (includeFields) { for (FieldNode fNode : cNode.getFields()) { - if (fNode.isStatic() || cNode.getProperty(fNode.getName()) != null || names.contains(fNode.getName())) { + if ((fNode.isStatic() && !includeStatic) || fNode.isSynthetic() || cNode.getProperty(fNode.getName()) != null || names.contains(fNode.getName())) { continue; } + + // internal field + if (fNode.getName().contains("$") && !allNames) { + continue; + } + if (fNode.isPrivate() && !cNode.equals(origType)) { continue; } http://git-wip-us.apache.org/repos/asf/groovy/blob/ba811fdf/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java index 4498e95..23cadd6 100644 --- a/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java +++ b/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java @@ -75,6 +75,15 @@ public abstract class AbstractASTTransformation implements Opcodes, ASTTransform return copiedAnnotations; } + /** + * If the transform is associated with a single annotation, returns a name suitable for displaying in error messages. + * + * @return The simple name of the annotation including the "@" or null if no such name is defined + */ + public String getAnnotationName() { + return null; + } + protected void init(ASTNode[] nodes, SourceUnit sourceUnit) { if (nodes == null || nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) { throw new GroovyBugError("Internal error: expecting [AnnotationNode, AnnotatedNode] but got: " + (nodes == null ? null : Arrays.asList(nodes))); @@ -434,15 +443,15 @@ public abstract class AbstractASTTransformation implements Opcodes, ASTTransform } protected boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties) { - return checkPropertyList(cNode, propertyNameList, listName, anno, typeName, includeFields, includeSuperProperties, allProperties, false); + return checkPropertyList(cNode, propertyNameList, listName, anno, typeName, includeFields, includeSuperProperties, allProperties, false, false); } - protected boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties, boolean includeSuperFields) { + protected boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties, boolean includeSuperFields, boolean includeStatic) { if (propertyNameList == null || propertyNameList.isEmpty()) { return true; } final List<String> pNames = new ArrayList<String>(); - for (PropertyNode pNode : BeanUtils.getAllProperties(cNode, includeSuperProperties, false, allProperties)) { + for (PropertyNode pNode : BeanUtils.getAllProperties(cNode, includeSuperProperties, includeStatic, allProperties)) { pNames.add(pNode.getField().getName()); } boolean result = true;
