Repository: groovy
Updated Branches:
  refs/heads/master de9c8803c -> 923fd338e


GROOVY-7600: @Immutable support for Optional (closes #367)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/923fd338
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/923fd338
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/923fd338

Branch: refs/heads/master
Commit: 923fd338eb4be18292cd99b7f1618ba752cb7d1b
Parents: de9c880
Author: paulk <pa...@asert.com.au>
Authored: Tue Jul 19 17:17:50 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Mon Jul 25 20:03:46 2016 +1000

----------------------------------------------------------------------
 src/main/groovy/transform/Immutable.java        |   5 +-
 .../transform/ImmutableASTTransformation.java   |   9 ++
 .../transform/ImmutableTransformTest.groovy     | 118 +++++++++++++++++--
 3 files changed, 118 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/923fd338/src/main/groovy/transform/Immutable.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/transform/Immutable.java 
b/src/main/groovy/transform/Immutable.java
index d0567e6..89876a4 100644
--- a/src/main/groovy/transform/Immutable.java
+++ b/src/main/groovy/transform/Immutable.java
@@ -52,8 +52,9 @@ import java.lang.annotation.Target;
  * <li>Properties must be of an immutable type or a type with a strategy for 
handling non-immutable
  * characteristics. Specifically, the type must be one of the primitive or 
wrapper types, Strings, enums,
  * other {@code @Immutable} classes or known immutables (e.g. java.awt.Color, 
java.net.URI, java.util.UUID).
- * Also handled are Cloneable classes, collections, maps and arrays, and other 
"effectively immutable"
- * classes with special handling (e.g. java.util.Date).
+ * Also handled are Cloneable classes, collections, maps and arrays, other 
"effectively immutable"
+ * classes with special handling (e.g. java.util.Date), and usages of 
java.util.Optional where the
+ * contained type is immutable (e.g. Optional&lt;String&gt;).
  * <li>Properties automatically have private, final backing fields with 
getters.
  * Attempts to update the property will result in a {@code 
ReadOnlyPropertyException}.
  * <li>A map-based constructor is provided which allows you to set properties 
by name.

http://git-wip-us.apache.org/repos/asf/groovy/blob/923fd338/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java 
b/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java
index f0b00cf..4378f0b 100644
--- a/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java
@@ -30,6 +30,7 @@ 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;
@@ -527,6 +528,14 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
             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()) {
+                String name = optionalType.getType().getName();
+                if (inImmutableList(name) || 
knownImmutableClasses.contains(name)) return true;
+                if (optionalType.getType().isEnum() || 
!optionalType.getType().getAnnotations(MY_TYPE).isEmpty()) return true;
+            }
+        }
         return fieldType.isEnum() ||
                 ClassHelper.isPrimitiveType(fieldType) ||
                 !fieldType.getAnnotations(MY_TYPE).isEmpty();

http://git-wip-us.apache.org/repos/asf/groovy/blob/923fd338/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 3c19842..f867c5b 100644
--- a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
@@ -18,12 +18,40 @@
  */
 package org.codehaus.groovy.transform
 
+import org.codehaus.groovy.control.MultipleCompilationErrorsException
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import static org.junit.Assume.assumeTrue
+
 /**
- * @author Paul King
- * @author Tim Yates
+ * Tests for the @Immutable transform.
  */
+@RunWith(JUnit4)
 class ImmutableTransformTest extends GroovyShellTestCase {
 
+    @Rule public TestName nameRule = new TestName()
+
+    @Before
+    void setUp() {
+        super.setUp()
+        // check java version requirements
+        def v = System.getProperty("java.specification.version")
+        assert v
+        assumeTrue('Test requires jre8+', 
nameRule.methodName.endsWith('_vm8').implies(new BigDecimal(v) >= 1.8))
+    }
+
+    @After
+    void tearDown() {
+        super.tearDown()
+    }
+
+    @Test
     void testImmutable() {
         def objects = evaluate('''
             import groovy.transform.Immutable
@@ -42,6 +70,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert objects[0].nums.class.name.contains("Unmodifiable")
     }
 
+    @Test
     void testImmutableClonesListAndCollectionFields() {
         def objects = evaluate("""
             import groovy.transform.Immutable
@@ -65,6 +94,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assertTrue objects[1].otherNums.class.name.contains("Unmodifiable")
     }
 
+    @Test
     void testImmutableField() {
         def person = evaluate("""
             import groovy.transform.Immutable
@@ -78,6 +108,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         }
     }
 
+    @Test
     void testCloneableField() {
         def (originalDolly, lab) = evaluate("""
             import groovy.transform.Immutable
@@ -105,6 +136,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert clonedDolly2.name == clonedDolly.name
     }
 
+    @Test
     void testCloneableFieldNotCloneableObject() {
         def cls = shouldFail(CloneNotSupportedException) {
             def objects = evaluate("""
@@ -127,6 +159,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert cls == 'Dolly'
     }
 
+    @Test
     void testImmutableListProp() {
         def objects = evaluate("""
             import groovy.transform.Immutable
@@ -146,6 +179,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert objects[0].nums.size() == 2
     }
 
+    @Test
     void testImmutableAsMapKey() {
         assertScript """
             import groovy.transform.Immutable
@@ -159,6 +193,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testImmutableWithOnlyMap() {
         assertScript """
             import groovy.transform.Immutable
@@ -169,6 +204,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testImmutableWithPrivateStaticFinalField() {
         assertScript """
           @groovy.transform.Immutable class Foo {
@@ -178,6 +214,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
       """
     }
 
+    @Test
     void testImmutableWithInvalidPropertyName() {
         def msg = shouldFail(MissingPropertyException) {
             assertScript """
@@ -189,6 +226,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert msg.contains('No such property: missing for class: Simple')
     }
 
+    @Test
     void testImmutableWithHashMap() {
         assertScript """
             import groovy.transform.Immutable
@@ -207,6 +245,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testDefaultValuesAreImmutable_groovy6293() {
         assertScript """
             import groovy.transform.Immutable
@@ -218,6 +257,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testNoArgConstructor_groovy6473() {
         assertScript """
             import groovy.transform.Immutable
@@ -229,6 +269,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testImmutableEquals() {
         assertScript """
             import groovy.transform.Immutable
@@ -246,6 +287,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testExistingToString() {
         assertScript """
             import groovy.transform.Immutable
@@ -269,6 +311,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testExistingEquals() {
         assertScript """
             import groovy.transform.Immutable
@@ -311,6 +354,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testExistingHashCode() {
         assertScript """
             import groovy.transform.Immutable
@@ -345,6 +389,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         """
     }
 
+    @Test
     void testBuiltinImmutables() {
         assertScript '''
             import java.awt.Color
@@ -364,6 +409,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testPrivateFieldAssignedViaConstructor() {
         assertScript '''
             import groovy.transform.Immutable
@@ -387,6 +433,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testPrivateFinalFieldAssignedViaConstructorShouldCauseError() {
         shouldFail(ReadOnlyPropertyException) {
             evaluate '''
@@ -399,6 +446,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         }
     }
 
+    @Test
     void testImmutableWithImmutableFields() {
         assertScript '''
             import groovy.transform.Immutable
@@ -409,6 +457,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testImmutableWithConstant() {
         assertScript '''
             import groovy.transform.Immutable
@@ -424,6 +473,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testStaticsAllowed_ThoughUsuallyBadDesign() {
         // design here is questionable as getDescription() method is not 
idempotent
         assertScript '''
@@ -454,6 +504,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testImmutableToStringVariants() {
         assertScript '''
             import groovy.transform.*
@@ -475,6 +526,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testImmutableUsageOnInnerClasses() {
         assertScript '''
             import groovy.transform.Immutable
@@ -492,6 +544,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testKnownImmutableClassesWithNamedParameters() {
         assertScript '''
             import groovy.transform.*
@@ -507,6 +560,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testKnownImmutableClassesWithExplicitConstructor() {
         assertScript '''
             @groovy.transform.Immutable(knownImmutableClasses = [Address])
@@ -522,6 +576,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testKnownImmutableClassesWithCoercedConstruction() {
         assertScript '''
             @groovy.transform.Immutable(knownImmutableClasses = [Address])
@@ -537,6 +592,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         '''
     }
 
+    @Test
     void testKnownImmutableClassesMissing() {
         def msg = shouldFail(RuntimeException) {
             evaluate '''
@@ -556,6 +612,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-5828
+    @Test
     void testKnownImmutableCollectionClass() {
         assertScript '''
             @groovy.transform.Immutable
@@ -572,6 +629,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-5828
+    @Test
     void testKnownImmutables() {
         assertScript '''
             // ok, Items not really immutable but pretend so for the purpose 
of this test
@@ -587,6 +645,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-5449
+    @Test
     void testShouldNotThrowNPE() {
         def msg = shouldFail(RuntimeException) {
             evaluate '''
@@ -600,6 +659,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-6192
+    @Test
     void testWithEqualsAndHashCodeASTOverride() {
         assertScript '''
             import groovy.transform.*
@@ -616,7 +676,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-6354
-    public void testCopyWith() {
+    @Test
+    void testCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |class Person {
@@ -651,7 +712,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert !alice.is( tim )
     }
 
-    public void testGenericsCopyWith() {
+    @Test
+    void testGenericsCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |class Person {
@@ -674,7 +736,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert !alice.is( tim )
     }
 
-    public void testWithPrivatesCopyWith() {
+    @Test
+    void testWithPrivatesCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith=true)
             |class Foo {
@@ -704,7 +767,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert alice.full() == 'Alice Yates (ali)'
     }
 
-    public void testStaticWithPrivatesCopyWith() {
+    @Test
+    void testStaticWithPrivatesCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith=true)
             |@groovy.transform.CompileStatic
@@ -735,7 +799,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert alice.full() == 'Alice Yates (ali)'
     }
 
-    public void testTypedWithPrivatesCopyWith() {
+    @Test
+    void testTypedWithPrivatesCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith=true)
             |@groovy.transform.TypeChecked
@@ -766,7 +831,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert alice.full() == 'Alice Yates (ali)'
     }
 
-    public void testStaticCopyWith() {
+    @Test
+    void testStaticCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |@groovy.transform.CompileStatic
@@ -802,7 +868,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert !alice.is( tim )
     }
 
-    public void testTypedCopyWith() {
+    @Test
+    void testTypedCopyWith() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |@groovy.transform.TypeChecked
@@ -838,7 +905,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert !alice.is( tim )
     }
 
-    public void testCopyWithSkipping() {
+    @Test
+    void testCopyWithSkipping() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |class Person {
@@ -859,7 +927,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert result.first == [ 'tim', 'tim' ]
     }
 
-    public void testStaticCopyWithSkipping() {
+    @Test
+    void testStaticCopyWithSkipping() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |@groovy.transform.CompileStatic
@@ -881,7 +950,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
         assert result.first == [ 'tim', 'tim' ]
     }
 
-    public void testTypedCopyWithSkipping() {
+    @Test
+    void testTypedCopyWithSkipping() {
         def tester = new GroovyClassLoader().parseClass(
                 '''@groovy.transform.Immutable(copyWith = true)
             |@groovy.transform.TypeChecked
@@ -904,6 +974,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-7227
+    @Test
     void testKnownImmutablesWithInvalidPropertyNameResultsInError() {
         def message = shouldFail {
             evaluate """
@@ -919,6 +990,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     }
 
     // GROOVY-7162
+    @Test
     void testImmutableWithSuperClass() {
         assertScript '''
             import groovy.transform.*
@@ -943,4 +1015,26 @@ class ImmutableTransformTest extends GroovyShellTestCase {
             assert d2.toString() == 'Athlete(sport:Tennis, name:Roger Federer)'
         '''
     }
+
+    // GROOVY-7600
+    @Test
+    void testImmutableWithOptional_vm8() {
+        assertScript '''
+            @groovy.transform.Immutable class Person {
+                String name
+                Optional<String> address
+            }
+            def p = new Person('Joe', Optional.of('Home'))
+            assert p.toString() == 'Person(Joe, Optional[Home])'
+            assert p.address.get() == 'Home'
+        '''
+        shouldFail(MultipleCompilationErrorsException) {
+            evaluate '''
+            @groovy.transform.Immutable class Person {
+                String name
+                Optional<Date> address
+            }
+            '''
+        }
+    }
 }

Reply via email to