Repository: incubator-groovy Updated Branches: refs/heads/master f568e2392 -> 3844f2835
GROOVY-6319: Canonical annotation should allow property names in toString (Closes #21) Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/3844f283 Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/3844f283 Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/3844f283 Branch: refs/heads/master Commit: 3844f28357a73fbdd178ebc1193475cbc1ff1437 Parents: f568e23 Author: Paul King <pa...@asert.com.au> Authored: Wed May 20 21:52:07 2015 +1000 Committer: Paul King <pa...@asert.com.au> Committed: Fri May 22 17:03:49 2015 +1000 ---------------------------------------------------------------------- src/main/groovy/transform/Canonical.groovy | 86 ++++++++++++ src/main/groovy/transform/Canonical.java | 132 ------------------- .../transform/BuilderASTTransformation.java | 15 +-- .../transform/CanonicalASTTransformation.java | 79 ----------- .../EqualsAndHashCodeASTTransformation.java | 5 - .../transform/ToStringASTTransformation.java | 5 - .../TupleConstructorASTTransformation.java | 5 - src/spec/doc/core-metaprogramming.adoc | 20 +-- .../test/CodeGenerationASTTransformsTest.groovy | 29 ++-- .../transform/CanonicalTransformTest.groovy | 38 ++++-- .../transform/ImmutableTransformTest.groovy | 14 -- 11 files changed, 143 insertions(+), 285 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/groovy/transform/Canonical.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/transform/Canonical.groovy b/src/main/groovy/transform/Canonical.groovy new file mode 100644 index 0000000..637ddd5 --- /dev/null +++ b/src/main/groovy/transform/Canonical.groovy @@ -0,0 +1,86 @@ +/* + * 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 +/** + * The {@code @Canonical} meta-annotation combines the {@code @EqualsAndHashCode}, + * {@code @ToString} and {@code @TupleConstructor} annotations. It is used to assist in + * the creation of mutable classes. It instructs the compiler to execute AST transformations + * which add positional constructors, equals, hashCode and a pretty print toString to your class. + * <p> + * You can write classes in this shortened form: + * <pre> + * {@code @Canonical} class Customer { + * String first, last + * int age + * Date since + * Collection favItems = ['Food'] + * def object + * } + * def d = new Date() + * def anyObject = new Object() + * def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:d, favItems:['Books', 'Games'], object: anyObject) + * def c2 = new Customer('Tom', 'Jones', 21, d, ['Books', 'Games'], anyObject) + * assert c1 == c2 + * </pre> + * + * You don't need to provide all arguments in constructor calls. If using named parameters, any property names not + * referenced will be given their default value (as per Java's default unless an explicit initialization constant is + * provided when defining the property). If using a tuple constructor, parameters are supplied in the order in which + * the properties are defined. Supplied parameters fill the tuple from the left. Any parameters missing on the right + * are given their default value. + * <pre> + * def c3 = new Customer(last: 'Jones', age: 21) + * def c4 = new Customer('Tom', 'Jones') + * + * assert null == c3.since + * assert 0 == c4.age + * assert c3.favItems == ['Food'] && c4.favItems == ['Food'] + * </pre> + * + * If you don't need all of the functionality of {@code @Canonical}, you can simply directly use one or more of the individual + * annotations which {@code @Canonical} aggregates. + * In addition, you can use {@code @Canonical} in combination with explicit use one or more of the individual annotations in + * cases where you need to further customize the annotation attributes. + * Any applicable annotation attributes from {@code @Canonical} missing from the explicit annotation will be merged + * but any existing annotation attributes within the explicit annotation take precedence. So, for example in this case here: + * <pre> + * {@code @Canonical}(includeNames=true, excludes='c') + * {@code @}{@link ToString}(excludes='a,b') + * class MyClass { ... } + * </pre> + * The generated {@code toString} will include property names and exclude the {@code a} and {@code b} properties. + * <p> + * A class created using {@code @Canonical} has the following characteristics: + * <ul> + * <li>A no-arg constructor is provided which allows you to set properties by name using Groovy's normal bean conventions. + * <li>Tuple-style constructors are provided which allow you to set properties in the same order as they are defined. + * <li>Default {@code equals}, {@code hashCode} and {@code toString} methods are provided based on the property values. + * See the GroovyDoc for the individual annotations for more details. + * <p> + * If you want similar functionality to what this annotation provides but also require immutability, see the + * {@code @}{@link Immutable} annotation. + * + * @see groovy.transform.EqualsAndHashCode + * @see groovy.transform.ToString + * @see groovy.transform.TupleConstructor + * @see groovy.transform.Immutable + * @since 1.8.0 + */ +@AnnotationCollector(value=[ToString, TupleConstructor, EqualsAndHashCode], mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED) +public @interface Canonical { } http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/groovy/transform/Canonical.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/transform/Canonical.java b/src/main/groovy/transform/Canonical.java deleted file mode 100644 index 4ab5f83..0000000 --- a/src/main/groovy/transform/Canonical.java +++ /dev/null @@ -1,132 +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. - */ -package groovy.transform; - -import org.codehaus.groovy.transform.GroovyASTTransformationClass; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Class annotation used to assist in the creation of mutable classes. - * <p> - * It allows you to write classes in this shortened form: - * <pre> - * {@code @Canonical} class Customer { - * String first, last - * int age - * Date since - * Collection favItems = ['Food'] - * def object - * } - * def d = new Date() - * def anyObject = new Object() - * def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:d, favItems:['Books', 'Games'], object: anyObject) - * def c2 = new Customer('Tom', 'Jones', 21, d, ['Books', 'Games'], anyObject) - * assert c1 == c2 - * </pre> - * - * You don't need to provide all arguments in constructor calls. If using named parameters, any property names not - * referenced will be given their default value (as per Java's default unless an explicit initialization constant is - * provided when defining the property). If using a tuple constructor, parameters are supplied in the order in which - * the properties are defined. Supplied parameters fill the tuple from the left. Any parameters missing on the right - * are given their default value. - * <pre> - * def c3 = new Customer(last: 'Jones', age: 21) - * def c4 = new Customer('Tom', 'Jones') - * - * assert null == c3.since - * assert 0 == c4.age - * assert c3.favItems == ['Food'] && c4.favItems == ['Food'] - * </pre> - * - * The {@code @Canonical} annotation instructs the compiler to execute an - * AST transformation which adds positional constructors, - * equals, hashCode and a pretty print toString to your class. There are additional - * annotations if you only need some of the functionality: {@code @EqualsAndHashCode}, - * {@code @ToString} and {@code @TupleConstructor}. In addition, you can add one of - * the other annotations if you need to further customize the behavior of the - * AST transformation. - * <p> - * A class created in this way has the following characteristics: - * <ul> - * <li>A no-arg constructor is provided which allows you to set properties by name using Groovy's normal bean conventions. - * <li>Tuple-style constructors are provided which allow you to set properties in the same order as they are defined. - * <li>Default {@code equals}, {@code hashCode} and {@code toString} methods are provided based on the property values. - * Though not normally required, you may write your own implementations of these methods. For {@code equals} and {@code hashCode}, - * if you do write your own method, it is up to you to obey the general contract for {@code equals} methods and supply - * a corresponding matching {@code hashCode} method. - * If you do provide one of these methods explicitly, the default implementation will be made available in a private - * "underscore" variant which you can call. E.g., you could provide a (not very elegant) multi-line formatted - * {@code toString} method for {@code Customer} above as follows: - * <pre> - * String toString() { - * _toString().replaceAll(/\(/, '(\n\t').replaceAll(/\)/, '\n)').replaceAll(/, /, '\n\t') - * } - * </pre> - * If an "underscore" version of the respective method already exists, then no default implementation is provided. - * </ul> - * <p> - * If you want similar functionality to what this annotation provides but also require immutability, see the - * {@code @}{@link Immutable} annotation. - * <p> - * Limitations: - * <ul> - * <li>If you explicitly add your own constructors, then the transformation will not add any other constructor to the class</li> - * <li>Groovy's normal map-style naming conventions will not be available if the first property - * has type {@code LinkedHashMap} or if there is a single Map, AbstractMap or HashMap property</li> - * </ul> - * - * @author Paulo Poiati - * @author Paul King - * @see groovy.transform.EqualsAndHashCode - * @see groovy.transform.ToString - * @see groovy.transform.TupleConstructor - * @see groovy.transform.Immutable - * @since 1.8.0 - */ -@java.lang.annotation.Documented -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.TYPE}) -@GroovyASTTransformationClass("org.codehaus.groovy.transform.CanonicalASTTransformation") -public @interface Canonical { - /** - * List of field and/or property names to exclude. - * Must not be used if 'includes' is used. For convenience, a String with comma separated names - * can be used in addition to an array (using Groovy's literal list notation) of String values. - * - * If the {@code @Canonical} behavior is customised by using it in conjunction with one of the more specific - * related annotations (i.e. {@code @ToString}, {@code @EqualsAndHashCode} or {@code @TupleConstructor}), then - * the value of this attribute can be overridden within the more specific annotation. - */ - String[] excludes() default {}; - - /** - * List of field and/or property names to include. - * Must not be used if 'excludes' is used. For convenience, a String with comma separated names - * can be used in addition to an array (using Groovy's literal list notation) of String values. - * - * If the {@code @Canonical} behavior is customised by using it in conjunction with one of the more specific - * related annotations (i.e. {@code @ToString}, {@code @EqualsAndHashCode} or {@code @TupleConstructor}), then - * the value of this attribute can be overridden within the more specific annotation. - */ - String[] includes() default {}; -} http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/org/codehaus/groovy/transform/BuilderASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/BuilderASTTransformation.java b/src/main/org/codehaus/groovy/transform/BuilderASTTransformation.java index d3c5eaf..7481b91 100644 --- a/src/main/org/codehaus/groovy/transform/BuilderASTTransformation.java +++ b/src/main/org/codehaus/groovy/transform/BuilderASTTransformation.java @@ -43,9 +43,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstancePropertyFiel /** * Handles generation of code for the {@link Builder} annotation. - * - * @author Marcin Grzejszczak - * @author Paul King */ @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class BuilderASTTransformation extends AbstractASTTransformation implements CompilationUnitAware { @@ -137,15 +134,15 @@ public class BuilderASTTransformation extends AbstractASTTransformation implemen List<String> directIncludes = transform.getMemberList(anno, "includes"); if (directIncludes != null) includes.addAll(directIncludes); if (includes.isEmpty() && excludes.isEmpty()) { - if (transform.hasAnnotation(cNode, CanonicalASTTransformation.MY_TYPE)) { - AnnotationNode canonical = cNode.getAnnotations(CanonicalASTTransformation.MY_TYPE).get(0); + if (transform.hasAnnotation(cNode, TupleConstructorASTTransformation.MY_TYPE)) { + AnnotationNode tupleConstructor = cNode.getAnnotations(TupleConstructorASTTransformation.MY_TYPE).get(0); if (excludes.isEmpty()) { - List<String> canonicalExcludes = transform.getMemberList(canonical, "excludes"); - if (canonicalExcludes != null) excludes.addAll(canonicalExcludes); + List<String> tupleExcludes = transform.getMemberList(tupleConstructor, "excludes"); + if (tupleExcludes != null) excludes.addAll(tupleExcludes); } if (includes.isEmpty()) { - List<String> canonicalIncludes = transform.getMemberList(canonical, "includes"); - if (canonicalIncludes != null) includes.addAll(canonicalIncludes); + List<String> tupleIncludes = transform.getMemberList(tupleConstructor, "includes"); + if (tupleIncludes != null) includes.addAll(tupleIncludes); } } } http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/org/codehaus/groovy/transform/CanonicalASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/CanonicalASTTransformation.java b/src/main/org/codehaus/groovy/transform/CanonicalASTTransformation.java deleted file mode 100644 index c729294..0000000 --- a/src/main/org/codehaus/groovy/transform/CanonicalASTTransformation.java +++ /dev/null @@ -1,79 +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. - */ -package org.codehaus.groovy.transform; - -import groovy.transform.Canonical; -import org.codehaus.groovy.ast.ASTNode; -import org.codehaus.groovy.ast.AnnotatedNode; -import org.codehaus.groovy.ast.AnnotationNode; -import org.codehaus.groovy.ast.ClassNode; -import org.codehaus.groovy.control.CompilePhase; -import org.codehaus.groovy.control.SourceUnit; - -import java.util.List; - -import static org.codehaus.groovy.ast.ClassHelper.make; -import static org.codehaus.groovy.transform.EqualsAndHashCodeASTTransformation.createEquals; -import static org.codehaus.groovy.transform.EqualsAndHashCodeASTTransformation.createHashCode; -import static org.codehaus.groovy.transform.ToStringASTTransformation.createToString; -import static org.codehaus.groovy.transform.TupleConstructorASTTransformation.createConstructor; - -/** - * Handles generation of code for the @Canonical annotation. - * - * @author Paulo Poiati - * @author Paul King - */ -@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) -public class CanonicalASTTransformation extends AbstractASTTransformation { - - static final Class MY_CLASS = Canonical.class; - static final ClassNode MY_TYPE = make(MY_CLASS); - static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); - - public void visit(ASTNode[] nodes, SourceUnit source) { - init(nodes, source); - AnnotatedNode parent = (AnnotatedNode) nodes[1]; - AnnotationNode anno = (AnnotationNode) nodes[0]; - if (!MY_TYPE.equals(anno.getClassNode())) return; - - if (parent instanceof ClassNode) { - ClassNode cNode = (ClassNode) parent; - // TODO remove - let other validation steps pick this up - if (hasAnnotation(cNode, ImmutableASTTransformation.MY_TYPE)) { - addError(MY_TYPE_NAME + " class '" + cNode.getName() + "' can't also be " + ImmutableASTTransformation.MY_TYPE_NAME, parent); - } - if (!checkNotInterface(cNode, MY_TYPE_NAME)) return; - List<String> excludes = getMemberList(anno, "excludes"); - List<String> includes = getMemberList(anno, "includes"); - if (!checkIncludeExclude(anno, excludes, includes, MY_TYPE_NAME)) return; - if (!hasAnnotation(cNode, TupleConstructorASTTransformation.MY_TYPE)) { - createConstructor(cNode, false, true, false, false, false, false, excludes, includes, false); - } - if (!hasAnnotation(cNode, EqualsAndHashCodeASTTransformation.MY_TYPE)) { - createHashCode(cNode, false, false, false, excludes, includes); - createEquals(cNode, false, false, true, excludes, includes); - } - if (!hasAnnotation(cNode, ToStringASTTransformation.MY_TYPE)) { - createToString(cNode, false, false, excludes, includes, false); - } - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java b/src/main/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java index f1d0efb..d1a46b5 100644 --- a/src/main/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java +++ b/src/main/org/codehaus/groovy/transform/EqualsAndHashCodeASTTransformation.java @@ -73,11 +73,6 @@ public class EqualsAndHashCodeASTTransformation extends AbstractASTTransformatio boolean includeFields = memberHasValue(anno, "includeFields", true); List<String> excludes = getMemberList(anno, "excludes"); List<String> includes = getMemberList(anno, "includes"); - if (hasAnnotation(cNode, CanonicalASTTransformation.MY_TYPE)) { - AnnotationNode canonical = cNode.getAnnotations(CanonicalASTTransformation.MY_TYPE).get(0); - if (excludes == null || excludes.isEmpty()) excludes = getMemberList(canonical, "excludes"); - if (includes == null || includes.isEmpty()) includes = getMemberList(canonical, "includes"); - } if (!checkIncludeExclude(anno, excludes, includes, MY_TYPE_NAME)) return; createHashCode(cNode, cacheHashCode, includeFields, callSuper, excludes, includes); createEquals(cNode, includeFields, callSuper, useCanEqual, excludes, includes); http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/org/codehaus/groovy/transform/ToStringASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/ToStringASTTransformation.java b/src/main/org/codehaus/groovy/transform/ToStringASTTransformation.java index f915887..e16ee63 100644 --- a/src/main/org/codehaus/groovy/transform/ToStringASTTransformation.java +++ b/src/main/org/codehaus/groovy/transform/ToStringASTTransformation.java @@ -81,11 +81,6 @@ public class ToStringASTTransformation extends AbstractASTTransformation { boolean includePackage = !memberHasValue(anno, "includePackage", false); boolean allProperties = !memberHasValue(anno, "allProperties", false); - if (hasAnnotation(cNode, CanonicalASTTransformation.MY_TYPE)) { - AnnotationNode canonical = cNode.getAnnotations(CanonicalASTTransformation.MY_TYPE).get(0); - if (excludes == null || excludes.isEmpty()) excludes = getMemberList(canonical, "excludes"); - if (includes == null || includes.isEmpty()) includes = getMemberList(canonical, "includes"); - } if (!checkIncludeExclude(anno, excludes, includes, MY_TYPE_NAME)) return; createToString(cNode, includeSuper, includeFields, excludes, includes, includeNames, ignoreNulls, includePackage, cacheToString, includeSuperProperties, allProperties); } http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java index c94d361..cf9c0ee 100644 --- a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java +++ b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java @@ -112,11 +112,6 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation boolean useSetters = memberHasValue(anno, "useSetters", true); List<String> excludes = getMemberList(anno, "excludes"); List<String> includes = getMemberList(anno, "includes"); - if (hasAnnotation(cNode, CanonicalASTTransformation.MY_TYPE)) { - AnnotationNode canonical = cNode.getAnnotations(CanonicalASTTransformation.MY_TYPE).get(0); - if (excludes == null || excludes.isEmpty()) excludes = getMemberList(canonical, "excludes"); - if (includes == null || includes.isEmpty()) includes = getMemberList(canonical, "includes"); - } if (!checkIncludeExclude(anno, excludes, includes, MY_TYPE_NAME)) return; // if @Immutable is found, let it pick up options and do work so we'll skip if (hasAnnotation(cNode, ImmutableASTTransformation.MY_TYPE)) return; http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/spec/doc/core-metaprogramming.adoc ---------------------------------------------------------------------- diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc index d05fca8..a6a6af6 100644 --- a/src/spec/doc/core-metaprogramming.adoc +++ b/src/spec/doc/core-metaprogramming.adoc @@ -948,7 +948,7 @@ include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags= [[xform-Canonical]] ===== @groovy.transform.Canonical -The `@Canonical` AST transformation combines the effects of the <<xform-ToString,@ToString>>, +The `@Canonical` meta-annotation combines the <<xform-ToString,@ToString>>, <<xform-EqualsAndHashCode,@EqualsAndHashCode>> and <<xform-TupleConstructor,@TupleConstructor>> annotations: @@ -958,22 +958,24 @@ include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags= ---- A similar immutable class can be generated using the <<xform-Immutable,@Immutable>> AST transformation instead. -The `@Canonical` AST transformation supports several configuration options: +The `@Canonical` meta-annotation supports the configuration options found in the annotations +it aggregates. See those annotations for more details. -[cols="1,1,2,3a",options="header"] -|======================================================================= -|Attribute|Default value|Description|Example -|excludes|Empty list|List of properties to exclude from tuple constructor generation| [source,groovy] ---- include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=canonical_example_excludes,indent=0] ---- -|includes|Empty list|List of fields to include in tuple constructor generation| + +The `@Canonical` meta-annotation can be used in conjunction with an explicit use one or more of its +component annotations, like this: + [source,groovy] ---- -include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=canonical_example_includes,indent=0] +include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=canonical_example_excludes,indent=0] ---- -|======================================================================= + +Any applicable annotation attributes from `@Caonical` are passed along to the explicit annotation but +attributes already existing in the explicit annotation take precedence. [[xform-InheritConstructors]] ===== @groovy.transform.InheritConstructors http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/spec/test/CodeGenerationASTTransformsTest.groovy ---------------------------------------------------------------------- diff --git a/src/spec/test/CodeGenerationASTTransformsTest.groovy b/src/spec/test/CodeGenerationASTTransformsTest.groovy index 5af1bb3..a8d27ef 100644 --- a/src/spec/test/CodeGenerationASTTransformsTest.groovy +++ b/src/spec/test/CodeGenerationASTTransformsTest.groovy @@ -612,34 +612,37 @@ class Person { String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') -assert p1.toString() == 'Person(Jack)' // Effect of @ToString +assert p1.toString() == 'Person(Jack)' // Effect of @ToString(excludes=['lastName']) -def p2 = new Person('Jack') // Effect of @TupleConstructor +def p2 = new Person('Jack') // Effect of @TupleConstructor(excludes=['lastName']) assert p2.toString() == 'Person(Jack)' -assert p1==p2 // Effect of @EqualsAndHashCode -assert p1.hashCode()==p2.hashCode() // Effect of @EqualsAndHashCode +assert p1==p2 // Effect of @EqualsAndHashCode(excludes=['lastName']) +assert p1.hashCode()==p2.hashCode() // Effect of @EqualsAndHashCode(excludes=['lastName']) // end::canonical_example_excludes[] ''' assertScript ''' -// tag::canonical_example_includes[] -import groovy.transform.Canonical +// tag::canonical_explicit_tostring[] +import groovy.transform.* -@Canonical(includes=['firstName']) +@Canonical(excludes=['lastName'], ignoreNulls=true) +@ToString(excludes=['firstName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') -assert p1.toString() == 'Person(Jack)' // Effect of @ToString +assert p1.toString() == 'Person(Nicholson)' // Effect of @ToString(excludes=['firstName'], ignoreNulls=true) -def p2 = new Person('Jack') // Effect of @TupleConstructor -assert p2.toString() == 'Person(Jack)' +def p2 = new Person('Jack') // Effect of @TupleConstructor(excludes=['lastName']) +assert p2.firstName == 'Jack' +assert p2.lastName == null +assert p2.toString() == 'Person()' // Effect of @ToString(excludes=['firstName'], ignoreNulls=true) -assert p1==p2 // Effect of @EqualsAndHashCode -assert p1.hashCode()==p2.hashCode() // Effect of @EqualsAndHashCode -// end::canonical_example_includes[] +assert p1 == p2 // Effect of @EqualsAndHashCode(excludes=['lastName']) +assert p1.hashCode() == p2.hashCode() // Effect of @EqualsAndHashCode(excludes=['lastName']) +// end::canonical_explicit_tostring[] ''' } http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/src/test/org/codehaus/groovy/transform/CanonicalTransformTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/org/codehaus/groovy/transform/CanonicalTransformTest.groovy b/src/test/org/codehaus/groovy/transform/CanonicalTransformTest.groovy index 8bcc67e..944b103 100644 --- a/src/test/org/codehaus/groovy/transform/CanonicalTransformTest.groovy +++ b/src/test/org/codehaus/groovy/transform/CanonicalTransformTest.groovy @@ -38,20 +38,6 @@ class CanonicalTransformTest extends GroovyShellTestCase { assertEquals objects[0], objects[1] } - void testCanonicalCantAlsoBeImmutable() { - def msg = shouldFail(RuntimeException) { - assertScript """ - import groovy.transform.* - @Canonical - @Immutable - class Foo { - String bar - } - """ - } - assert msg.contains("@Canonical class 'Foo' can't also be @Immutable") - } - void testCanonicalWithDeclaredConstructor() { def msg = shouldFail(GroovyRuntimeException) { assertScript """ @@ -102,6 +88,30 @@ class CanonicalTransformTest extends GroovyShellTestCase { """ } + void testCanonicalWithSuper() { + // related to Apache PR#9: "Add includeSuper property to @Canonical annotation" + assertScript """ + import groovy.transform.* + @Canonical + class Foo { + int a + } + @Canonical(callSuper=true, includeSuperProperties=true, includeNames=true) + class Bar extends Foo { + int b + } + @Canonical(callSuper=true, includeSuper=true) + @TupleConstructor(includeSuperProperties=true) + class Baz extends Foo { + int b + } + def (b1, b2, b3) = [new Bar(a:5, b:20), new Bar(10, 20), new Baz(15, 20)] + assert [b1, b2, b3].toString() == '[Bar(b:20, a:5), Bar(b:20, a:10), Baz(20, Foo(15))]' + assert b1 != b2 + assert b1.hashCode() != b2.hashCode() + """ + } + void testCanonicalChange() { def objects = evaluate(""" @groovy.transform.Canonical class Foo { http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/3844f283/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 fb95bd7..1d03e2b 100644 --- a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy +++ b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy @@ -127,20 +127,6 @@ class ImmutableTransformTest extends GroovyShellTestCase { assert cls == 'Dolly' } - void testImmutableCantAlsoBeMutable() { - def msg = shouldFail(RuntimeException) { - assertScript """ - import groovy.transform.* - @Immutable - @Canonical - class Foo { - String bar - } - """ - } - assert msg.contains("@Canonical class 'Foo' can't also be @Immutable") - } - void testImmutableListProp() { def objects = evaluate(""" import groovy.transform.Immutable