<commit_before>:source-highlighter: pygments
:pygments-style: emacs
:icons: font

Groovy 4 builds upon existing features and streamlines various legacy options.

[width="80%",align="center"]
|===
a| NOTE: _WARNING:_
Material on this page is still under development! +
We are currently releasing alpha versions of Groovy 4.0 with a goal
of gathering feedback on the language changes from our community. In addition, early versions assist other projects
and tool vendors within the Groovy ecosystem to begin assessing the impact of moving to/supporting Groovy 4.0.
Caution should be exercised if using new features as the details may change before final release. +
Some features described here as "incubating" may become stable before 4.0.0 final is released,
others are expected to remain incubating for version 4.
We don't recommend using alpha versions or incubating features for production systems.
|===


[[Groovy4.0-naming-changes]]
== Important naming/structuring changes

[[Groovy4.0-maven-coordinates]]
=== Maven coordinate change

In Groovy 4.0, the _groupId_ of the maven coordinates for Groovy have changed from `org.codehaus.groovy`
to `org.apache.groovy`. Please update your Gradle/Maven/other build settings appropriately.

[[Groovy4.0-split-package-renaming]]
=== Legacy package removal

The Java Platform Module System (JPMS) requires that classes in distinct modules
have distinct package names. Groovy has its own "modules" but these haven't
historically been structured according to the above requirement.

Groovy 3 provided duplicate versions of numerous classes (in old and new packages)
to allow Groovy users to migrate towards the new JPMS compliant package names.
See the link:http://groovy-lang.org/releasenotes/groovy-3.0.html#Groovy3.0releasenotes-Splitpackages[Groovy 3 release notes]
for more details.
In short, time to stop using `groovy.util.XmlSlurper` and start using `groovy.xml.XmlSlurper`.
Similarly, you should now be using `groovy.xml.XmlParser`, `groovy.ant.AntBuilder`, `groovy.test.GroovyTestCase`
and the other classes mentioned in the prior mentioned Groovy 3 release notes.

[[Groovy4.0-module-changes]]
=== Module changes for groovy-all

Based on user feedback and download statistics, we rejigged which modules are included in the `groovy-all` pom
(link:https://issues.apache.org/jira/browse/GROOVY-9647[GROOVY-9647]).
The `groovy-yaml` module is fairly widely used and is now included in `groovy-all`.
The `groovy-testng` module is less widely used and is no longer included in `groovy-all`.
Please adjust your build script dependencies if needed.
If you are using the Groovy distribution, no changes are required since it
includes the optional modules.

[[Groovy4.0-consolidation]]
== Legacy consolidation

[[Groovy4.0-parrot-only]]
=== Old parser removal

Groovy 3 introduced the new "Parrot" parser which supports lambdas, method
references, and numerous other tweaks. In Groovy 3, You could still revert back to the old parser
if you wanted. In Groovy 4, the old Antlr2 based parser is removed.
Please use older versions of Groovy if you require the old parser.

[[Groovy4.0-indy-only]]
=== Classic bytecode generation removal

For many versions, Groovy could generate classic _call-site based_ bytecode
or bytecode targeting the JDK7+ invoke dynamic ("indy") bytecode instructions.
You could switch between them with a compiler switch and we had two sets of
jars ("normal" and "-indy") built with and without the switch enabled.
In Groovy 4.0, only bytecode using the latter approach can be generated.
There is now one set of jars and they happen to be indy flavored.

Currently, the Groovy runtime still contains any necessary support for
classes compiled using older versions of Groovy.
Please use Groovy versions up to 3.x if you need to generate the older
style bytecode.

This work was originally planned for Groovy 3.0, but there were numerous places
where "indy" code was noticeably slower than "classic" bytecode.
We have made numerous speed improvements (starting with https://issues.apache.org/jira/browse/GROOVY-8298[GROOVY-8298])
and have some ability to tune internal thresholds (search the code base for
`groovy.indy.optimize.threshold` and `groovy.indy.fallback.threshold`).
That work gave us useful speed improvements, but we welcome further feedback
to help improve overall performance of the indy bytecode.

[[Groovy4.0-new]]
== New features

[[Groovy4.0-new-checkers]]
=== Built-in type checkers

Groovy's static nature includes an extensible type-checking mechanism.
This mechanism allows users to:

* selectively weaken type checking to allow more dynamic style code to parse static checking, or
* strengthen type checking, allowing Groovy to be much stricter than Java in scenarios where that is desirable

So far, we know this feature has been used internally by companies (e.g. type-checked DSLs),
but we haven't seen widespread sharing of type checker extensions.
From Groovy 4, we plan to bundle some select type checkers within the optional
`groovy-typecheckers` module,
to encourage further use of this feature.

The first inclusion is a checker for regular expressions. Consider the following code:

[source,groovy]
--------------------------------------
def newYearsEve = '2020-12-31'
def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/
--------------------------------------

This passes compilation but fails at runtime with a `PatternSyntaxException`
since we "accidentally" left off the final closing bracket.
We can get this feedback at compilation time using the new checker as follows:

[source,groovy]
--------------------------------------
import groovy.transform.TypeChecked

@TypeChecked(extensions = 'groovy.typecheckers.RegexChecker')
def whenIs2020Over() {
    def newYearsEve = '2020-12-31'
    def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/
}
--------------------------------------

Which gives this expected compilation error:

--------------------------------------
1 compilation error:
[Static type checking] - Bad regex: Unclosed group near index 26
(\d{4})-(\d{1,2})-(\d{1,2}
 at line: 6, column: 19
--------------------------------------

As usual, Groovy's compiler customization mechanisms would allow you to
simplify application of such checkers, e.g. make it apply globally
using a compiler configuration script, as just one example.

We welcome further feedback on additional type checker extensions to include within Groovy.

[[Groovy4.0-new-macro-builtins]]
=== Built-in macro methods

Groovy macros were introduced in Groovy 2.5 to make it easier to create AST transforms
and other code which manipulates the compiler AST data structures.
One part of macros, known as macro methods, allows what looks like a global method call
to be replaced with transformed code during compilation.

A bit like type checker extensions, we know this feature has been used in numerous places,
but so far, we haven't seen widespread sharing of macro methods.
From Groovy 4, we plan to bundle some select macro methods within the optional
`groovy-macro-library` module,
to encourage further use of this feature.

The first inclusions assist with old-school debugging (poor man's serialization?).
Suppose during coding you have defined numerous variables:

[source,groovy]
--------------------------------------
def num = 42
def list = [1 ,2, 3]
def range = 0..5
def string = 'foo'
--------------------------------------

Suppose now you want to print those out for debugging purposes.
You could write some appropriate `println` statements and maybe sprinkle in some
calls to `format()`. You might even have an IDE help you do that.
Alternatively, the `NV` macro method comes to the rescue:

[source,groovy]
--------------------------------------
println NV(num, list, range, string)
--------------------------------------

which outputs:

--------------------------------------
num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo
--------------------------------------

Here, the `NV` macro method springs into action during the compilation process.
The compiler replaces the apparent global `NV` method call with an expression
which combines the names and `toString()` values of the supplied variables.

Two other variations exist. `NVI` calls Groovy's `inspect()` method rather than
`toString()` and `NVD` calls Groovy's `dump()` method. So this code:

[source,groovy]
--------------------------------------
println NVI(range)
--------------------------------------

produces the following output:

--------------------------------------
range=0..5
--------------------------------------

And this code:

[source,groovy]
--------------------------------------
println NVD(range)
--------------------------------------

yields:

--------------------------------------
range=<groovy.lang.IntRange@14 from=0 to=5 reverse=false inclusive=true modCount=0>
--------------------------------------

We welcome further feedback on additional macro methods to include within Groovy.
If you do enable this optional module but want to limit which macro methods are enabled,
there is now a mechanism to disable individual macro methods (and extension methods)
link:https://issues.apache.org/jira/browse/GROOVY-9675[GROOVY-9675].

[[Groovy4.0-new-javashell]]
=== JavaShell (incubating)

A Java equivalent of GroovyShell, allowing to more easily work with snippets of Java code.
As an example, the following snippet shows compiling a _record_ (JDK14) and checking its `toString` with Groovy:

[source,groovy]
--------------------------------------
import org.apache.groovy.util.JavaShell
def opts = ['--enable-preview', '--release', '14']
def src = 'record Coord(int x, int y) {}'
Class coordClass = new JavaShell().compile('Coord', opts, src)
assert coordClass.newInstance(5, 10).toString() == 'Coord[x=5, y=10]'
--------------------------------------

This feature is used in numerous places within the Groovy codebase for testing purposes.
Various code snippets are compiled using both Java and Groovy to ensure the compiler is behaving as intended.
We also use this feature to provide a productivity enhancement for polyglot developers allowing
Java code to be compiled and/or run (as Java) from within the Groovy Console:

image:img/groovyconsole_run_as_java.png[image] +

[[Groovy4.0-new-pojo]]
=== POJO Annotation (incubating)

Groovy supports both dynamic and static natures.
Dynamic Groovy's power and flexibility comes from making (potentially extensive) use of the runtime.
Static Groovy relies on the runtime library much less. Many method calls will have bytecode
corresponding to direct JVM method calls (similar to Java bytecode)
while the Groovy runtime is often bypassed altogether.
But even for static Groovy, hard-links to the Groovy jars remain.
All Groovy classes still implement the `GroovyObject` interface (and so have methods like `getMetaClass` and `invokeMethod`)
and there are some other places which call into the Groovy runtime.

The `@POJO` marker interface is used to indicate that the generated class is more like a plain old Java object
than an enhanced Groovy object. The annotation is currently ignored unless combined with `@CompileStatic`.
For such a class, the compiler won't generate methods typically needed by Groovy, e.g. `getMetaClass()`.
This feature is typically used for generating classes which need to be used with Java or Java frameworks
in situations where Java might become confused by Groovy's "plumbing" methods.

The feature is incubating. Currently, the presence of the annotation should be
treated like a _hint_ to the compiler to produce bytecode not relying on the
Groovy runtime if it can, but _not a guarantee_.

Users of `@CompileStatic` will know that certain dynamic
features aren't possible when they switch to static Groovy.
They might expect that using `@CompileStatic` and `@POJO`
might result in even more restrictions.
This isn't strictly the case.
Adding `@POJO` does result in more Java-like code in certain places,
but numerous Groovy features still work.

Consider the following example. First a Groovy `Point` class:

[source,groovy]
--------------------------------------
@CompileStatic
@POJO
@Canonical(includeNames = true)
class Point {
    Integer x, y
}
--------------------------------------

And now a Groovy `PointList` class:

[source,groovy]
--------------------------------------
@CompileStatic
@POJO
class PointList {
    @Delegate
    List<Point> points
}
--------------------------------------

We can compile those classes using `groovyc` in the normal way
and should see the expected _Point.class_ and _PointList.class_ files produced.

We can then compile the following Java code.
We do not need the Groovy jars available for `javac` or `java`,
we only need the class files produced from the previous step.

[source,java]
--------------------------------------
Predicate<Point> xNeqY = p -> p.getX() != p.getY();  // <1>

Point p13 = new Point(1, 3);
List<Point> pts = List.of(p13, new Point(2, 2), new Point(3, 1));
PointList list = new PointList();
list.setPoints(pts);

System.out.println(list.size());
System.out.println(list.contains(p13));

list.forEach(System.out::println);

long count = list.stream().filter(xNeqY).collect(counting());  // <2>
System.out.println(count);
--------------------------------------
<1> Check whether x not equal to y
<2> Count points where x neq y

Note that while our `PointList` class has numerous list methods available
(`size`, `contains`, `forEach`, `stream`, etc.) courtesy of Groovy's `@Delegate` transform,
these are baked into the class file, and the bytecode produced doesn't call
into any Groovy libraries or rely on any runtime code.

When run, the following output is produced:

--------------------------------------
3
true
Point(x:1, y:3)
Point(x:2, y:2)
Point(x:3, y:1)
2
--------------------------------------

In essence, this opens up the possibility to use Groovy
as a kind of pre-processor similar to https://projectlombok.org/[Lombok] but backed by the Groovy language.

[width="80%",align="center"]
|===
a| NOTE: _WARNING:_
Not all parts of the compiler and not all AST transforms yet know about `POJO`.
Your mileage may vary as to whether using this approach will or won't require
the Groovy jars to be on the classpath. While we anticipate some improvements over time
allowing more Groovy constructs to work with `@POJO`, we currently make no guarantees that
all constructs will eventually be supported. Hence the incubating status.
|===

[[Groovy4.0-new-records]]
=== Record-like classes (incubating)

Java 14 introduced _records_ as a _preview_ feature.
As per this https://www.infoq.com/articles/java-14-feature-spotlight/[records spotlight article],
records "model _plain data_ aggregates with less ceremony".
Groovy has features like the `@Immutable` transformation which already support
modeling data aggregates with less ceremony, and while these features overlap to some degree
with the design of records, they are not a direct equivalent.
Records are a slight re-mixing of the features of `@Immutable` with a fex variations added to the mix.

Luckily for us, Groovy's `@Immutable` is itself a meta-annotation (also known as annotation collector)
which combines more fine-grained features. It is relatively simple to provide a record-like re-mix<commit_msg>fix typo thanks to @chagmed<commit_after>:source-highlighter: pygments
:pygments-style: emacs
:icons: font

Groovy 4 builds upon existing features and streamlines various legacy options.

[width="80%",align="center"]
|===
a| NOTE: _WARNING:_
Material on this page is still under development! +
We are currently releasing alpha versions of Groovy 4.0 with a goal
of gathering feedback on the language changes from our community. In addition, early versions assist other projects
and tool vendors within the Groovy ecosystem to begin assessing the impact of moving to/supporting Groovy 4.0.
Caution should be exercised if using new features as the details may change before final release. +
Some features described here as "incubating" may become stable before 4.0.0 final is released,
others are expected to remain incubating for version 4.
We don't recommend using alpha versions or incubating features for production systems.
|===


[[Groovy4.0-naming-changes]]
== Important naming/structuring changes

[[Groovy4.0-maven-coordinates]]
=== Maven coordinate change

In Groovy 4.0, the _groupId_ of the maven coordinates for Groovy have changed from `org.codehaus.groovy`
to `org.apache.groovy`. Please update your Gradle/Maven/other build settings appropriately.

[[Groovy4.0-split-package-renaming]]
=== Legacy package removal

The Java Platform Module System (JPMS) requires that classes in distinct modules
have distinct package names. Groovy has its own "modules" but these haven't
historically been structured according to the above requirement.

Groovy 3 provided duplicate versions of numerous classes (in old and new packages)
to allow Groovy users to migrate towards the new JPMS compliant package names.
See the link:http://groovy-lang.org/releasenotes/groovy-3.0.html#Groovy3.0releasenotes-Splitpackages[Groovy 3 release notes]
for more details.
In short, time to stop using `groovy.util.XmlSlurper` and start using `groovy.xml.XmlSlurper`.
Similarly, you should now be using `groovy.xml.XmlParser`, `groovy.ant.AntBuilder`, `groovy.test.GroovyTestCase`
and the other classes mentioned in the prior mentioned Groovy 3 release notes.

[[Groovy4.0-module-changes]]
=== Module changes for groovy-all

Based on user feedback and download statistics, we rejigged which modules are included in the `groovy-all` pom
(link:https://issues.apache.org/jira/browse/GROOVY-9647[GROOVY-9647]).
The `groovy-yaml` module is fairly widely used and is now included in `groovy-all`.
The `groovy-testng` module is less widely used and is no longer included in `groovy-all`.
Please adjust your build script dependencies if needed.
If you are using the Groovy distribution, no changes are required since it
includes the optional modules.

[[Groovy4.0-consolidation]]
== Legacy consolidation

[[Groovy4.0-parrot-only]]
=== Old parser removal

Groovy 3 introduced the new "Parrot" parser which supports lambdas, method
references, and numerous other tweaks. In Groovy 3, You could still revert back to the old parser
if you wanted. In Groovy 4, the old Antlr2 based parser is removed.
Please use older versions of Groovy if you require the old parser.

[[Groovy4.0-indy-only]]
=== Classic bytecode generation removal

For many versions, Groovy could generate classic _call-site based_ bytecode
or bytecode targeting the JDK7+ invoke dynamic ("indy") bytecode instructions.
You could switch between them with a compiler switch and we had two sets of
jars ("normal" and "-indy") built with and without the switch enabled.
In Groovy 4.0, only bytecode using the latter approach can be generated.
There is now one set of jars and they happen to be indy flavored.

Currently, the Groovy runtime still contains any necessary support for
classes compiled using older versions of Groovy.
Please use Groovy versions up to 3.x if you need to generate the older
style bytecode.

This work was originally planned for Groovy 3.0, but there were numerous places
where "indy" code was noticeably slower than "classic" bytecode.
We have made numerous speed improvements (starting with https://issues.apache.org/jira/browse/GROOVY-8298[GROOVY-8298])
and have some ability to tune internal thresholds (search the code base for
`groovy.indy.optimize.threshold` and `groovy.indy.fallback.threshold`).
That work gave us useful speed improvements, but we welcome further feedback
to help improve overall performance of the indy bytecode.

[[Groovy4.0-new]]
== New features

[[Groovy4.0-new-checkers]]
=== Built-in type checkers

Groovy's static nature includes an extensible type-checking mechanism.
This mechanism allows users to:

* selectively weaken type checking to allow more dynamic style code to parse static checking, or
* strengthen type checking, allowing Groovy to be much stricter than Java in scenarios where that is desirable

So far, we know this feature has been used internally by companies (e.g. type-checked DSLs),
but we haven't seen widespread sharing of type checker extensions.
From Groovy 4, we plan to bundle some select type checkers within the optional
`groovy-typecheckers` module,
to encourage further use of this feature.

The first inclusion is a checker for regular expressions. Consider the following code:

[source,groovy]
--------------------------------------
def newYearsEve = '2020-12-31'
def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/
--------------------------------------

This passes compilation but fails at runtime with a `PatternSyntaxException`
since we "accidentally" left off the final closing bracket.
We can get this feedback at compilation time using the new checker as follows:

[source,groovy]
--------------------------------------
import groovy.transform.TypeChecked

@TypeChecked(extensions = 'groovy.typecheckers.RegexChecker')
def whenIs2020Over() {
    def newYearsEve = '2020-12-31'
    def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/
}
--------------------------------------

Which gives this expected compilation error:

--------------------------------------
1 compilation error:
[Static type checking] - Bad regex: Unclosed group near index 26
(\d{4})-(\d{1,2})-(\d{1,2}
 at line: 6, column: 19
--------------------------------------

As usual, Groovy's compiler customization mechanisms would allow you to
simplify application of such checkers, e.g. make it apply globally
using a compiler configuration script, as just one example.

We welcome further feedback on additional type checker extensions to include within Groovy.

[[Groovy4.0-new-macro-builtins]]
=== Built-in macro methods

Groovy macros were introduced in Groovy 2.5 to make it easier to create AST transforms
and other code which manipulates the compiler AST data structures.
One part of macros, known as macro methods, allows what looks like a global method call
to be replaced with transformed code during compilation.

A bit like type checker extensions, we know this feature has been used in numerous places,
but so far, we haven't seen widespread sharing of macro methods.
From Groovy 4, we plan to bundle some select macro methods within the optional
`groovy-macro-library` module,
to encourage further use of this feature.

The first inclusions assist with old-school debugging (poor man's serialization?).
Suppose during coding you have defined numerous variables:

[source,groovy]
--------------------------------------
def num = 42
def list = [1 ,2, 3]
def range = 0..5
def string = 'foo'
--------------------------------------

Suppose now you want to print those out for debugging purposes.
You could write some appropriate `println` statements and maybe sprinkle in some
calls to `format()`. You might even have an IDE help you do that.
Alternatively, the `NV` macro method comes to the rescue:

[source,groovy]
--------------------------------------
println NV(num, list, range, string)
--------------------------------------

which outputs:

--------------------------------------
num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo
--------------------------------------

Here, the `NV` macro method springs into action during the compilation process.
The compiler replaces the apparent global `NV` method call with an expression
which combines the names and `toString()` values of the supplied variables.

Two other variations exist. `NVI` calls Groovy's `inspect()` method rather than
`toString()` and `NVD` calls Groovy's `dump()` method. So this code:

[source,groovy]
--------------------------------------
println NVI(range)
--------------------------------------

produces the following output:

--------------------------------------
range=0..5
--------------------------------------

And this code:

[source,groovy]
--------------------------------------
println NVD(range)
--------------------------------------

yields:

--------------------------------------
range=<groovy.lang.IntRange@14 from=0 to=5 reverse=false inclusive=true modCount=0>
--------------------------------------

We welcome further feedback on additional macro methods to include within Groovy.
If you do enable this optional module but want to limit which macro methods are enabled,
there is now a mechanism to disable individual macro methods (and extension methods)
link:https://issues.apache.org/jira/browse/GROOVY-9675[GROOVY-9675].

[[Groovy4.0-new-javashell]]
=== JavaShell (incubating)

A Java equivalent of GroovyShell, allowing to more easily work with snippets of Java code.
As an example, the following snippet shows compiling a _record_ (JDK14) and checking its `toString` with Groovy:

[source,groovy]
--------------------------------------
import org.apache.groovy.util.JavaShell
def opts = ['--enable-preview', '--release', '14']
def src = 'record Coord(int x, int y) {}'
Class coordClass = new JavaShell().compile('Coord', opts, src)
assert coordClass.newInstance(5, 10).toString() == 'Coord[x=5, y=10]'
--------------------------------------

This feature is used in numerous places within the Groovy codebase for testing purposes.
Various code snippets are compiled using both Java and Groovy to ensure the compiler is behaving as intended.
We also use this feature to provide a productivity enhancement for polyglot developers allowing
Java code to be compiled and/or run (as Java) from within the Groovy Console:

image:img/groovyconsole_run_as_java.png[image] +

[[Groovy4.0-new-pojo]]
=== POJO Annotation (incubating)

Groovy supports both dynamic and static natures.
Dynamic Groovy's power and flexibility comes from making (potentially extensive) use of the runtime.
Static Groovy relies on the runtime library much less. Many method calls will have bytecode
corresponding to direct JVM method calls (similar to Java bytecode)
while the Groovy runtime is often bypassed altogether.
But even for static Groovy, hard-links to the Groovy jars remain.
All Groovy classes still implement the `GroovyObject` interface (and so have methods like `getMetaClass` and `invokeMethod`)
and there are some other places which call into the Groovy runtime.

The `@POJO` marker interface is used to indicate that the generated class is more like a plain old Java object
than an enhanced Groovy object. The annotation is currently ignored unless combined with `@CompileStatic`.
For such a class, the compiler won't generate methods typically needed by Groovy, e.g. `getMetaClass()`.
This feature is typically used for generating classes which need to be used with Java or Java frameworks
in situations where Java might become confused by Groovy's "plumbing" methods.

The feature is incubating. Currently, the presence of the annotation should be
treated like a _hint_ to the compiler to produce bytecode not relying on the
Groovy runtime if it can, but _not a guarantee_.

Users of `@CompileStatic` will know that certain dynamic
features aren't possible when they switch to static Groovy.
They might expect that using `@CompileStatic` and `@POJO`
might result in even more restrictions.
This isn't strictly the case.
Adding `@POJO` does result in more Java-like code in certain places,
but numerous Groovy features still work.

Consider the following example. First a Groovy `Point` class:

[source,groovy]
--------------------------------------
@CompileStatic
@POJO
@Canonical(includeNames = true)
class Point {
    Integer x, y
}
--------------------------------------

And now a Groovy `PointList` class:

[source,groovy]
--------------------------------------
@CompileStatic
@POJO
class PointList {
    @Delegate
    List<Point> points
}
--------------------------------------

We can compile those classes using `groovyc` in the normal way
and should see the expected _Point.class_ and _PointList.class_ files produced.

We can then compile the following Java code.
We do not need the Groovy jars available for `javac` or `java`,
we only need the class files produced from the previous step.

[source,java]
--------------------------------------
Predicate<Point> xNeqY = p -> p.getX() != p.getY();  // <1>

Point p13 = new Point(1, 3);
List<Point> pts = List.of(p13, new Point(2, 2), new Point(3, 1));
PointList list = new PointList();
list.setPoints(pts);

System.out.println(list.size());
System.out.println(list.contains(p13));

list.forEach(System.out::println);

long count = list.stream().filter(xNeqY).collect(counting());  // <2>
System.out.println(count);
--------------------------------------
<1> Check whether x not equal to y
<2> Count points where x neq y

Note that while our `PointList` class has numerous list methods available
(`size`, `contains`, `forEach`, `stream`, etc.) courtesy of Groovy's `@Delegate` transform,
these are baked into the class file, and the bytecode produced doesn't call
into any Groovy libraries or rely on any runtime code.

When run, the following output is produced:

--------------------------------------
3
true
Point(x:1, y:3)
Point(x:2, y:2)
Point(x:3, y:1)
2
--------------------------------------

In essence, this opens up the possibility to use Groovy
as a kind of pre-processor similar to https://projectlombok.org/[Lombok] but backed by the Groovy language.

[width="80%",align="center"]
|===
a| NOTE: _WARNING:_
Not all parts of the compiler and not all AST transforms yet know about `POJO`.
Your mileage may vary as to whether using this approach will or won't require
the Groovy jars to be on the classpath. While we anticipate some improvements over time
allowing more Groovy constructs to work with `@POJO`, we currently make no guarantees that
all constructs will eventually be supported. Hence the incubating status.
|===

[[Groovy4.0-new-records]]
=== Record-like classes (incubating)

Java 14 introduced _records_ as a _preview_ feature.
As per this https://www.infoq.com/articles/java-14-feature-spotlight/[records spotlight article],
records "model _plain data_ aggregates with less ceremony".
Groovy has features like the `@Immutable` transformation which already support
modeling data aggregates with less ceremony, and while these features overlap to some degree
with the design of records, they are not a direct equivalent.
Records are a slight re-mixing of the features of `@Immutable` with a few variations added to the mix.

Luckily for us, Groovy's `@Immutable` is itself a meta-annotation (also known as annotation collector)
which combines more fine-grained features. It is relatively simple to provide a record-like re-mix
{'fraction_non_alphanumeric': 0.14411975735438, 'fraction_numerical': 0.012393190268084274, 'mean_word_length': 3.308416467612758, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 21, 'https://': 10, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7648323', 'n_tokens_mistral': 8512, 'n_tokens_neox': 7785, 'n_words': 3825}