You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/02/14 21:39:54 UTC
[1/3] groovy git commit: GROOVY-8477: @Immutable-related
transformations should be more configurable
Repository: groovy
Updated Branches:
refs/heads/master 011b98e13 -> e7aeae058
http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
index 5fb6874..ad4001f 100644
--- a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
@@ -20,9 +20,8 @@ package org.codehaus.groovy.transform;
import groovy.lang.MetaClass;
import groovy.lang.MissingPropertyException;
-import groovy.lang.ReadOnlyPropertyException;
import groovy.transform.ImmutableBase;
-import groovy.transform.KnownImmutable;
+import org.apache.groovy.ast.tools.ImmutablePropertyUtils;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
@@ -31,42 +30,35 @@ import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
-import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.VariableScope;
-import org.codehaus.groovy.ast.expr.ArrayExpression;
-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.expr.VariableExpression;
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.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
-import org.codehaus.groovy.runtime.ReflectionMethodInvoker;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import static org.codehaus.groovy.ast.ClassHelper.make;
+import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.builtinOrMarkedImmutableClass;
+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.createErrorMessage;
+import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.derivesFromDate;
+import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.getKnownImmutables;
+import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.implementsCloneable;
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;
@@ -74,33 +66,24 @@ 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.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.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX;
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.getGetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceProperties;
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.isInstanceOfX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.isOneX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.list2args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.neX;
-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.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.safeExpression;
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;
/**
@@ -108,88 +91,14 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
*/
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class ImmutableASTTransformation extends AbstractASTTransformation {
-
- /*
- 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(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 static final String KNOWN_IMMUTABLE_NAME = KnownImmutable.class.getName();
private static final Class<? extends Annotation> MY_CLASS = ImmutableBase.class;
- private static final ClassNode IMMUTABLE_BASE_TYPE = makeWithoutCaching(MY_CLASS, false);
- public static final ClassNode MY_TYPE = make(MY_CLASS);
- static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
- private static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = "knownImmutableClasses";
- private static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables";
+ public static final ClassNode MY_TYPE = makeWithoutCaching(MY_CLASS, false);
+ private static final String MY_TYPE_NAME = MY_TYPE.getNameWithoutPackage();
+
private static final String MEMBER_ADD_COPY_WITH = "copyWith";
private static final String COPY_WITH_METHOD = "copyWith";
- private static final ClassNode DATE_TYPE = make(Date.class);
- private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class);
- private static final ClassNode COLLECTION_TYPE = makeWithoutCaching(Collection.class, false);
- private static final ClassNode READONLYEXCEPTION_TYPE = make(ReadOnlyPropertyException.class);
- private static final ClassNode DGM_TYPE = make(DefaultGroovyMethods.class);
- private static final ClassNode SELF_TYPE = make(ImmutableASTTransformation.class);
private static final ClassNode HMAP_TYPE = makeWithoutCaching(HashMap.class, false);
- private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, false);
- private static final ClassNode REFLECTION_INVOKER_TYPE = make(ReflectionMethodInvoker.class);
- 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);
public static final String IMMUTABLE_SAFE_FLAG = "Immutable.Safe";
public void visit(ASTNode[] nodes, SourceUnit source) {
@@ -233,7 +142,7 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
if (unsupportedTupleAttribute(tupleCons, "includeProperties")) return;
if (unsupportedTupleAttribute(tupleCons, "includeSuperFields")) return;
if (unsupportedTupleAttribute(tupleCons, "callSuper")) return;
- if (unsupportedTupleAttribute(tupleCons, "useSetters")) return;
+// if (unsupportedTupleAttribute(tupleCons, "useSetters")) return;
if (unsupportedTupleAttribute(tupleCons, "force")) return;
}
if (!validateConstructors(cNode)) return;
@@ -253,7 +162,7 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
return false;
}
- private static void doAddConstructor(final ClassNode cNode, final ConstructorNode constructorNode) {
+ static void doAddConstructor(final ClassNode cNode, final ConstructorNode constructorNode) {
cNode.addConstructor(constructorNode);
// GROOVY-5814: Immutable is not compatible with @CompileStatic
Parameter argsParam = null;
@@ -283,50 +192,6 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
}
}
- 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;
- }
-
- private 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;
- }
-
private static void makeClassFinal(AbstractASTTransformation xform, ClassNode cNode) {
int modifiers = cNode.getModifiers();
if ((modifiers & ACC_FINAL) == 0) {
@@ -390,90 +255,6 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
return stmt(fieldExpr);
}
- private static 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 static Expression createIfInstanceOfAsImmutableS(Expression expr, ClassNode type, Expression elseStatement) {
- return ternaryX(isInstanceOfX(expr, type), createAsImmutableX(expr, type), elseStatement);
- }
-
- private static Expression createAsImmutableX(final Expression expr, final ClassNode type) {
- return callX(DGM_TYPE, "asImmutable", castX(type, expr));
- }
-
- private 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);
- }
-
- static void createConstructorMapCommon(ClassNode cNode, BlockStatement body, Parameter[] params) {
- final List<FieldNode> fList = cNode.getFields();
- for (FieldNode fNode : fList) {
- if (fNode.isPublic()) continue; // public fields will be rejected elsewhere
- if (cNode.getProperty(fNode.getName()) != null) continue; // a property
- if (fNode.isFinal() && fNode.isStatic()) continue;
- if (fNode.getName().contains("$") || fNode.isSynthetic()) continue; // internal field
- if (fNode.isFinal() && fNode.getInitialExpression() != null)
- body.addStatement(checkFinalArgNotOverridden(cNode, fNode));
- body.addStatement(createConstructorStatementDefault(fNode, true));
- }
- doAddConstructor(cNode, new ConstructorNode(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, body));
- }
-
- private static 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()))
- )));
- }
-
- static Statement createConstructorStatementMapSpecial(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);
- }
-
private static void ensureNotPublic(AbstractASTTransformation xform, String cNode, FieldNode fNode) {
String fName = fNode.getName();
// TODO: do we need to lock down things like: $ownClass
@@ -507,137 +288,11 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
}
static boolean makeImmutable(ClassNode cNode) {
- List<AnnotationNode> annotations = cNode.getAnnotations(IMMUTABLE_BASE_TYPE);
+ List<AnnotationNode> annotations = cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_BASE_TYPE);
AnnotationNode annoImmutable = annotations.isEmpty() ? null : annotations.get(0);
return annoImmutable != null;
}
- static Statement createConstructorStatement(AbstractASTTransformation xform, ClassNode cNode, PropertyNode pNode, boolean namedArgs) {
- List<AnnotationNode> annotations = cNode.getAnnotations(IMMUTABLE_BASE_TYPE);
- AnnotationNode annoImmutable = annotations.isEmpty() ? null : annotations.get(0);
- final List<String> knownImmutableClasses = getKnownImmutableClasses(xform, annoImmutable);
- final List<String> knownImmutables = getKnownImmutables(xform, annoImmutable);
- FieldNode fNode = pNode.getField();
- final ClassNode fieldType = fNode.getType();
- Statement statement;
- if (isKnownImmutableType(fieldType, knownImmutableClasses) || isKnownImmutable(pNode.getName(), knownImmutables)) {
- statement = createConstructorStatementDefault(fNode, namedArgs);
- } else if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
- statement = createConstructorStatementArrayOrCloneable(fNode, namedArgs);
- } else if (fieldType.isDerivedFrom(DATE_TYPE)) {
- statement = createConstructorStatementDate(fNode, namedArgs);
- } else if (isOrImplements(fieldType, COLLECTION_TYPE) || fieldType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fieldType, MAP_TYPE) || fieldType.isDerivedFrom(MAP_TYPE)) {
- statement = createConstructorStatementCollection(fNode, namedArgs);
- } else if (fieldType.isResolved()) {
- xform.addError(createErrorMessage(cNode.getName(), fNode.getName(), fieldType.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 static 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 Expression cloneDateExpr(Expression origDate) {
- return ctorX(DATE_TYPE, callX(origDate, "getTime"));
- }
-
private static void adjustPropertyForImmutability(PropertyNode pNode, List<PropertyNode> newNodes) {
final FieldNode fNode = pNode.getField();
fNode.setModifiers((pNode.getModifiers() & (~ACC_PUBLIC)) | ACC_FINAL | ACC_PRIVATE);
@@ -654,9 +309,9 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
BlockStatement body = new BlockStatement();
final ClassNode fieldType = fNode.getType();
final Statement statement;
- if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
+ if (fieldType.isArray() || implementsCloneable(fieldType)) {
statement = createGetterBodyArrayOrCloneable(fNode);
- } else if (fieldType.isDerivedFrom(DATE_TYPE)) {
+ } else if (derivesFromDate(fieldType)) {
statement = createGetterBodyDate(fNode);
} else {
statement = createGetterBodyDefault(fNode);
@@ -665,20 +320,6 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
return body;
}
- private 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 " + MY_TYPE_NAME + " for further details";
- }
-
- private static String prettyTypeName(String name) {
- return name.equals("java.lang.Object") ? name + " or def" : name;
- }
-
private static Statement createGetterBodyArrayOrCloneable(FieldNode fNode) {
final Expression fieldExpr = varX(fNode);
final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType());
@@ -791,7 +432,7 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
*/
@SuppressWarnings("Unchecked")
public static Object checkImmutable(String className, String fieldName, Object field) {
- if (field == null || field instanceof Enum || isBuiltinImmutable(field.getClass().getName())) return field;
+ if (field == null || field instanceof Enum || ImmutablePropertyUtils.isBuiltinImmutable(field.getClass().getName())) return field;
if (field instanceof Collection) return DefaultGroovyMethods.asImmutable((Collection) field);
if (getAnnotationByName(field, "groovy.transform.Immutable") != null) return field;
@@ -882,62 +523,6 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
throw new RuntimeException(createErrorMessage(clazz.getName(), fieldName, typeName, "constructing"));
}
- private 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 hasImmutableAnnotation(Class clazz) {
- Annotation[] annotations = clazz.getAnnotations();
- for (Annotation next : annotations) {
- String name = next.annotationType().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);
- }
-
- private static boolean isKnownImmutable(String fieldName, List<String> knownImmutables) {
- return knownImmutables.contains(fieldName);
- }
-
- private static boolean isBuiltinImmutable(String typeName) {
- return builtinImmutables.contains(typeName);
- }
-
- private static boolean builtinOrMarkedImmutableClass(Class<?> clazz) {
- return isBuiltinImmutable(clazz.getName()) || hasImmutableAnnotation(clazz);
- }
-
public static void checkPropNames(Object instance, Map<String, Object> args) {
final MetaClass metaClass = InvokerHelper.getMetaClass(instance);
for (String k : args.keySet()) {
http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
index bb6becb..3859f72 100644
--- a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -18,7 +18,10 @@
*/
package org.codehaus.groovy.transform;
+import groovy.lang.GroovyClassLoader;
+import groovy.transform.CompilationUnitAware;
import groovy.transform.MapConstructor;
+import groovy.transform.construction.PropertyHandler;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
@@ -28,7 +31,6 @@ import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.DynamicVariable;
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.ClosureExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MapExpression;
@@ -36,6 +38,7 @@ 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.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
@@ -50,38 +53,33 @@ import static org.apache.groovy.ast.tools.ClassNodeUtils.hasNoArgConstructor;
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.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuperAdjustment;
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.getAllProperties;
-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.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.stmt;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
-import static org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorMapCommon;
-import static org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorStatement;
-import static org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorStatementMapSpecial;
+import static org.codehaus.groovy.transform.ImmutableASTTransformation.doAddConstructor;
/**
* Handles generation of code for the @MapConstructor annotation.
*/
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
-public class MapConstructorASTTransformation extends AbstractASTTransformation {
+public class MapConstructorASTTransformation extends AbstractASTTransformation implements CompilationUnitAware {
+
+ private CompilationUnit compilationUnit;
static final Class MY_CLASS = MapConstructor.class;
static final ClassNode MY_TYPE = make(MY_CLASS);
static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, false);
- private static final ClassNode IMMUTABLE_XFORM_TYPE = make(ImmutableASTTransformation.class);
private static final ClassNode LHMAP_TYPE = makeWithoutCaching(LinkedHashMap.class, false);
+ @Override
+ public String getAnnotationName() {
+ return MY_TYPE_NAME;
+ }
+
public void visit(ASTNode[] nodes, SourceUnit source) {
init(nodes, source);
AnnotatedNode parent = (AnnotatedNode) nodes[1];
@@ -95,9 +93,11 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
boolean includeProperties = !memberHasValue(anno, "includeProperties", false);
boolean includeSuperProperties = memberHasValue(anno, "includeSuperProperties", true);
boolean includeSuperFields = memberHasValue(anno, "includeSuperFields", true);
- boolean useSetters = memberHasValue(anno, "useSetters", true);
+// boolean useSetters = memberHasValue(anno, "useSetters", true);
+ boolean includeStatic = memberHasValue(anno, "includeStatic", true);
boolean allProperties = memberHasValue(anno, "allProperties", true);
boolean noArg = memberHasValue(anno, "noArg", true);
+ boolean specialNamedArgHandling = !memberHasValue(anno, "specialNamedArgHandling", false);
List<String> excludes = getMemberStringList(anno, "excludes");
List<String> includes = getMemberStringList(anno, "includes");
boolean allNames = memberHasValue(anno, "allNames", true);
@@ -106,6 +106,10 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
return;
if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, false))
return;
+ final GroovyClassLoader classLoader = compilationUnit != null ? compilationUnit.getTransformLoader() : source.getClassLoader();
+ final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, anno, classLoader);
+ if (handler == null) return;
+ if (!handler.validateAttributes(this, anno)) return;
Expression pre = anno.getMember("pre");
if (pre != null && !(pre instanceof ClosureExpression)) {
@@ -118,7 +122,7 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
return;
}
- createConstructors(this, cNode, includeFields, includeProperties, includeSuperProperties, includeSuperFields, useSetters, noArg, allNames, allProperties, excludes, includes, (ClosureExpression) pre, (ClosureExpression) post, source);
+ createConstructors(this, anno, handler, cNode, includeFields, includeProperties, includeSuperProperties, includeSuperFields, noArg, allNames, allProperties, specialNamedArgHandling, includeStatic, excludes, includes, (ClosureExpression) pre, (ClosureExpression) post, source);
if (pre != null) {
anno.setMember("pre", new ClosureExpression(new Parameter[0], EmptyStatement.INSTANCE));
@@ -129,7 +133,10 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
}
}
- private static void createConstructors(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperProperties, boolean includeSuperFields, boolean useSetters, boolean noArg, boolean allNames, boolean allProperties, List<String> excludes, List<String> includes, ClosureExpression pre, ClosureExpression post, SourceUnit source) {
+ private static void createConstructors(AbstractASTTransformation xform, AnnotationNode anno, PropertyHandler handler, ClassNode cNode, boolean includeFields, boolean includeProperties,
+ boolean includeSuperProperties, boolean includeSuperFields, boolean noArg,
+ boolean allNames, boolean allProperties, boolean specialNamedArgHandling, boolean includeStatic,
+ List<String> excludes, List<String> includes, ClosureExpression pre, ClosureExpression post, SourceUnit source) {
List<ConstructorNode> constructors = cNode.getDeclaredConstructors();
boolean foundEmpty = constructors.size() == 1 && constructors.get(0).getFirstStatement() == null;
// HACK: JavaStubGenerator could have snuck in a constructor we don't want
@@ -138,13 +145,11 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
Set<String> names = new HashSet<String>();
List<PropertyNode> superList;
if (includeSuperProperties || includeSuperFields) {
- superList = getAllProperties(names, cNode, cNode.getSuperClass(), includeSuperProperties, includeSuperFields, false, allProperties, true, true);
+ superList = getAllProperties(names, cNode, cNode.getSuperClass(), includeSuperProperties, includeSuperFields, false, allProperties, true, false, false, allNames, includeStatic);
} else {
superList = new ArrayList<PropertyNode>();
}
- List<PropertyNode> list = getAllProperties(names, cNode, true, includeFields, false, allProperties, false, true);
-
- boolean makeImmutable = ImmutableASTTransformation.makeImmutable(cNode);
+ List<PropertyNode> list = getAllProperties(names, cNode, cNode, includeProperties, includeFields, false, allProperties, false, false, false, allNames, includeStatic);
Parameter map = param(MAP_TYPE, "args");
final BlockStatement body = new BlockStatement();
@@ -155,39 +160,30 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
}
final BlockStatement inner = new BlockStatement();
superList.addAll(list);
- boolean specialNamedArgCase = ImmutableASTTransformation.isSpecialNamedArgCase(superList, true);
- if (!specialNamedArgCase) {
- processProps(xform, cNode, makeImmutable, useSetters, allNames, excludes, includes, superList, map, inner);
- body.addStatement(ifS(equalsNullX(varX("args")), assignS(varX("args"), new MapExpression())));
+
+ if (!handler.validateProperties(xform, body, cNode, superList)) {
+ return;
}
+
+ boolean specialNamedArgCase = specialNamedArgHandling && ImmutableASTTransformation.isSpecialNamedArgCase(superList, true);
+ processProps(xform, anno, cNode, handler, allNames, excludes, includes, superList, map, inner);
body.addStatement(inner);
+ Parameter[] params = params(specialNamedArgCase ? new Parameter(LHMAP_TYPE, "args") : map);
if (post != null) {
ClosureExpression transformed = (ClosureExpression) transformer.transform(post);
body.addStatement(transformed.getCode());
}
- if (makeImmutable && !specialNamedArgCase) {
- body.addStatement(stmt(callX(IMMUTABLE_XFORM_TYPE, "checkPropNames", args("this", "args"))));
- createConstructorMapCommon(cNode, body, params(map));
- } else if (specialNamedArgCase) {
- inner.addStatement(createConstructorStatementMapSpecial(superList.get(0).getField()));
- createConstructorMapCommon(cNode, body, params(new Parameter(LHMAP_TYPE, "args")));
- } else {
- cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params(map), ClassNode.EMPTY_ARRAY, body));
- }
- if (noArg && !superList.isEmpty() && !hasNoArgConstructor(cNode) && !specialNamedArgCase) {
+ doAddConstructor(cNode, new ConstructorNode(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, body));
+ if (noArg && !superList.isEmpty() && !hasNoArgConstructor(cNode)/* && !specialNamedArgCase*/) {
createNoArgConstructor(cNode);
}
}
- private static void processProps(AbstractASTTransformation xform, ClassNode cNode, boolean makeImmutable, boolean useSetters, boolean allNames, List<String> excludes, List<String> includes, List<PropertyNode> superList, Parameter map, BlockStatement inner) {
+ private static void processProps(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, PropertyHandler handler, boolean allNames, List<String> excludes, List<String> includes, List<PropertyNode> superList, Parameter map, BlockStatement inner) {
for (PropertyNode pNode : superList) {
String name = pNode.getName();
if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
- if (makeImmutable) {
- inner.addStatement(createConstructorStatement(xform, cNode, pNode, true));
- } else {
- assignField(useSetters, map, inner, name);
- }
+ handler.createStatement(xform, anno, inner, cNode, pNode, map);
}
}
@@ -196,13 +192,6 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body));
}
- private static void assignField(boolean useSetters, Parameter map, BlockStatement body, String name) {
- ArgumentListExpression nameArg = args(constX(name));
- body.addStatement(ifS(callX(varX(map), "containsKey", nameArg), useSetters ?
- stmt(callThisX(getSetterName(name), callX(varX(map), "get", nameArg))) :
- assignS(propX(varX("this"), name), callX(varX(map), "get", nameArg))));
- }
-
private static ClassCodeExpressionTransformer makeMapTypedArgsTransformer() {
return new ClassCodeExpressionTransformer() {
@Override
@@ -228,4 +217,8 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
};
}
+ @Override
+ public void setCompilationUnit(CompilationUnit unit) {
+ this.compilationUnit = unit;
+ }
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
index fc7daa9..018079d 100644
--- a/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/ToStringASTTransformation.java
@@ -192,11 +192,11 @@ public class ToStringASTTransformation extends AbstractASTTransformation {
Set<String> names = new HashSet<String>();
List<PropertyNode> superList;
if (includeSuperProperties || includeSuperFields) {
- superList = getAllProperties(names, cNode, cNode.getSuperClass(), includeSuperProperties, includeSuperFields, allProperties, false, true, true, true);
+ superList = getAllProperties(names, cNode, cNode.getSuperClass(), includeSuperProperties, includeSuperFields, allProperties, false, true, true, true, allNames, false);
} else {
superList = new ArrayList<PropertyNode>();
}
- List<PropertyNode> list = getAllProperties(names, cNode, true, includeFields, allProperties, false, false, true);
+ List<PropertyNode> list = getAllProperties(names, cNode, cNode,true, includeFields, allProperties, false, false, true, false, allNames, false);
list.addAll(superList);
for (PropertyNode pNode : list) {
http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
index 278ad8f..13decb9 100644
--- a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -18,8 +18,11 @@
*/
package org.codehaus.groovy.transform;
+import groovy.lang.GroovyClassLoader;
+import groovy.transform.CompilationUnitAware;
import groovy.transform.MapConstructor;
import groovy.transform.TupleConstructor;
+import groovy.transform.construction.PropertyHandler;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
@@ -37,6 +40,7 @@ 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.classgen.VariableScopeVisitor;
+import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
@@ -71,15 +75,15 @@ 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.throwS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
-import static org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorStatement;
import static org.codehaus.groovy.transform.ImmutableASTTransformation.makeImmutable;
/**
* Handles generation of code for the @TupleConstructor annotation.
*/
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
-public class TupleConstructorASTTransformation extends AbstractASTTransformation {
+public class TupleConstructorASTTransformation extends AbstractASTTransformation implements CompilationUnitAware {
+ private CompilationUnit compilationUnit;
static final Class MY_CLASS = TupleConstructor.class;
static final ClassNode MY_TYPE = make(MY_CLASS);
static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
@@ -103,6 +107,11 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
primitivesInitialValues.put(boolean.class, ConstantExpression.FALSE);
}
+ @Override
+ public String getAnnotationName() {
+ return MY_TYPE_NAME;
+ }
+
public void visit(ASTNode[] nodes, SourceUnit source) {
init(nodes, source);
AnnotatedNode parent = (AnnotatedNode) nodes[1];
@@ -116,17 +125,18 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
boolean includeProperties = !memberHasValue(anno, "includeProperties", false);
boolean includeSuperFields = memberHasValue(anno, "includeSuperFields", true);
boolean includeSuperProperties = memberHasValue(anno, "includeSuperProperties", true);
- boolean callSuper = memberHasValue(anno, "callSuper", true);
- boolean force = memberHasValue(anno, "force", true);
- boolean defaults = !memberHasValue(anno, "defaults", false);
- boolean useSetters = memberHasValue(anno, "useSetters", true);
boolean allProperties = memberHasValue(anno, "allProperties", true);
List<String> excludes = getMemberStringList(anno, "excludes");
List<String> includes = getMemberStringList(anno, "includes");
boolean allNames = memberHasValue(anno, "allNames", true);
if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, MY_TYPE_NAME)) return;
- if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, false, includeSuperFields)) return;
- if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, false, includeSuperFields)) return;
+ if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, allProperties, includeSuperFields, false)) return;
+ if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, allProperties, includeSuperFields, false)) return;
+ final GroovyClassLoader classLoader = compilationUnit != null ? compilationUnit.getTransformLoader() : source.getClassLoader();
+ final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, anno, classLoader);
+ if (handler == null) return;
+ if (!handler.validateAttributes(this, anno)) return;
+
Expression pre = anno.getMember("pre");
if (pre != null && !(pre instanceof ClosureExpression)) {
addError("Expected closure value for annotation parameter 'pre'. Found " + pre, cNode);
@@ -138,9 +148,9 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
return;
}
- createConstructor(this, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties,
- callSuper, force, excludes, includes, useSetters, defaults, allNames, allProperties,
- sourceUnit, (ClosureExpression) pre, (ClosureExpression) post);
+ createConstructor(this, anno, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties,
+ excludes, includes, allNames, allProperties,
+ sourceUnit, handler, (ClosureExpression) pre, (ClosureExpression) post);
if (pre != null) {
anno.setMember("pre", new ClosureExpression(new Parameter[0], EmptyStatement.INSTANCE));
@@ -151,32 +161,14 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
}
- public static void createConstructor(ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List<String> excludes, List<String> includes, boolean useSetters) {
- createConstructor(null, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, true);
- }
-
- public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List<String> excludes, List<String> includes, boolean useSetters, boolean defaults) {
- createConstructor(xform, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults, false);
- }
-
- public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List<String> excludes, List<String> includes, boolean useSetters, boolean defaults, boolean allNames) {
- createConstructor(xform, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties,
- callSuper, force, excludes, includes, useSetters, defaults, false, null, null, null);
- }
-
- public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields,
- boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties,
- boolean callSuper, boolean force, List<String> excludes, final List<String> includes,
- boolean useSetters, boolean defaults, boolean allNames,
- SourceUnit sourceUnit, ClosureExpression pre, ClosureExpression post) {
- createConstructor(xform, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults, allNames,false, sourceUnit, pre, post);
- }
-
- public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields,
- boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties,
- boolean callSuper, boolean force, List<String> excludes, final List<String> includes,
- boolean useSetters, boolean defaults, boolean allNames, boolean allProperties,
- SourceUnit sourceUnit, ClosureExpression pre, ClosureExpression post) {
+ private static void createConstructor(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, boolean includeFields,
+ boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties,
+ List<String> excludes, final List<String> includes, boolean allNames, boolean allProperties,
+ SourceUnit sourceUnit, PropertyHandler handler, ClosureExpression pre, ClosureExpression post) {
+ boolean callSuper = xform.memberHasValue(anno, "callSuper", true);
+ boolean force = xform.memberHasValue(anno, "force", true);
+ boolean defaults = !xform.memberHasValue(anno, "defaults", false);
+ boolean useSetters = xform.memberHasValue(anno, "useSetters", true);
Set<String> names = new HashSet<String>();
List<PropertyNode> superList;
if (includeSuperProperties || includeSuperFields) {
@@ -205,7 +197,15 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
"closure and also 'callSuper' enabled", cNode);
}
}
+
final BlockStatement body = new BlockStatement();
+
+ List<PropertyNode> tempList = new ArrayList<PropertyNode>(list);
+ tempList.addAll(superList);
+ if (!handler.validateProperties(xform, body, cNode, tempList)) {
+ return;
+ }
+
for (PropertyNode pNode : superList) {
String name = pNode.getName();
FieldNode fNode = pNode.getField();
@@ -215,15 +215,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
if (callSuper) {
superParams.add(varX(name));
} else if (!superInPre && !specialNamedArgCase) {
- if (makeImmutable) {
- body.addStatement(createConstructorStatement(xform, cNode, pNode, false));
- } else {
- if (useSetters && hasSetter) {
- body.addStatement(stmt(callThisX(getSetterName(name), varX(name))));
- } else {
- body.addStatement(assignS(propX(varX("this"), name), varX(name)));
- }
- }
+ handler.createStatement(xform, anno, body, cNode, pNode, null);
}
}
if (callSuper) {
@@ -239,16 +231,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
Parameter nextParam = createParam(fNode, name, defaults, xform, makeImmutable);
params.add(nextParam);
- boolean hasSetter = cNode.getProperty(name) != null && !fNode.isFinal();
- if (makeImmutable) {
- body.addStatement(createConstructorStatement(xform, cNode, pNode, false));
- } else {
- if (useSetters && hasSetter) {
- body.addStatement(stmt(callThisX(getSetterName(name), varX(nextParam))));
- } else {
- body.addStatement(assignS(propX(varX("this"), name), varX(nextParam)));
- }
- }
+ handler.createStatement(xform, anno, body, cNode, pNode, null);
}
if (post != null) {
@@ -265,9 +248,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
boolean hasMapCons = hasAnnotation(cNode, MapConstructorASTTransformation.MY_TYPE);
- if (!specialNamedArgCase || !hasMapCons) {
- cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params.toArray(new Parameter[params.size()]), ClassNode.EMPTY_ARRAY, body));
- }
+ cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params.toArray(new Parameter[params.size()]), ClassNode.EMPTY_ARRAY, body));
if (sourceUnit != null && !body.isEmpty()) {
VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(sourceUnit);
@@ -346,4 +327,9 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
block.addStatement(stmt(callX(CHECK_METHOD_TYPE, "checkPropNames", args(varX("this"), namedArgs))));
return block;
}
+
+ @Override
+ public void setCompilationUnit(CompilationUnit unit) {
+ this.compilationUnit = unit;
+ }
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
index e457855..2d817e5 100644
--- a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
@@ -230,7 +230,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
void testImmutableWithHashMap() {
assertScript """
import groovy.transform.Immutable
- @Immutable final class HasHashMap {
+ @Immutable(propertyHandler = groovy.transform.construction.LegacyHashMapPropertyHandler, noArg = false)
+ final class HasHashMap {
HashMap map = [d:4]
}
assert new HasHashMap([a:1]).map == [a:1]
@@ -413,7 +414,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
void testPrivateFieldAssignedViaConstructor() {
assertScript '''
import groovy.transform.Immutable
- @Immutable class Numbers {
+ @Immutable(includeStatic = true)
+ class Numbers {
private int a1 = 1
private int b1 = -1
private int c1
[2/3] groovy git commit: GROOVY-8477: @Immutable-related
transformations should be more configurable
Posted by pa...@apache.org.
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/69fc7ab3
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/69fc7ab3
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/69fc7ab3
Branch: refs/heads/master
Commit: 69fc7ab3c4055192fce096f69f8d4a90768863ac
Parents: 011b98e
Author: paulk <pa...@asert.com.au>
Authored: Tue Feb 13 15:06:07 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Feb 15 07:37:12 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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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/69fc7ab3/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;
[3/3] groovy git commit: GROOVY-8471: Contents of
META-INF/services/org.codehaus.groovy.source.Extensions conflict with
Maven/Jisaw
Posted by pa...@apache.org.
GROOVY-8471: Contents of META-INF/services/org.codehaus.groovy.source.Extensions conflict with Maven/Jisaw
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/e7aeae05
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/e7aeae05
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/e7aeae05
Branch: refs/heads/master
Commit: e7aeae058f6d98f556f29068bbc51ec778e4cf6c
Parents: 69fc7ab
Author: paulk <pa...@asert.com.au>
Authored: Wed Feb 14 13:16:22 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Feb 15 07:38:42 2018 +1000
----------------------------------------------------------------------
.../groovy/control/SourceExtensionHandler.java | 5 ++++-
.../org.codehaus.groovy.source.Extensions | 22 ++++++++++++++++++++
.../org.codehaus.groovy.source.Extensions | 22 --------------------
3 files changed, 26 insertions(+), 23 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/groovy/blob/e7aeae05/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java b/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
index a12cae1..774b128 100644
--- a/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
+++ b/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java
@@ -37,7 +37,10 @@ public class SourceExtensionHandler {
Set<String> extensions = new LinkedHashSet<String>();
extensions.add("groovy");
try {
- Enumeration<URL> globalServices = loader.getResources("META-INF/services/org.codehaus.groovy.source.Extensions");
+ Enumeration<URL> globalServices = loader.getResources("META-INF/groovy/org.codehaus.groovy.source.Extensions");
+ if (!globalServices.hasMoreElements()) {
+ globalServices = loader.getResources("META-INF/services/org.codehaus.groovy.source.Extensions");
+ }
while (globalServices.hasMoreElements()) {
BufferedReader svcIn = null;
URL service = globalServices.nextElement();
http://git-wip-us.apache.org/repos/asf/groovy/blob/e7aeae05/src/resources/META-INF/groovy/org.codehaus.groovy.source.Extensions
----------------------------------------------------------------------
diff --git a/src/resources/META-INF/groovy/org.codehaus.groovy.source.Extensions b/src/resources/META-INF/groovy/org.codehaus.groovy.source.Extensions
new file mode 100644
index 0000000..c17fe8b
--- /dev/null
+++ b/src/resources/META-INF/groovy/org.codehaus.groovy.source.Extensions
@@ -0,0 +1,22 @@
+# 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.
+
+# Format: one extension on each line without the leading "*."
+# Listed below are default groovy source file extensions.
+
+# NOTE: This implementation of supporting multiple file extensions is experimental and
+# the exact implementation details may vary when modularization gets introduced in
+# groovy 2.0. However, in terms of the behavior, this support will remain intact.
+groovy
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/groovy/blob/e7aeae05/src/resources/META-INF/services/org.codehaus.groovy.source.Extensions
----------------------------------------------------------------------
diff --git a/src/resources/META-INF/services/org.codehaus.groovy.source.Extensions b/src/resources/META-INF/services/org.codehaus.groovy.source.Extensions
deleted file mode 100644
index c17fe8b..0000000
--- a/src/resources/META-INF/services/org.codehaus.groovy.source.Extensions
+++ /dev/null
@@ -1,22 +0,0 @@
-# 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.
-
-# Format: one extension on each line without the leading "*."
-# Listed below are default groovy source file extensions.
-
-# NOTE: This implementation of supporting multiple file extensions is experimental and
-# the exact implementation details may vary when modularization gets introduced in
-# groovy 2.0. However, in terms of the behavior, this support will remain intact.
-groovy
\ No newline at end of file