This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 6bc78d3  update sealed documentation
6bc78d3 is described below

commit 6bc78d3211b2e7a17f47c082dd56ba791213fd9e
Author: Paul King <[email protected]>
AuthorDate: Thu Nov 4 22:35:48 2021 +1000

    update sealed documentation
---
 src/spec/doc/_sealed.adoc                    | 81 ++++++++++++++++++++++------
 src/spec/test/SealedSpecificationTest.groovy | 55 +++++++++++++++++--
 2 files changed, 116 insertions(+), 20 deletions(-)

diff --git a/src/spec/doc/_sealed.adoc b/src/spec/doc/_sealed.adoc
index 7b8da7c..11c0606 100644
--- a/src/spec/doc/_sealed.adoc
+++ b/src/spec/doc/_sealed.adoc
@@ -22,26 +22,43 @@
 = Sealed hierarchies (incubating)
 
 Sealed classes, interfaces and traits restrict which subclasses can 
extend/implement them.
-A final class allows no extension. A public non-final class allows extension 
by anyone.
-Access modifiers like protected and package-private give some ability to 
restrict inheritance
+Prior to sealed classes, class hierarchy designers had two main options:
+
+* Make a class final to allow no extension.
+* Make the class public and non-final to allow extension by anyone.
+
+Sealed classes provide a middle-ground compared to these all or nothing 
choices.
+
+Sealed classes are also more flexible than other tricks previously used
+to try to achieve a middle-ground. For example, for class hierarchies,
+access modifiers like protected and package-private give some ability to 
restrict inheritance
 hierarchies but often at the expense of flexible use of those hierarchies.
 
-Sealed hierarchies provide full inheritance within a known hierarchy of 
classes, interfaces and traits
-but disable or only provide controlled inheritance outside the hierarchy.
+Sealed hierarchies provide full inheritance within a known hierarchy of 
classes, interfaces
+and traits but disable or only provide controlled inheritance outside the 
hierarchy.
 
-For example, suppose we want to create a shape hierarchy containing
+As an example, suppose we want to create a shape hierarchy containing
 only circles and squares. We also want a shape interface to
-be able to refer to instances in our hierarchy. We can create the
-hierarchy as follows:
+be able to refer to instances in our hierarchy.
+We can create the hierarchy as follows:
 
 [source,groovy]
 ----
-include::../test/SealedSpecificationTest.groovy[tags=simple_interface,indent=0]
+include::../test/SealedSpecificationTest.groovy[tags=simple_interface_keyword,indent=0]
 ----
 
-We can have a reference of type `ShapeI` which can point to either a `Circle` 
or `Square`
-and, since our classes are `final`, we know no additional classes will be 
added to our hierarchy in the future.
-At least not without changing the `permittedSubclasses` and recompiling.
+Groovy also supports an alternative annotation syntax.
+We think the keyword style is nicer but you might choose the annotation style 
if your editor doesn't yet have Groovy 4 support.
+
+[source,groovy]
+----
+include::../test/SealedSpecificationTest.groovy[tags=simple_interface_annotations,indent=0]
+----
+
+We can have a reference of type `ShapeI` which, thanks to the `permits` clause,
+can point to either a `Circle` or `Square` and, since our classes are `final`,
+we know no additional classes will be added to our hierarchy in the future.
+At least not without changing the `permits` clause and recompiling.
 
 In general, we might want to have some parts of our class hierarchy
 immediately locked down like we have here, where we marked the
@@ -53,9 +70,19 @@ controlled inheritance.
 
include::../test/SealedSpecificationTest.groovy[tags=general_sealed_class,indent=0]
 ----
 
+.<Click to see the alternate annotations syntax>
+[%collapsible]
+====
+[source,groovy]
+----
+include::../test/SealedSpecificationTest.groovy[tags=general_sealed_class_annotations,indent=0]
+----
+====
+
+&nbsp; +
 In this example, our permitted subclasses for `Shape` are `Circle`, `Polygon`, 
and `Rectangle`.
 `Circle` is `final` and hence that part of the hierarchy cannot be extended.
-`Polygon` is implicitly non-sealed and `RegularPolygon` is explicitly marked 
as `@NonSealed`.
+`Polygon` is implicitly non-sealed and `RegularPolygon` is explicitly marked 
as `non-sealed`.
 That means our heiarchy is open to any further extension by subclassing,
 as seen with `Polygon -> RegularPolygon` and `RegularPolygon -> Hexagon`.
 `Rectangle` is itself sealed which means that part of the hierarchy can be 
extended
@@ -84,15 +111,39 @@ Sealed hierarchies are also useful when specifying 
Algebraic or Abstract Data Ty
 include::../test/SealedSpecificationTest.groovy[tags=sealed_ADT,indent=0]
 ----
 
+Sealed hierarchies work well with records as shown in the following example:
+
+[source,groovy]
+----
+include::../test/SealedSpecificationTest.groovy[tags=sealedRecord_ADT,indent=0]
+----
+
 == Differences to Java
 
 * Java provides no default modifier for subclasses of sealed classes
 and requires that one of `final`, `sealed` or `non-sealed` be specified.
-Groovy defaults to _non-sealed_ but you can still use `@NonSealed` if you wish.
+Groovy defaults to _non-sealed_ but you can still use `non-sealed/@NonSealed` 
if you wish.
 We anticipate the style checking tool CodeNarc will eventually have a rule that
-looks for the presence of `@Nonsealed` so developers wanting that stricter
+looks for the presence of `non-sealed` so developers wanting that stricter
 style will be able to use CodeNarc and that rule if they want.
 
 * Currently, Groovy doesn't check that all classes mentioned in 
`permittedSubclasses`
 are available at compile-time and compiled along with the base sealed class.
-This may change in a future version of Groovy.
\ No newline at end of file
+This may change in a future version of Groovy.
+
+Groovy supports annotating classes as sealed as well as "native" sealed 
classes.
+
+The `@SealedOptions` annotation supports a `mode` annotation attribute
+which can take one of three values (with `AUTO` being the default):
+
+NATIVE::
+Produces a class similar to what Java would do.
+Produces an error when compiling on JDKs earlier than JDK17.
+EMULATE::
+Indicates the class is sealed using the `@Sealed` annotation.
+This mechanism works with the Groovy compiler for JDK8+ but is not recognised 
by the Java compiler.
+AUTO::
+Produces a native record for JDK17+ and emulates the record otherwise.
+
+Whether you use the `sealed` keyword or the `@Sealed` annotation
+is independent of the mode.
diff --git a/src/spec/test/SealedSpecificationTest.groovy 
b/src/spec/test/SealedSpecificationTest.groovy
index dbfd2de..8b9d63b 100644
--- a/src/spec/test/SealedSpecificationTest.groovy
+++ b/src/spec/test/SealedSpecificationTest.groovy
@@ -29,7 +29,7 @@ class SealedSpecificationTest extends GroovyTestCase {
 // tag::sealed_ADT[]
 import groovy.transform.*
 
-@Sealed interface Tree<T> {}
+sealed interface Tree<T> {}
 @Singleton final class Empty implements Tree {
     String toString() { 'Empty' }
 }
@@ -44,15 +44,41 @@ assert tree.toString() == 'Node(42, Node(0, Empty, Empty), 
Empty)'
 '''
     }
 
+    void testSealedRecordADT() {
+        assertScript '''
+// tag::sealedRecord_ADT[]
+sealed interface Expr {}
+record ConstExpr(int i) implements Expr {}
+record PlusExpr(Expr e1, Expr e2) implements Expr {}
+record MinusExpr(Expr e1, Expr e2) implements Expr {}
+record NegExpr(Expr e) implements Expr {}
+
+def threePlusNegOne = new PlusExpr(new ConstExpr(3), new NegExpr(new 
ConstExpr(1)))
+assert threePlusNegOne.toString() == 'PlusExpr[e1=ConstExpr[i=3], 
e2=NegExpr[e=ConstExpr[i=1]]]'
+// end::sealedRecord_ADT[]
+'''
+    }
+
     void testSimpleSealedHierarchyInterfaces() {
         assertScript '''
 import groovy.transform.Sealed
 
-// tag::simple_interface[]
+// tag::simple_interface_keyword[]
+sealed interface ShapeI permits Circle,Square { }
+final class Circle implements ShapeI { }
+final class Square implements ShapeI { }
+// end::simple_interface_keyword[]
+
+assert [new Circle(), new Square()]*.class.name == ['Circle', 'Square']
+'''
+        assertScript '''
+import groovy.transform.Sealed
+
+// tag::simple_interface_annotations[]
 @Sealed(permittedSubclasses=[Circle,Square]) interface ShapeI { }
 final class Circle implements ShapeI { }
 final class Square implements ShapeI { }
-// end::simple_interface[]
+// end::simple_interface_annotations[]
 
 assert [new Circle(), new Square()]*.class.name == ['Circle', 'Square']
 '''
@@ -64,6 +90,25 @@ import groovy.transform.Sealed
 import groovy.transform.NonSealed
 
 // tag::general_sealed_class[]
+sealed class Shape permits Circle,Polygon,Rectangle { }
+
+final class Circle extends Shape { }
+
+class Polygon extends Shape { }
+non-sealed class RegularPolygon extends Polygon { }
+final class Hexagon extends Polygon { }
+
+sealed class Rectangle extends Shape permits Square{ }
+final class Square extends Rectangle { }
+// end::general_sealed_class[]
+
+assert [new Circle(), new Square(), new Hexagon()]*.class.name == ['Circle', 
'Square', 'Hexagon']
+'''
+        assertScript '''
+import groovy.transform.Sealed
+import groovy.transform.NonSealed
+
+// tag::general_sealed_class_annotations[]
 @Sealed(permittedSubclasses=[Circle,Polygon,Rectangle]) class Shape { }
 
 final class Circle extends Shape { }
@@ -74,7 +119,7 @@ final class Hexagon extends Polygon { }
 
 @Sealed(permittedSubclasses=Square) class Rectangle extends Shape { }
 final class Square extends Rectangle { }
-// end::general_sealed_class[]
+// end::general_sealed_class_annotations[]
 
 assert [new Circle(), new Square(), new Hexagon()]*.class.name == ['Circle', 
'Square', 'Hexagon']
 '''
@@ -95,7 +140,7 @@ assert forecast.toString() == '[Rainy, Sunny, Cloudy]'
 import groovy.transform.*
 
 // tag::weather_sealed[]
-@Sealed abstract class Weather { }
+sealed abstract class Weather { }
 @Immutable(includeNames=true) class Rainy extends Weather { Integer 
expectedRainfall }
 @Immutable(includeNames=true) class Sunny extends Weather { Integer 
expectedTemp }
 @Immutable(includeNames=true) class Cloudy extends Weather { Integer 
expectedUV }

Reply via email to