Technical Documentation: apache/groovy
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Src Spec Doc Core Async Channels (`src/spec/doc/_core-async-channels.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
[[async-channels]]
== Channels
Channels provide Go-style inter-task communication. A producer sends values
into a channel; a consumer receives them as they arrive. The channel handles
synchronization and optional buffering โ no shared mutable state needed:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=channel,indent=0]
Channels support unbuffered (rendezvous, create()) and buffered (create(n))
modes. Sending blocks when the buffer is full; receiving blocks when empty.
With virtual threads, this blocking is essentially free.
Since channels implement Iterable, they also work with regular for loops
and Groovy collection methods.
[[async-channel-composition]]
== Channel Composition
AsyncChannel supports composable pipeline operations. Each
composition method returns a new channel connected to the source
by a background task.
=== filter
[source,groovy]
def source = AsyncChannel.create(10)
def evens = source.filter { it % 2 == 0 }
async {
(1..10).each { source.send(it) }
source.close()
}
for await (val in evens) { println val } // 2, 4, 6, 8, 10
=== map
[source,groovy]
def doubled = source.map { it * 2 }
=== Chaining
Operations compose naturally:
[source,groovy]
def pipeline = source
.filter { it > 50 }
.map { it * 2 }
for await (val in pipeline) { process(val) }
=== merge
Combines two channels into one, interleaving values as they arrive:
[source,groovy]
def merged = channelA.merge(channelB) for await (event in merged) { handle(event) }
=== split
Partitions a channel into two based on a predicate:
[source,groovy]
def (highPriority, normal) = orders.split { it.priority > 5 }
=== tap
Sends a copy of each value to a monitoring channel while passing
through unchanged:
[source,groovy]
def monitor = AsyncChannel.create(100) def output = source.tap(monitor) // output gets all values; monitor gets copies for logging
[[async-channel-select]]
== Channel Select
ChannelSelect waits for the first available value from multiple
channels โ like Go's select statement:
[source,groovy]
import groovy.concurrent.ChannelSelect
def prices = AsyncChannel.create(10)
def alerts = AsyncChannel.create(10)
def sel = ChannelSelect.from(prices, alerts) def result = await sel.select() println "Channel ${result.index}: ${result.value}"
A select takes exactly one value from exactly one channel; the other
channels are left untouched. When several channels already have a value
waiting, the one listed first is taken. If every channel is closed and
drained, select() fails with ChannelClosedException.
That priority by list order means a channel that always has a value
waiting starves the channels listed after it. fair() returns a select
that rotates instead โ each call starts at the channel after the one
that last won, like JCSP's fairSelect โ so keep the instance across
calls:
[source,groovy]
def sel = ChannelSelect.from(requests, control).fair()
while (true) {
def result = await sel.select()
// control is served even while requests never runs dry
}
random() chooses uniformly at random among the ready channels instead,
as Go's select does. It keeps no state between calls, so it is equally
fair from any number of threads, but it gives no bound on how long a
ready channel may be passed over; fair() guarantees every ready channel
is taken within n calls.
=== Mixed offers
A select may also offer to send โ the mixed choice of the CSP
literature: "I will send my opener, or take my peer's if it sends
first". Offers are built with the static factories send and receive
and combined with offers, mirroring the send cases of Go's select:
[source,groovy]
import static groovy.concurrent.ChannelSelect.*
def result = await offers(send(ping, 1), receive(pong)).select()
if (result.send) {
// my opener committed: continue the ping branch
} else {
// my peer opened first: result.value is the pong opener
}
Exactly one offer commits; a committed send behaves exactly likeping.send(1), while a retired one leaves no trace โ no buffered
residue, no waiting sender โ just as a losing receive leaves its channel
untouched. fair() and random() order the scan over offers exactly as
they do over channels, and send offers require channels created byAsyncChannel.create.
A send offer commits as soon as its channel can accept the value: when a
receiver is waiting, or when buffer space holds it. Two peers racing a
mixed choice against each other therefore agree on a single opener only
over rendezvous (capacity-0) channels, where a send cannot complete
unilaterally; over buffered channels with space, each peer's send offer
commits at once under its own select and the peers proceed down
different branches of the same conversation.
=== Preconditions
Each branch may also carry a precondition โ the guarded choice of CSP.when writes the guard onto the offer itself, as occam, Ada, Erlang and
Kotlin do:
[source,groovy]
def sel = offers(receive(input).when { held.size() < capacity }, // <1>
receive(request).when { !held.isEmpty() }) // <2>
while (true) {
def result = await sel.select()
if (result.index == 0) {
held.addLast(result.value)
} else {
await reply.send(held.removeFirst())
}
}
<1> take input only while there is room
<2> answer a request only while there is something to hand over
A guarded-off offer is not registered on its channel, so nothing is taken
from it and nothing is sent to it, but it keeps its position, soresult.index denotes the same branch either way. Dropping an offer from
the argument list instead would renumber every branch after it, silently
changing what an index denotes.
The guard is a closure rather than a boolean because it is consulted
afresh on every select call โ that is what lets one select be built
once and reused as the state it guards on changes. when returns a
guarded copy, leaving the original offer alone, and guards conjoin: over
a second when, both must hold.
select also accepts the same preconditions positionally, one flag per
offer, which suits a mask computed as a whole:
[source,groovy]
def result = await sel.select(held.size() < capacity, !held.isEmpty())
A flag is conjoined with whatever guard its offer carries โ both must
hold for the offer to be registered โ and passing a number of flags other
than the number of offers is an IllegalArgumentException. Enabling no
branch at all is a deadlock rather than a wait, so select fails withIllegalStateException.
A branch can also be recognised by identity instead of by position:result.channel is the channel the winning offer transferred over.
=== Timeouts
A deadline can be a branch of the select, as it is in Go, Rust, Clojure
and Kotlin, instead of an exception thrown around the whole select byorTimeout. There are two forms, one for a timeout that re-arms on every
round and one for a fixed deadline.
The timer offer after arms a fresh timer each time select is called
and commits, with the Instant it fired, if no channel offer could
commit first. It is the "wait for work, but not forever" branch of a
select that is built once and reused, and because the timer lives inside
the call, the held instance keeps its fair() rotation from round to
round:
[source,groovy]
import static groovy.concurrent.ChannelSelect.*
def sel = offers(receive(work), after(100)).fair()
while (true) {
def result = await sel.select()
if (result.timeout) {
// nothing arrived within 100 ms of this call: result.value is the Instant
} else {
handle(result.value)
}
}
A timer offer that loses its round is cancelled with the other losing
branches, so it leaves nothing armed. It reports result.timeout rather
than a channel: result.channel is null for a timeout, and result.index
still names the branch. So a select that carries a timer offer and
dispatches on result.channel must test result.timeout first, as the
loop above does. Like any offer it can be guarded with when, and
a guarded-off timer offer arms nothing. Several timer offers give
per-branch deadlines.
A timer offer with a positive delay is never ready at the moment the
select registers its offers, so the choice policy considers only the
channel offers that are ready at once; the timer commits only when none
was. A delay that is not positive has already elapsed, so that offer is
ready at registration and the policy treats it like any other ready
branch: priority takes whichever is listed first, and fair() rotates
over it. Listed last, after(0) is the default clause of Go'sselect โ take a transfer if one can complete at once, otherwise
proceed without waiting:
[source,groovy]
def result = await offers(receive(work), after(0)).select()
if (result.timeout) {
// nothing was ready; do something else and come back
}
The timer channel AsyncChannel.after is the fixed-deadline form: a
capacity-1 channel that delivers one value, the Instant it fired, once
the given delay has elapsed from its creation, and then closes, as Go'stime.After does. Received from within a select, it is a deadline shared
by every round that selects on it:
[source,groovy]
def deadline = AsyncChannel.after(5000)
def sel = ChannelSelect.from(work, deadline)
while (true) {
def result = await sel.select()
if (result.index == 1) break // five seconds since the deadline was set
handle(result.value)
}
Because it is an ordinary channel, a timer channel composes with
everything a channel does: it can be guarded with when, and once firedfair() and random() treat it like any other ready channel. A timer
channel that loses a select keeps its value: it fires all the same and
holds the instant until received. Once that value has been taken, further
receives fail with ChannelClosedException rather than waiting forever,
and isClosed() reports that the timer has fired. Closing a timer channel
before it fires cancels it.
Both forms accept a java.time.Duration in place of milliseconds, and in
both a delay that is not positive has already elapsed: the timer channel
is created already holding its instant, and the timer offer is ready at
registration. That suits a deadline computed from an absolute time that
has already passed.
[[async-broadcast-channel]]
== BroadcastChannel
A BroadcastChannel delivers every value to all subscribers
(one-to-many), unlike AsyncChannel which is point-to-point:
[source,groovy]
import groovy.concurrent.BroadcastChannel
def broadcast = BroadcastChannel.create()
def sub1 = broadcast.subscribe()
def sub2 = broadcast.subscribe()
async {
broadcast.send('hello')
broadcast.send('world')
broadcast.close()
}
// Both subscribers receive both messages for await (msg in sub1) { println "Sub1: $msg" } for await (msg in sub2) { println "Sub2: $msg" }
Late subscribers only receive messages sent after they subscribe.
=== Flow.Publisher interop
A BroadcastChannel can also be exposed as a java.util.concurrent.Flow.Publisher
via asPublisher(). Each call to subscribe(Flow.Subscriber) on the returned
publisher creates a new per-subscriber AsyncChannel under the hood and respects
standard Reactive Streams demand (request(n)):
[source,groovy]
def broadcast = BroadcastChannel.create()
def publisher = broadcast.asPublisher()
// Compose with any Reactive Streams operator, or consume with for await:
for await (msg in publisher) {
println "Got: $msg"
}
[WARNING]
Backpressure policy. The publisher bridge uses lossless, sender-gated
backpressure: send() awaits delivery to every live subscriber, and each
per-subscriber channel has a bounded buffer (default 16). A subscriber that
never calls request(n) โ or requests slowly โ will fill its buffer, after
which broadcast.send(...) stalls for all subscribers until that
subscriber catches up. In other words, the slowest subscriber controls
producer throughput.
This matches the point-to-point semantics of `subscribe()` (values are neither dropped nor reordered). If you need decoupled per-subscriber policies (drop-newest, drop-oldest, latest-only, or unbounded buffering), wrap the publisher with a Reactive Streams operator of your choice, or use a subscriber that drains promptly with `request(Long.MAX_VALUE)`.
[NOTE]
*Terminal signals.* Per Reactive Streams ยง1.7, terminal completion is not gated by demand: once the underlying `BroadcastChannel` is closed and the per-subscriber buffer is drained, the subscriber receives `onComplete` even if it has no outstanding `request(n)`. Likewise, `onError` for a spec-violating `request(n <= 0)` (ยง3.9) is signalled through the same single-threaded delivery path as `onNext` / `onComplete`, preserving ยง1.3 signal serialization; subscribers do not see those signals racing on the caller's thread.
[[async-channels-csp]]
== Coming from JCSP or GroovyCSP
Channels, select, preconditions and timer branches give groovy.concurrent
the core vocabulary of CSP, so process networks written with
https://www.cs.kent.ac.uk/projects/ofa/jcsp/[JCSP], or with the GroovyCSP
helpers (PAR, ALT) that shipped in GPars, translate shape for shape.
A process becomes an async task, a PAR becomes a scope, and the poison
pill that shuts a network down becomes close():
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=csp_network,indent=0]
Each stage reads until its input is closed and drained, then closes its own
output, so shutdown flows down the network just as poison does.AsyncScope.withScope does not return until every process in it has
finished, and a failing process cancels the rest.
[cols="2,3", options="header"]
|===
| JCSP / GroovyCSP | groovy.concurrent
| Channel.one2one() (unbuffered)
| AsyncChannel.create(), a rendezvous channel: a send waits for its receive
| Channel.one2one(new Buffer(n))
| AsyncChannel.create(n)
| any2one, one2any, any2any
| Any AsyncChannel. Channel ends are not owned, so any task may send or receive
| out.write(v) / in.read()
| await c.send(v) / await c.receive(), or for await (v in c) to read until closed
| A CSProcess
| An async { } task, or a method that returns one
| PAR
| AsyncScope.withScope { scope -> scope.async { โฆ } โฆ }
| Poison
| close(). Receivers drain what was sent, then see the channel as closed
| GDelta and other copy-to-every-output processes
| BroadcastChannel and subscribe(), or tap for a single copy
| ALT with priSelect()
| ChannelSelect.from(a, b).select(), which prefers channels in list order
| ALT with fairSelect()
| ChannelSelect.from(a, b).fair(), built once and kept across calls
| ALT with select(), an arbitrary choice
| random()
| select(boolean[] preCondition)
| receive(c).when { โฆ } on each offer, or select(boolean... enabled)
| CSTimer as an ALT guard
| ChannelSelect.after(millis) for a timeout that re-arms every round,
or AsyncChannel.after(millis) for one fixed deadline
| Output guards (sending inside an ALT)
| offers(send(c, v), receive(d))
| Barrier (sync, enroll, resign)
| java.util.concurrent.Phaser (arriveAndAwaitAdvance, register, arriveAndDeregister)
| Crew (concurrent read, exclusive write)
| @WithReadLock / @WithWriteLock
|===
The rendezvous default matters when porting: a JCSP network that relies on
unbuffered channels for synchronisation should use AsyncChannel.create(),
not a buffered channel, or a producer will run ahead of its consumer.
JCSP's networked and mobile channels have no counterpart.
Src Spec Doc Core Gpars Migration (`src/spec/doc/_core-gpars-migration.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
[[gpars-migration]]
== Coming from GPars
Before Groovy 6, http://gpars.org/[GPars] was the usual library for
parallel collections, actors, agents and dataflow in Groovy. Thegroovy.concurrent package covers most of the same ground, rebuilt around
virtual threads, structured concurrency and async/await. GPars still
works with Groovy 6, so existing code can keep using it and move over one
feature at a time, but new code rarely needs it. The tables below map each
GPars construct to its Groovy 6 counterpart.
The biggest change in style is that results are Awaitable values rather
than GPars Promise or Future objects, and that you await them instead
of calling get() or getVal(). await also accepts anyjava.util.concurrent.Future, including the one GPars' callAsync returns.
[[gpars-migration-parallel]]
=== Parallel collections
[cols="2,3", options="header"]
|===
| GPars | Groovy 6
| GParsPool.withPool { โฆ }, withPool(n) { โฆ }
| ParallelScope.withPool(Pool.cpu()) { scope -> โฆ }, withPool(n) { scope -> โฆ }.
Parallel methods also work outside any scope, on the common pool
| withExistingPool(pool) { โฆ }
| ParallelScope.withPool(pool) { scope -> โฆ } with a Pool
| eachParallel, eachWithIndexParallel, collectParallel,
collectManyParallel, findAllParallel, findParallel,
findAnyParallel, grepParallel, splitParallel, anyParallel,
everyParallel, countParallel, groupByParallel, injectParallel
| Same names, on any Collection
| sumParallel()
| sumParallel { a, b -> a + b }. The accumulator is required
| minParallel { it.age }, maxParallel()
| minParallel { a, b -> a.age <=> b.age }. A two-argument comparator is required
| foldParallel
| injectParallel
| makeConcurrent(), asConcurrent { }, ParallelEnhancer
| Call the โฆParallel methods directly
| collection.parallel (map, filter, reduce on a parallel array)
| parallelStream(), or chain the โฆParallel methods
| GParsExecutorsPool
| Pool.io() or Pool.virtual()
| executeAsync(โฆ), executeAsyncAndWait(โฆ)
| async { } per task, then await Awaitable.all(โฆ)
| speculate(โฆ)
| await Awaitable.first(โฆ), the first to succeed
| runForkJoin(โฆ)
| java.util.concurrent.RecursiveTask on a ForkJoinPool
|===
GPars added its parallel methods to maps, strings and other iterable
objects as well. Groovy 6 adds them to Collection only, so callentrySet(), toList() or similar first.
The closures passed to the Groovy 6 methods are converted tojava.util.function types, so their parameter count is fixed. For example,minParallel and maxParallel take a two-argument comparator and do not
accept GPars' one-argument form.
[[gpars-migration-async]]
=== Asynchronous functions
[cols="2,3", options="header"]
|===
| GPars | Groovy 6
| closure.callAsync(args)
| async { closure(args) }
| closure.asyncFun()
| A closure or method that returns async { โฆ }. See
<<async-closure-factories,Async Closure Factories>>
| Promise.then { โฆ }
| awaitable.then { โฆ }, or plain sequential code after await
| Dataflow.whenAllBound(a, b) { x, y -> โฆ }
| def (x, y) = await(a, b)
|===
[[gpars-migration-agents-actors]]
=== Agents and actors
[cols="2,3", options="header"]
|===
| GPars | Groovy 6
| new Agent(0)
| Agent.create(0)
| agent << { updateValue(it + 1) }
| agent.send { it + 1 }. The closure returns the new value
| agent.val, agent.valAsync { โฆ }
| await agent.getAsync() waits for pending updates; agent.get() reads at once
| agent.sendAndWait { โฆ }
| await agent.sendAndGet { โฆ }
| agent.addListener { oldValue, newValue -> โฆ }
| for await (v in agent.changes()), or any Flow.Subscriber
| Actors.reactor { it * 2 }
| Actor.reactor { it * 2 }
| Actors.actor { loop { react { msg -> โฆ } } } holding state in local variables
| Actor.stateful(initial) { state, msg -> โฆ }, returning the new state
| Actors.messageHandler { when { String s -> โฆ } }, DynamicDispatchActor
| A switch on the message inside Actor.reactor or Actor.stateful
| actor << msg, actor.send(msg)
| actor.send(msg)
| actor.sendAndWait(msg)
| await actor.sendAndGet(msg)
| actor.sendAndContinue(msg) { reply -> โฆ }
| actor.sendAndGet(msg).then { reply -> โฆ }
| Nested react calls that change what the actor accepts next
| ctx.become(handler), ctx.stash() and ctx.unstashAll() with the ActorContext handler forms
| @groovyx.gpars.activeobject.ActiveObject, @ActiveMethod
| @groovy.transform.ActiveObject, @groovy.transform.ActiveMethod.
blocking defaults to true in both
| DefaultPGroup, actor.parallelGroup = group
| ActorOptions.DEFAULTS.withExecutor(pool)
|===
[[gpars-migration-dataflow]]
=== Dataflow
[cols="2,3", options="header"]
|===
| GPars | Groovy 6
| Dataflow.task { โฆ }
| async { โฆ }
| DataflowVariable, v << value
| groovy.concurrent.DataflowVariable, v << value
| v.val, v.get()
| await v
| v.whenBound { โฆ }, v.then { โฆ }
| v.then { โฆ }
| v.bindError(e)
| v.bindError(e)
| Dataflows
| groovy.concurrent.Dataflows
| DataflowQueue
| AsyncChannel.create(n). Buffers are bounded, where DataflowQueue is not
| SyncDataflowQueue
| AsyncChannel.create(), a rendezvous channel
| DataflowBroadcast, createReadChannel()
| BroadcastChannel, subscribe()
| Dataflow.select(a, b)
| ChannelSelect.from(a, b)
| PoisonPill
| close()
| Dataflow.operator(โฆ), selector(โฆ), splitter(โฆ), and channel methods such as chainWith, filter and tap
| An async loop over input channels, or map, filter, merge, split and tap on AsyncChannel
|===
GPars' CSP helpers (PAR, ALT) are covered in
<<async-channels-csp,Coming from JCSP or GroovyCSP>>.
[[gpars-migration-not-included]]
=== Not carried over
Some GPars features have no groovy.concurrent counterpart:
- Software transactional memory (
GParsStm, built on Multiverse) - Remote actors and remote dataflow
KanbanFlowLazyDataflowVariable- Dataflow operator lifecycle features such as event listeners,
StopGentlyand pause/resume
Keep GPars for code that depends on them, or use a dedicated library for
that concern.
Src Spec Doc Design Pattern Abstract Factory (`src/spec/doc/_design-pattern-abstract-factory.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Abstract Factory Pattern
The http://en.wikipedia.org/wiki/Abstract_factory_pattern[Abstract Factory Pattern] provides a way to encapsulate a group of individual factories that have a common theme. It embodies the intent of a normal factory, i.e. remove the need for code using an interface to know the concrete implementation behind the interface, but applies to a set of interfaces and selects an entire family of concrete classes which implement those interfaces.
As an example, I might have interfaces Button, TextField and Scrollbar. I might have WindowsButton, MacButton, FlashButton as concrete classes for Button. I might have WindowsScrollBar, MacScrollBar and FlashScrollBar as concrete implementations for ScrollBar. Using the Abstract Factory Pattern should allow me to select which windowing system (i.e. Windows, Mac, Flash) I want to use once and from then on should be able to write code that references the interfaces but is always using the appropriate concrete classes (all from the one windowing system) under the covers.
== Example
Suppose we want to write a game system. We might note that many games have very similar features and control.
We decide to try to split the common and game-specific code into separate classes.
First let's look at the game-specific code for a http://en.wikipedia.org/wiki/Two-Up[Two-up] game:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=abstract_factory_example1,indent=0]
Now, let's look at the game-specific code for a number guessing game:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=abstract_factory_example2,indent=0]
Now, let's write our factory code:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=abstract_factory_code,indent=0]
The important aspect of this factory is that it allows selection of an entire family of concrete classes.
Here is how we would use the factory:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=abstract_factory_usage,indent=0]
Note that the first line configures which family of concrete game classes we will use. It's not important that we selected which family to use by using the factory property as shown in the first line. Other ways would be equally valid examples of this pattern. For example, we may have asked the user which game they wanted to play or determined which game from an environment setting.
With the code as shown, the game might look like this when run:
Welcome to the twoup game, you start with $1000 You have 1000, how much would you like to bet? 300 Draw You have 1000, how much would you like to bet? 700 You win You have 1700, how much would you like to bet? 1700 You lose Sorry, you have no money left, goodbye
If we change the first line of the script to ++GameFactory.factory = guessFactory++, then the sample run might look like this:
Welcome to the guessing game, my secret number is between 1 and 100 Enter a number between 1 and 100 75 Enter a number between 1 and 75 35 Enter a number between 1 and 35 15 Enter a number between 1 and 15 5 Enter a number between 5 and 15 10 Correct
Src Spec Doc Design Pattern Adapter (`src/spec/doc/_design-pattern-adapter.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Adapter Pattern
The https://en.wikipedia.org/wiki/Adapter_pattern[Adapter Pattern] (sometimes called the wrapper pattern) allows objects satisfying one interface to be used where another type of interface is expected. There are two typical flavours of the pattern: the delegation flavour and the inheritance flavour.
== Delegation Example
Suppose we have the following classes:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_delegation_classes,indent=0]
We can ask the RoundHole class if a RoundPeg fits in it, but if we ask the same question for a SquarePeg, then it will fail because the SquarePeg class doesn't have a radius property (i.e. doesn't satisfy the required interface).
To get around this problem, we can create an adapter to make it appear to have the correct interface. It would look like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_delegation_code,indent=0]
We can use the adapter like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_delegation_usage,indent=0]
Which results in the following output:
peg SquarePegAdapter with peg width 4 (and notional radius 2.8284271247461903) fits in hole RoundHole with radius 4.0 peg SquarePegAdapter with peg width 5 (and notional radius 3.5355339059327378) fits in hole RoundHole with radius 4.0 peg SquarePegAdapter with peg width 6 (and notional radius 4.242640687119285) does not fit in hole RoundHole with radius 4.0 peg SquarePegAdapter with peg width 7 (and notional radius 4.949747468305833) does not fit in hole RoundHole with radius 4.0
== Inheritance Example
Let's consider the same example again using inheritance. First, here are the original classes (unchanged):
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_inheritance_classes,indent=0]
An adapter using inheritance:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_inheritance_code,indent=0]
Using the adapter:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_inheritance_usage,indent=0]
The output:
peg SquarePegAdapter with width 4 (and notional radius 2.8284271247461903) fits in hole RoundHole with radius 4.0 peg SquarePegAdapter with width 5 (and notional radius 3.5355339059327378) fits in hole RoundHole with radius 4.0 peg SquarePegAdapter with width 6 (and notional radius 4.242640687119285) does not fit in hole RoundHole with radius 4.0 peg SquarePegAdapter with width 7 (and notional radius 4.949747468305833) does not fit in hole RoundHole with radius 4.0
== Adapting using Closures
As a variation of the previous examples, we could instead define the following interface:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_closure_interface,indent=0]
We can then define an adapter as a closure as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_closure_define,indent=0]
And use it like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_closure_usage,indent=0]
== Adapting using the ExpandoMetaClass
As of Groovy 1.1, there is a built-in MetaClass which can automatically add properties and methods dynamically.
Here is how the example would work using that feature:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=adapter_expando_meta_class,indent=0]
After you create a peg object, you can simply add a property to it on the fly. No need to change the original class and no need for an adapter class.
Src Spec Doc Design Pattern Bouncer (`src/spec/doc/_design-pattern-bouncer.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Bouncer Pattern
The http://www.c2.com/cgi/wiki?BouncerPattern[Bouncer Pattern] describes usage of a method whose sole purpose is to either throw an exception (when particular conditions hold) or do nothing. Such methods are often used to defensively guard pre-conditions of a method.
When writing utility methods, you should always guard against faulty input arguments. When writing internal methods, you may be able to ensure that certain pre-conditions always hold by having sufficient unit tests in place. Under such circumstances, you may reduce the desirability to have guards on your methods.
Groovy differs from other languages in that you frequently use the assert method within your methods rather than having a large number of utility checker methods or classes.
== Null Checking Example
We might have a utility method such as:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=bouncer_null_check,indent=0]
And we would use it like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=bouncer_null_check_usage,indent=0]
But a more Groovy way to do this would simply be like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=bouncer_null_check_usage_groovy_way,indent=0]
== Validation Example
As an alternative example, we might have this utility method:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=bouncer_validation,indent=0]
And we would use it like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=bouncer_validation_usage,indent=0]
But with Groovy we could just as easily use:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=bouncer_validation_usage_groovy_way,indent=0]
Src Spec Doc Design Pattern Chain Of Responsibility (`src/spec/doc/_design-pattern-chain-of-responsibility.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Chain of Responsibility Pattern
In the Chain of Responsibility Pattern, objects using and implementing an interface (one or more methods) are intentionally loosely coupled. A set of objects that implement the interface are organised in a list (or in rare cases a tree). Objects using the interface make requests from the first implementor object. It will decide whether to perform any action itself and whether to pass the request further down the line in the list (or tree). Sometimes a default implementation for some request is also coded into the pattern if none of the implementors respond to the request.
== Example using traditional classes
In this example, the script sends requests to the lister object. The lister points to a UnixLister object. If it can't handle the request, it sends the request to the WindowsLister. If it can't handle the request, it sends the request to the DefaultLister.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility,indent=0]
The output will be a list of files (with slightly different format depending on the operating system).
Here is a UML representation:
[plantuml, ChainOfResponsibilityClasses, png]
....
!pragma layout smetana
skinparam nodesep 200
skinparam ObjectBorderColor<
skinparam ObjectBackgroundColor<
skinparam ObjectFontColor<
skinparam ObjectStereotypeFontColor<
class UnixLister {
nextInLine : Object
+listFiles(dir)
}
object dummy1<
class WindowsLister {
nextInLine : Object
+listFiles(dir)
}
object dummy2<
class DefaultLister {
+listFiles(dir)
}
hide DefaultLister fields
object script
script ..r..> "<>" UnixLister
UnixLister --> "forwardIfRequired" WindowsLister
UnixLister <-[hidden]- dummy1
WindowsLister <-[hidden]- dummy2
WindowsLister --> "forwardIfRequired" DefaultLister
hide <
....
== Example using simplifying strategies
For simple cases, consider simplifying your code by not requiring the chain of classes.
Instead, use Groovy truth and the elvis operator as shown here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_elvis,indent=0]
Or Groovy's switch as shown here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_switch,indent=0]
Alternatively, for Groovy 3+, consider using streams of lambdas as shown here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_lambda,indent=0]
== When not to use
If your use of chain of responsibility involves frequent use of the instanceof operator, like here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape,indent=0]
<1> instanceof code smell
It could indicate that instead of using the chain of responsibility pattern, you might consider
using richer types, perhaps in combination with Groovy's multimethods. For example, perhaps this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape_multimethods,indent=0]
or using more traditional object-oriented style like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape_oo,indent=0]
== Going further
Other variations to this pattern:
- we could have an explicit interface in the traditional example, e.g.
Lister, to statically type the implementations but because of duck-typing this is optional - we could use a chain tree instead of a list, e.g.
if (animal.hasBackbone())delegate toVertebrateHandlerelse delegate toInvertebrateHandler - we could always pass down the chain even if we processed a request (no early return)
- we could decide at some point to not respond and not pass down the chain (pre-emptive abort)
- we could use Groovyโs meta-programming capabilities to pass unknown methods down the chain, e.g. combine
chain of responsibility with the use ofmethodMissing
Src Spec Doc Design Pattern Command (`src/spec/doc/_design-pattern-command.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Command Pattern
The https://en.wikipedia.org/wiki/Command_pattern[Command Pattern] is a pattern for
loosely coupling a client object which wants to execute a series of commands and
receiver objects which enact those commands.
Instead of talking to receivers directly, clients interact with an intermediary object
which then relays the necessary commands to the receivers.
The pattern is in common use within the JDK, for example the api:javax.swing.Action[] class in Swing
decouples swing code from receivers like buttons, menu items and panels.
The class diagram showing the typical classes is:
[plantuml, CommandClasses, png]
....
!pragma layout smetana
skinparam nodesep 100
hide fields
interface Command {
+execute(String command)
}
object client
class Command1 implements Command {
+execute(String command)
}
class Receiver1 {
+action1(args1)
}
client ..r..> "command" Command
Command1 --r--> Receiver1
....
The sequence of interactions is as shown below for an arbitrary receiver:
[plantuml, CommandSequence, png]
....
!pragma layout smetana
client -> intermediary: command
intermediary -> receiverN: actionN
....
== Example with traditional classes
The relevant classes required for turning a light on and off (see the example in the earlier wikipedia reference)
would be as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=command_traditional,indent=0]
Our client scripts sends execute commands to an intermediary and knows nothing
about any specific receivers, or any specific action method names and arguments.
== Simplifying variations
Given that Groovy has first-class function support, we can do away with the
actual command classes (like SwitchOnCommand) by instead using closures as shown here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=command_closures,indent=0]
<1> Command closures (here method closures) but could be lambdas/method references for Groovy 3+
We can simplify further using the JDK's existing Runnable interface
and using a switch map rather than a separate Switch class as shown here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=command_lambda,indent=0]
We have added an additional Door receiver to illustrate how to expand the original example.
Running this script results in:
The light is on The light is off The door is unlocked
As a variation, if the command names aren't important to us,
we can forgo using the switch map and just have a list of tasks to invoke as shown here:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=command_lambda_variant,indent=0]
Src Spec Doc Design Pattern Composite (`src/spec/doc/_design-pattern-composite.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Composite Pattern
The https://en.wikipedia.org/wiki/Composite_pattern[Composite Pattern] allows you to treat single instances of an object the same way as a group of objects. The pattern is often used with hierarchies of objects. Typically, one or more methods should be callable in the same way for either leaf or composite nodes within the hierarchy. In such a case, composite nodes typically invoke the same named method for each of their children nodes.
== Example
Consider this usage of the composite pattern where we want to call toString() on either Leaf or Composite objects.
[plantuml, CompositeClasses, png]
....
!pragma layout smetana
skinparam linetype ortho
skinparam nodesep 100
class Component {
+toString()
}
class Leaf {
+toString()
}
class Composite {
+toString()
}
object componentClient
hide fields
componentClient ..r..> "<>" Component
Component <|-- Leaf
Composite "1" -- "" Component : children
Component <|-- Composite
....
In Java, the Component class is essential as it provides the type used for both leaf and composite nodes. In Groovy, because of duck-typing, we don't need it for that purpose, however, it can still serve as a useful place to place common behaviour between the leaf and composite nodes.
For our purposes, we will assemble the following hierarchy of components.
[plantuml, CompositeComponents, png]
....
!pragma layout smetana
object root
object "leaf A" as leafA
object "comp B" as compB
object "leaf C" as leafC
object "leaf B1" as leafB1
object "leaf B2" as leafB2
root -- leafA
root -- compB
root -- leafC
compB -- leafB1
compB -- leafB2
....
Here is the code:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=composite_code,indent=0]
Here is the resulting output:
root -leaf A -comp B --leaf B1 --leaf B2 -leaf C
Src Spec Doc Design Pattern Decorator (`src/spec/doc/_design-pattern-decorator.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Decorator Pattern
The https://en.wikipedia.org/wiki/Decorator_pattern[Decorator Pattern] provides a mechanism to embellish the behaviour of an object without changing its essential interface. A decorated object should be able to be substituted wherever the original (non-decorated) object was expected. Decoration typically does not involve modifying the source code of the original object and decorators should be able to be combined in flexible ways to produce objects with several embellishments.
== Traditional Example
Suppose we have the following Logger class.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_logger_class,indent=0]
There might be times when it is useful to timestamp a log message, or times when we might want to change the case of the message. We could try to build all of this functionality into our Logger class. If we did that, the Logger class would start to be very complex. Also, everyone would obtain all of the features even when they might want only a small subset of the features. Finally, feature interaction would become quite difficult to control.
To overcome these drawbacks, we instead define two decorator classes. Uses of the Logger class are free to embellish their base logger with zero or more decorator classes in whatever order they desire. The classes look like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_traditional_classes,indent=0]
We can use the decorators like so:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_traditional_usage,indent=0]
You can see that we embellish the logger behaviour with both decorators. Because of the order we chose to apply the decorators, our log message comes out capitalised and the timestamp is in normal case. If we swap the order around, let's see what happens:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_traditional_usage2,indent=0]
Now the timestamp itself has also been changed to be uppercase.
== Simplifying with closures or lambdas
Closures make it easy to represent code. We can use that fact to
make a general purpose logger class that accepts the decoration code as a closure.
This saves us defining many decoration classes.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorating_logger_closure,indent=0]
We can use the same approach with lambdas:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorating_logger_lambda,indent=0]
== A touch of dynamic behaviour
Our previous decorators were specific to Logger objects. We can use Groovy's Meta-Object Programming capabilities to create a decorator which is far more general purpose in nature. Consider this class:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_dynamic_behaviour_class,indent=0]
It takes any class and decorates it so that any String method parameter will automatically be changed to lower case.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_dynamic_behaviour_usage,indent=0]
Just be careful with ordering here. The original decorators were restricted to decorating Logger objects. This decorator works with any object type, so we can't swap the ordering around, i.e. this won't work:
// Can't mix and match Interface-Oriented and Generic decorators // logger = new TimeStampingLogger(new GenericLowerDecorator(new Logger()))
We could overcome this limitation be generating an appropriate Proxy type at runtime but we won't complicate the example here.
== Runtime behaviour embellishment
You can also consider using the ExpandoMetaClass from Groovy 1.1 to dynamically embellish a class with behaviour. This isn't the normal style of usage of the decorator pattern (it certainly isn't nearly as flexible) but may help you to achieve similar results in some cases without creating a new class.
Here's what the code looks like:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_runtime_behaviour,indent=0]
This achieves a similar result to applying a single decorator but we have no way to easily apply and remove embellishments on the fly.
== More dynamic decorating
Suppose we have a calculator class (Actually any class would do).
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_calc_class,indent=0]
We might be interested in observing usage of the class over time. If it is buried deep within our codebase, it might be hard to determine when it is being called and with what parameters. Also, it might be hard to know if it is performing well. We can easily make a generic tracing decorator that prints out tracing information whenever any method on the Calc class is called and also provide timing information about how long it took to execute. Here is the code for the tracing decorator:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_tracing_decorator,indent=0]
Here is how to use the class in a script:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_tracing_decorator_usage,indent=0]
And here is what you would see after running this script:
Calling add{3, 12} Got 15 in 31 ms
== Decorating with an Interceptor
The above timing example hooks into the lifecycle of Groovy objects (via invokeMethod). This is such an important style performing meta-programming that Groovy has special support for this style of decorating using interceptors.
Groovy even comes with a built-in TracingInterceptor. We can extend the built-in class like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_interceptor,indent=0]
Here is an example of using this new class:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_interceptor_usage,indent=0]
And here is the output:
before Calc.ctor() after Calc.ctor() Duration: 0 ms before Calc.add(java.lang.Integer, java.lang.Integer) after Calc.add(java.lang.Integer, java.lang.Integer) Duration: 2 ms
== Decorating with java.lang.reflect.Proxy
If you are trying to decorate an object (i.e. just a particular instance of the class, not the class generally), then you can use Java's java.lang.reflect.Proxy. Groovy makes working with this easier than just Java. Below is a code sample taken out of a grails project that wraps a java.sql.Connection so that it's close method is a no-op:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_reflect_proxy,indent=0]
If there were many methods to intercept, then this approach could be modified to look up closure in a map by method name and invoke it.
== Decorating with Spring
The https://spring.io/[Spring Framework] allows decorators to be applied with interceptors (you may have heard the terms advice or aspect). You can leverage this mechanism from Groovy as well.
First define a class that you want to decorate (we'll also use an interface as is normal Spring practice):
Here's the interface:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_spring_calc,indent=0]
Here's the class:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_spring_calc_impl,indent=0]
Now, we define our wiring in a file called beans.xml as follows:
[source,xml]
Now, our script looks like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_spring_context,indent=0]
And when we run it, we see the results:
21/05/2007 23:02:35 org.springframework.aop.interceptor.PerformanceMonitorInterceptor invokeUnderTrace FINEST: StopWatch 'util.Calc.add': running time (millis) = 16
You may have to adjust your logging.properties file for messages at log level FINEST to be displayed.
== Asynchronous Decorators using Awaitable
The following example is inspired by some of the early example code for the
http://design.cs.iastate.edu/~panini/[Panini] programming language.
These days, you'll see this style used with async functions in JavaScript.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=decorator_async,indent=0]
Src Spec Doc Design Pattern Delegation (`src/spec/doc/_design-pattern-delegation.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Delegation Pattern
The https://en.wikipedia.org/wiki/Delegation_pattern[Delegation Pattern] is a technique where an object's behavior (public methods) is implemented by delegating responsibility to one or more associated objects.
Groovy allows the traditional style of applying the delegation pattern, e.g. see https://refactoring.com/catalog/replaceSuperclassWithDelegate.html[Replace Inheritance with Delegation].
== Implement Delegation Pattern using ExpandoMetaClass
The gapi:groovy.lang.ExpandoMetaClass[] allows usage of this pattern to be encapsulated in a library. This allows Groovy to emulate similar libraries available for the Ruby language.
Consider the following library class:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_delegator,indent=0]
With this in your classpath, you can now apply the delegation pattern dynamically as shown in the following examples. First, consider we have the following classes:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_classes,indent=0]
We can now use the delegator to automatically borrow methods from the lender object to extend the Person class. We can borrow the methods as is or with a rename:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_usage,indent=0]
The first line above, adds the borrowFor method to the Person class by delegating to the lender object. The second line adds a getMoney method to the Person class by delegating to the lender object's borrowAmount method.
Alternatively, we could borrow multiple methods like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_usage2,indent=0]
Which adds these two methods to the Person class.
Or if we want all the methods, like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_usage3,indent=0]
Which will make all the methods in the delegate object available in the Person class.
Alternatively, we can use a map notation to rename multiple methods:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_usage4,indent=0]
== Implement Delegation Pattern using @Delegate annotation
Since version 1.6 you can use the built-in delegation mechanism which is based on AST transformation.
This make delegation even easier:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=delegation_annotation,indent=0]
Src Spec Doc Design Pattern Flyweight (`src/spec/doc/_design-pattern-flyweight.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
ifndef::reldir_root[]
:reldir_root: .
endif::[]
= Flyweight Pattern
The http://en.wikipedia.org/wiki/Flyweight_pattern[Flyweight Pattern] is a pattern for greatly reducing memory requirements by not requiring that heavy-weight objects be created in large numbers when dealing with systems that contain many things that are mostly the same. If for instance, a document was modelled using a complex character class that knew about unicode, fonts, positioning, etc., then the memory requirements could be quite large for large documents if each physical character in the document required its own character class instance. Instead, characters themselves might be kept within Strings and we might have one character class (or a small number such as one character class for each font type) that knew the specifics of how to deal with characters.
In such circumstances, we call the state that is shared with many other things (e.g. the character type) intrinsic state. It is captured within the heavy-weight class. The state which distinguishes the physical character (maybe just its ASCII code or Unicode) is called its extrinsic state.
== Example
First we are going to model some complex aircraft (the first being a hoax competitor of the second - though that is not relevant to the example).
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=flyweight_boeing797,indent=0]
image::{reldir_root}/assets/img/b797-hoax.jpg[]
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=flyweight_airbus380,indent=0]
image::{reldir_root}/assets/img/a380.jpg[]
If we want to model our fleet, our first attempt might involve using many instances of these heavy-weight objects. It turns out though that only a few small pieces of state (our extrinsic state) change for each aircraft, so we will have singletons for the heavy-weight objects and capture the extrinsic state (bought date and asset number in the code below) separately.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=flyweight_factory,indent=0]
So here, even if our fleet contained hundreds of planes, we would only have one heavy-weight object for each type of aircraft.
As a further efficiency measure, we might use lazy creation of the flyweight objects rather than create the initial map up front as in the above example.
Running this script results in:
Asset Number: 1001
Capacity: 555 people
Speed: 912 km/h
Range: 10370 km
Bought: 10-May-2007
Asset Number: 1002
Capacity: 555 people
Speed: 912 km/h
Range: 10370 km
Bought: 10-Nov-2007
Asset Number: 1003
Capacity: 1000 people
Speed: 1046 km/h
Range: 14400 km
Bought: 10-May-2008
Asset Number: 1004 Capacity: 1000 people Speed: 1046 km/h Range: 14400 km Bought: 10-Nov-2008
Src Spec Doc Design Pattern Iterator (`src/spec/doc/_design-pattern-iterator.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Iterator Pattern
The https://en.wikipedia.org/wiki/Iterator_pattern[Iterator Pattern] allows sequential access to the elements of an aggregate object without exposing its underlying representation.
Groovy has the iterator pattern built right in to many of its closure operators, e.g. each and eachWithIndex as well as the for .. in loop.
For example:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=iterator_example,indent=0]
Results in the output:
1 2 3 4 May=31 Mar=31 Apr=30 java.awt.Color[r=0,g=0,b=0] java.awt.Color[r=255,g=255,b=255]
Another example:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=iterator_example2,indent=0]
Results in:
Position 0 contains 'java.awt.Color[r=0,g=0,b=0]' Position 1 contains 'java.awt.Color[r=255,g=255,b=255]'
The iterator pattern is also built in to other special operators such as the eachByte, eachFile, eachDir, eachLine, eachObject, eachMatch operators for working with streams, URLs, files, directories and regular expressions matches.
Src Spec Doc Design Pattern Loan My Resource (`src/spec/doc/_design-pattern-loan-my-resource.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Loan my Resource Pattern
The https://wiki.scala-lang.org/display/SYGN/Loan[Loan my Resource] pattern ensures that a resource is deterministically disposed of once it goes out of scope.
This pattern is built in to many Groovy helper methods. You should consider using it yourself if you need to work with resources in ways beyond what Groovy supports.
== Example
Consider the following code which works with a file. First we might write some line to the file and then print its size:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=loan_my_resource_example,indent=0]
We could also read back the contents of the file a line at a time and print each line out:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=loan_my_resource_example2,indent=0]
Note that normal Java Reader and PrintWriter objects were used under the covers by Groovy but the code writer did not have to worry about explicitly creating or closing those resources. The built-in Groovy methods loan the respective reader or writer to the closure code and then tidy up after themselves. So, you are using this pattern without having to do any work.
Sometimes however, you wish to do things slightly differently to what you can get for free using Groovy's built-in mechanisms. You should consider utilising this pattern within your own resource-handling operations.
Consider how you might process the list of words on each line within the file. We could actually do this one too using Groovy's built-in functions, but bear with us and assume we have to do some resource handling ourselves. Here is how we might write the code without using this pattern:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=loan_my_resource_example3,indent=0]
Notice that we now have an explicit call to close() in our code. If we didn't code it just right (here we didn't surround the code in a try ... finally block, we run the risk of leaving the file handle open.
Let's now apply the loan pattern. First, we'll write a helper method:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=loan_my_resource_example4,indent=0]
Now, we can re-write our code as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=loan_my_resource_example5,indent=0]
This is much simpler and has removed the explicit close(). This is now catered for in one spot so we can apply the appropriate level of testing or reviewing in just one spot to be sure we have no problems.
Src Spec Doc Design Pattern Monoid (`src/spec/doc/_design-pattern-monoid.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Using Monoids
https://en.wikipedia.org/wiki/Monoid#Monoids_in_computer_science[Monoids] allow
the mechanics of an aggregation algorithm to be separated from the algorithm-specific logic associated with that aggregation.
It is often thought to be a functional design pattern.
Perhaps, it is easiest seen with an example. Consider the code for integer sum, integer product and string concatenation.
We might note various similarities:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_intro,indent=0]
<1> Initialize an aggregate counter
<2> Loop throw elements with for/while/iteration adjusting counter
We can remove the duplicate aggregation coding and the tease out the important differences for each algorithm.
We might instead use Groovy's inject method. This is a fold operation in functional programming jargon.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_inject,indent=0]
Here the first parameter is the initial value, and the supplied closure contains the algorithm-specific logic.
Similarly, for Groovy 3+, we can use the JDK stream API and lambda syntax as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_lambdas,indent=0]
== A touch of formalism
Looking at these examples, we might think all aggregation can be supported this way.
In fact, we look for certain characteristics to ensure that this aggregation pattern will apply:
- Closure: performing the aggregation step should produce a result of the same type as the elements being aggregated.
Examples: `1L + 3L` produces a `Long`, and `'foo' + 'bar'` produces a `String`. + Non-monoid examples: `'foo'.size() + 'bar'.size()` (takes strings, returns an integer), the type _odd numbers_ with respect to addition, algorithms which don't handle null arguments if such arguments are possible. **** [NOTE]
When using the term _closure_ here, we simply mean closed under the operation, not the Groovy `Closure` class.
- Associativity: the order in which we apply the aggregation step should not matter.
Examples: (1 + 3) + 5 is the same as 1 + (3 + 5), and ('a' + 'b') + 'c' is the same as 'a' + ('b' + 'c'). +
Non-monoid example: (10 - 5) - 3 is not equal to 10 - (5 - 3) therefore integers are not a monoid with respect to subtraction.
- Identity element (sometimes also called a 'zero' element):
there should be an element which aggregated with any element returns the original element.
Examples: 0 + 42 == 42, 42 + 0 == 42, 1 * 42 == 42, and '' + 'foo' == 'foo'. +
Non-monoid example: the type non-empty strings is not a monoid with respect to concatenation.
If your algorithm doesn't satisfy all the monoid properties, that doesn't mean aggregation isn't possible.
It just means that you won't get all the benefits from monoids, which we'll cover shortly, or you might have
a little more work to do.
Also, you might be able to convert your data structures slightly to turn your problem into one involving monoids.
We'll cover that topic a little later in this section.
== Benefits of monoids
Consider adding the integers 10 through 16.
Because the operation of addition for integers is a monoid, we already know that we can save writing code
and instead use the approach we saw in the earlier inject examples.
There are some other nice properties.
Because of the closure property,
if we have a pairwise method like sum(Integer a, Integer b), then for a monoid, we can always
extend that method to work with a list, e.g. sum(List nums) or sum(Integer first, Integer... rest).
Because of associativity,
we can employ some interesting ways to solve the aggregation including:
- Divide and conquer algorithms which break the problem into smaller pieces
- Various incremental algorithms for example memoization would allow summing from 1..5
to potentially start part way through be reusing a cached value of summing 1..4 if that had been calculated earlier - Inherent parallelization can make use of multiple cores
Let's just look at the first of these in more detail. With a multicore
processor, one core could add 10 plus 11, another core 12 plus 13, and so on.
We'd use the identity element if needed (shown being added to 16 in our example).
Then the intermediate results could also be added together concurrently and so on until the result was reached.
[plantuml, MonoidAddition, png]
....
!pragma layout smetana
skinparam shadowing false
skinparam ClassFontSize 18
skinparam ClassBackgroundColor<> Transparent
skinparam ClassBorderColor<> grey
skinparam ClassStereotypeFontSize<> 4
skinparam ClassStereotypeFontColor<> Transparent
hide circle
class " 0 " as zero << (I,lightgrey) Identity >>
show zero circle
class " 10 " as a1
class " 11 " as a2
class " 12 " as a3
class " 13 " as a4
class " 14 " as a5
class " 15 " as a6
class " 16 " as a7
class " 21 " as b1
class " 25 " as b2
class " 29 " as b3
class " 16 " as b4
class " 46 " as c1
class " 45 " as c2
class " 91 " as d1
a1 .r[hidden].> a2
a2 .r[hidden].> a3
a3 .r[hidden].> a4
a4 .r[hidden].> a5
a5 .r[hidden].> a6
a6 .r[hidden].> a7
a7 .r[hidden].> zero
b1 <.d. a1
b1 <.d. a2
b2 <.d. a3
b2 <.d. a4
b3 <.d. a5
b3 <.d. a6
b4 <.d. a7
b4 <.d. zero
c1 <.d. b1
c1 <.d. b2
c2 <.d. b3
c2 <.d. b4
d1 <.d. c1
d1 <.d. c2
hide empty members
....
We have reduced the amount of code we need to write, and we also have potential performance gains.
Here is how we might code the previous example using Groovy's
parallel collections:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_parallel,indent=0]
== Working with Non-monoids
Suppose we want to find the average of the numbers 1..10. Groovy has a built-in method for this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_1to10,indent=0]
Now, suppose we want to build our own monoid solution instead of using the built-in version.
It might seem difficult to find the identity element. After all:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_0to10,indent=0]
Similarly, if we are tempted to write the pairwise aggregation closure it might be something like:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_bad,indent=0]
What b can we use for the identity element here so that our equation returns the original?
We need to use a, but that isn't a fixed value, so there is no identity.
Also, associativity doesn't hold for this initial attempt at defining avg as these examples show:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_assoc,indent=0]
Also, what about our closure property? Our original numbers were integers, but our average (5.5)
is not. We can solve this by making our average work for any Number instances, but it might not always be this easy.
It might appear that this problem is not amenable to a monoidal solution.
However, there are numerous ways to bring monoids into the solution.
We can split it into two parts:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_split,indent=0]
The calculation of sum() can follow monoid rules and then our last step can calculate the average.
We can even do a parallel version:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_split_parallel,indent=0]
Here, we were using the built-in sum() method (and sumParallel() for the parallel example),
but if you were doing it by hand, the monoid nature of that part of your calculation would
make it easier to write your own code for that step.
Alternatively, we can introduce a helper data structure that reworks the problem to be a monoid.
Instead of just keeping the total, let's keep a list containing the total and count of numbers.
The code could look something like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_reworked_simple,indent=0]
Or, to be a little fancier, we could introduce a class for our data structure and even calculate
in parallel:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=monoids_average_reworked_parallel,indent=0]
Src Spec Doc Design Pattern Null Object (`src/spec/doc/_design-pattern-null-object.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Null Object Pattern
The https://en.wikipedia.org/wiki/Null_object_pattern[Null Object Pattern] involves using a special object place-marker object representing null. Typically, if you have a reference to null, you can't invoke reference.field or reference.method() You receive the dreaded NullPointerException. The null object pattern uses a special object representing null, instead of using an actual null. This allows you to invoke field and method references on the null object. The result of using the null object should semantically be equivalent to doing nothing.
== Simple Example
Suppose we have the following system:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=null_object_simple_example,indent=0]
When run, this prints out 1200. Suppose now that we now invoke:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=null_object_simple_example2,indent=0]
If we now try to calculate biggestSalary again, we receive a null pointer exception.
To overcome this problem, we can introduce a NullJob class and change the above statement to become:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=null_object_simple_example3,indent=0]
This works as we require but it's not always the best way to do this with Groovy. Groovy's safe-dereference operator (?.) operator and null aware closures often allow Groovy to avoid the need to create a special null object or null class. This is illustrated by examining a groovier way to write the above example:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=null_object_simple_example4,indent=0]
Two things are going on here to allow this to work. First of all, max() is 'null aware' so that ++[300, null, 400].max() == 400++. Secondly, with the ?. operator, an expression like p?.job?.salary will be equal to null if salary is equal to null, or if job is equal null or ifpis equal to null. You don't need to code a complex nested ++if ... then ... else++ to avoid aNullPointerException`.
== Tree Example
Consider the following example where we want to calculate size, cumulative sum and cumulative product of all the values in a tree structure.
Our first attempt has special logic within the calculation methods to handle null values.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=null_object_tree_example,indent=0]
If we introduce the null object pattern (here by defining the NullTree class), we can now simplify the logic in the size(), sum() andproduct() methods. These methods now much more clearly represent the logic for the normal (and now universal) case. Each of the methods within NullTree returns a value which represents doing nothing.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=null_object_tree_example2,indent=0]
The result of running either of these examples is:
4 14 120
Note: a slight variation with the null object pattern is to combine it with the singleton pattern. So, we wouldn't write ++new NullTree()++ wherever we needed a null object as shown above. Instead we would have a single null object instance which we would place within our data structures as needed.
Src Spec Doc Design Pattern Observer (`src/spec/doc/_design-pattern-observer.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Observer Pattern
The https://en.wikipedia.org/wiki/Observer_pattern[Observer Pattern] allows one or more observers to be notified
about changes or events from a subject object.
[plantuml, ObserverClasses, png]
....
!pragma layout smetana
skinparam ClassBorderColor<
skinparam ClassBackgroundColor<
skinparam ClassStereotypeFontColor<
skinparam ClassFontSize<
skinparam ClassFontStyle<
skinparam shadowing<
hide <
class "..." as ConcreteHidden
class ConcreteHidden <
}
class Observer {
+update()
}
class ConcreteObserver1 {
+update()
}
class ConcreteObserverN {
+update()
}
hide Observer fields
class Subject {
-observerCollection
+registerObserver(observer)
+unregisterObserver(observer)
+notifyObservers()
}
Observer <|-- ConcreteObserver1
Observer <|-[hidden]- ConcreteHidden
Observer <|-- ConcreteObserverN
Observer ---r---o Subject
ConcreteObserver1 .r[hidden]. ConcreteHidden
....
== Example
Here is a typical implementation of the classic pattern:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=observer_classic,indent=0]
Using Closures, we can avoid creating the concrete observer classes as shown below:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=observer_closures,indent=0]
As a variation for Groovy 3+, let's consider dropping the Observer interface and using lambdas as shown below:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=observer_lambdas,indent=0]
We are now calling the accept method from Consumer rather
than the update method from Observer.
== @Bindable and @Vetoable
The JDK has some built-in classes which follow the observer pattern.
The java.util.Observer and java.util.Observable classes are deprecated from JDK 9 due to various limitations.
Instead, you are recommended to use various more powerful classes in the java.beans package such as java.beans.PropertyChangeListener.
Luckily, Groovy has some built-in transforms (gapi:groovy.beans.Bindable[] and gapi:groovy.beans.Vetoable[])
which support for some key classes from that package.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=observer_bindable,indent=0]
Here, methods like addPropertyChangeListener perform the same role as registerObserver in previous examples.
There is a firePropertyChange method corresponding to notifyAll/notifyObservers in previous examples but Groovy adds that
automatically here, so it isn't visible in the source code. There is also a propertyChange method that corresponds
to the update method in previous examples, though again, that isn't visible here.
Src Spec Doc Design Pattern Pimp My Library (`src/spec/doc/_design-pattern-pimp-my-library.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Pimp my Library Pattern
The http://www.artima.com/weblogs/viewpost.jsp?thread=179766[Pimp my Library] Pattern suggests an approach for extending a library that nearly does everything that you need but just needs a little more. It assumes that you do not have source code for the library of interest.
== Example
Suppose we want to make use of the built-in Integer facilities in Groovy (which build upon the features already in Java). Those libraries have nearly all of the features we want but not quite everything. We may not have all of the source code to the Groovy and Java libraries so we can't just change the library. Instead we augment the library. Groovy has a number of ways to do this. One way is to use a Category.
First, we'll define a suitable category.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=pimp_my_library_example,indent=0]
We have added two methods which augment the Integer methods by providing the greaterThanAll method. Categories follow conventions where they are defined as static methods with a special first parameter representing the class we wish to extend. The ++greaterThanAll(Integer self, others)++ static method becomes the greaterThanAll(other) instance method.
We defined two versions of greaterThanAll. One which works for collections, ranges etc. The other which works with a variable number of Integer arguments.
Here is how you would use the category.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=pimp_my_library_example2,indent=0]
As you can see, using this technique you can effectively enrich an original class without having access to its source code. Moreover, you can apply different enrichments in different parts of the system as well as work with un-enriched objects if we need to.
Src Spec Doc Design Pattern Proxy (`src/spec/doc/_design-pattern-proxy.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Proxy Pattern
The https://en.wikipedia.org/wiki/Proxy_pattern[Proxy Pattern] allows one object to act as a pretend replacement for some other object. In general, whoever is using the proxy, doesn't realise that they are not using the real thing. The pattern is useful when the real object is hard to create or use: it may exist over a network connection, or be a large object in memory, or be a file, database or some other resource that is expensive or impossible to duplicate.
== Example
One common use of the proxy pattern is when talking to remote objects in a different JVM. Here is the client code for creating a proxy that talks via sockets to a server object as well as an example usage:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=proxy_client,indent=0]
Here is what your server code might look like (start this first):
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=proxy_server,indent=0]
Src Spec Doc Design Pattern Singleton (`src/spec/doc/_design-pattern-singleton.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Singleton Pattern
The http://en.wikipedia.org/wiki/Singleton_pattern[Singleton Pattern] is used to make sure only one object of a particular class is ever created. This can be useful when exactly one object is needed to coordinate actions across a system; perhaps for efficiency where creating lots of identical objects would be wasteful, perhaps because a particular algorithm needing a single point of control is required or perhaps when an object is used to interact with a non-shareable resource.
Weaknesses of the Singleton pattern include:
- It can reduce reuse. For instance, there are issues if you want to use inheritance with Singletons. If
SingletonBextendsSingletonA, should there be exactly (at most) one instance of each or should the creation of an object from one of the classes prohibit creation from the other. Also, if you decide both classes can have an instance, how do you override thegetInstance()method which is static? - It is also hard to test singletons in general because of the static methods but Groovy can support that if required.
== Example: The Classic Java Singleton
Suppose we wish to create a class for collecting votes. Because getting the right number of votes may be very important, we decide to use the singleton pattern. There will only ever be one VoteCollector object, so it makes it easier for us to reason about that objects creation and use.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_vote_collector,indent=0]
Some points of interest in this code:
- it has a private constructor, so no
VoteCollectorobjects can be created in our system (except for theINSTANCEwe create) - the
INSTANCEis also private, so it can't be changed once set - we haven't made the updating of votes thread-safe at this point (it doesn't add to this example)
- the vote collector instance is not lazily created (if we never reference the class, the instance won't be created; however, as soon as we reference the class, the instance will be created even if not needed initially)
We can use this singleton class in some script code as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_vote_collector_usage,indent=0]
Here we used the instance 3 times. The second usage was even in a different thread (but don't try this in a scenario with a new class loader).
Running this script yields (your hashcode value will vary):
Collector:15959960, Votes:0 Collector:15959960, Votes:1 Collector:15959960, Votes:2
Variations to this pattern:
- To support lazy-loading and multi-threading, we could just use the
synchronizedkeyword with thegetInstance()method. This has a performance hit but will work. - We can consider variations involving double-checked locking and the
volatilekeyword, but see the limitations of this approach http://www.ibm.com/developerworks/java/library/j-dcl/index.html[here].
== Example: Singleton via MetaProgramming
Groovy's meta-programming capabilities allow concepts like the singleton pattern to be enacted in a far more fundamental way. This example illustrates a simple way to use Groovy's meta-programming capabilities to achieve the singleton pattern but not necessarily the most efficient way.
Suppose we want to keep track of the total number of calculations that a calculator performs. One way to do that is to use a singleton for the calculator class and keep a variable in the class with the count.
First we define some base classes. A Calculator class which performs calculations and records how many such calculations it performs and a Client class which acts as a facade to the calculator.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_meta_programming_classes,indent=0]
Now we can define and register a MetaClass which intercepts all attempts to create a Calculator object and always provides a pre-created instance instead. We also register this MetaClass with the Groovy system:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_meta_programming_meta_class,indent=0]
Now we use instances of our Client class from within a script. The client class will attempt to create new instances of the calculator but will always get the singleton.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_meta_programming_usage,indent=0]
Here is the result of running this script (your hashcode values may vary):
Client: 7306473, Calc: 24230857, Total Calculations: 1 Client: 31436753, Calc: 24230857, Total Calculations: 2
== Guice Example
We can also implement the Singleton Pattern using https://github.com/google/guice[Guice].
Consider the Calculator example again.
Guice is a Java-oriented framework that supports Interface-Oriented design. Hence we create a Calculator interface first. We can then create our CalculatorImpl implementation and a Client object which our script will interact with. The Client class isn't strictly needed for this example but allows us to show that non-singleton instances are the default. Here is the code:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_guice,indent=0]
Note the @Inject annotation in the Client class. We can always tell right in the source code which fields will be injected.
In this example we chose to use an explicit binding. All of our dependencies (ok, only one in this example at the moment) are configured in the binding. The Guide injector knows about the binding and injects the dependencies as required when we create objects. For the singleton pattern to hold, you must always use Guice to create your instances. Nothing shown so far would stop you creating another instance of the calculator manually using ++new CalculatorImpl()++ which would of course violate the desired singleton behaviour.
In other scenarios (though probably not in large systems), we could choose to express dependencies using annotations, such as the following example shows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_guice2,indent=0]
Note the @Singleton annotation on the CalculatorImpl class and the @ImplementedBy annotation in the Calculator interface.
When run, the above example (using either approach) yields (your hashcode values will vary):
Client: 8897128, Calc: 17431955, Total Calculations: 1 Client: 21145613, Calc: 17431955, Total Calculations: 2
You can see that we obtained a new client object whenever we asked for an instance but it was injected with the same calculator object.
== Spring Example
We can do the Calculator example again using Spring as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=singleton_spring,indent=0]
And here is the result (your hashcode values will vary):
Client: 29418586, Calc: 10580099, Total Calculations: 1 Client: 14800362, Calc: 10580099, Total Calculations: 2
== Further information
- http://www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html?page=1[Simply Singleton]
- http://www.ibm.com/developerworks/webservices/library/co-single/index.html[Use your singletons wisely]
- http://www.ibm.com/developerworks/java/library/j-dcl/index.html[Double-checked locking and the Singleton pattern]
- https://web.archive.org/web/20160807234810/http://blog.crazybob.org/2007/01/lazy-loading-singletons.html[Lazy Loading Singletons]
- https://csharpindepth.com/Articles/Singleton[Implementing the Singleton Pattern in C#]
Src Spec Doc Design Pattern State (`src/spec/doc/_design-pattern-state.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= State Pattern
The https://en.wikipedia.org/wiki/State_pattern[State Pattern] provides a structured approach to partitioning the behaviour within complex systems. The overall behaviour of a system is partitioned into well-defined states. Typically, each state is implemented by a class. The overall system behaviour can be determined firstly by knowing the current state of the system; secondly, by understanding the behaviour possible while in that state (as embodied in the methods of the class corresponding to that state).
== Example
Here is an example:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_example,indent=0]
Here is the output:
offline error: not connected connected "Hello" sent error: already connected message received offline
One of the great things about a dynamic language like Groovy though is that we can take this example and express it in many different ways depending on our particular needs. Some potential variations for this example are shown below.
== Variation 1: Leveraging Interface-Oriented Design
One approach we could take is to leverage http://www.pragmaticprogrammer.com/titles/kpiod/index.html[Interface-Oriented Design]. To do this, we could introduce the following interface:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation1_interface,indent=0]
Then our Client, Online and 'Offline` classes could be modified to implement that interface, e.g.:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation1_impl,indent=0]
You might ask: Haven't we just introduced additional boilerplate code? Can't we rely on duck-typing for this? The answer is 'yes' and 'no'. We can get away with duck-typing but one of the key intentions of the state pattern is to partition complexity. If we know that the client class and each state class all satisfy one interface, then we have placed some key boundaries around the complexity. We can look at any state class in isolation and know the bounds of behaviour possible for that state.
We don't have to use interfaces for this, but it helps express the intent of this particular style of partitioning and it helps reduce the size of our unit tests (we would have to have additional tests in place to express this intent in languages which have less support for interface-oriented design).
== Variation 2: Extract State Pattern Logic
Alternatively, or in combination with other variations, we might decide to extract some of our State Pattern logic into helper classes. For example, we could define the following classes in a state pattern package/jar/script:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation2_classes,indent=0]
This is all quite generic and can be used wherever we want to introduce the state pattern. Here is what our code would look like now:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation2_impl,indent=0]
You can see here the startFrom and transitionTo methods begin to give our example code a DSL feel.
== Variation 3: Bring on the DSL
Alternatively, or in combination with other variations, we might decide to fully embrace a Domain Specific Language (DSL) approach to this example.
We can define the following generic helper functions (first discussed https://web.archive.org/web/20170624004445/http://www.bytemycode.com:80/snippets/snippet/640/[here]):
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation31,indent=0]
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation32,indent=0]
Now we can define and test our state machine like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=state_variation33,indent=0]
This example isn't an exact equivalent of the others. It doesn't use predefined Online and Offline classes.
Instead, it defines the entire state machine on the fly as needed.
See the https://web.archive.org/web/20170624004445/http://www.bytemycode.com:80/snippets/snippet/640/[previous reference] for more elaborate examples of this style.
See also: https://web.archive.org/web/20150102211229/http://groovy.codehaus.org/Model-based+testing+using+ModelJUnit[Model-based testing using ModelJUnit]
Src Spec Doc Design Pattern Strategy (`src/spec/doc/_design-pattern-strategy.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Strategy Pattern
The http://en.wikipedia.org/wiki/Strategy_pattern[Strategy Pattern] allows you to abstract away particular algorithms from their usage. This allows you to easily swap the algorithm being used without having to change the calling code. The general form of the pattern is:
[plantuml, StrategyClasses, png]
....
!pragma layout smetana
hide fields
hide <
skinparam ClassBorderColor<
skinparam ClassBackgroundColor<
skinparam ClassStereotypeFontColor<
skinparam ClassFontSize<
skinparam ClassFontStyle<
skinparam shadowing<
class Context {
+Strategy getStrategy()
}
class Strategy {
+algorithmMethod()
}
class ConcreteStrategy1 {
+algorithmMethod()
}
class ConcreteStrategy2 {
+algorithmMethod()
}
class "..." as ConcreteHidden
class ConcreteHidden <
}
class ConcreteStrategyN {
+algorithmMethod()
}
Context o---r--- Strategy
Strategy <|-- ConcreteStrategy1
Strategy <|-- ConcreteStrategy2
Strategy <|-[hidden]- ConcreteHidden
Strategy <|-- ConcreteStrategyN
....
In Groovy, because of its ability to treat code as a first class object using anonymous methods (which we loosely call Closures), the need for the strategy pattern is greatly reduced. You can simply place algorithms inside Closures.
== Example using traditional class hierarchy
First let's look at the traditional way of encapsulating the Strategy Pattern.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=strategy_traditional,indent=0]
Here we have defined an interface Calc which our concrete strategy classes will implement (we could also have used an abstract class).
We then defined two algorithms for doing simple multiplication: CalcByMult the normal way, and ++CalcByManyAdds++ using only addition (don't try this one using negative numbers - yes we could fix this but it would just make the example longer).
We then use normal http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming[polymorphism] to invoke the algorithms.
== Example using closures
Here is the Groovier way to achieve the same thing using Closures:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=strategy_groovy_way,indent=0]
== Example using lambdas
For Groovy 3+, we can leverage lambda syntax:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=strategy_lambdas_with_explicit_interface,indent=0]
Or we can use the built-in JDK BiFunction class:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=strategy_lambdas_using_bifunction,indent=0]
Src Spec Doc Design Pattern Template Method (`src/spec/doc/_design-pattern-template-method.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Template Method Pattern
The https://en.wikipedia.org/wiki/Template_method_pattern[Template Method Pattern] abstracts
away the details of several algorithms.
The generic part of an algorithm is contained within a base class.
Particular implementation details are captured within child classes.
The generic pattern of classes involved looks like this:
[plantuml, TemplateMethodClasses, png]
....
!pragma layout smetana
skinparam nodesep 100
class AbstractClass {
+algorithm1()
+algorithm2()
{abstract} +primitiveOperationA()
{abstract} +primitiveOperationB()
}
class ConcreteClass1 {
+primitiveOperationA()
+primitiveOperationB()
}
class ConcreteClass2 {
+primitiveOperationA()
+primitiveOperationB()
}
object templateMethodUser
hide fields
templateMethodUser ......r......> "<>" AbstractClass
AbstractClass <|-- ConcreteClass1
AbstractClass <|-- ConcreteClass2
....
== Example with traditional classes
In this example, the base Accumulator class captures the essence of the accumulation algorithm.
The child classes Sum and Product provide particular customised ways to use the generic accumulation algorithm.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=template_method_example,indent=0]
== Example with simplifying strategies
In this particular case, you could use Groovy's inject method to achieve a similar result using Closures:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=template_method_example2,indent=0]
Thanks to duck-typing, this would also work with other objects which support an add (plus() in Groovy) method, e.g.:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=template_method_example3,indent=0]
We could also do the multiplication case as follows (re-writing as a one-liner):
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=template_method_example4,indent=0]
Using closures this way looks like the <<_strategy_pattern,Strategy Pattern>>, but if we realise
that Groovy's inject method is the generic part of the algorithm for our template method,
then the Closures become the customised parts of the template method pattern.
For Groovy 3+, we can use lambda syntax as an alternative to the closure syntax:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=template_method_example5,indent=0]
Here the stream api's reduce method is the generic part of the algorithm for our template method,
and the lambdas are the customised parts of the template method pattern.
Src Spec Doc Design Pattern Visitor (`src/spec/doc/_design-pattern-visitor.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Visitor Pattern
The https://en.wikipedia.org/wiki/Visitor_pattern[Visitor Pattern] is one of those well-known but not
often used patterns. Perhaps this is because it seems a little complex at first.
But once you become familiar with it, it becomes a powerful way to evolve your code
and as we'll see, Groovy provides ways to reduce some to the complexity, so there is
no reason not to consider using this pattern.
The goal of the pattern is to separate an algorithm from an object structure.
A practical result of this separation is the ability to add new operations to existing
object structures without modifying those structures.
== Simple Example
This example considers how to calculate the bounds of shapes (or collections of shapes).
Our first attempt uses the traditional visitor pattern.
We will see a more Groovy way to do this shortly.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_simple_example,indent=0]
That took quite a bit of code, but the idea now is that we could add
further algorithms just by adding new visitors with our shape classes remaining
unchanged, e.g. we could add a total area visitor or a collision detection visitor.
We can improve the clarity of our code (and shrink it to about half the size) by
making use of Groovy Closures as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_simple_example2,indent=0]
Or, using lambdas as follows:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_simple_example3,indent=0]
== Advanced Example
Let's consider another example to illustrate some more points about this pattern.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_advanced_example,indent=0]
If we now use NodeType1Counter on a tree like this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_advanced_example2,indent=0]
Then we have one NodeType1 object as root and one of the children is also a NodeType1 instance.
The other child is a NodeType2 instance.
That means using NodeType1Counter here should count 2 NodeType1 objects as the last statement verifies.
=== When to use this
This example illustrates some of the advantages of the visitor pattern.
For example, while our visitor has state (the count of NodeType1 objects), the tree of objects itself is not changed.
Similarly, if we wanted to have a visitor counting all node types,
or one that counts how many different types are used, or one that gathers information
using methods special to the node types, again, the visitor alone is all that would need to be written.
=== What happens if we add a new type?
In this case we might have a fair bit of work to do. We probably have to change the Visitor interface to accept the new type,
and change potentially most existing visitors based on that interface change,
and we have to write the new type itself.
A better approach is to write a default implementation of the visitor which all concrete visitors will extend.
We'll see this approach in use shortly.
=== What if we want to have different iteration patterns?
Then you have a problem.
Since the node describes how to iterate, you have no influence and stop iteration at a point or change the order.
So maybe we should change this a little to this:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_advanced_example3,indent=0]
Some small changes but with big effect.
The visitor is now recursive and tells me how to iterate.
The implementation in the Nodes is minimized to visitor.visit(this), DefaultVisitor is now able to catch the new types, we can stop iteration by not delegating to super.
Of course the big disadvantage now is that it is no longer iterative, but you can't get all the benefits.
=== Make it Groovy
The question now is how to make that a bit more Groovy.
Didn't you find this visitor.visit(this) strange? Why is it there?
The answer is to simulate double dispatch.
In Java, the compile time type is used, so for visitor.visit(children[i]) the compiler won't be
able to find the correct method, because Visitor does not contain a method visit(Visitable).
And even if it would, we would like to visit the more special methods with NodeType1 or NodeType2.
Now Groovy is not using the static type, Groovy uses the runtime type.
This means we can use visitor.visit(children[i]) without any problem.
Since we minimized the accept method to just do the double dispatch part and
since the runtime type system of Groovy will already cover that, do we need the accept method?
Not really, but we can do even more.
We had the disadvantage of not knowing how to handle unknown tree elements.
We had to extend the interface Visitor for that, resulting in changes to DefaultVisitor and
then we have the task to provide a useful default like iterating the node or not doing anything at all.
Now with Groovy we can catch that case by adding a visit(Visitable) method that does nothing.
That would be the same in Java btw.
But don't let us stop here. Do we need the Visitor interface?
If we don't have the accept method, then we don't need the Visitor interface at all.
So the new code would be:
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_advanced_example4,indent=0]
Looks like we saved a few lines of code here, but we made more.
The Visitable nodes now do not refer to any Visitor class or interface.
This is about the best level of separation you might expect here, but we can go further.
Let's change the Visitable interface a little and let it return the children we want to visit next.
This allows us a general iteration method.
[source,groovy]
include::../test/DesignPatternsTest.groovy[tags=visitor_advanced_example5,indent=0]
DefaultVisitor now looks a bit different.
It has a doIteration method that will get the children it should iterate over and then call visit on each element.
Per default this will call visit(Visitable) which then iterates over the children of this child.Visitable has also changed to ensure that any node will be able to return children (even if empty).
We didn't have to change the NodeType1 and NodeType2 class, because the way the children field was
defined already made them a property, which means Groovy is so nice to generate a get method for us.
Now the really interesting part is NodeType1Counter, it is interesting because we have not changed it.super.visit(n1) will now call visit(Visitable) which will call doIteration which will start the next level of iteration.
So no change. But visit(it) will call visit(NodeType1) if it is of type NodeType1.
In fact, we don't need the doIteration method, we could do that in visit(Visitable) too,
but this variant has some benefits. It allows us to write a new Visitor that overwrites visit(Visitable)
for error cases which of course means we must not do super.visit(n1) but doIteration(n1).
=== Summary
In the end we got ~40% less code, a robust and stable architecture,
and we completely removed the Visitor from the Visitable.
To achieve the same in Java, you would probably need to resort to reflection.
The visitor pattern has sometimes been described as a poor fit for extreme programming
techniques because you need to make changes to so many classes all the time.
With our design, if we add new types we don't need to change anything.
So, the pattern is a good fit for agile approaches when using Groovy.
There are variants of the Visitor pattern, like the https://java-design-patterns.com/patterns/acyclic-visitor/[acyclic visitor pattern],
that try to solve the problem of adding new node types with special visitors.
The implementations of these visitors have their own code smells, like using casts, overuse of instanceof, and other tricks.
What's more the problems such approaches are trying to solve don't occur within the Groovy version. We recommend avoiding that variant of this pattern.
Finally, in case it isn't obvious, NodeType1Counter could be implemented in Java as well.
Groovy will recognize the visit methods and call them as needed because DefaultVisitor is still Groovy and does all the magic.
== Further Information
- http://se.ethz.ch/~meyer/publications/computer/visitor.pdf[Componentization: the Visitor example]
Src Spec Doc Records (`src/spec/doc/_records.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Record classes (incubating)
Record classes, or records for short, are a special kind of class
useful for modelling plain data aggregates.
They provide a compact syntax with less ceremony than normal classes.
Groovy already has AST transforms such as @Immutable and @Canonical
which already dramatically reduce ceremony but records have been
introduced in Java and record classes in Groovy are designed to align
with Java record classes.
For example, suppose we want to create a Message record
representing an email message. For the purposes of this example,
let's simplify such a message to contain just a from email address,
a to email address, and a message body. We can define such
a record as follows:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_message_defn,indent=0]
We'd use the record class in the same way as a normal class, as shown below:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_message_usage,indent=0]
The reduced ceremony saves us from defining explicit fields, getters andtoString, equals and hashCode methods. In fact, it's a shorthand
for the following rough equivalent:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_message_equivalent,indent=0]
Note the special naming convention for record getters. They are the same name as the field
(rather than the often common JavaBean convention of capitalized with a "get" prefix).
Rather than referring to a record's fields or properties, the term component
is typically used for records. So our Message record has from, to, and body components.
Like in Java, you can override the normally implicitly supplied methods
by writing your own:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point3d,indent=0]
You can also use generics with records in the normal way. For example, consider the following Coord record definition:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_generics_defn,indent=0]
It can be used as follows:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_generics_usage,indent=0]
== Special record features
=== Compact constructor
Records have an implicit constructor. This can be overridden in the normal way
by providing your own constructor - you need to make sure you set all the fields
if you do this.
However, for succinctness, a compact constructor syntax can be used where
the parameter declaration part of a normal constructor is elided.
For this special case, the normal implicit constructor is still provided
but is augmented by the supplied statements in the compact constructor definition:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_compact_constructor,indent=0]
=== Serializability
Groovy native records follow the
https://docs.oracle.com/en/java/javase/16/docs/specs/records-serialization.html[special conventions]
for serializability which apply to Java records.
Groovy record-like classes (discussed below) follow normal Java class serializability conventions.
== Groovy enhancements
=== Argument defaults
Groovy supports default values for constructor arguments.
This capability is also available for records as shown in the following record definition
which has default values for y and color:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point_defn,indent=0]
Arguments when left off (dropping one or more arguments from the right) are replaced
with their defaults values as shown in the following example:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point_defaults,indent=0]
This processing follows normal Groovy conventions for default arguments for constructors, essentially automatically providing the constructors with the following signatures:
[source,groovy]
ColoredPoint(int, int, String) ColoredPoint(int, int) ColoredPoint(int)
Named arguments may also be used (default values also apply here):
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point_named_args,indent=0]
You can disable default argument processing as shown here:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point_named_args_off,indent=0]
This will produce a single constructor as per the default with Java.
It will be an error if you drop off arguments in this scenario.
You can force all properties to have a default value as shown here:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point_named_args_on,indent=0]
Any property/field without an explicit initial value will be given the default value for the argument's type (null, or zero/false for primitives).
.Diving deeper
We previously described a Message record and displayed it's rough equivalent.
Groovy in fact steps through an intermediate stage where the record keyword
is replaced by the class keyword and an accompanying @RecordType annotation:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_message_annotation_defn,indent=0]
Then @RecordType itself is processed as a meta-annotation (annotation collector)
and expanded into its constituent sub-annotations such as @TupleConstructor, @POJO,@RecordBase, and others. This is in some sense an implementation detail which can often be ignored.
However, if you wish to customise or configure the record implementation,
you may wish to drop back to the @RecordType style or augment your record class
with one of the constituent sub-annotations.
=== Declarative toString customization
As per Java, you can customize a record's toString method by writing your own.
If you prefer a more declarative style, you can alternatively use Groovy's @ToString transform
to override the default record toString.
As an example, you can a three-dimensional point record as follows:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point3d_tostring_annotation,indent=0]
We customise the toString by including the package name (excluded by default for records)
and by caching the toString value since it won't change for this immutable record.
We are also ignoring null values (the default value for z in our definition).
We can have a similar definition for a two-dimensional point:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_point2d_tostring_annotation,indent=0]
We can see here that without the package name it would have the same toString as our previous example.
=== Obtaining a list of the record component values
We can obtain the component values from a record as a list like so:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_to_list,indent=0]
You can use @RecordOptions(toList=false) to disable this feature.
=== Obtaining a map of the record component values
We can obtain the component values from a record as a map like so:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_to_map,indent=0]
You can use @RecordOptions(toMap=false) to disable this feature.
=== Obtaining the number of components in a record
We can obtain the number of components in a record like so:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_size,indent=0]
You can use @RecordOptions(size=false) to disable this feature.
=== Obtaining the n^th^ component from a record
We can use Groovy's normal positional indexing to obtain a particular component in a record like so:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_get_at,indent=0]
You can use @RecordOptions(getAt=false) to disable this feature.
== Optional Groovy features
=== Copying
It can be useful to make a copy of a record with some components changed.
This can be done using an optional copyWith method which takes named arguments.
Record components are set from the supplied arguments.
For components not mentioned, a (shallow) copy of the original record component is used.
Here is how you might use copyWith for the Fruit record:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_copywith,indent=0]
The copyWith functionality can be disabled by setting theRecordOptions#copyWith annotation attribute to false.
=== Deep immutability
As with Java, records by default offer shallow immutability.
Groovy's @Immutable transform performs defensive copying for a range of mutable
data types. Records can make use of this defensive copying to gain deep immutability as follows:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_immutable,indent=0]
These examples illustrate the principal behind
Groovy's record feature offering three levels of convenience:
- Using the
recordkeyword for maximum succinctness - Supporting low-ceremony customization using declarative annotations
- Allowing normal method implementations when full control is required
=== Obtaining the components of a record as a typed tuple
You can obtain the components of a record as a typed tuple:
[source,groovy]
include::../test/RecordSpecificationTest.groovy[tags=record_components,indent=0]
Groovy has a limited number of TupleN classes.
If you have a large number of components in your record, you might not be able to use this feature.
== Other differences to Java
Groovy supports creating record-like classes as well as native records.
Record-like classes don't extend Java's Record class and such classes
won't be seen by Java as records but will otherwise have similar properties.
The @RecordOptions annotation (part of @RecordType) 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.
EMULATE::
Produces a record-like class.
AUTO::
Equivalent to NATIVE at Groovy 6's JDK17+ baseline (default).
Whether you use the record keyword or the @RecordType annotation
is independent of the mode.
Src Spec Doc Sealed (`src/spec/doc/_sealed.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Sealed hierarchies
Sealed classes, interfaces and traits restrict which subclasses can extend/implement them.
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.
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:
[source,groovy]
include::../test/SealedSpecificationTest.groovy[tags=simple_interface_keyword,indent=0]
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
subclasses as final but other times we might want to allow further
controlled inheritance.
[source,groovy]
include::../test/SealedSpecificationTest.groovy[tags=general_sealed_class,indent=0]
.
[%collapsible]
[source,groovy]
include::../test/SealedSpecificationTest.groovy[tags=general_sealed_class_annotations,indent=0]
====
+
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 non-sealed.
That means our hierarchy 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
but only in a controlled way (only Square is permitted).
Sealed classes are useful for creating enum-like related classes
which need to contain instance specific data. For instance, we might have the following enum:
[source,groovy]
include::../test/SealedSpecificationTest.groovy[tags=weather_enum,indent=0]
but we now wish to also add weather specific instance data to weather forecasts.
We can alter our abstraction as follows:
[source,groovy]
include::../test/SealedSpecificationTest.groovy[tags=weather_sealed,indent=0]
Sealed hierarchies are also useful when specifying Algebraic or Abstract Data Types (ADTs) as shown in the following example:
[source,groovy]
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 offinal,sealedornon-sealedbe specified.
Groovy defaults to non-sealed but you can still usenon-sealed/@NonSealedif you wish.
We anticipate the style checking tool CodeNarc will eventually have a rule that
looks for the presence ofnon-sealedso 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.
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 sealed class for JDK17+ and emulates the sealed nature otherwise.
Whether you use the sealed keyword or the @Sealed annotation
is independent of the mode.
Src Spec Doc Traits (`src/spec/doc/_traits.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Traits
Traits are a structural construct of the language which allows:
- composition of behaviors
- runtime implementation of interfaces
- behavior overriding
- compatibility with static type checking/compilation
They can be seen as interfaces carrying both default implementations and state. A trait is defined using thetrait keyword:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=flying_simple,indent=0]
<1> declaration of a trait
<2> declaration of a method inside a trait
Then it can be used like a normal interface using the implements keyword:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=bird,indent=0]
<1> Adds the trait FlyingAbility to the Bird class capabilities
<2> instantiate a new Bird
<3> the Bird class automatically gets the behavior of the FlyingAbility trait
Traits allow a wide range of capabilities, from simple composition to testing, which are described thoroughly in this section.
== Methods
=== Public methods
Declaring a method in a trait can be done like any regular method in a class:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=flying_simple,indent=0]
<1> declaration of a trait
<2> declaration of a method inside a trait
=== Abstract methods
In addition, traits may declare abstract methods too, which therefore need to be implemented in the class implementing the trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=greetable,indent=0]
<1> implementing class will have to declare the name method
<2> can be mixed with a concrete method
Then the trait can be used like this:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=greetable_person,indent=0]
<1> implement the trait Greetable
<2> since name was abstract, it is required to implement it
<3> then greeting can be called
=== Private methods
Traits may also define private methods. Those methods will not appear in the trait contract interface:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=private_method_in_trait,indent=0]
<1> define a private method greetingMessage in the trait
<2> the public greet message calls greetingMessage by default
<3> create a class implementing the trait
<4> greet can be called
<5> but not greetingMessage
WARNING: Traits only support public and private methods. Neither protected nor package private scopes are
supported.
=== Final methods
If we have a class implementing a trait, conceptually implementations from the trait methods
are "inherited" into the class. But, in reality, there is no base class containing such
implementations. Rather, they are woven directly into the class. A final modifier on a method
just indicates what the modifier will be for the woven method. While it would likely be
considered bad style to inherit and override or multiply inherit methods with the same
signature but a mix of final and non-final variants, Groovy doesn't prohibit this scenario.
Normal method selection applies and the modifier used will be determined from the resulting method.
You might consider creating a base class which implements the desired trait(s) if you
want trait implementation methods that can't be overridden.
== The meaning of this
this represents the implementing instance. Think of a trait as a superclass. This means that when you write:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=meaningofthis_header,indent=0]
then calling:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=meaningofthis_snippet,indent=0]
will return the same instance:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=meaningofthis_assert,indent=0]
== Interfaces
Traits may implement interfaces, in which case the interfaces are declared using the implements keyword:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=trait_implementing_interface,indent=0]
<1> declaration of a normal interface
<2> add Named to the list of implemented interfaces
<3> declare a class that implements the Greetable trait
<4> implement the missing name method
<5> the greeting implementation comes from the trait
<6> make sure Person implements the Named interface
<7> make sure Person implements the Greetable trait
== Properties
A trait may define properties, like in the following example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=trait_with_property,indent=0]
<1> declare a property name inside a trait
<2> declare a class which implements the trait
<3> the property is automatically made visible
<4> it can be accessed using the regular property accessor
<5> or using the regular getter syntax
== Fields
=== Private fields
Since traits allow the use of private methods, it can also be interesting to use private fields to store state. Traits
will let you do that:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=trait_with_private_field,indent=0]
<1> declare a private field count inside a trait
<2> declare a public method count that increments the counter and returns it
<3> declare a class that implements the Counter trait
<4> the count method can use the private field to keep state
TIP: This is a major difference with http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html[Java 8 virtual extension methods]. While virtual extension methods
do not carry state, traits can. Moreover, traits in Groovy are supported starting with Java 6, because their implementation does not rely on virtual extension methods. This
means that even if a trait can be seen from a Java class as a regular interface, that interface will not have default methods, only abstract ones.
=== Public fields
Public fields work the same way as private fields, but in order to avoid the http://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem[diamond problem],
field names are remapped in the implementing class:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=trait_with_public_field,indent=0]
<1> declare a public field inside the trait
<2> declare a class implementing the trait
<3> create an instance of that class
<4> the public field is available, but renamed
The name of the field depends on the fully qualified name of the trait. All dots (.) in package are replaced with an underscore (_), and the final name includes a double underscore.
So if the type of the field is String, the name of the package is my.package, the name of the trait is Foo and the name of the field is bar,
in the implementing class, the public field will appear as:
[source,groovy]
String my_package_Foo__bar
WARNING: While traits support public fields, it is not recommended to use them and considered as a bad practice.
== Composition of behaviors
Traits can be used to implement multiple inheritance in a controlled way. For example, we can have the following traits:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=flying_simple,indent=0] include::../test/TraitsSpecificationTest.groovy[tags=speaking_simple,indent=0]
And a class implementing both traits:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=speakingduck,indent=0]
<1> the Duck class implements both FlyingAbility and SpeakingAbility
<2> creates a new instance of Duck
<3> we can call the method fly from FlyingAbility
<4> but also the method speak from SpeakingAbility
Traits encourage the reuse of capabilities among objects, and the creation of new classes by the composition of existing behavior.
== Overriding default methods
Traits provide default implementations for methods, but it is possible to override them in the implementing class. For example, we
can slightly change the example above, by having a duck which quacks:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=quackingduck,indent=0]
<1> define a method specific to Duck, named quack
<2> override the default implementation of speak so that we use quack instead
<3> the duck is still flying, from the default implementation
<4> quack comes from the Duck class
<5> speak no longer uses the default implementation from SpeakingAbility
== Extending traits
=== Simple inheritance
Traits may extend another trait, in which case you must use the extends keyword:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=trait_inherit,indent=0]
<1> the Named trait defines a single name property
<2> the Polite trait extends the Named trait
<3> Polite adds a new method which has access to the name property of the super-trait
<4> the name property is visible from the Person class implementing Polite
<5> as is the introduce method
=== Multiple inheritance
Alternatively, a trait may extend multiple traits. In that case, all super traits must be declared in the implements
clause:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=trait_multiple_inherit,indent=0]
<1> WithId trait defines the id property
<2> WithName trait defines the name property
<3> Identified is a trait which inherits both WithId and WithName
== Duck typing and traits
=== Dynamic code
Traits can call any dynamic code, like a normal Groovy class. This means that you can, in the body of a method, call
methods which are supposed to exist in an implementing class, without having to explicitly declare them in an interface.
This means that traits are fully compatible with duck typing:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=ducktyping,indent=0]
<1> the SpeakingDuck expects the quack method to be defined
<2> the Duck class does implement the method using methodMissing
<3> calling the speak method triggers a call to quack which is handled by methodMissing
=== Dynamic methods in a trait
It is also possible for a trait to implement MOP methods like methodMissing or propertyMissing, in which case implementing classes
will inherit the behavior from the trait, like in this example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=dynamicobject,indent=0]
<1> create a trait implementing several MOP methods
<2> the Dynamic class defines a property
<3> the Dynamic class defines a method
<4> calling an existing property will call the method from Dynamic
<5> calling a non-existing property will call the method from the trait
<6> will call setProperty defined on the trait
<7> will call getProperty defined on the trait
<8> calling an existing method on Dynamic
<9> but calling a non-existing method thanks to the trait methodMissing
== Multiple inheritance conflicts
=== Default conflict resolution
It is possible for a class to implement multiple traits. If some trait defines a method with the same signature as a
method in another trait, we have a conflict:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=multiple_inherit_default,indent=0]
<1> trait A defines a method named exec returning a String
<2> trait B defines the very same method
<3> class C implements both traits
In this case, the default behavior is that the method from the last declared trait in the implements clause wins.
Here, B is declared after A so the method from B will be picked up:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=multiple_inherit_default_assert,indent=0]
=== User conflict resolution
In case this behavior is not the one you want, you can explicitly choose which method to call using the Trait.super.foo syntax.
In the example above, we can ensure the method from trait A is invoked by writing this:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=multiple_inherit_user,indent=0]
<1> explicit call of exec from the trait A
<2> calls the version from A instead of using the default resolution, which would be the one from B
== Runtime implementation of traits
=== Implementing a trait at runtime
Groovy also supports implementing traits dynamically at runtime. It allows you to "decorate" an existing object using a
trait. As an example, let's start with this trait and the following class:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=runtime_header,indent=0]
<1> the Extra trait defines an extra method
<2> the Something class does not implement the Extra trait
<3> Something only defines a method doSomething
Then if we do:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=runtime_fail,indent=0]
the call to extra would fail because Something is not implementing Extra. It is possible to do it at runtime with
the following syntax:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=runtime_success,indent=0]
<1> use of the as keyword to coerce an object to a trait at runtime
<2> then extra can be called on the object
<3> and doSomething is still callable
IMPORTANT: When coercing an object to a trait, the result of the operation is not the same instance. It is guaranteed
that the coerced object will implement both the trait and the interfaces that the original object implements, but
the result will not be an instance of the original class.
=== Implementing multiple traits at once
Should you need to implement several traits at once, you can use the withTraits method instead of the as keyword:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=withtraits_header,indent=0] include::../test/TraitsSpecificationTest.groovy[tags=withtraits_fail,indent=0] include::../test/TraitsSpecificationTest.groovy[tags=withtraits_success,indent=0]
<1> call to methodFromA will fail because C doesn't implement A
<2> call to methodFromB will fail because C doesn't implement B
<3> withTraits will wrap c into something which implements A and B
<4> methodFromA will now pass because d implements A
<5> methodFromB will now pass because d also implements B
IMPORTANT: When coercing an object to multiple traits, the result of the operation is not the same instance. It is guaranteed
that the coerced object will implement both the traits and the interfaces that the original object implements, but
the result will not be an instance of the original class.
== Chaining behavior
Groovy supports the concept of stackable traits. The idea is to delegate from one trait to the other if the current trait
is not capable of handling a message. To illustrate this, let's imagine a message handler interface like this:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=messagehandler,indent=0]
Then you can compose a message handler by applying small behaviors. For example, let's define a default handler in the
form of a trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=defaulthandler,indent=0]
Then any class can inherit the behavior of the default handler by implementing the trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=simplehandler,indent=0]
Now what if you want to log all messages, in addition to the default handler? One option is to write this:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=simplehandlerwithlogging,indent=0]
<1> explicitly implement the on method
<2> perform logging
<3> continue by delegating to the DefaultHandler trait
This works but this approach has drawbacks:
. the logging logic is bound to a "concrete" handler
. we have an explicit reference to DefaultHandler in the on method, meaning that if we happen to change the trait that our class implements, code will be broken
As an alternative, we can write another trait which responsibility is limited to logging:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=logginghandler,indent=0]
<1> the logging handler is itself a handler
<2> prints the message it receives
<3> then super makes it delegate the call to the next trait in the chain
Then our class can be rewritten as this:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=handlerwithlogger,indent=0] include::../test/TraitsSpecificationTest.groovy[tags=handlerwithlogger_assert,indent=0]
which will print:
include::../test/TraitsSpecificationTest.groovy[tags=handlerwithlogger_assert_output,indent=0]
As the priority rules imply that LoggerHandler wins because it is declared last, then a call to on will use
the implementation from LoggingHandler. But the latter has a call to super, which means the next trait in the
chain. Here, the next trait is DefaultHandler so both will be called:
The interest of this approach becomes more evident if we add a third handler, which is responsible for handling messages
that start with say:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=sayhandler,indent=0]
<1> a handler specific precondition
<2> if the precondition is not met, pass the message to the next handler in the chain
Then our final handler looks like this:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=implementinghandler,indent=0] include::../test/TraitsSpecificationTest.groovy[tags=implementinghandler_assert,indent=0]
Which means:
- messages will first go through the logging handler
- the logging handler calls
superwhich will delegate to the next handler, which is theSayHandler - if the message starts with
say, then the handler consumes the message - if not, the
sayhandler delegates to the next handler in the chain
This approach is very powerful because it allows you to write handlers that do not know each other and yet let you
combine them in the order you want. For example, if we execute the code, it will print:
include::../test/TraitsSpecificationTest.groovy[tags=implementinghandler_output,indent=0]
but if we move the logging handler to be the second one in the chain, the output is different:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=alternatehandler,indent=0] include::../test/TraitsSpecificationTest.groovy[tags=alternatehandler_assert,indent=0]
prints:
include::../test/TraitsSpecificationTest.groovy[tags=alternatehandler_output,indent=0]
The reason is that now, since the SayHandler consumes the message without calling super, the logging handler is
not called anymore.
=== Semantics of super inside a trait
If a class implements multiple traits and a call to an unqualified super is found, then:
. if the class implements another trait, the call delegates to the next trait in the chain
. if there isn't any trait left in the chain, super refers to the super class of the implementing class (this)
For example, it is possible to decorate final classes thanks to this behavior:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=decoratefinalclass,indent=0]
<1> define a trait named Filtering, supposed to be applied on a StringBuilder at runtime
<2> redefine the append method
<3> remove all 'o's from the string
<4> then delegate to super
<5> in case toString is called, delegate to super.toString
<6> runtime implementation of the Filtering trait on a StringBuilder instance
<7> the string which has been appended no longer contains the letter o
In this example, when super.append is encountered, there is no other trait implemented by the target object, so the
method which is called is the original append method, that is to say the one from StringBuilder. The same trick
is used for toString, so that the string representation of the proxy object which is generated delegates to thetoString of the StringBuilder instance.
== Advanced features
=== SAM type coercion
If a trait defines a single abstract method, it is candidate for SAM (Single Abstract Method) type coercion. For example,
imagine the following trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=sam_trait,indent=0]
<1> the greet method is not abstract and calls the abstract method getName
<2> getName is an abstract method
Since getName is the single abstract method in the Greeter trait, you can write:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=sam_trait_assignment,indent=0]
<1> the closure "becomes" the implementation of the getName single abstract method
or even:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=sam_trait_method,indent=0]
<1> the greet method accepts the SAM type Greeter as parameter
<2> we can call it directly with a closure
=== Differences with Java 8 default methods
In Java 8, interfaces can have default implementations of methods. If a class implements an interface and does not provide
an implementation for a default method, then the implementation from the interface is chosen. Traits behave the same but
with a major difference: the implementation from the trait is always used if the class declares the trait in its interface
list and that it doesn't provide an implementation even if a super class does.
This feature can be used to compose behaviors in a very precise way, in case you want to override the behavior of an
already implemented method.
To illustrate the concept, let's start with this simple example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=forceoverride_header,indent=0]
In this example, we create a simple test case which uses two properties (config and shell) and uses those in
multiple test methods. Now imagine that you want to test the same, but with another distinct compiler configuration.
One option is to create a subclass of SomeTest:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=forceoverride_extends,indent=0]
It works, but what if you have actually multiple test classes, and that you want to test the new configuration for all
those test classes? Then you would have to create a distinct subclass for each test class:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=forceoverride_extends2,indent=0]
Then what you see is that the setup method of both tests is the same. The idea, then, is to create a trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=forceoverride_trait,indent=0]
Then use it in the subclasses:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=forceoverride_miss_header,indent=0] ...
It would allow us to dramatically reduce the boilerplate code, and reduces the risk of forgetting to change the setup
code in case we decide to change it. Even if setup is already implemented in the super class, since the test class declares
the trait in its interface list, the behavior will be borrowed from the trait implementation!
This feature is in particular useful when you don't have access to the super class source code. It can be used to
mock methods or force a particular implementation of a method in a subclass. It lets you refactor your code to keep
the overridden logic in a single trait and inherit a new behavior just by implementing it. The alternative, of course,
is to override the method in every place you would have used the new code.
IMPORTANT: It's worth noting that if you use runtime traits, the methods from the trait are always preferred to those of the proxied
object:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=runtime_forceoverride,indent=0]
<1> the Person class defines a name property which results in a getName method
<2> Bob is a trait which defines getName as returning Bob
<3> the default object will return Alice
<4> p2 coerces p into Bob at runtime
<5> getName returns Bob because getName is taken from the trait
IMPORTANT: Again, don't forget that dynamic trait coercion returns a distinct object which only implements the original
interfaces, as well as the traits.
== Differences with mixins
There are several conceptual differences with mixins, as they are available in Groovy. Note that we are talking about
runtime mixins, not the @Mixin annotation which is deprecated in favour of traits.
First of all, methods defined in a trait are visible in bytecode:
- internally, the trait is represented as an interface (without default methods; public static methods are exposed as JVM-native interface statics) and several helper classes
- this means that an object implementing a trait effectively implements an interface
- those methods are visible from Java
- they are compatible with type checking and static compilation
Methods added through a mixin are, on the contrary, only visible at runtime:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=diff_mixin,indent=0]
<1> class A defines methodFromA
<2> class B defines methodFromB
<3> mixin B into A
<4> we can call methodFromA
<5> we can also call methodFromB
<6> the object is an instance of A
<7> but it's not an instanceof B
The last point is actually a very important and illustrates a place where mixins have an advantage over traits: the instances
are not modified, so if you mixin some class into another, there isn't a third class generated, and methods which respond to
A will continue responding to A even if mixed in.
== Static methods, properties and fields
NOTE: Static member support in traits is incubating: the behaviour described here is settled for {groovyVersion}
but may still be refined. GEP-22 gives the full specification.
A trait may declare static methods, properties and fields:
- The trait is interpreted as a template for the implementing class: each implementing class gets its own copy
of the trait's static members. A static member declared on a trait belongs to the implementing class, not to a
single copy shared across all implementers. - Public static methods are declarer-bound by default and are promoted onto the generated trait interface as
JVM-native static methods, so bothTrait.m(...)andImpl.m(...)resolve. A private static method stays internal
to the trait and is not promoted. - Static methods may be declared in, and called from,
@CompileStaticand@TypeCheckedcode; they are not
restricted to dynamic Groovy. - You should typically not mix static and instance methods of the same signature. The normal trait method-selection
rules apply (including multiple inheritance conflict resolution): if the selected method is static but some
implemented trait has an instance variant, a compilation error occurs; if the instance variant is selected, the
static variant is ignored (as with static methods in Java interfaces for this case).
Let's start with a simple example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=staticfield_header,indent=0]
<1> the static field is declared in the trait
<2> a static method is also declared in the trait
<3> the static field is updated within the trait
<4> a static method init is made available to the implementing class
<5> the static field is remapped to avoid the diamond issue
As usual, it is not recommended to use public fields. Anyway, should you want this, you must understand that the following code would fail:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=staticfield_notontrait,indent=0]
because there is no static field CALLED defined on the trait itself. Likewise, if you have two distinct implementing classes, each one gets a distinct static field:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=staticfield_distinct,indent=0]
<1> class Bar implements the trait
<2> class Baz also implements the trait
<3> init is only called on Bar
<4> the static field CALLED on Bar is updated
<5> but the static field CALLED on Baz is not, because it is distinct
=== Overridable static defaults with @Virtual
By default a trait's static method is declarer-bound: when other trait code calls it, it always resolves to the
trait's own copy, even if an implementing class declares a static method of the same signature. That is the right
choice for an internal helper or a constant that the trait owns outright.
Sometimes, though, a trait wants to publish a static default that an implementing class can override, and have
the trait's own logic honour that override. This is the template-method pattern applied to a static member โ the
same override-visibility that trait instance methods already have. Annotate the method with@groovy.transform.Virtual to opt into per-implementer override dispatch:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=virtual_default,indent=0]
<1> the overridable default, marked @Virtual
<2> trait logic that consults the setting
<3> an unqualified call, so it dispatches through the implementing class
<4> uses the trait's default
<5> overrides the default with its own static
<6> the default is seen for StrictEntity
<7> the override is seen by the trait's own describe() for LenientEntity
<8> and externally via Impl.m()
Reach for @Virtual when the overridable thing is genuinely a property of the type rather than of an instance โ
a policy or configuration default that answers a question about the class as a whole (Grails uses this for hooks
such as Validateable.defaultNullable()), that may be consulted where no instance exists yet (class
initialisation, static factory methods, building metadata), and that external callers can read statically asImpl.setting(). For overridable static data rather than behaviour, use a @Virtual accessor
(@Virtual static getSetting()) rather than a static field, since a bare static-field read from trait code is not
override-visible (see <<_inheritance_of_state_gotchas,Inheritance of state gotchas>>).
A @Virtual static is deliberately not exposed on the trait interface (interface statics are declarer-bound by
JVM rule, which is incompatible with override dispatch). It therefore has no qualified Trait.m(...) form: read a
concrete class's value with Impl.m(...), or use T.super.m(...) from within trait code for the trait's own
default. A qualified Trait.m(...) call to a @Virtual static is rejected at compile time:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=virtual_qualified,indent=0]
<1> rejected: a @Virtual static has no qualified Trait.m(...) form
== Inheritance of state gotchas
We have seen that traits are stateful. It is possible for a trait to define fields or properties, but when a class implements a trait, it gets those fields/properties on
a per-trait basis. So consider the following example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=intcouple,indent=0]
The trait defines two properties, x and y, as well as a sum method. Now let's create a class which implements the trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=intcouple_impl,indent=0]
The result of calling f is 3, because f delegates to sum in the trait, which has state. But what if we write this instead?
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=intcouple_impl_override,indent=0]
<1> Override property x
<2> Override property y
<3> Call sum from trait
If you call elem.f(), what is the expected output? Actually it is:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=intcouple_impl_override_assert,indent=0]
The reason is that the sum method accesses the fields of the trait. So it is using the x and y values defined
in the trait. If you want to use the values from the implementing class, then you need to dereference fields by using
getters and setters, like in this last example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=intcouple_impl_override_directgetter,indent=0]
== Self types
=== Type constraints on traits
Sometimes you will want to write a trait that can only be applied to some type. For example, you may want to apply a
trait on a class that extends another class which is beyond your control, and still be able to call those methods.
To illustrate this, let's start with this example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=selftype_intro,indent=0]
<1> A Service class, beyond your control (in a library, ...) defines a sendMessage method
<2> A Device class, beyond your control (in a library, ...)
<3> Defines a communicating trait for devices that can call the service
<4> Defines MyDevice as a communicating device
<5> The method from the trait is called, and id is resolved
It is clear, here, that the Communicating trait can only apply to Device. However, there's no explicit
contract to indicate that, because traits cannot extend classes. However, the code compiles and runs perfectly
fine, because id in the trait method will be resolved dynamically. The problem is that there is nothing that
prevents the trait from being applied to any class which is not a Device. Any class which has an id would
work, while any class that does not have an id property would cause a runtime error.
The problem is even more complex if you want to enable type checking or apply @CompileStatic on the trait: because
the trait knows nothing about itself being a Device, the type checker will complain saying that it does not find
the id property.
One possibility is to explicitly add a getId method in the trait, but it would not solve all issues. What if a method
requires this as a parameter, and actually requires it to be a Device?
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=selftype_securityservice,indent=0]
If you want to be able to call this in the trait, then you will explicitly need to cast this into a Device. This can
quickly become unreadable with explicit casts to this everywhere.
[[traits-selftype]]
=== The @SelfType annotation
In order to make this contract explicit, and to make the type checker aware of the type of itself, Groovy provides
a @SelfType annotation that will:
- let you declare the types that a class that implements this trait must inherit or implement
- throw a compile-time error if those type constraints are not satisfied
So in our previous example, we can fix the trait using the @groovy.transform.SelfType annotation:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=selftype_fixed,indent=0]
Now if you try to implement this trait on a class that is not a device, a compile-time error will occur:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=selftype_compiletimeerror,indent=0]
The error will be:
class 'MyDevice' implements trait 'Communicating' but does not extend self type class 'Device'
In conclusion, self types are a powerful way of declaring constraints on traits without having to declare the contract
directly in the trait or having to use casts everywhere, maintaining separation of concerns as tight as it should be.
=== Differences with Sealed annotation (incubating)
Both @Sealed and @SelfType restrict classes which use a trait but in orthogonal ways.
Consider the following example:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=selftype_sealed_volume,indent=0]
<1> All usages of the HasVolume trait must implement or extend both HasHeight and HasArea
<2> Only UnitCube or UnitCylinder can use the trait
For the degenerate case where a single class implements a trait, e.g.:
[source,groovy]
final class Foo implements FooTrait {}
Then, either:
[source,groovy]
@SelfType(Foo) trait FooTrait {}
or:
[source,groovy]
@Sealed(permittedSubclasses='Foo') // <1> trait FooTrait {}
<1> Or just @Sealed if Foo and FooTrait are in the same source file
could express this constraint. Generally, the former of these is preferred.
== Limitations
=== Compatibility with AST transformations
CAUTION: Traits are not officially compatible with AST transformations. Some of them, like @CompileStatic will be applied
on the trait itself (not on implementing classes), while others will apply on both the implementing class and the trait.
There is absolutely no guarantee that an AST transformation will run on a trait as it does on a regular class, so use it
at your own risk!
=== Prefix and postfix operations
Within traits, prefix and postfix operations are not allowed if they update a field of the trait:
[source,groovy]
include::../test/TraitsSpecificationTest.groovy[tags=prefix_postfix,indent=0]
<1> x is defined within the trait, postfix increment is not allowed
<2> x is defined within the trait, prefix decrement is not allowed
A workaround is to use the += operator instead.
Src Spec Doc Type Checking Extensions (`src/spec/doc/_type-checking-extensions.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
[[type-checking-extensions]]
= Type checking extensions
[TIP]
This chapter is the SDK reference for *writing* a type checking extension. For the extensions Groovy already ships with (regex checking, format-string checking, null safety, frame conditions, purity, parallel-reduction associativity, and monadic comprehensions), see the link:typecheckers.html#built-in-auxiliary-type-checkers[Built-in auxiliary type checkers] chapter.
== Writing a type checking extension
=== Towards a smarter type checker
Despite being a dynamic language, Groovy can be used with a <<static-type-checking,static type checker>>
at compile time, enabled using the @TypeChecked annotation. In this mode, the
compiler becomes more verbose and throws errors for, example, typos, non-existent
methods, etc. This comes with a few limitations though, most of them coming from
the fact that Groovy remains inherently a dynamic language. For example, you
wouldnโt be able to use type checking on code that uses the markup builder:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=intro_stc_extensions,indent=0]
In the previous example, none of the html, head, body or p methods
exist. However if you execute the code, it works because Groovy uses dynamic dispatch
and converts those method calls at runtime. In this builder, thereโs no limitation about
the number of tags that you can use, nor the attributes, which means there is no chance
for a type checker to know about all the possible methods (tags) at compile time, unless
you create a builder dedicated to HTML for example.
Groovy is a platform of choice when it comes to implement internal DSLs. The flexible syntax,
combined with runtime and compile-time metaprogramming capabilities make Groovy an interesting
choice because it allows the programmer to focus on the DSL rather than
on tooling or implementation. Since Groovy DSLs are Groovy code, itโs
easy to have IDE support without having to write a dedicated plugin for
example.
In a lot of cases, DSL engines are written in Groovy (or Java) then user
code is executed as scripts, meaning that you have some kind of wrapper
on top of user logic. The wrapper may consist, for example, in aGroovyShell or GroovyScriptEngine that performs some tasks transparently
before running the script (adding imports, applying AST transforms,
extending a base script,โฆ). Often, user written scripts come to
production without testing because the DSL logic comes to a point
whereย any user may write code using the DSL syntax. In the end, a user
may just ignore that what they write is actuallyย code. This adds some
challenges for the DSL implementer, such as securing execution of user
code or, in this case, early reporting of errors.
For example, imagine a DSL which goal is to drive a rover on Mars
remotely. Sending a message to the rover takes around 15 minutes. If the
rover executes the script and fails with an error (say a typo), you have
two problems:
- first, feedback comes only after 30 minutes (the time needed for the
rover to get the script and the time needed to receive the error) - second, some portion of the script has been executed and you may have
to change the fixed script significantly (implying that you need to know
the current state of the roverโฆ)
Type checking extensions is a mechanism that will
allow the developer of a DSL engine to make those scripts safer by
applying the same kind of checks that static type checking allows on
regular groovy classes.
The principle, here, is to fail early, that is
to say fail compilation of scripts as soon as possible, and if possible
provide feedback to the user (including nice error messages).
In short, the idea behind type checking extensions is to make the compiler
aware of all the runtime metaprogramming tricks that the DSL uses, so that
scripts can benefit the same level of compile-time checks as a verbose statically
compiled code would have. We will see that you can go even further by performing
checks that a normal type checker wouldn't do, delivering powerful compile-time
checks for your users.
[[Typecheckingextensions-Howdoesitwork]]
=== The extensions attribute
Theย @TypeChecked annotation supports an attribute
namedย extensions. This parameter takes an array of strings
corresponding to a list of type checking extensions scripts. Those
scripts are found atย compile time on classpath. For example, you would
write:
[source,groovy]
@TypeChecked(extensions='/path/to/myextension.groovy') void foo() { ...}
In that case, theย foo methods would be type checked with the rules of
the normal type checker completed by those found in
theย myextension.groovy script. Note that while internally the type
checker supports multiple mechanisms to implement type checking
extensions (including plain old java code), the recommended way is to
use those type checking extension scripts.
==== Parameterized extensions
Extensions can accept named parameters using Groovy's named-argument style within the extension string.
The example below uses NullChecker, one of the
link:typecheckers.html#built-in-auxiliary-type-checkers[bundled checkers]; the same syntax applies
to any extension that reads the options map.
[source,groovy]
@TypeChecked(extensions = 'groovy.typecheckers.NullChecker(strict: true)') void foo() { ... }
Parameters are passed as key: value pairs inside parentheses appended to the extension name.
Supported value types include booleans (true/false), integers, strings (single or double quoted), and null.
Multiple parameters are separated by commas.
Note that string values cannot contain commas or colons since these are used as delimiters:
[source,groovy]
@TypeChecked(extensions = 'com.example.MyChecker(strict: true, threshold: 5, mode: "lenient")') void foo() { ... }
Within an extension, the parameters are available via the options property, which returns an unmodifiable Map<String, Object>:
[source,groovy]
// in a TypeCheckingDSL extension:
Object run() {
boolean strict = options?.strict ?: false
// ...
}
For precompiled Java extensions (subclasses of TypeCheckingExtension or AbstractTypeCheckingExtension),
options are accessible via the getOptions() method.
The parameterized syntax also works when configuring extensions programmatically:
[source,groovy]
new ASTTransformationCustomizer(TypeChecked, extensions: ['my.Extension(debug: true)'])
=== A DSL for type checking
The idea behind type checking extensions is to use a DSL to extend the
type checker capabilities. This DSL allows you to hook into the
compilation process, more specifically the type checking phase, using an
"event-driven" API. For example, when the type checker enters a method
body, it throws aย beforeVisitMethod event that the extension can react to:
[source,groovy]
beforeVisitMethod { methodNode -> println "Entering ${methodNode.name}" }
Imagine that you have this rover DSL at hand. A user would write:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_script,indent=0]
If you have a class defined as such:
[source,groovy]
include::../test/typing/Robot.groovy[tags=example_robot_classdef,indent=0]
The script can be type checked before being executed using the following
script:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_setup,indent=0]
<1> a compiler configuration adds the @TypeChecked annotation to all classes
<2> use the configuration in a GroovyShell
<3> so that scripts compiled using the shell are compiled with @TypeChecked without the user having to add it explicitly
Using the compiler configuration above, we can applyย @TypeChecked
transparently to the script. In that case, it will fail at compile
time:
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_expected_err,indent=0]
Now, we will slightly update the configuration to include the
``extensions'' parameter:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_fixed_conf,indent=0]
Then add the following to your classpath:
[source,groovy] .robotextension.groovy
include::../test-resources/robotextension.groovy[tags=example_robot_extension,indent=0]
Here, weโre telling the compiler that if an unresolved variable is found
and that the name of the variable isย robot, then we can make sure that the type of this
variable is Robot.
[[Typecheckingextensions-TheAPI]]
=== Type checking extensions API
[[Typecheckingextensions-AST]]
==== AST
The type checking API is a low level API, dealing with the Abstract
Syntax Tree. You will have to know your AST well to develop extensions,
even if the DSL makes it much easier than just dealing with AST code
from plain Java or Groovy.
[[Typecheckingextensions-Events]]
==== Events
The type checker sends the following events, to which an extension
script can react:
[[event-setup]]
[cols="1,3a",width="100%"]
|===
| Event name
| setup
| Called When
| Called after the type checker finished initialization
| Arguments
| none
| Usage
|
[source,groovy]
include::../test-resources/setup.groovy[tags=event,indent=0]
Can be used to perform setup of your extension
|===
[[event-finish]]
[cols="1,3a",width="100%"]
|===
| Event name
| finish
| Called When
| Called after the type checker completed type checking
| Arguments
| none
| Usage
|
[source,groovy]
include::../test-resources/finish.groovy[tags=event,indent=0]
Can be used to perform additional checks after the type checker has finished its job.
|===
[[event-unresolvedVariable]]
[cols="1,3a",width="100%"]
|===
| Event name
| unresolvedVariable
| Called When
| Called when the type checker finds an
unresolved variable
| Arguments
| VariableExpression vexp
| Usage
|
[source,groovy]
include::../test-resources/unresolvedvariable.groovy[tags=event,indent=0]
Allows the developer to help the type checker with user-injected variables.
|===
[[event-unresolvedProperty]]
[cols="1,3a",width="100%"]
|===
| Event name
| unresolvedProperty
| Called When
| Called when the type checker cannot find
a property on the receiver
| Arguments
| PropertyExpression pexp
| Usage
|
[source,groovy]
include::../test-resources/unresolvedproperty.groovy[tags=event,indent=0]
Allows the developer to handle "dynamic" properties
|===
[[event-unresolvedAttribute]]
[cols="1,3a",width="100%"]
|===
| Event name
| unresolvedAttribute
| Called When
| Called when the type checker cannot
find an attribute on the receiver
| Arguments
| AttributeExpression aexp
| Usage
|
[source,groovy]
include::../test-resources/unresolvedattribute.groovy[tags=event,indent=0]
Allows the developer to handle missing attributes
|===
[[event-beforeMethodCall]]
[cols="1,3a",width="100%"]
|===
| Event name
| beforeMethodCall
| Called When
| Called before the type checker starts type
checking a method call
| Arguments
| MethodCall call
| Usage
|
[source,groovy]
include::../test-resources/beforemethodcall.groovy[tags=event,indent=0]
Allows you to intercept method calls before the
type checker performs its own checks. This is useful if you want to
replace the default type checking with a custom one for a limited scope.
In that case, you must set the handled flag to true, so that the type
checker skips its own checks.
|===
[[event-afterMethodCall]]
[cols="1,3a",width="100%"]
|===
| Event name
| afterMethodCall
| Called When
| Called once the type checker has finished
type checking a method call
| Arguments
| MethodCall call
| Usage
|
[source,groovy]
include::../test-resources/aftermethodcall.groovy[tags=event,indent=0]
Allow you to perform additional checks after the type
checker has done its own checks. This is in particular useful if you
want to perform the standard type checking tests but also want to ensure
additional type safety, for example checking the arguments against each
other.Note that afterMethodCall is called even if you didbeforeMethodCall and set the handled flag to true.
|===
[[event-onMethodSelection]]
[cols="1,3a",width="100%"]
|===
| Event name
| onMethodSelection
| Called When
| Called by the type checker when it finds
a method appropriate for a method call
| Arguments
| Expression expr, MethodNode node
| Usage
|
[source,groovy]
include::../test-resources/onmethodselection.groovy[tags=event,indent=0]
The type checker works by inferring
argument types of a method call, then chooses a target method. If it
finds one that corresponds, then it triggers this event. It is for
example interesting if you want to react on a specific method call, such
as entering the scope of a method that takes a closure as argument (as
in builders).Please note that this event may be thrown for various types
of expressions, not only method calls (binary expressions for example).
|===
[[event-methodNotFound]]
[cols="1,3a",width="100%"]
|===
| Event name
| methodNotFound
| Called When
| Called by the type checker when it fails to
find an appropriate method for a method call
| Arguments
| ClassNode receiver, String name, ArgumentListExpression argList, ClassNode[] argTypes,MethodCall call
| Usage
|
[source,groovy]
include::../test-resources/methodnotfound.groovy[tags=event,indent=0]
Unlike onMethodSelection, this event is
sent when the type checker cannot find a target method for a method call
(instance or static). It gives you the chance to intercept the error
before it is sent to the user, but also set the target method.For this,
you need to return a list of MethodNode. In most situations, you would
either return: an empty list, meaning that you didnโt find a
corresponding method, a list with exactly one element, saying that thereโs
no doubt about the target methodIf you return more than one MethodNode,
then the compiler would throw an error to the user stating that the
method call is ambiguous, listing the possible methods.For convenience,
if you want to return only one method, you are allowed to return it
directly instead of wrapping it into a list.
|===
[[event-beforeVisitMethod]]
[cols="1,3a",width="100%"]
|===
| Event name
| beforeVisitMethod
| Called When
| Called by the type checker before type
checking a method body
| Arguments
| MethodNode node
| Usage
|
[source,groovy]
include::../test-resources/beforevisitmethod.groovy[tags=event,indent=0]
The type checker will call this method before
starting to type check a method body. If you want, for example, to
perform type checking by yourself instead of letting the type checker do
it, you have to set the handled flag to true. This event can also be used
to help define the scope of your extension (for example, applying it
only if you are inside method foo).
|===
[[event-afterVisitMethod]]
[cols="1,3a",width="100%"]
|===
| Event name
| afterVisitMethod
| Called When
| Called by the type checker after type
checking a method body
| Arguments
| MethodNode node
| Usage
|
[source,groovy]
include::../test-resources/aftervisitmethod.groovy[tags=event,indent=0]
Gives you the opportunity to perform additional
checks after a method body is visited by the type checker. This is
useful if you collect information, for example, and want to perform
additional checks once everything has been collected.
|===
[[event-beforeVisitClass]]
[cols="1,3a",width="100%"]
|===
| Event name
| beforeVisitClass
| Called When
| Called by the type checker before type checking a class
| Arguments
| ClassNode node
| Usage
|
[source,groovy]
include::../test-resources/beforevisitclass.groovy[tags=event,indent=0]
If a class is type checked, then
before visiting the class, this event will be sent. It is also the case
for inner classes defined inside a class annotated with @TypeChecked. It
can help you define the scope of your extension, or you can even totally
replace the visit of the type checker with a custom type checking
implementation. For that, you would have to set the handled flag totrue.ย
|===
[[event-afterVisitClass]]
[cols="1,3a",width="100%"]
|===
| Event name
| afterVisitClass
| Called When
| Called by the type checker after having finished the visit of a type checked class
| Arguments
| ClassNode node
| Usage
|
[source,groovy]
include::../test-resources/aftervisitclass.groovy[tags=event,indent=0]
Called
for every class being type checked after the type checker finished its
work. This includes classes annotated with @TypeChecked and any
inner/anonymous class defined in the same class with is not skipped.
|===
[[event-incompatibleAssignment]]
[cols="1,3a",width="100%"]
|===
| Event name
| incompatibleAssignment
| Called When
| Called when the type checker thinks
that an assignment is incorrect, meaning that the right-hand side of an
assignment is incompatible with the left-hand side
| Arguments
| ClassNode lhsType, ClassNode rhsType,ย Expression assignment
| Usage
|
[source,groovy]
include::../test-resources/incompatibleassignment.groovy[tags=event,indent=0]
Gives the
developer the ability to handle incorrect assignments. This is for
example useful if a class overrides setProperty, because in that case it
is possible that assigning a variable of one type to a property of
another type is handled through that runtime mechanism. In that case, you
can help the type checker just by telling it that the assignment is
valid (using handled set to true).
|===
[[event-incompatibleReturnType]]
[cols="1,3a",width="100%"]
|===
| Event name
| incompatibleReturnType
| Called When
| Called when the type checker thinks that a return value is incompatibe with
the return type of the enclosing closure or method
| Arguments
| ReturnStatement statement, ClassNode valueType
| Usage
|
[source,groovy]
include::../test-resources/incompatiblereturntype.groovy[tags=event,indent=0]
Gives the developer the ability to handle incorrect return values. This is for
example useful when the return value will undergo implicit conversion or the
enclosing closure's target type is difficult to infer properly. In that case,
you can help the type checker just by telling it that the assignment is valid
(by setting the handled property).
|===
[[event-ambiguousMethods]]
[cols="1,3a",width="100%"]
|===
| Event name
| ambiguousMethods
| Called When
| Called when the type checker cannot choose between several candidate methods
| Arguments
| List
| Usage
|
[source,groovy]
include::../test-resources/ambiguousmethods.groovy[tags=event,indent=0]
Gives the
developer the ability to handle incorrect assignments. This is for
example useful if a class overrides setProperty, because in that case it
is possible that assigning a variable of one type to a property of
another type is handled through that runtime mechanism. In that case, you
can help the type checker just by telling it that the assignment is
valid (using handled set to true).
|===
Of course, an extension script may consist of several blocks, and you
can have multiple blocks responding to the same event. This makes the
DSL look nicer and easier to write. However, reacting to events is far
from sufficient. If you know you can react to events, you also need to
deal with the errors, which implies several helper methods that will
make things easier.
[[Typecheckingextensions-Workingwithextensions]]
=== Working with extensions
[[Typecheckingextensions-Supportclasses]]
==== Support classes
The DSL relies on a support class calledย gapi:org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport[] .
This class itself extendsย gapi:org.codehaus.groovy.transform.stc.TypeCheckingExtension[] .
Those two classes define a number of helper methods that will make working
with the AST easier, especially regarding type checking. One interesting
thing to know is that youย have access to the type checker. This means
that you can programmatically call methods of the type checker,
including those that allow you to throw compilation errors.
The extension script delegates to theย gapi:org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport[] class,
meaning that you have direct access to the following variables:
- context: the type checker context, of typeย gapi:org.codehaus.groovy.transform.stc.TypeCheckingContext[]
- typeCheckingVisitor: the type checker itself, aย gapi:org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor[] instance
- generatedMethods: a list of "generated methods", which is in fact the list of "dummy" methods that you can create
inside a type checking extension using theยnewMethodcalls
The type checking context contains a lot of information that is useful
in context for the type checker. For example, the current stack of
enclosing method calls, binary expressions, closures, โฆ This information
is in particular important if you have to know where you are when an
error occurs and that you want to handle it.
In addition to facilities provided by GroovyTypeCheckingExtensionSupport and StaticTypeCheckingVisitor,
a type-checking DSL script imports static members from gapi:org.codehaus.groovy.ast.ClassHelper[] and
gapi:org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport[] granting access to common types viaOBJECT_TYPE, STRING_TYPE, THROWABLE_TYPE, etc. and checks like missesGenericsTypes(ClassNode),isClassClassNodeWrappingConcreteType(ClassNode) and so on.
[[Typecheckingextensions-Classnodes]]
==== Class nodes
Handling class nodes is something that needs particular attention when
you work with a type checking extension. Compilation works with an
abstract syntax tree (AST) and the tree may not be complete when you are
type checking a class. This also means that when you refer to types, you
must not use class literals such asย String orย HashSet, but to class
nodes representing those types. This requires a certain level of
abstraction and understanding how Groovy deals with class nodes. To make
things easier, Groovy supplies several helper methods to deal with class
nodes. For example, if you want to say "the type forย String", you can
write:
[source,groovy]
include::../test-resources/selfcheck.groovy[tags=classnodefor,indent=0]
You would also note that there is a variant ofย classNodeFor that takes
aย String as an argument, instead of aย Class. In general, you
shouldย not use that one, because it would create a class node for
which the name is String, but without any method, any property, โฆ
defined on it. The first version returns a class node that isย resolved
but the second one returns one that isย not. So the latter should be
reserved for very special cases.
The second problem that you might encounter is referencing a type which
is not yet compiled. This may happen more often than you think. For
example, when you compile a set of files together. In that case, if you
want to say "that variable is of type Foo" but Foo is not yet
compiled, you can still refer to the Foo class node
usingย lookupClassNodeFor:
[source,groovy]
include::../test-resources/selfcheck.groovy[tags=lookupclassnodefor,indent=0]
[[Typecheckingextensions-Helpingthetypechecker]]
==== Helping the type checker
Say that you know that variableย foo is of typeย Foo and you want to
tell the type checker about it. Then you can use theย storeType method,
which takes two arguments: the first one is the node for which you want
to store the type and the second one is the type of the node. If you
look at the implementation ofย storeType, you would see that it
delegates to the type checker equivalent method, which itself does a lot
of work to store node metadata. You would also see that storing the type
is not limited to variables: you can set the type of any expression.
Likewise, getting the type of an AST node is just a matter of
callingย getType on that node. This would in general be what you want,
but thereโs something that you must understand:
getTypereturns theย inferred type of an expression. This means
that it will not return, for a variable declared of typeยObjectthe
class node forยObject, but the inferred type of this variableย at this
point of the code (flow typing)- if you want to access the origin type of a variable (or
field/parameter), then you must call the appropriate method on the AST
node
[[Typecheckingextensions-Throwinganerror]]
==== Throwing an error
To throw a type checking error, you only have to call theaddStaticTypeError method which takes two arguments:
- aย message which is a string that will be displayed to the end user
- an AST node responsible for the error. Itโs better to provide the best
suiting AST node because it will be used to retrieve the line and column
numbers
[[Typecheckingextensions-isXXXExpression]]
==== isXXXExpression
It is often required to know the type of an AST node. For readability,
the DSL provides a special isXXXExpression method that will delegate tox instance of XXXExpression. For example, instead of writing:
[source,groovy]
if (node instanceof BinaryExpression) { ... }
you can just write:
[source,groovy]
if (isBinaryExpression(node)) { ... }
[[Typecheckingextensions-Virtualmethods]]
==== Virtual methods
When you perform type checking of dynamic code, you may often face the
case when you know that a method call is valid but there is no "real"
method behind it. As an example, take the Grails dynamic finders. You
can have a method call consisting of a method namedย findByName(โฆ). As
thereโs noย findByName method defined in the bean, the type checker
would complain. Yet, you would know that this method wouldnโt fail at
runtime, and you can even tell what is the return type of this method.
For this case, the DSL supports two special constructs that consist of
phantom methods. This means that you will return a method node that
doesnโt really exist but is defined in the context of type checking.
Three methods exist:
newMethod(String name, Class returnType)newMethod(String name, ClassNode returnType)newMethod(String name, Callable<ClassNode> return Type)
All three variants do the same: they create a new method node which name
is the supplied name and define the return type of this method.
Moreover, the type checker would add those methods in
theย generatedMethods list (seeย isGenerated below). The reason why we
only set a name and a return type is that it is only what you need in
90% of the cases. For example, in theย findByName example upper, the
only thing you need to know is thatย findByName wouldnโt fail at
runtime, and that it returns a domain class. Theย Callable version of
return type is interesting because it defers the computation of the
return type when the type checker actually needs it. This is interesting
because in some circumstances, you may not know the actual return type
when the type checker demands it, so you can use a closure that will be
called each timeย getReturnType is called by the type checker on this
method node. If you combine this with deferred checks, you can achieve
pretty complex type checking including handling of forward references.
[source,groovy]
include::../test-resources/newmethod.groovy[tags=newmethod,indent=0]
Should you need more than the name and return type, you can always
create a newย MethodNode by yourself.
[[Typecheckingextensions-Scoping]]
==== Scoping
Scoping is very important in DSL type checking and is one of the reasons
why we couldnโt use aย pointcut based approach to DSL type checking.
Basically, you must be able to define very precisely when your extension
applies and when it does not. Moreover, you must be able to handle
situations that a regular type checker would not be able to handle, such
as forward references:
[source,groovy]
point a(1,1) line a,b // b is referenced afterwards! point b(5,2)
Say for example that you want to handle a builder:
[source,groovy]
builder.foo { bar baz(bar) }
Your extension, then, should only be active once youโve entered
theย foo method, and inactive outside this scope. But you could have
complex situations like multiple builders in the same file or embedded
builders (builders in builders). While you should not try to fix all
this from start (you must accept limitations to type checking), the type
checker does offer a nice mechanism to handle this: a scoping stack,
using theย newScope andย scopeExit methods.
newScopecreates a new scope and puts it on top of the stackscopeExitspops a scope from the stack
A scope consists of:
- a parent scope
- a map of custom data
If you want to look at the implementation, itโs simply a LinkedHashMap
(gapi:org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport.TypeCheckingScope[]),
but itโs quite powerful. For example, you can use such a scope to store
a list of closures to be executed when you exit the scope. This is how
you would handle forward references:ย
[source,groovy]
include::../test-resources/scoping.groovy[tags=newscope,indent=0] include::../test-resources/scoping.groovy[tags=scopeexit,indent=0]
That is to say, that if at some point you are not able to determine the
type of an expression, or that you are not able to check at this point
that an assignment is valid or not, you can still make the check laterโฆ
This is a very powerful feature. Now,ย newScope andย scopeExit
provide some interesting syntactic sugar:
[source,groovy]
include::../test-resources/scoping_alt.groovy[tags=newscope,indent=0]
At anytime in the DSL, you can access the current scope
usingย getCurrentScope() or more simplyย currentScope:
[source,groovy]
include::../test-resources/scoping_alt.groovy[tags=currentscope,indent=0]
The general schema would then be:
- determine a pointcut where you push a new scope on stack and
initialize custom variables within this scope - using the various events, you can use the information stored in your
custom scope to perform checks, defer checks,โฆ - determine a pointcut where you exit the scope, callย
scopeExit
and eventually perform additional checks
[[Typecheckingextensions-Otherusefulmethods]]
==== Other useful methods
For the complete list of helper methods, please refer to
theย gapi:org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport[] andย
gapi:org.codehaus.groovy.transform.stc.TypeCheckingExtension[] classes.ย However,
take special attention to those methods:
isDynamic: takes a VariableExpression as argument and returns true
if the variable is a DynamicExpression, which means, in a script, that
it wasnโt defined using a type ordef.isGenerated: takes a MethodNode as an argument and tells if the
method is one that was generated by the type checker extension using
theยnewMethodmethodisAnnotatedBy: takes an AST node and a Class (or ClassNode), and
tells if the node is annotated with this class. For example:isAnnotatedBy(node, NotNull)getTargetMethod: takes a method call as argument and returns
theยMethodNodethat the type checker has determined for itdelegatesTo: emulates the behaviour of theย@DelegatesTo
annotation. It allows you to tell that the argument will delegate to a
specific type (you can also specify the delegation strategy)
== Advanced type checking extensions
=== Precompiled type checking extensions
All the examples above use type checking scripts. They are found in source form in classpath, meaning that:
- a Groovy source file, corresponding to the type checking extension, is available on compilation classpath
- this file is compiled by the Groovy compiler for each source unit being compiled (often, a source unit corresponds
to a single file)
It is a very convenient way to develop type checking extensions, however it implies a slower compilation phase, because
of the compilation of the extension itself for each file being compiled. For those reasons, it can be practical to rely
on a precompiled extension. You have two options to do this:
- write the extension in Groovy, compile it, then use a reference to the extension class instead of the source
- write the extension in Java, compile it, then use a reference to the extension class
Writing a type checking extension in Groovy is the easiest path. Basically, the idea is that the type checking extension
script becomes the body of the main method of a type checking extension class, as illustrated here:
[source,groovy]
include::../test/typing/PrecompiledExtension.groovy[tags=precompiled_groovy_extension,indent=0]
<1> extending the TypeCheckingDSL class is the easiest
<2> then the extension code needs to go inside the run method
<3> and you can use the very same events as an extension written in source form
Setting up the extension is very similar to using a source form extension:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=setup_precompiled,indent=0]
The difference is that instead of using a path in classpath, you just specify the fully qualified class name of the
precompiled extension.
In case you really want to write an extension in Java, then you will not benefit from the type checking extension DSL.
The extension above can be rewritten in Java this way:
[source,java]
include::../test/typing/PrecompiledJavaExtension.java[tags=precompiled_java_extension,indent=0]
<1> extend the AbstractTypeCheckingExtension class
<2> then override the handleXXX methods as required
=== Using @Grab in a type checking extension
It is totally possible to use the @Grab annotation in a type checking extension.
This means you can include libraries that would only be
available at compile time. In that case, you must understand that you
would increase the time of compilation significantly (at least, the
first time it grabs the dependencies).
=== Sharing or packaging type checking extensions
A type checking extension is just a script that need to be on classpath. As such,
you can share it as is, or bundle it in a jar file that would be added to classpath.
=== Global type checking extensions
While you can configure the compiler to transparently add type checking extensions to your
script, there is currently no way to apply an extension transparently just by having it on
classpath.
=== Type checking extensions and @CompileStatic
Type checking extensions are used with @TypeChecked but can also be used with @CompileStatic. However, you must
be aware that:
- a type checking extension used with
@CompileStaticwill in general not be sufficient to let the compiler know how
to generate statically compilable code from "unsafe" code - it is possible to use a type checking extension with
@CompileStaticjust to enhance type checking, that is to say
introduce more compilation errors, without actually dealing with dynamic code
Let's explain the first point, which is that even if you use an extension, the compiler will not know how to compile
your code statically: technically, even if you tell the type checker what is the type of a dynamic
variable, for example, it would not know how to compile it. Is it getBinding('foo'), getProperty('foo'),delegate.getFoo(),โฆ? There's absolutely no direct way to tell the static compiler how to compile such
code even if you use a type checking extension (that would, again, only give hints about the type).
One possible solution for this particular example is to instruct the compiler to use <<mixed-mode,mixed mode compilation>>.
The more advanced one is to use <<ast-xform-as-extension,AST transformations during type checking>> but it is far more
complex.
Type checking extensions allow you to help the type checker where it
fails, but it also allows you to fail where it doesnโt. In that context,
it makes sense to support extensions forย @CompileStatic too. Imagine
an extension that is capable of type checking SQL queries. In that case,
the extension would be valid in both dynamic and static context, because
without the extension, the code would still pass.
[[mixed-mode]]
=== Mixed mode compilation
In the previous section, we highlighted the fact that you can activate type checking extensions with@CompileStatic. In that context, the type checker would not complain anymore about some unresolved variables or
unknown method calls, but it would still wouldn't know how to compile them statically.
Mixed mode compilation offers a third way, which is to instruct the compiler that whenever an unresolved variable
or method call is found, then it should fall back to a dynamic mode. This is possible thanks to type checking extensions
and a special makeDynamic call.
To illustrate this, let's come back to the Robot example:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_script,indent=0]
And let's try to activate our type checking extension using @CompileStatic instead of @TypeChecked:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_setup_compilestatic,indent=0]
<1> Apply @CompileStatic transparently
<2> Activate the type checking extension
The script will run fine because the static compiler is told about the type of the robot variable, so it is capable
of making a direct call to move. But before that, how did the compiler know how to get the robot variable? In fact
by default, in a type checking extension, setting handled=true on an unresolved variable will automatically trigger
a dynamic resolution, so in this case you don't have anything special to make the compiler use a mixed mode. However,
let's slightly update our example, starting from the robot script:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_script_direct,indent=0]
Here you can notice that there is no reference to robot anymore. Our extension will not help then because we will not
be able to instruct the compiler that move is done on a Robot instance. This example of code can be executed in a
totally dynamic way thanks to the help of a gapi:groovy.util.DelegatingScript[]:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_setup_dynamic,indent=0]
<1> we configure the compiler to use a DelegatingScript as the base class
<2> the script source needs to be parsed and will return an instance of DelegatingScript
<3> we can then call setDelegate to use a Robot as the delegate of the script
<4> then execute the script. move will be directly executed on the delegate
If we want this to pass with @CompileStatic, we have to use a type checking extension, so let's update our configuration:
[source,groovy]
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=example_robot_setup_compilestatic2,indent=0]
<1> apply @CompileStatic transparently
<2> use an alternate type checking extension meant to recognize the call to move
Then in the previous section we have learnt how to deal with unrecognized method calls, so we are able to write this
extension:
[source,groovy] .robotextension2.groovy
include::../test-resources/robotextension2.groovy[tags=example_robot_extension,indent=0]
<1> if the call is a method call (not a static method call)
<2> that this call is made on "implicit this" (no explicit this.)
<3> that the method being called is move
<4> and that the call is done with a single argument
<5> and that argument is of type int
<6> then tell the type checker that the call is valid
<7> and that the return type of the call is Robot
If you try to execute this code, then you could be surprised that it actually fails at runtime:
include::../test/typing/TypeCheckingExtensionSpecTest.groovy[tags=robot_runtime_error_cs,indent=0]
The reason is very simple: while the type checking extension is sufficient for @TypeChecked, which does not involve
static compilation, it is not enough for @CompileStatic which requires additional information. In this case, you told
the compiler that the method existed, but you didn't explain to it what method it is in reality, and what is the
receiver of the message (the delegate).
Fixing this is very easy and just implies replacing the newMethod call with something else:
[source,groovy] .robotextension3.groovy
include::../test-resources/robotextension3.groovy[tags=example_robot_extension,indent=0]
<1> tell the compiler that the call should be made dynamic
The makeDynamic call does 3 things:
- it returns a virtual method just like
newMethod - automatically sets the
handledflag totruefor you - but also marks the
callto be done dynamically
So when it comes time for the compiler to generate bytecode for the call to move, since it is now marked as a dynamic call,
it falls back to the dynamic compiler and lets it handle the call. And since the extension tells us that the return
type of the dynamic call is a Robot, subsequent calls will be done statically!
Some might wonder why the static compiler doesn't do this by default without an extension. It is a design decision.
If the code is statically compiled, we normally want compile-time type safety and the best performance.
If unrecognized variables/method calls were automatically made dynamic, you'd partially lose compile-time type safety.
Typos and some scenarios of incorrect typing would no longer be caught at compile time!
In short, if you want to have mixed mode compilation, it has to be explicit, through a type checking extension, so
that the compiler, and the designer of the DSL, are totally aware of exactly when typing rules are relaxed.
makeDynamic can be used on 3 kinds of AST nodes:
- a method node (
MethodNode) - a variable (
VariableExpression) - a property expression (
PropertyExpression)
If that is not enough, then it means that static compilation cannot be done directly and that you have to rely on AST
transformations.
[[ast-xform-as-extension]]
=== Transforming the AST in an extension
Type checking extensions look very attractive from an AST transformation design point of view: extensions have access
to context like inferred types, which is often nice to have. And an extension has a direct access to the abstract
syntax tree. Since you have access to the AST, there is nothing in theory that prevents
you from modifying the AST. However, we do not recommend you to do so, unless you are an advanced AST transformation
designer and well aware of the compiler internals:
- First of all, you would explicitly break the contract of type checking, which is to annotate,
and only annotate the AST. Type checking should not modify the AST tree because you wouldnโt be able to
guarantee anymore that code without the @TypeChecked annotation
behaves the same without the annotation. - If your extension is meant to work with @CompileStatic, then you can modify the AST because
this is indeed what @CompileStatic will eventually do. Static compilation doesnโt guarantee the same semantics at
dynamic Groovy so there is effectively a difference between code compiled with @CompileStatic and code compiled
with @TypeChecked. Itโs up to you to choose whatever strategy you want to update the AST, but probably
using an AST transformation that runs before type checking is easier. - if you cannot rely on a transformation that kicks in before the type checker, then you must be very careful
WARNING: The type checking phase is the last phase running in the compiler before bytecode generation. All other AST
transformations run before that and the compiler does a very good job at "fixing" incorrect AST generated before the
type checking phase. As soon as you perform a transformation during type checking, for example directly in a type
checking extension, then you have to do all this work of generating a 100% compiler compliant abstract syntax tree by
yourself, which can easily become complex. That's why we do not recommend to go that way if you are beginning with
type checking extensions and AST transformations.
=== Examples
Examples of real life type checking extensions are easy to find. You can download the source code for Groovy and
take a look at the
https://github.com/apache/groovy/blob/master/src/test/groovy/groovy/transform/stc/TypeCheckingExtensionsTest.groovy[TypeCheckingExtensionsTest]
class which is linked to
https://github.com/apache/groovy/tree/master/src/test-resources/groovy/transform/stc[various extension scripts].
An example of a complex type checking extension can be found in the link:templating.html#_the_markuptemplateengine[Markup Template Engine]
source code: this template engine relies on a type checking extension and AST transformations to transform templates into
fully statically compiled code. Sources for this can be found
https://github.com/apache/groovy/tree/master/subprojects/groovy-templates/src/main/groovy/groovy/text/markup[here].
Src Spec Doc Working With Arrays (`src/spec/doc/_working-with-arrays.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Working with arrays
:gdk: http://www.groovy-lang.org/gdk.html[Groovy development kit]
Groovy provides array support based on Java arrays with several extensions found in the {gdk}. The overall
intention is that whether you are using an array or a collection, the code for working with the aggregate remains the same.
[[Arrays]]
== Arrays
=== Array literals
You can create arrays as follows. Notice that [] is also used as the empty array
expression when given an explicit array type.
[source,groovy]
include::../test/gdk/WorkingWithArraysTest.groovy[tags=array_literals,indent=0]
=== Iterating on an array
Iterating on elements of an array is usually done calling the each and eachWithIndex methods, which execute code on each
item of an array:
[source,groovy]
include::../test/gdk/WorkingWithArraysTest.groovy[tags=array_each,indent=0]
=== Other useful methods
There are numerous other GDK methods for working with arrays.
Just be a little careful to read the documentation.
For collections, there are some mutating methods which alter
the original collection and others which produce new collections,
leaving the original untouched.
Since arrays are of a fixed size, we wouldn't expect mutating
methods which altered an array's size. Often instead, such methods return
collections. Here are some interesting array GDK methods:
[source,groovy]
include::../test/gdk/WorkingWithArraysTest.groovy[tags=array_gdk,indent=0]
Src Spec Doc Working With Collections (`src/spec/doc/_working-with-collections.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Working with collections
:gdk: http://www.groovy-lang.org/gdk.html[Groovy development kit]
:java-util-list: http://docs.oracle.com/javase/8/docs/api/java/util/List.html[java.util.List]
:java-lang-comparable: java.lang.Comparable
Groovy provides native support for various collection types, including <<Collections-Lists,lists>>,
<<Collections-Maps,maps>> or <<Collections-Ranges,ranges>>. Most of those are based on the Java
collection types and decorated with additional methods found in the {gdk}.
[[Collections-Lists]]
== Lists
=== List literals
You can create lists as follows. Notice that [] is the empty list
expression.
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_literals,indent=0]
Each list expression creates an implementation of {java-util-list}.
Of course lists can be used as a source to construct another list:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_construct,indent=0]
A list is an ordered collection of objects:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_usecases,indent=0]
=== List as a boolean expression
Lists can be evaluated as a boolean value:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_to_bool,indent=0]
=== Iterating on a list
Iterating on elements of a list is usually done calling the each and eachWithIndex methods, which execute code on each
item of a list:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_each,indent=0]
In addition to iterating, it is often useful to create a new list by transforming each of its elements into
something else. This operation, often called mapping, is done in Groovy thanks to the collect method:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collect,indent=0]
When each element maps to a collection of results, collectMany transforms and flattens in a single step.
It is the idiomatic (and more efficient) alternative to collect { ... }.flatten():
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collectMany,indent=0]
To build a Map from a list, collectEntries collects a key/value pair for each element:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collectEntries,indent=0]
When you need each element together with its position, withIndex provides a more composable alternative
to eachWithIndex, while indexed returns a map from index to element:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_withIndex,indent=0]
=== Manipulating lists
[[List-Filtering]]
==== Filtering and searching
The {gdk} contains a lot of methods on collections that enhance
the standard collections with pragmatic methods, some of which are illustrated here:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk1,indent=0]
And here is idiomatic Groovy code for finding the maximum and minimum in a collection:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk2,indent=0]
In addition to closures, you can use a Comparator to define the comparison criteria:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_comparator,indent=0]
==== Adding or removing elements
We can use [] to assign a new empty list and << to append items to it:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_leftshift,indent=0]
We can add to a list in many ways:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_add,indent=0]
It is however important that the + operator on a list is not mutating. Compared to <<, it will create a new
list, which is often not what you want and can lead to performance issues.
The {gdk} also contains methods allowing you to easily remove elements from a list by value:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk3,indent=0]
It is also possible to remove an element by passing its index to the remove method, in which case the list is mutated:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk_remove_index,indent=0]
In case you only want to remove the first element having the same value in a list, instead of removing all
elements, you can call the remove method passing the value:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk5,indent=0]
As you can see, there are two remove methods available. One that takes an integer and removes an element
by its index, and another that will remove the first element that matches the passed value. So what should we
do when we have a list of integers? In this case, you may wish to use removeAt to remove an element by its
index, and removeElement to remove the first element that matches a value.
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk4,indent=0]
Of course, removeAt and removeElement will work with lists of any type.
Additionally, removing all the elements in a list can be done by calling the clear method:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk6,indent=0]
==== Set operations
The {gdk} also includes methods making it easy to reason on sets:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk7,indent=0]
==== Sorting
Working with collections often implies sorting. Groovy offers a variety of options to sort lists,
from using closures to comparators, as in the following examples:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_sort,indent=0]
==== Removing duplicates
The unique method removes duplicate elements in place, while toUnique returns a new list, leaving the
original untouched. Both accept an optional closure or comparator to define what "the same" means:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_unique,indent=0]
==== Taking and dropping elements
Groovy can select or discard elements from either end of a list, either by count (take, drop, takeRight,dropRight) or by predicate (takeWhile, dropWhile). None of these methods mutate the original list:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_take_drop,indent=0]
==== Duplicating elements
The {gdk} also takes advantage of operator overloading to provide methods allowing duplication of elements
of a list:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_multiply,indent=0]
[[Collections-Maps]]
== Maps
=== Map literals
In Groovy, maps (also known as associative arrays) can be created using the map literal syntax: [:]:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_literal,indent=0]
Map keys are strings by default: [a:1] is equivalent to ['a':1]. This can be confusing if you define a variable
named a and that you want the value of a to be the key in your map. If this is the case, then you must escape
the key by adding parenthesis, like in the following example:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_literal_gotcha,indent=0]
In addition to map literals, it is possible, to get a new copy of a map, to clone it:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_construct,indent=0]
The resulting map is a shallow copy of the original one, as illustrated in the previous example.
=== Map property notation
Maps also act like beans so you can use the property notation to get/set
items inside the Map as long as the keys are strings which are valid
Groovy identifiers:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_property,indent=0]
Note: by design map.foo will always look for the key foo in the map. This
means foo.class will return null on a map that doesn't contain the class key. Should you really want to know
the class, then you must use getClass():
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_property_gotcha,indent=0]
[[Maps-PropertyVsSubscript]]
==== Property access versus subscript and get
Because map.k reads the key k (rather than a bean property), property access, the subscript operator
and get can diverge once a key collides with a real property. Every cell in the tables below is included
from a passing assertion in WorkingWithCollectionsTest, so they always reflect the current runtime.
For a plain map there are only entries, so all four forms agree:
[cols="2,^1a,^1a,^1a,^1a",options="header"]
.Plain map ([foo:'bar'], [class:'C'], [:])
|===
| key | m.k | m.k (@CompileStatic) | m['k'] | m.get('k')
| ordinary ('foo')
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_ordinary,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_ordinary,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_ordinary,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_ordinary,indent=0]
| meta-name present ('class')
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_present,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_present,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_present,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_present,indent=0]
| meta-name absent ('class')
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_absent,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_absent,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_absent,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t1_absent,indent=0]
|===
A map subclass can declare real properties. Then m.k reads the property (identically under dynamic and@CompileStatic compilation), while m['k'] and m.get('k') read the entry:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_class,indent=0]
then, after m.put('foo', 11) and m.put('baz', 33):
[cols="2,^1a,^1a,^1a,^1a",options="header"]
.Map subclass with declared properties foo, bar
|===
| key | m.k | m.k (@CompileStatic) | m['k'] | m.get('k')
| 'foo' (property=1, entry=11)
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_foo_prop,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_foo_prop,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_foo_entry,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_foo_entry,indent=0]
| 'bar' (property=2, no entry)
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_bar_prop,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_bar_prop,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_bar_entry,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_bar_entry,indent=0]
| 'baz' (no property, entry=33)
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_baz,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_baz,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_baz,indent=0]
a|
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=t2_baz,indent=0]
|===
Two things to take away: after the addition of getAt(Map, String)/putAt(Map, String, V), m['k'] is
equivalent to m.get('k') in every row and both compilation modes; and m.k resolves to a declared
property when one exists (use ['k']/get to be sure of reading the entry).
The tables above all use an explicit receiver m. A separate gotcha applies to an unqualified name read
inside the body of a Map subclass -- a method, a with(map) { ... } block, or an anonymous-inner-class
initializer. There the implicit receiver is the map itself, so the name is a property read on this and
resolves to the entry, not to any binding in an enclosing scope. From Groovy 5, this self property takes
precedence over an enclosing-class field of the same name (see https://issues.apache.org/jira/browse/GROOVY-10985[GROOVY-10985]),
so such a reference silently yields null for a missing entry rather than reading the field:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tag=bare_self_shadow,indent=0]
Qualify the outer reference (Outer.NAME above) to read the enclosing field.
=== Iterating on maps
As usual in the {gdk}, idiomatic iteration on maps makes use of the each and eachWithIndex methods.
It's worth noting that maps created using the map literal notation are ordered, that is to say that if you iterate
on map entries, it is guaranteed that the entries will be returned in the same order they were added in the map.
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_iteration,indent=0]
=== Manipulating maps
==== Adding or removing elements
Adding an element to a map can be done either using the put method, the subscript operator or using putAll:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_add,indent=0]
Removing all the elements of a map can be done by calling the clear method:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk1,indent=0]
Maps generated using the map literal syntax are using the object equals and hashcode methods. This means that
you should never use an object which hash code is subject to change over time, or you wouldn't be able to get
the associated value back.
It is also worth noting that you should never use a GString as the key of a map, because the hash code of a GString
is not the same as the hash code of an equivalent String:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=gstring_gotcha,indent=0]
[[JN1035-Maps-Collectionviewsofamap]]
==== Keys, values and entries
We can inspect the keys, values, and entries in a view:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_views,indent=0]
Mutating values returned by the view (be it a map entry, a key or a value) is highly discouraged because success
of the operation directly depends on the type of the map being manipulated. In particular, Groovy relies on collections
from the JDK that in general make no guarantee that a collection can safely be manipulated through keySet, entrySet, orvalues.
==== Filtering and searching
The {gdk} contains filtering, searching and collecting methods similar to those found
for <<List-Filtering,lists>>:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk2,indent=0]
[[Maps-Grouping]]
==== Grouping
We can group a list into a map using some criteria:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk3,indent=0]
[[Maps-WithDefault]]
==== Maps with a default value
The withDefault method wraps a map so that reading a missing key returns (and stores) a value computed by
a closure. This is particularly handy for accumulating into per-key collections without having to pre-populate
them:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_withDefault,indent=0]
NOTE: Since Groovy 6, under static compilation, calling withDefault on a map with statically-known key/value
types automatically selects the type-checked variant: the wrapped map rejects ill-typed keys and values at
runtime instead of silently growing entries that violate the map's declared generics
(link:https://issues.apache.org/jira/browse/GROOVY-11807[GROOVY-11807]). Code that (usually inadvertently)
relied on the lenient behaviour can defer the upgrade with thegroovy.classtag.preemption.disable system property โ see the
link:core-semantics.html#classtag-annotation[@ClassTag documentation] for details. Dynamic code is
unaffected.
[[Collections-Ranges]]
== Ranges
Ranges allow you to create a list of sequential values. These can be
used as List since
http://docs.groovy-lang.org/latest/html/api/groovy/lang/Range.html[Range] extends
{java-util-list}.
Ranges defined with the .. notation are inclusive (that is the list
contains the from and to value).
Ranges defined with the ..< notation are half-open, they include the
first value but not the last value.
Ranges defined with the <.. notation are also half-open, they include the
last value but not the first value.
Ranges defined with the <..< notation are full-open, they do not include the
first value nor the last value.
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=intrange,indent=0]
Note that int ranges are implemented efficiently, creating a lightweight
Java object containing a from and to value.
Ranges can be used for any Java object which implements {java-lang-comparable}
for comparison and also have methods next() and previous() to return the
next / previous item in the range. For example, you can create a range of String elements:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=stringrange,indent=0]
You can iterate on a range using a for loop:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=range_for,indent=0]
but alternatively you can achieve the same effect in a more Groovy idiomatic style, by iterating a range
with each method:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=range_each,indent=0]
Ranges can be also used in the switch statement:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=range_switch,indent=0]
== Syntax enhancements for collections
=== GPath support
Thanks to the support of property notation for both lists and maps, Groovy provides syntactic sugar making it
really easy to deal with nested collections, as illustrated in the following examples:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=gpath_support_1,indent=0] include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=gpath_support_2,indent=0]
=== Spread operator
The spread operator can be used to "inline" a collection into another. It is syntactic sugar which often avoids calls
to putAll and facilitates the realization of one-liners:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=gpath_support_3,indent=0]
[[Collections-Gettingefficientwiththestar-dotoperator]]
=== The star-dot `*.' operator
The "star-dot" operator is a shortcut operator allowing you to call a method or a property on all elements of a
collection:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=stardot_1,indent=0]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=stardot_2,indent=0]
[[Collections-Slicingwiththesubscriptoperator]]
=== Slicing with the subscript operator
You can index into lists, arrays, maps using the subscript expression. It is interesting that strings
are considered as special kinds of collections in that context:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript,indent=0]
Notice that you can use ranges to extract part of a collection:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_2,indent=0]
The subscript operator can be used to update an existing collection (for collection type which are not immutable):
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_3,indent=0]
It is worth noting that negative indices are allowed, to extract more easily from the end of a collection:
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_4,indent=0]
You can use negative indices to count from the end of the List, array,
String etc.
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_4a,indent=0]
Eventually, if you use a backwards range (the starting index is greater than
the end index), then the answer is reversed.
[source,groovy]
include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_5,indent=0]
[[Collections-EnhancedCollectionMethods]]
== Enhanced Collection Methods
In addition to <<Collections-Lists,lists>>, <<Collections-Maps,maps>> or <<Collections-Ranges,ranges>>, Groovy offers
a lot of additional methods for filtering, collecting, grouping, counting, ... which are directly available on either
collections or more easily iterables.
In particular, we invite you to read the {gdk} API docs and specifically:
- methods added to
Iterablecan be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Iterable.html[here] - methods added to
Iteratorcan be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Iterator.html[here] - methods added to
Collectioncan be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Collection.html[here] - methods added to
Listcan be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/List.html[here] - methods added to
Mapcan be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Map.html[here]
Src Spec Doc Working With Io (`src/spec/doc/_working-with-io.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Working with IO
Groovy provides a number of
link:gdk.html[helper methods] for working
with I/O. While you could use standard Java code in Groovy to deal with those,
Groovy provides much more convenient ways to handle files, streams, readers, ...
In particular, you should take a look at methods added to:
- the
java.io.Fileclass : http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html - the
java.io.InputStreamclass: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html - the
java.io.OutputStreamclass: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html - the
java.io.Readerclass: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.html - the
java.io.Writerclass: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.html - the
java.nio.file.Pathclass: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html
The following section focuses on sample idiomatic constructs using helper methods available above but is not meant
to be a complete description of all available methods. For that, please read the link:gdk.html[GDK API].
== Reading files
As a first example, let's see how you would print all lines of a text file in Groovy:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=print_file_lines,indent=0]
The eachLine method is a method added to the File class automatically by Groovy and has many variants, for example
if you need to know the line number, you can use this variant:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=print_file_lines2,indent=0]
If for whatever reason an exception is thrown in the eachLine body, the method makes sure that the resource
is properly closed. This is true for all I/O resource methods that Groovy adds.
For example in some cases you will prefer to use a Reader, but still benefit from the automatic resource management
from Groovy. In the next example, the reader will be closed even if the exception occurs:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=withreader_exception,indent=0]
Should you need to collect the lines of a text file into a list, you can do:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=collect_lines,indent=0]
Or you can even leverage the as operator to get the contents of the file into an array of lines:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=lines_as_strings,indent=0]
How many times did you have to get the contents of a file into a byte[] and how much code does it require? Groovy
makes it very easy actually:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=file_bytes,indent=0]
Groovy also provides asynchronous variants for reading and writing files via the groovy-nio module.
These return a CompletableFuture, making them composable and easy to use with async frameworks.
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=async_read,indent=0]
Similarly, path.bytesAsync returns a CompletableFuture, andpath.writeAsync(text) writes content asynchronously.
Working with I/O is not limited to dealing with files. In fact, a lot of operations rely on input/output streams,
hence why Groovy adds a lot of support methods to those, as you can see in the
http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html[documentation].
As an example, you can obtain an InputStream from a File very easily:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=newinputstream,indent=0]
However you can see that it requires you to deal with closing the inputstream. In Groovy it is in general a better
idea to use the withInputStream idiom that will take care of that for you:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=withinputstream,indent=0]
== Writing files
Of course in some cases you won't want to read but write a file. One of the options is to use a Writer:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=withwriter_example,indent=0]
But for such a simple example, using the << operator would have been enough:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=file_leftshift,indent=0]
Of course we do not always deal with text contents, so you could use the Writer or directly write bytes as in
this example:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=file_setbytes,indent=0]
Of course you can also directly deal with output streams. For example, here is how you would create an output
stream to write into a file:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=newoutputstream,indent=0]
However you can see that it requires you to deal with closing the output stream. Again it is in general a better
idea to use the withOutputStream idiom that will handle the exceptions and close the stream in any case:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=withoutputstream,indent=0]
== Traversing file trees
In scripting contexts it is a common task to traverse a file tree in order to find some specific files and do
something with them. Groovy provides multiple methods to do this. For example you can perform something on all files
of a directory:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=eachfile,indent=0]
<1> executes the closure code on each file found in the directory
<2> executes the closure code on files in the directory matching the specified pattern
Often you will have to deal with a deeper hierarchy of files, in which case you can use eachFileRecurse:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=eachfilerecurse,indent=0]
<1> executes the closure code on each file or directory found in the directory, recursively
<2> executes the closure code only on files, but recursively
For more complex traversal techniques you can use the traverse method, which requires you to set a special flag
indicating what to do with the traversal:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=traverse,indent=0]
<1> if the current file is a directory and its name is bin, stop the traversal
<2> otherwise print the file name and continue
== Data and objects
In Java it is not uncommon to serialize and deserialize data using the java.io.DataOutputStream andjava.io.DataInputStream classes respectively. Groovy will make it even easier to deal with them. For example, you could
serialize data into a file and deserialize it using this code:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=data_in_out,indent=0]
And similarly, if the data you want to serialize implements the Serializable interface, you can proceed with
an object output stream, as illustrated here:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=object_in_out,indent=0]
[[process-management]]
== Executing External Processes
The previous section described how easy it was to deal with files, readers or streams in Groovy. However in domains
like system administration or devops it is often required to communicate with external processes.
Groovy provides execute and toProcessBuilder methods as two mechanisms to execute external processes.
In simple cases, calls to execute()
are invoked directly on a command string:
[source,groovy]
"javac src/main/java/*.java".execute()
For more sophisticated workloads, a named-parameter variant configures
the process with options like working directory,
environment, and stream redirection:
[source,groovy]
def result = "javac src/main/java/*.java".execute(
dir: new File('/myproject'),
redirectErrorStream: true
).waitForResult(2, TimeUnit.MINUTES)
if (!result.ok) System.err.println result.out
The toProcessBuilder() method lets you piggyback directly onto
the ProcessBuilder fluent API:
[source,groovy]
def result = "javac src/main/java/*.java".toProcessBuilder()
.directory(new File('/myproject'))
.redirectErrorStream(true)
.start()
.waitForResult(2, TimeUnit.MINUTES)
if (!result.ok) System.err.println result.out
The execute() and start() methods return a java.lang.Process instance, andwaitForResult() returns a ProcessResult capturing stdout, stderr, and the exit code.
The following sections cover each approach in detail.
=== Simple execution
Simply write the command line as a string and call the execute() method.
E.g., on a *nix machine (or a Windows machine with appropriate *nix
commands installed), you can execute this:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=process_list_files,indent=0]
<1> executes the ls command in an external process
<2> consume the output of the command and retrieve the text
Here is the same command as above but we will now process the
resulting stream a line at a time:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=process_list_files_line_by_line,indent=0]
<1> executes the ls command in an external process
<2> for each line of the input stream of the process
<3> print the line
It is worth noting that in corresponds to an input stream to the standard output of the command. out will refer
to a stream where you can send data to the process (its standard input).
Remember that many commands are shell built-ins and need special
handling. So if you want a listing of files in a directory on a Windows
machine and write:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=dir_windows,indent=0]
you will receive an IOException saying Cannot run program "dir":
CreateProcess error=2, The system cannot find the file specified.
This is because dir is built-in to the Windows shell (cmd.exe) and
can't be run as a simple executable. Instead, you will need to write:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=dir_windows_fixed,indent=0]
=== Named parameters for execute
The execute() method accepts named parameters to configure the process.
Supported options include dir (working directory), env (environment
variables), redirectErrorStream, inheritIO, outputFile, errorFile,inputFile, appendOutput, and appendError.
The file-related options (dir, outputFile, errorFile, inputFile,appendOutput, appendError) accept a File, Path, or String:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=execute_named_params,indent=0]
To merge stderr into stdout:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=execute_redirect_error,indent=0]
=== Using ProcessBuilder directly
For full control over process construction, the toProcessBuilder() method
creates a java.lang.ProcessBuilder from a command string, array, or list:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=to_process_builder,indent=0]
=== Capturing output, error, and exit code
A common need is to capture a process's standard output, standard error,
and exit code all at once. The waitForResult() method returns aProcessResult with out, err, exitCode, and ok properties:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=waitforresult,indent=0]
If the process produces an error:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=waitforresult_error,indent=0]
A timeout variant is available that forcibly kills the process if it
exceeds the given duration:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=waitforresult_timeout,indent=0]
=== Stream handling
Because some native platforms only provide limited buffer size for
standard input and output streams, failure to promptly write the input
stream or read the output stream of the subprocess may cause the
subprocess to block, and even deadlock.
Because of this, Groovy provides some additional helper methods which
make stream handling for processes easier.
Here is how to gobble all of the output (including the error stream
output) from your process:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=consumeoutput,indent=0]
There are also variations of consumeProcessOutput that make use of StringBuffer, InputStream,OutputStream etc... For a complete list, please read the
http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Process.html[GDK API for java.lang.Process]
=== Piping
The pipeTo command (mapped to | to allow overloading) lets the output
stream of one process be fed into the input stream of another process.
Here are some examples of use:
[source,groovy] .Pipes in action
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=pipe_example_1,indent=0]
[source,groovy] .Consuming errors
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=pipe_example_2,indent=0]
For more robust piping, the pipeline() method uses native OS-level piping
via ProcessBuilder.startPipeline():
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=pipeline,indent=0]
=== Asynchronous notifications
The onExit method registers a closure to be called when the process
terminates:
[source,groovy]
include::../test/gdk/WorkingWithIOSpecTest.groovy[tags=on_exit,indent=0]
Src Spec Doc Working With Numbers (`src/spec/doc/_working-with-numbers.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Numbers
:gdk: http://www.groovy-lang.org/gdk.html[Groovy development kit]
Groovy supports different kinds of integral literals and decimal literals, backed by the usual Number types of Java.
== Integral literals
The integral literal types are the same as in Java:
bytecharshortintlongjava.math.BigInteger
You can create integral numbers of those types with the following declarations:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=int_decl,indent=0]
If you use optional typing by using the def keyword, the type of the integral number will vary:
it'll adapt to the capacity of the type that can hold that number.
For positive numbers:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=wide_int_positive,indent=0]
As well as for negative numbers:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=wide_int_negative,indent=0]
=== Alternative non-base 10 representations
Numbers can also be represented in binary, octal, hexadecimal and decimal bases.
==== Binary literal
Binary numbers start with a 0b prefix:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=binary_literal_example,indent=0]
==== Octal literal
Octal numbers are specified in the typical format of 0 followed by octal digits.
[source,groovy]
include::../test/SyntaxTest.groovy[tags=octal_literal_example,indent=0]
==== Hexadecimal literal
Hexadecimal numbers are specified in the typical format of 0x followed by hex digits.
[source,groovy]
include::../test/SyntaxTest.groovy[tags=hexadecimal_literal_example,indent=0]
== Decimal literals
The decimal literal types are the same as in Java:
floatdoublejava.math.BigDecimal
You can create decimal numbers of those types with the following declarations:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=float_decl,indent=0]
Decimals can use exponents, with the e or E exponent letter, followed by an optional sign,
and an integral number representing the exponent:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=float_exp,indent=0]
Conveniently for exact decimal number calculations, Groovy chooses java.math.BigDecimal as its decimal number type.
In addition, both float and double are supported, but require an explicit type declaration, type coercion or suffix.
Even if BigDecimal is the default for decimal numbers, such literals are accepted in methods or closures taking float or double as parameter types.
NOTE: Decimal numbers can't be represented using a binary, octal or hexadecimal representation.
== Underscore in literals
When writing long literal numbers, itโs harder on the eye to figure out how some numbers are grouped together, for example with groups of thousands, of words, etc. By allowing you to place underscore in number literals, itโs easier to spot those groups:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=underscore_in_number_example,indent=0]
== Number type suffixes
We can force a number (including binary, octals and hexadecimals) to have a specific type by giving a suffix (see table below), either uppercase or lowercase.
[cols="1,2" options="header"]
|====
|Type
|Suffix
|BigInteger
|G or g
|Long
|L or l
|Integer
|I or i
|BigDecimal
|G or g
|Double
|D or d
|Float
|F or f
|====
Examples:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=number_type_suffixes_example,indent=0]
== Math operations
Although <<{core-operators}#groovy-operators,operators>> are covered in more detail elsewhere, it's important to discuss the behavior of math operations
and what their resulting types are.
Division and power binary operations aside (covered below),
- binary operations between
byte,char,shortandintresult inint - binary operations involving
longwithbyte,char,shortandintresult inlong - binary operations involving
BigIntegerand any other integral type result inBigInteger - binary operations involving
BigDecimalwithbyte,char,short,intandBigIntegerresult inBigDecimal - binary operations between
float,doubleandBigDecimalresult indouble - binary operations between two
BigDecimalresult inBigDecimal
The following table summarizes those rules:
[cols="10" options="header"]
|====
|
| byte
| char
| short
| int
| long
| BigInteger
| float
| double
| BigDecimal
| byte
| int
| int
| int
| int
| long
| BigInteger
| double
| double
| BigDecimal
| char
|
| int
| int
| int
| long
| BigInteger
| double
| double
| BigDecimal
| short
|
|
| int
| int
| long
| BigInteger
| double
| double
| BigDecimal
| int
|
|
|
| int
| long
| BigInteger
| double
| double
| BigDecimal
| long
|
|
|
|
| long
| BigInteger
| double
| double
| BigDecimal
| BigInteger
|
|
|
|
|
| BigInteger
| double
| double
| BigDecimal
| float
|
|
|
|
|
|
| double
| double
| double
| double
|
|
|
|
|
|
|
| double
| double
| BigDecimal
|
|
|
|
|
|
|
|
| BigDecimal
|====
[NOTE]
Thanks to Groovy's operator overloading, the usual arithmetic operators work as well with BigInteger and BigDecimal,
unlike in Java where you have to use explicit methods for operating on those numbers.
[[integer_division]]
=== The case of the division operator
The division operators / (and /= for division and assignment) produce a double result
if either operand is a float or double, and a BigDecimal result otherwise
(when both operands are any combination of an integral type short, char, byte, int, long,BigInteger or BigDecimal).
Under static type checking, the inferred type of a division follows the same rule from the
static types of the operands. An operand typed as Number may hold a Float or Double at
runtime as well as any of the other types, so a division involving one is inferred as Number.
BigDecimal division is performed with the divide() method if the division is exact
(i.e. yielding a result that can be represented within the bounds of the same precision and scale),
or using a MathContext with a http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html#precision()[precision]
of the maximum of the two operands' precision plus an extra precision of 10,
and a http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html#scale()[scale]
of the maximum of 10 and the maximum of the operands' scale.
[NOTE]
For integer division like in Java, you should use the intdiv() method,
as Groovy doesn't provide a dedicated integer division operator symbol.
[[power_operator]]
=== The case of the power operator
The power operation is represented by the ** operator, with two parameters: the base and the exponent.
The result of the power operation depends on its operands, and the result of the operation
(in particular if the result can be represented as an integral value).
The following rules are used by Groovy's power operation to determine the resulting type:
- If the exponent is a decimal value
** if the result can be represented as anInteger, then return anInteger
** else if the result can be represented as aLong, then return aLong
** otherwise return aDouble - If the exponent is an integral value
** if the exponent is strictly negative, then return anInteger,LongorDoubleif the result value fits in that type
** if the exponent is positive or zero
*** if the base is aBigDecimal, then return aBigDecimalresult value
*** if the base is aBigInteger, then return aBigIntegerresult value
*** if the base is anInteger, then return anIntegerif the result value fits in it, otherwise aBigInteger
*** if the base is aLong, then return aLongif the result value fits in it, otherwise aBigInteger
We can illustrate those rules with a few examples:
[source,groovy]
include::../test/SyntaxTest.groovy[tags=number_power,indent=0]
Src Spec Doc Contributors (`src/spec/doc/contributors.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Contributors
The Groovy team would like to thank the contributors of this documentation (in alphabetical order of last/surname):
- https://github.com/mojavelinux[Dan Allen]
- https://github.com/and-dmitry[Dmitry Andreychuk]
- http://hamletdarcy.blogspot.fr/[Hamlet D'Arcy]
- https://github.com/anshbansal[Aseem Bansal]
- https://github.com/bura[Andrey Bloschetsov]
- https://github.com/JBrownVisualSpection[J Brown]
- https://github.com/jeffbrown[Jeff Scott Brown]
- http://twitter.com/CedricChampeau[Cรฉdric Champeau]
- https://github.com/tobia[Tobia Conforto]
- https://github.com/ddimtirov[Dimitar Dimitrov]
- http://twitter.com/werdnagreb[Andrew Eisenberg]
- https://github.com/erdi[Marcin Erdmann]
- https://github.com/christoph-frick[Christoph Frick]
- http://twitter.com/marioggar[Mario Garcรญa]
- https://github.com/davidmichaelkarr[David Michael Karr]
- http://twitter.com/paulk_asert[Paul King]
- http://twitter.com/glaforge[Guillaume Laforge]
- http://twitter.com/pledbrook[Peter Ledbrook]
- http://grantmcconnaughey.github.io/[Grant McConnaughey]
- https://github.com/eric-milles[Eric Milles]
- https://github.com/dnahodil[David Nahodil]
- https://github.com/jnorthr[James Northrop]
- https://github.com/marcpa00[Marc Paquette]
- https://github.com/michaelss[Michael Schuenck]
- https://github.com/PascalSchumacher[Pascal Schumacher]
- https://github.com/shils[Shil Sinha]
- https://github.com/stavytskyi[Maksym Stavytskyi]
- https://twitter.com/asteingr[Andrรฉ Steingreร]
- https://twitter.com/daniel_sun[Daniel Sun]
- https://github.com/EPadronU[Edinson Padrรณn Urdaneta]
- https://github.com/keeganwitt[Keegan Witt]
Src Spec Doc Core Async Await (`src/spec/doc/core-async-await.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Async/Await (Incubating)
[[async-intro]]
== Introduction
Groovy provides native async/await support, enabling developers to write
concurrent code in a sequential, readable style. Rather than dealing with
callbacks, CompletableFuture chains, or manual thread management, you express
concurrency with two constructs:
async { ... }โ start a closure on a background thread, returning anAwaitableawait exprโ block until an asynchronous result is available
On JDK 21+, async tasks automatically leverage
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#ofVirtual()[virtual threads]
for optimal scalability. On JDK 17โ20, a cached thread pool is used as a
fallback.
The examples throughout this guide use an online multiplayer card game as a
running theme โ dealing hands, racing for the fastest play, streaming cards,
and managing tournament rounds.
[[async-getting-started]]
== Getting Started
=== Your first async/await
async { ... } starts work immediately on a background thread and returns anAwaitable. Use await to collect the result:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=draw_card,indent=0]
=== Exception handling
await unwraps CompletionException and ExecutionException automatically.
Standard try/catch works exactly as with synchronous code:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=exception_handling,indent=0]
=== CompletableFuture interop
await works directly with CompletableFuture, CompletionStage, and Future
from Java libraries:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=cf_interop,indent=0]
[[async-parallel]]
== Running Tasks in Parallel
The real power of async/await appears when you need to run several tasks
concurrently and coordinate their results.
=== Waiting for all: Awaitable.all()
Deal cards to multiple players at the same time and wait for all hands:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=deal_hands,indent=0]
Multi-argument await is syntactic sugar for Awaitable.all():
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=multi_arg_await,indent=0]
=== Racing: Awaitable.any()
Like JavaScript's Promise.race() โ settles as soon as the first task
settles, returning its result or propagating its error. A task that fails
first therefore fails the whole any, even if a later task would have
succeeded (use first if you want the first success instead). Useful for
fallback patterns or latency-sensitive code:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=fastest_server,indent=0]
=== First success: Awaitable.first()
Like JavaScript's Promise.any() โ returns the first successful result,
silently ignoring individual failures. Only fails when every task fails:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=first_success,indent=0]
=== Inspecting all outcomes: Awaitable.allSettled()
Waits for all tasks to finish (succeed or fail) without throwing. Returns anAwaitResult list where each entry has success, value, and error fields:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=all_settled,indent=0]
=== Combinator summary
[cols="1,2,2,2", options="header"]
|===
| Combinator | Completes when | On failure | Use case
| Awaitable.all
| All succeed
| Fails immediately on first failure (fail-fast)
| Gather results from independent tasks
| Awaitable.allSettled
| All complete (success or fail)
| Never throws; failures captured in AwaitResult list
| Inspect every outcome, e.g. partial-success reporting
| Awaitable.any
| First task settles (success or failure)
| Propagates the first settlement's result or error (JS Promise.race)
| Latency-sensitive races, fastest-response wins
| Awaitable.first
| First task succeeds, or all fail
| Throws only when every source fails (aggregate error)
| Hedged requests, graceful degradation with fallbacks
|===
NOTE: all and allSettled accept an empty source list and complete with an
empty result. any and first require at least one source (and reject null
elements), throwing IllegalArgumentException otherwise โ a "race" or "first
success" with no participants has no meaningful result.
[[async-generators]]
== Generators and Streaming
=== Producing values with yield return
An async closure containing yield return becomes a generator โ it lazily
produces a sequence of values. The generator runs on a background thread and
blocks on each yield return until the consumer is ready, providing natural
back-pressure:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=deck_generator,indent=0]
Generators return a standard Iterable, so regular for loops and Groovy
collection methods (collect, findAll, take, etc.) work out of the box:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=generator_regular_for,indent=0]
include::_core-async-channels.adoc[]
[[async-defer]]
== Deferred Cleanup with defer
The defer keyword schedules a cleanup action to run when the enclosingasync closure completes, regardless of success or failure. Multiple
deferred actions execute in LIFO order โ last registered, first to run โ
making it natural to pair resource acquisition with cleanup:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=defer_basic,indent=0]
Deferred actions always run, even when an exception occurs:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=defer_exception,indent=0]
If a deferred action returns an Awaitable or Future, the result is awaited
before the next deferred action runs, ensuring orderly cleanup of asynchronous
resources.
[[async-structured]]
== Structured Concurrency
Structured concurrency ensures that concurrent tasks have clear ownership and
bounded lifetimes โ no orphaned background work, no silent failures leaking
across your application. This idea is gaining momentum across the industry
(Java's https://openjdk.org/jeps/453[JEP 453], Go's errgroup, Kotlin's
coroutine scopes) because it makes concurrent code easier to reason about,
test, and debug. Groovy's AsyncScope provides these guarantees today, even
on JDK versions before Project Loom's StructuredTaskScope ships as a final API.
AsyncScope binds the lifetime of child tasks to a scope. When the scope
exits, all children are guaranteed complete (or cancelled). This prevents
orphaned tasks and silent failures:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=structured_concurrency,indent=0]
By default, the scope uses fail-fast semantics โ if any child fails,
all siblings are cancelled immediately:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=scope_fail_fast,indent=0]
The scope waits for every child to finish, even without explicit await calls:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=scope_waits,indent=0]
On JDK 25+, scope tracking uses ScopedValue for optimal virtual thread
performance (no per-thread storage, automatic inheritance). On JDK 17โ24,
a ThreadLocal fallback is used transparently.
[[async-advanced]]
== Advanced Topics
[[async-for-await]]
=== Consuming with for await
for await iterates over any async source. For generators and plain collections,
it works identically to a regular for loop:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=for_await_generator,indent=0]
The key value of for await is with reactive types (Reactor Flux, RxJavaObservable, java.util.concurrent.Flow.Publisher) where it automatically
converts the source to a blocking iterable via the adapter SPI. Withoutfor await, you would need to call the conversion manually (e.g.,flux.toIterable()). For generators and plain collections, a regular for
loop works identically.
The JDK's Flow.Publisher is supported out of the box โ no adapter module
needed. This means streams exposed by built-in sources such asAgent.changes() (see <<{core-concurrent-actors}#actors-agent,Agent>>) and BroadcastChannel.asPublisher()
(see xref:async-broadcast-channel[]), or any third-party publisher using the JDK
interfaces, compose directly with for await and await:
[source,groovy]
import groovy.concurrent.Agent
def agent = Agent.create(0)
try {
async {
3.times { agent.send { it + 1 } }
agent.shutdown()
}
for await (v in agent.changes()) {
println "value is now $v"
}
} finally {
agent.shutdown()
}
[[async-timeouts]]
=== Timeouts and Delays
Apply a deadline to any task. If it doesn't complete in time, aTimeoutException is thrown:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=timeout,indent=0]
Or use a fallback value instead of throwing:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=timeout_fallback,indent=0]
Awaitable.delay() pauses without blocking a thread:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=delay,indent=0]
[[async-adapters]]
=== Framework Adapters
await natively understands Awaitable, CompletableFuture, CompletionStage,
and Future. for await natively understands java.util.concurrent.Flow.Publisher
in addition to regular Iterable sources. Third-party async types can be
supported by implementing the AwaitableAdapter SPI and registering it viaMETA-INF/services/groovy.concurrent.AwaitableAdapter.
Drop-in adapter modules are provided for:
groovy-reactorโawaitonMono,for awaitoverFluxgroovy-rxjavaโawaitonSingle/Maybe/Completable,for awaitoverObservable/Flowable
For example, without the adapter you must manually convert RxJava types:
[source,groovy]
// Without groovy-rxjava โ manual conversion def result = Single.just('hello').toCompletionStage().toCompletableFuture().join()
With groovy-rxjava on the classpath, the conversion is transparent:
[source,groovy]
// With groovy-rxjava โ adapter handles the plumbing def result = await Awaitable.from(Single.just('hello'))
[[async-executor]]
=== Executor Configuration
By default, async uses:
- JDK 21+: a virtual-thread-per-task executor
- JDK 17โ20: a cached daemon thread pool (max 256 threads, configurable
viagroovy.async.parallelismsystem property)
You can override the executor:
[source,groovy]
import org.apache.groovy.runtime.async.AsyncSupport
import java.util.concurrent.Executors
AsyncSupport.setExecutor(Executors.newFixedThreadPool(4)) AsyncSupport.setExecutor(null) // restore platform default // or equivalently: AsyncSupport.resetExecutor()
[[async-jdk-integration]]
=== Integration with JDK Classes
await works with any JDK API that returns a CompletableFuture, CompletionStage,
or Future. This means you can combine process execution, asynchronous file I/O,
and HTTP calls in a single async block โ all running concurrently:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=jdk_integration,indent=0]
Task 3 uses Groovy's HttpBuilder (from groovy-http-builder), which wraps
JDK HttpClient with a concise DSL. For lower-level control, you can also useHttpClient directly:
[source,groovy]
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
def webContent = async {
def client = HttpClient.newHttpClient()
def req = HttpRequest.newBuilder()
.uri(URI.create('https://api.example.com/data'))
.build()
def resp = await client.sendAsync(req, HttpResponse.BodyHandlers.ofString())
resp.body()
}
Key JDK classes that work with await out of the box:
[cols="2,2", options="header"]
|===
| JDK class | Method returning async result
| Process
| onExit() โ CompletableFuture
| HttpClient
| sendAsync(...) โ CompletableFuture<HttpResponse<T>>
| Path (via groovy-nio)
| getTextAsync(), getBytesAsync(), writeAsync(...) โ CompletableFuture
| AsynchronousFileChannel
| read(...), write(...), lock(...) โ Future / Future<FileLock>
| CompletableFuture
| supplyAsync(...), allOf(...), anyOf(...) โ CompletableFuture<T>
|===
[[async-closure-factories]]
=== Async Closure Factories
Because async { ... } is just an expression that returns an Awaitable, you
can wrap it in an ordinary method or closure to create a reusable factory:
[source,groovy]
include::../test/AsyncAwaitSpecTest.groovy[tags=closure_factory,indent=0]
This is a natural way to build reusable async building blocks without any
special framework support.
[[async-best-practices]]
== Best Practices
[[async-prefer-values]]
=== Prefer returning values over mutating shared state
Async closures run on separate threads. Mutating shared variables from multiple
closures is a race condition:
[source,groovy]
// UNSAFE โ shared mutation without synchronization var count = 0 def tasks = (1..100).collect { async { count++ } } tasks.each { await it } // count may not be 100!
Instead, return values and collect results:
[source,groovy]
// SAFE โ no shared mutation def tasks = (1..100).collect { n -> async { n } } def results = await Awaitable.all(*tasks) assert results.sum() == 5050
When shared mutable state is unavoidable, use the appropriate concurrency-aware
type, e.g. AtomicInteger for a shared counter, or thread-safe types fromjava.util.concurrent for players concurrently drawing cards from a shared deck.
[[async-choose-right-tool]]
=== Choosing the right tool
[cols="2,3", options="header"]
|===
| Feature | Use when...
| async/await
| You have sequential steps involving I/O or blocking work and want code that reads top-to-bottom.
| Awaitable.all / any / first
| You need to launch independent tasks and collect all results, race them, or take the first success.
| yield return / for await
| You're producing or consuming a stream of values โ paginated APIs, card dealing, log tailing.
| defer
| You acquire resources at different points and want guaranteed cleanup without nested try/finally.
| AsyncChannel
| Two or more tasks need to communicate โ producer/consumer, fan-out/fan-in, or hand-off.
| AsyncScope
| You want child task lifetimes tied to a scope with automatic cancellation on failure.
| Framework adapters
| You're already using Reactor or RxJava and want await / for await to work with their types.
|===
[[async-summary]]
== Quick Reference
[cols="1,3"]
|===
| Construct | Description
| async { ... }
| Start a closure on a background thread. Returns Awaitable (or Iterable for generators).
| await expr
| Block until the result is available. Rethrows the original exception.
| await(a, b, c)
| Wait for all โ syntactic sugar for await Awaitable.all(a, b, c).
| yield return expr
| Produce a value from an async generator. Consumer blocks until ready.
| for await (x in src)
| Iterate over an async source (generator, channel, Flux, Observable, etc.).
| defer expr
| Schedule a cleanup action (LIFO order) inside an async closure.
| AsyncChannel.create(n)
| Create a buffered (or unbuffered) channel for inter-task communication.
| ChannelSelect.after(ms)
| Timer offer: a ChannelSelect branch that re-arms each round and commits with the Instant it fired.
| AsyncChannel.after(ms)
| Timer channel: delivers the Instant it fired, then closes. A fixed deadline as a ChannelSelect branch.
| AsyncScope.withScope { ... }
| Structured concurrency โ all children complete (or are cancelled) on scope exit.
| Awaitable.orTimeoutMillis
| Apply a deadline. Throws TimeoutException if the task exceeds it.
| Awaitable.completeOnTimeoutMillis
| Apply a deadline with a fallback value instead of throwing.
| Awaitable.delay(ms)
| Non-blocking pause.
|===
All keywords (async, await, defer) are contextual โ they can still be
used as variable or method names in existing code.
include::_core-gpars-migration.adoc[]
include::../../../subprojects/groovy-macro-library/src/spec/doc/_monadic-comprehensions.adoc[leveloffset=+1]
Src Spec Doc Core Closures (`src/spec/doc/core-closures.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Closures
:lambdas: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html[lambda expressions in Java 8]
This chapter covers Groovy Closures. A closure in Groovy is an open, anonymous, block of code that can take arguments,
return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope. In
opposition to the formal definition of a closure, Closure in the Groovy language can also contain free variables which
are defined outside of its surrounding scope. While breaking the formal concept of a closure, it offers a variety of
advantages which are described in this chapter.
== Syntax
=== Defining a closure
A closure definition follows this syntax:
[source,groovy]
{ [closureParameters -> ] statements }
Where +[closureParameters->]+ is an optional comma-delimited list of
parameters, and statements are 0 or more Groovy statements. The parameters
look similar to a method parameter list, and these parameters may be
typed or untyped.
When a parameter list is specified, the +->+ character
is required and serves to separate the arguments from the closure body.
The statements portion consists of 0, 1, or many Groovy statements.
Some examples of valid closure definitions:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_1,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_1bis,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_2,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_3,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_4,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_5,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_syntax_6,indent=0]
<1> A closure referencing a variable named item
<2> It is possible to explicitly separate closure parameters from code by adding an arrow (+->+)
<3> A closure using an implicit parameter (it)
<4> An alternative version where it is an explicit parameter
<5> In that case it is often better to use an explicit name for the parameter
<6> A closure accepting two typed parameters
<7> A closure can contain multiple statements
[[closure-as-object]]
=== Closures as an object
A closure is an instance of the groovy.lang.Closure class, making it assignable to a variable or a field as any
other variable, despite being a block of code:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_is_an_instance_of_Closure,indent=0]
<1> You can assign a closure to a variable, and it is an instance of groovy.lang.Closure
<2> If not using def or var, use groovy.lang.Closure as the type
<3> Optionally, you can specify the return type of the closure by using the generic type of groovy.lang.Closure
=== Calling a closure
A closure, as an anonymous block of code, can be called like any other method. If you define a closure which takes
no argument like this:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_call_1,indent=0]
Then the code inside the closure will only be executed when you call the closure, which can be done by using the
variable as if it was a regular method:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_call_1_direct,indent=0]
Alternatively, you can be explicit and use the call method:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_call_1_explicit,indent=0]
The principle is the same if the closure accepts arguments:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_call_2,indent=0]
<1> define a closure which accepts an int as a parameter
<2> it can be called directly
<3> or using the call method
<4> same goes for a closure with an implicit argument (it)
<5> which can be called directly using (arg)
<6> or using call
Unlike a method, a closure always returns a value when called. The next section discusses how to declare closure arguments, when to use them and what is the <>.
== Parameters
=== Normal parameters
Parameters of closures follow the same principle as parameters of regular methods:
- an optional type
- a name
- an optional default value
Parameters are separated with commas:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_param_declaration,indent=0]
[[implicit-it]]
=== Implicit parameter
When a closure does not explicitly define a parameter list (using +->+), a closure always defines an implicit
parameter, named it. This means that this code:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=implicit_it,indent=0]
is strictly equivalent to this one:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=implicit_it_equiv,indent=0]
If you want to declare a closure which accepts no argument and must be restricted to calls without arguments,
then you must declare it with an explicit empty argument list:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_no_arg_def,indent=0]
include::../test/ClosuresSpecTest.groovy[tags=closure_no_arg_fail,indent=0]
=== Varargs
It is possible for a closure to declare variable arguments like any other method. Vargs methods are methods that
can accept a variable number of arguments if the last parameter is of variable length (or an array) like in the next
examples:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_vargs,indent=0]
<1> A closure accepting a variable number of strings as first parameter
<2> It may be called using any number of arguments without having to explicitly wrap them into an array
<3> The same behavior is directly available if the args parameter is declared as an array
<4> As long as the last parameter is an array or an explicit vargs type
== Delegation strategy
=== Groovy closures vs lambda expressions
Groovy defines closures as <<closure-as-object, instances of the Closure class>>. It makes it very different from
{lambdas}. Delegation is a
key concept in Groovy closures which has no equivalent in lambdas. The ability to change the delegate or change the
delegation strategy of closures make it possible to design beautiful domain specific languages (DSLs) in Groovy.
[[closure-owner]]
=== Owner, delegate and this
To understand the concept of delegate, we must first explain the meaning of this inside a closure. A closure actually
defines 3 distinct things:
thiscorresponds to the enclosing class where the closure is definedownercorresponds to the enclosing object where the closure is defined, which may be either a class or a closuredelegatecorresponds to a third party object where methods calls or properties are resolved whenever the receiver of
the message is not defined
[[closure-this]]
==== The meaning of this
In a closure, calling getThisObject will return the enclosing class where the closure is defined. It is equivalent to
using an explicit this:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_this,indent=0]
<1> a closure is defined inside the Enclosing class, and returns getThisObject
<2> calling the closure will return the instance of Enclosing where the closure is defined
<3> in general, you will just want to use the shortcut this notation
<4> and it returns exactly the same object
<5> if the closure is defined in an inner class
<6> this in the closure will return the inner class, not the top-level one
<7> in case of nested closures, like here cl being defined inside the scope of nestedClosures
<8> then this corresponds to the closest outer class, not the enclosing closure!
It is of course possible to call methods from the enclosing class this way:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_this_call,indent=0]
<1> the closure calls toString on this, which will actually call the toString method on the enclosing object,
that is to say the Person instance
==== Owner of a closure
The owner of a closure is very similar to the definition of <<closure-this,this in a closure>> with a subtle difference:
it will return the direct enclosing object, be it a closure or a class:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_owner,indent=0]
<1> a closure is defined inside the Enclosing class, and returns getOwner
<2> calling the closure will return the instance of Enclosing where the closure is defined
<3> in general, you will just want to use the shortcut owner notation
<4> and it returns exactly the same object
<5> if the closure is defined in an inner class
<6> owner in the closure will return the inner class, not the top-level one
<7> but in case of nested closures, like here cl being defined inside the scope of nestedClosures
<8> then owner corresponds to the enclosing closure, hence a different object from this!
==== Delegate of a closure
The delegate of a closure can be accessed by using the delegate property or calling the getDelegate method. It is a
powerful concept for building domain specific languages in Groovy. While <<closure-this,this>> and <<closure-owner,owner>>
refer to the lexical scope of a closure, the delegate is a user defined object that a closure will use. By default, the
delegate is set to owner:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=delegate_is_owner,indent=0]
<1> you can get the delegate of a closure calling the getDelegate method
<2> or using the delegate property
<3> both return the same object
<4> which is the enclosing class or closure
<5> in particular in case of nested closures
<6> delegate will correspond to the owner
The delegate of a closure can be changed to any object. Let's illustrate this by creating two classes which are not
subclasses of each other but both define a property called name:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=change_delegate_classes,indent=0]
Then let's define a closure which fetches the name property on the delegate:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=change_delegate_closure,indent=0]
Then by changing the delegate of the closure, you can see that the target object will change:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=change_delegate_asserts,indent=0]
At this point, the behavior is not different from having a target variable defined in the lexical scope of the closure:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=delegate_alernative,indent=0]
However, there are major differences:
- in the last example, target is a local variable referenced from within the closure
- the delegate can be used transparently, that is to say without prefixing method calls with
delegate.as explained
in the next paragraph.
==== Delegation strategy
Whenever, in a closure, a property is accessed without explicitly setting a receiver object, then a delegation strategy
is involved:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=delegation_strategy_intro,indent=0]
<1> name is not referencing a variable in the lexical scope of the closure
<2> we can change the delegate of the closure to be an instance of Person
<3> and the method call will succeed
The reason this code works is that the name property will be resolved transparently on the delegate object! This is
a very powerful way to resolve properties or method calls inside closures. There's no need to set an explicit delegate.
receiver: the call will be made because the default delegation strategy of the closure makes it so. A closure actually
defines multiple resolution strategies that you can choose:
Closure.OWNER_FIRSTis the default strategy. If a property/method exists on the owner, then it will be called on
the owner. If not, then the delegate is used.Closure.DELEGATE_FIRSTreverses the logic: the delegate is used first, then the ownerClosure.OWNER_ONLYwill only resolve the property/method lookup on the owner: the delegate will be ignored.Closure.DELEGATE_ONLYwill only resolve the property/method lookup on the delegate: the owner will be ignored.Closure.TO_SELFcan be used by developers who need advanced meta-programming techniques and wish to implement a
custom resolution strategy: the resolution will not be made on the owner or the delegate but only on the closure class
itself. It makes only sense to use this if you implement your own subclass ofClosure.
Let's illustrate the default "owner first" strategy with this code:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_owner_first,indent=0]
<1> for the illustration, we define a closure member which references "name"
<2> both the Person and the Thing class define a name property
<3> Using the default strategy, the name property is resolved on the owner first
<4> so if we change the delegate to t which is an instance of Thing
<5> there is no change in the result: name is first resolved on the owner of the closure
However, it is possible to change the resolution strategy of the closure:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_delegate_first,indent=0]
By changing the resolveStrategy, we are modifying the way Groovy will resolve the "implicit this" references: in this
case, name will first be looked in the delegate, then if not found, on the owner. Since name is defined in the
delegate, an instance of Thing, then this value is used.
The difference between "delegate first" and "delegate only" or "owner first" and "owner only" can be illustrated if one
of the delegate (resp. owner) does not have such a method or property:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=delegate_only,indent=0]
<1> for "owner first" it doesn't matter what the delegate is
<2> for "delegate only" having p as the delegate succeeds
<3> for "delegate only" having t as the delegate fails
In this example, we define two classes which both have a name property but only the Person class declares an age.
The Person class also declares a closure which references age. We can change the default resolution strategy from
"owner first" to "delegate only". Since the owner of the closure is the Person class, then we can check that if the
delegate is an instance of Person, calling the closure is successful, but if we call it with a delegate being an
instance of Thing, it fails with a groovy.lang.MissingPropertyException. Despite the closure being defined inside
the Person class, the owner is not used.
NOTE: A comprehensive explanation about how to use this feature to develop DSLs can be found in a
link:core-domain-specific-languages.html[dedicated section of the manual].
==== Delegation strategy in the presence of metaprogramming
When describing the "owner first" delegation strategy we spoke
about using a property/method from the owner if it "existed" otherwise
using the respective property/method from the delegate. And a similar
story for "delegate first" but in reverse. Instead of using the word
"existed", it would have been more accurate to use the wording "handled".
That means that for "owner first", if the property/method exists in
the owner, or it has a propertyMissing/methodMissing hook, then the owner
will handle the member access.
We can see this in action with a slightly altered version of our previous example:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=delegate_only_prop_missing,indent=0]
In this example, even though our instance of the Thing class (our delegate for the last use of cl) has no age property,
the fact that it handles the missing property via its propertyMissing hook,
means that age will be -1.
== Closures in GStrings
Take the following code:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=gstring_eager_intro,indent=0]
The code behaves as you would expect, but what happens if you add:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=gstring_eager_outro,indent=0]
You will see that the assert fails! There are two reasons for this:
- a GString only evaluates lazily the
toStringrepresentation of values - the syntax
${x}in a GString does not represent a closure but an expression to$x, evaluated when the GString
is created.
In our example, the GString is created with an expression referencing x. When the GString is created, the value
of x is 1, so the GString is created with a value of 1. When the assert is triggered, the GString is evaluated
and 1 is converted to a String using toString. When we change x to 2, we did change the value of x, but it is
a different object, and the GString still references the old one.
TIP: A GString will only change its toString representation if the values it references are mutating. If the references
change, nothing will happen.
If you need a real closure in a GString and for example enforce lazy evaluation of variables, you need to use the
alternate syntax ${-> x} like in the fixed example:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=gstring_lazy,indent=0]
And let's illustrate how it differs from mutation with this code:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=gstring_mutation,indent=0]
<1> the Person class has a toString method returning the name property
<2> we create a first Person named Sam
<3> we create another Person named Lucy
<4> the p variable is set to Sam
<5> and a closure is created, referencing the value of p, that is to say Sam
<6> so when we evaluate the string, it returns Sam
<7> if we change p to Lucy
<8> the string still evaluates to Sam because it was the value of p when the GString was created
<9> so if we mutate Sam to change the name to Lucy
<10> this time the GString is correctly mutated
So if you don't want to rely on mutating objects or wrapping objects, you must use closures in GString by explicitly
declaring an empty argument list:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=gstring_no_mutation,indent=0]
== Closure coercion
Closures can be converted into interfaces or single-abstract method types. Please refer to
link:core-semantics.html#closure-coercion[this section of the manual] for a complete description.
== Functional programming
Closures, like {lambdas} are at the core of the functional programming paradigm in Groovy. Some functional programming
operations on functions are available directly on the Closure class, like illustrated in this section.
=== Currying
In Groovy, currying refers to the concept of partial application. It does not correspond to the real concept of currying
in functional programming because of the different scoping rules that Groovy applies on closures. Currying in Groovy will
let you set the value of one parameter of a closure, and it will return a new closure accepting one less argument.
==== Left currying
Left currying is the fact of setting the left-most parameter of a closure, like in this example:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=left_curry,indent=0]
<1> the nCopies closure defines two parameters
<2> curry will set the first parameter to 2, creating a new closure (function) which accepts a single String
<3> so the new function call be called with only a String
<4> and it is equivalent to calling nCopies with two parameters
==== Right currying
Similarly to left currying, it is possible to set the right-most parameter of a closure:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=right_curry,indent=0]
<1> the nCopies closure defines two parameters
<2> rcurry will set the last parameter to bla, creating a new closure (function) which accepts a single int
<3> so the new function call be called with only an int
<4> and it is equivalent to calling nCopies with two parameters
==== Index based currying
In case a closure accepts more than 2 parameters, it is possible to set an arbitrary parameter using ncurry:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=ncurry,indent=0]
<1> the volume function defines 3 parameters
<2> ncurry will set the second parameter (index = 1) to 2d, creating a new volume function which accepts length and height
<3> that function is equivalent to calling volume omitting the width
<4> it is also possible to set multiple parameters, starting from the specified index
<5> the resulting function accepts as many parameters as the initial one minus the number of parameters set by ncurry
==== Currying functional interfaces
When working with java.util.function types โ for example BiPredicate,BiFunction or BiConsumer โ Closure.rcurry is not applicable. Two
sibling utility classes provide right-partial helpers, differing only in
the type returned:
org.apache.groovy.util.Lambdas#curryWithreturns a barePredicate/Function/Consumer. Use when the result will flow into
JDK APIs (e.g.Stream.filter,Optional.map) or into DGM methods
that already accept SAM types (e.g.partitionPoint,takeIf,groupConsecutive).org.apache.groovy.util.Closures#curryWithreturns a hybrid value
that is both the SAM type and aClosure. Use when the result
should also be usable with DGM methods that accept aClosure
(e.g.findAll,collect,each,find,any,every).
[source,groovy]
import static org.apache.groovy.util.Lambdas.curryWith as curryWithSam
import static org.apache.groovy.util.Closures.curryWith as curryWithClosure
import java.util.function.BiPredicate
BiPredicate divisibleBy = (n, d) -> n % d == 0
// Lambdas.curryWith: flows into JDK streams / SAM-accepting DGM
assert [1, 2, 3, 4, 5, 6].stream().filter(curryWithSam(divisibleBy, 2)).toList() == [2, 4, 6]
// Closures.curryWith: flows into Closure-accepting DGM as well assert [1, 2, 3, 4, 5, 6].findAll(curryWithClosure(divisibleBy, 2)) == [2, 4, 6]
In both forms a single allocation is paid per curryWith call rather
than per element of the traversal.
Closures.from(Predicate) (and the Function/Consumer overloads)
lifts an existing single-argument SAM into the same hybrid form, useful
when an external API hands you a Predicate you want to feed into aClosure-accepting DGM method:
[source,groovy]
import static org.apache.groovy.util.Closures.from
import java.util.function.Predicate
Predicate isEven = n -> n % 2 == 0 assert [1, 2, 3, 4].findAll(from(isEven)) == [2, 4]
Three overloads are provided:
curryWith(BiPredicate<T,P>, P) -> Predicate<T>curryWith(BiFunction<T,P,R>, P) -> Function<T,R>curryWith(BiConsumer<T,P>, P) -> Consumer<T>
=== Memoization
Memoization allows the result of the call of a closure to be cached. It is interesting if the computation done by a
function (closure) is slow, but you know that this function is going to be called often with the same arguments. A
typical example is the Fibonacci suite. A naive implementation may look like this:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=naive_fib,indent=0]
It is a naive implementation because 'fib' is often called recursively with the same arguments, leading to an exponential
algorithm:
- computing
fib(15)requires the result offib(14)andfib(13) - computing
fib(14)requires the result offib(13)andfib(12)
Since calls are recursive, you can already see that we will compute the same values again and again, although they could
be cached. This naive implementation can be "fixed" by caching the result of calls using memoize:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=memoized_fib,indent=0]
WARNING: The cache works using the actual values of the arguments. This means that you should be very careful if you use
memoization with something else than primitive or boxed primitive types.
The behavior of the cache can be tweaked using alternate methods:
memoizeAtMostwill generate a new closure which caches at most n valuesmemoizeAtLeastwill generate a new closure which caches at least n valuesmemoizeBetweenwill generate a new closure which caches at least n values and at most n values
The cache used in all memoize variants is an LRU cache.
=== Composition
Closure composition corresponds to the concept of function composition, that is to say creating a new function by
composing two or more functions (chaining calls), as illustrated in this example:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=closure_composition,indent=0]
=== Trampoline
Recursive algorithms are often restricted by a physical limit: the maximum stack height. For example, if you call a method
that recursively calls itself too deep, you will eventually receive a StackOverflowException.
An approach that helps in those situations is by using Closure and its trampoline capability.
Closures are wrapped in a TrampolineClosure. Upon calling, a trampolined Closure will call the original Closure waiting
for its result. If the outcome of the call is another instance of a TrampolineClosure, created perhaps as a result
to a call to the trampoline() method, the Closure will again be invoked. This repetitive invocation of returned
trampolined Closures instances will continue until a value other than a trampolined Closure is returned. That value
will become the final result of the trampoline. That way, calls are made serially, rather than filling the stack.
Hereโs an example of the use of trampoline() to implement the factorial function:
[source,groovy]
include::../test/ClosuresSpecTest.groovy[tags=trampoline,indent=0]
=== Method pointers
It is often practical to be able to use a regular method as a closure. For example, you might want to use the currying
abilities of a closure, but those are not available to normal methods. In Groovy, you can obtain a closure from any
method with the link:core-operators.html#method-pointer-operator[method pointer operator].
Src Spec Doc Core Concurrent Actors (`src/spec/doc/core-concurrent-actors.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Actors and Agents (Incubating)
[[actors-intro]]
== Introduction
Actors and agents provide thread-safe concurrent state management
through message passing. Instead of protecting shared state with locks,
you send messages to an actor or agent which processes them one at a
time on a dedicated thread. This eliminates data races by design.
Groovy provides two levels of abstraction:
- Agent โ a thread-safe mutable value updated via functions
- Actor โ a message-processing entity with flexible dispatch
Both use virtual threads on JDK 21+ for efficient scaling.
[[actors-agent]]
== Agent
An Agent wraps a value that can be read by any thread but modified
only through serialized update functions:
[source,groovy]
import groovy.concurrent.Agent
def counter = Agent.create(0)
counter.send { it + 1 }
counter.send { it + 1 }
counter.send { it + 1 }
assert await(counter.getAsync()) == 3
=== Reading
[source,groovy]
// Snapshot read โ non-blocking, returns current value
def current = counter.get()
// Consistent read โ waits for pending updates to complete def consistent = await(counter.getAsync())
=== Updating
[source,groovy]
// Fire-and-forget update
counter.send { it + 1 }
// Update and get new value def newValue = await(counter.sendAndGet { it * 2 })
=== Complex state
Agents work with any value type:
[source,groovy]
def inventory = Agent.create([:])
inventory.send { state -> state + [apples: 10] }
inventory.send { state -> state + [bananas: 5] }
inventory.send { state ->
state.collectEntries { k, v -> [k, v * 2] }
}
assert await(inventory.getAsync()) == [apples: 20, bananas: 10]
=== Observing state changes
An agent exposes a Flow.Publisher<T> of state transitions viachanges(). Each successful update emits the new value to every
subscriber that is already subscribed at the time of the update. The
stream is hot (no replay of prior state), per-subscriber buffered,
and closes with onComplete when shutdown() is called:
[source,groovy]
def agent = Agent.create(0)
try {
async {
3.times { agent.send { it + 1 } }
Thread.sleep(50)
agent.shutdown()
}
def seen = []
for await (v in agent.changes()) {
seen << v
}
assert seen == [1, 2, 3]
} finally {
agent.shutdown()
}
Slow subscribers drop newly-offered values rather than backpressuring
the agent's update loop โ buffered values are still delivered in order,
but the most recent update may be skipped if a subscriber's buffer is
full (default size 256). If changes() is first called aftershutdown(), the returned publisher is already closed and subscribers
receive onComplete immediately.
[[actors-actor]]
== Actor
An Actor processes messages from a queue on a dedicated thread.
Two factory methods cover the common patterns:
[[actors-reactor]]
=== Reactor (stateless)
A reactor applies a function to each message. The return value
becomes the reply for sendAndGet callers:
[source,groovy]
import groovy.concurrent.Actor
def doubler = Actor.reactor { it * 2 } assert await(doubler.sendAndGet(5)) == 10 assert await(doubler.sendAndGet(21)) == 42 doubler.stop()
Reactors are ideal for pure-function message processing โ validators,
transformers, calculators:
[source,groovy]
def validator = Actor.reactor { msg ->
if (msg instanceof String && msg.length() > 0) 'valid'
else 'invalid'
}
[[actors-stateful]]
=== Stateful
A stateful actor maintains state across messages. The handler
receives (state, message) and returns the new state:
[source,groovy]
def counter = Actor.stateful(0) { state, msg ->
switch (msg) {
case 'increment': return state + 1
case 'decrement': return state - 1
case 'reset': return 0
default: return state
}
}
counter.send('increment') counter.send('increment') counter.send('decrement') assert await(counter.sendAndGet('increment')) == 2 counter.stop()
For sendAndGet, the new state is the reply. This makes it easy
to query the current state:
[source,groovy]
// Send a no-op message to read the state def currentState = await(counter.sendAndGet('query'))
=== Typed message dispatch
Use pattern matching for rich message protocols:
[source,groovy]
def account = Actor.stateful(0.0) { balance, msg ->
switch (msg) {
case { it instanceof Map && it.deposit }:
return balance + msg.deposit
case { it instanceof Map && it.withdraw }:
if (msg.withdraw > balance)
throw new RuntimeException('Insufficient funds')
return balance - msg.withdraw
default:
return balance
}
}
account.send([deposit: 100]) account.send([withdraw: 30]) def balance = await(account.sendAndGet([deposit: 0])) assert balance == 70.0 account.stop()
[[actors-options]]
== Construction options
Actors take an optional ActorOptions configuring the mailbox, the
executor, and a handful of opt-in behaviours. The builder is
value-based; each with* method returns a new configuration.
[source,groovy]
import groovy.concurrent.Actor
import groovy.concurrent.ActorOptions
def options = ActorOptions.DEFAULTS
.withBoundedMailbox(1000, ActorOptions.Overflow.BLOCK)
def actor = Actor.reactor(handler, options)
=== Bounded mailbox
By default the mailbox is unbounded. For backpressure โ or to cap
memory when producers can outrun the actor โ configure a capacity and
an overflow policy:
[source,groovy]
import groovy.concurrent.ActorOptions.Overflow
// BLOCK โ sender blocks until capacity is free
ActorOptions.DEFAULTS.withBoundedMailbox(100, Overflow.BLOCK)
// FAIL โ send throws IllegalStateException when the mailbox is full
ActorOptions.DEFAULTS.withBoundedMailbox(100, Overflow.FAIL)
// DROP_NEWEST โ the incoming message is silently dropped; for // sendAndGet, the returned Awaitable binds to IllegalStateException ActorOptions.DEFAULTS.withBoundedMailbox(100, Overflow.DROP_NEWEST)
A handler that calls ctx.self().send(...) on a full BLOCK mailbox
would deadlock (the handler is the actor's only consumer). The actor
detects this and fails fast with IllegalStateException rather than
parking the worker thread.
=== Per-actor executor
Actors default to a shared async executor (virtual threads on
JDK 21+). For workload isolation, hand the actor its own executor:
[source,groovy]
import java.util.concurrent.Executors
def pool = Executors.newSingleThreadExecutor()
def actor = Actor.reactor(handler,
ActorOptions.DEFAULTS.withExecutor(pool))
Other options โ withStashBound and withCurrentSelf โ are covered
in the <<actors-fsm,FSM section>> where they're directly used.
[[actors-lifecycle]]
== Lifecycle
Both actors and agents support lifecycle management. An actor has a
three-state lifecycle:
[cols="1,1,3"]
|===
|isActive() |isTerminated() |Meaning
|true |false |Accepting new sends and processing them.
|false |false |Draining โ stop() was called, no new sends are
accepted, but already-queued messages are still being processed.
|false |true |Terminated โ the worker thread has exited; all
queued messages are processed and any stashed sendAndGet replies
have been rejected.
|===
[source,groovy]
def actor = Actor.reactor { it }
assert actor.isActive()
assert !actor.isTerminated()
actor.stop() // flips isActive() to false immediately
assert !actor.isActive() // new sends fail from here (a send racing with stop() may still land)
// The worker may still be draining queued messages. Poll for terminated // when you actually need to be sure the actor has finished shutting down. while (!actor.isTerminated()) Thread.sleep(10) assert actor.isTerminated()
Actors implement AutoCloseable, so they work with
try-with-resources (Groovy or Java):
[source,groovy]
Actor.reactor { it * 2 }.withCloseable { actor ->
assert await(actor.sendAndGet(5)) == 10
}
// actor is stopped
[[actors-errors]]
== Error Handling
Exceptions thrown by a handler are delivered to sendAndGet callers
through the awaited reply:
[source,groovy]
def risky = Actor.reactor { throw new RuntimeException('oops') }
try {
await(risky.sendAndGet('anything'))
} catch (RuntimeException e) {
assert e.message == 'oops'
}
risky.stop()
Fire-and-forget send calls have no reply to carry the exception.
To observe handler failures from those messages โ for logging,
metrics, or shutting the actor down โ register an onError callback:
[source,groovy]
def actor = Actor.reactor { msg -> process(msg) }
.onError { Throwable t, msg ->
log.warn("handler failed on {}: {}", msg, t.message)
}
The context-aware overload receives an ActorContext first, useful
when the callback needs to react beyond logging โ for example,
stopping the actor:
[source,groovy]
actor.onError { ctx, t, msg -> ctx.self().stop() }
Exceptions thrown from inside the onError callback itself are
caught and discarded so they cannot destabilise the dispatch loop.
[[actors-fsm]]
== FSM-style actors
A stateful actor models a single behaviour. For multi-phase protocols
โ where the actor responds differently depending on which "state" it
is in โ three additional capabilities on ActorContext cover most of
what you'd reach for from a dedicated FSM library:
ctx.become(newHandler)โ swap the active handler at runtime;
state is preserved across the swap.ctx.stash()/ctx.unstashAll()โ defer the current message for
later replay (typically because it arrived in the wrong phase).ctx.scheduleOnce(msg, delay)/ctx.scheduleAtFixedRate(msg, initialDelay, interval)โ self-send a
message after a delay, with a cancellable handle.
These capabilities are only available via the context-aware factory
overloads, which give the handler an ActorContext parameter.
=== Swapping behaviour with become
[source,groovy]
import groovy.concurrent.Actor
import groovy.concurrent.ActorContext
import groovy.concurrent.StatefulHandler
// Phase A increments by 1; phase B increments by 10.
StatefulHandler phaseB = { ctx, s, m -> s + 10 } as StatefulHandler
def actor = Actor.stateful(0, { ActorContext ctx, int s, m ->
if (m == 'swap') { ctx.become(phaseB); return s }
s + 1
} as StatefulHandler)
assert await(actor.sendAndGet('inc')) == 1 assert await(actor.sendAndGet('swap')) == 1 // state preserved assert await(actor.sendAndGet('inc')) == 11 // now phase B
The swap takes effect on the next message: the current handler
invocation completes normally (including binding any reply). State is
preserved verbatim; the new handler receives the same state value the
current one would have returned.
=== Deferring messages with stash
When a message arrives while the actor is in the wrong phase, defer
it with ctx.stash(). When the phase transitions to one that can
handle it, call ctx.unstashAll() to replay all deferred messages in
FIFO order at the head of the mailbox:
[source,groovy]
StatefulHandler connected = { ctx, s, m -> "got($m)" } as StatefulHandler
def actor = Actor.stateful('init', { ActorContext ctx, s, m ->
if (m == 'ready') {
ctx.become(connected)
ctx.unstashAll() // replay anything stashed while not ready
return s
}
ctx.stash() // any other message is deferred for now
s
} as StatefulHandler)
def r1 = actor.sendAndGet('A') // stashed def r2 = actor.sendAndGet('B') // stashed actor.send('ready') // โ connected, then replays A and B assert await(r1) == 'got(A)' assert await(r2) == 'got(B)'
The stash is unbounded by default.
WARNING: An actor that stashes messages from a source whose volume you
do not control (network input, external clients, untrusted callers)
can grow the stash without limit and exhaust the JVM heap if the phase
transition that would call unstashAll() never arrives. For any such
actor, set a bound via withStashBound at construction time.
[source,groovy]
def options = ActorOptions.DEFAULTS
.withStashBound(64, ActorOptions.StashOverflow.REJECT)
Policies are FAIL (throw from stash()), DROP_OLDEST (evict the
oldest stashed message, binding its sendAndGet reply to error), andREJECT (bind the current message's reply to error without stashing
it).
=== Scheduled self-sends
For state timeouts, heartbeats, retries, and periodic work, the
context exposes one-shot and recurring schedules over the shared
async scheduler:
[source,groovy]
import java.time.Duration
def actor = Actor.stateful(0, { ActorContext ctx, s, m ->
if (m == 'start') {
ctx.scheduleAtFixedRate('tick',
Duration.ofSeconds(1), Duration.ofSeconds(5))
return s
}
if (m == 'tick') return s + 1
s
} as StatefulHandler)
scheduleOnce returns a Cancellable so a phase transition can call
off a pending timer:
[source,groovy]
def timer = ctx.scheduleOnce('auth-timeout', Duration.ofSeconds(30)) // ... later, when AuthResult arrives: timer.cancel()
Actor.stop() cancels every outstanding timer the actor scheduled โ
no zombies after shutdown.
=== End-to-end: a connection handshake
A common FSM shape โ disconnected โ authenticating โ connected โ
combining all three capabilities:
[source,groovy]
import groovy.concurrent.Actor
import groovy.concurrent.ActorContext
import groovy.concurrent.StatefulHandler
StatefulHandler disconnected, authenticating, connected
disconnected = { ctx, s, m ->
if (m == 'connect') { ctx.become(authenticating); return s }
s
} as StatefulHandler
authenticating = { ctx, s, m ->
if (m == 'auth-ok') {
ctx.become(connected)
ctx.unstashAll()
return s
}
ctx.stash() // commands arriving during auth are deferred
s
} as StatefulHandler
connected = { ctx, s, m ->
// Real connected handlers use s โ here we bump an "ops processed"
// counter to demonstrate that state survives every transition.
[ops: s.ops + 1]
} as StatefulHandler
def actor = Actor.stateful([ops: 0], disconnected) actor.send('connect') def r1 = actor.sendAndGet([cmd: 'read']) // stashed in authenticating actor.send('auth-ok') // โ connected; r1 now processes assert await(r1) == [ops: 1] // state preserved across phases actor.stop()
[[actors-vs-agent]]
== Choosing Between Agent and Actor
[cols="1,1,1"]
|===
|Aspect |Agent |Actor
|State
|Single value, updated via function
|Arbitrary, managed by handler
|Messages
|Update functions only
|Any message type with dispatch
|Reply
|getAsync() / sendAndGet(fn) returns new value
|sendAndGet(msg) returns handler result
|Multi-phase behaviour
|โ
|become / stash / scheduled self-sends
|Use case
|Thread-safe counters, caches, accumulators
|State machines, services, typed protocols
|===
Both guarantee sequential message processing โ no locks needed.
[[actors-active-object]]
== @ActiveObject / @ActiveMethod
For a more OOP approach, annotate a class with @ActiveObject and
its methods with @ActiveMethod. The compiler automatically routes
annotated method calls through an internal actor โ callers just see
a normal class:
[source,groovy]
import groovy.transform.ActiveObject
import groovy.transform.ActiveMethod
@ActiveObject
class Account {
private double balance = 0
@ActiveMethod
void deposit(double amount) { balance += amount }
@ActiveMethod
void withdraw(double amount) {
if (amount > balance) throw new RuntimeException('Insufficient funds')
balance -= amount
}
@ActiveMethod
double getBalance() { balance }}
def account = new Account() account.deposit(100) // async, serialized, blocks until done account.deposit(50) account.withdraw(30) assert account.getBalance() == 120.0
Methods without @ActiveMethod run on the caller's thread as normal.
=== Blocking vs non-blocking
By default, @ActiveMethod calls block until the actor processes
them. For non-blocking calls, set blocking = false:
[source,groovy]
@ActiveObject
class Service {
@ActiveMethod(blocking = false)
def compute(int x) { x * x }
}
def svc = new Service() def result = svc.compute(7) // returns Awaitable immediately assert await(result) == 49
=== Thread safety by annotation
The key benefit: you write normal-looking classes with normal methods.
Thread safety is guaranteed by the annotation โ all @ActiveMethod
calls are serialized through the actor. No locks, no concurrent
collections, no race conditions.
This also makes the code highly readable for AI tools โ the@ActiveObject annotation explicitly declares the concurrency model,
and each @ActiveMethod contains pure business logic without
messaging boilerplate.
Src Spec Doc Core Concurrent Dataflow (`src/spec/doc/core-concurrent-dataflow.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Concurrent Dataflow (Incubating)
[[dataflow-intro]]
== Introduction
Dataflow programming lets you express dependencies between concurrent
computations declaratively. Values are bound once and read many times โ
the runtime handles ordering automatically. You never need to think
about which thread runs first.
Groovy's dataflow support integrates natively with
<<{core-async-await}#async-intro,async/await>> and theAwaitable API.
[[dataflow-variables]]
== DataflowVariable
A DataflowVariable is a single-assignment variable. It starts unbound,
can be written to exactly once, and any thread that reads it before
binding will block until a value is available.
[source,groovy]
import groovy.concurrent.DataflowVariable
def x = new DataflowVariable()
def y = new DataflowVariable()
def z = new DataflowVariable()
async {
z << await(x) + await(y) // blocks until x and y are bound
}
async { x << 10 } // bind x โ order doesn't matter
async { y << 5 } // bind y
println "Result: ${await(z)}" // 15
Key properties:
- Write-once: calling
bind()or<<a second time throwsIllegalStateException - Implements
Awaitable: works withawait, combinators (Awaitable.all), andthenchaining - Thread-safe: safe to bind from one thread and read from any number of others
=== Error binding
[source,groovy]
def v = new DataflowVariable() v.bindError(new RuntimeException('computation failed')) // Any thread that awaits v will receive the exception
=== Chaining with then
[source,groovy]
def v = new DataflowVariable()
def doubled = v.then { it * 2 }
async { v << 21 } assert await(doubled) == 42
=== Combining multiple variables
[source,groovy]
def a = new DataflowVariable()
def b = new DataflowVariable()
def c = new DataflowVariable()
async { a << 1 }
async { b << 2 }
async { c << 3 }
def results = await(a, b, c) assert results == [1, 2, 3]
[[dataflow-dataflows]]
== Dataflows
Dataflows is a convenience class that auto-creates DataflowVariable
instances on demand. Property reads block until bound; property writes
bind the variable.
[source,groovy]
import groovy.concurrent.Dataflows
def df = new Dataflows()
async { df.z = df.x + df.y } // blocks on x and y, binds z
async { df.x = 10 }
async { df.y = 5 }
println "Result: ${df.z}" // 15
This is the most concise way to write dataflow programs in Groovy.
Variables are created lazily โ no declarations needed.
[source,groovy]
def df = new Dataflows()
async { df.fullName = "${df.first} ${df.last}" }
async { df.first = 'John' }
async { df.last = 'Doe' }
assert df.fullName == 'John Doe'
=== Accessing the underlying variable
Use getVariable() when you need the DataflowVariable itself
(e.g., to pass to Awaitable.all or check isBound):
[source,groovy]
def df = new Dataflows() def v = df.getVariable('x') // DataflowVariable, does not block assert !v.isBound() async { df.x = 42 } assert await(v) == 42
Src Spec Doc Core Concurrent Java (`src/spec/doc/core-concurrent-java.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Groovy Concurrent API for Java (Incubating)
[[concurrent-java-intro]]
== Introduction
The groovy-concurrent-java module provides Groovy's concurrent and
parallel API as a standalone Java library โ no Groovy runtime required.
Java developers get access to:
- Structured concurrency โ
AsyncScope,Awaitable - Pools โ
Pool,ParallelScope,ConcurrentConfig - Actors โ
Actor.reactor(),Actor.stateful() - Agents โ
Agent(thread-safe mutable state) - Dataflow โ
DataflowVariable,AsyncChannel,BroadcastChannel,ChannelSelect
This module is published as org.apache.groovy:groovy-concurrent-java
and contains a filtered subset of the classes from Groovy core. It must
not be used alongside the full Groovy runtime โ Gradle enforces mutual
exclusion via a shared capability, and a runtime warning is logged if
both jars are detected on the classpath.
[[concurrent-java-setup]]
== Setup
=== Gradle
[source,groovy]
dependencies {
implementation 'org.apache.groovy:groovy-concurrent-java:6.0.0'
}
=== Maven
[source,xml]
IMPORTANT: Do not add groovy-concurrent-java if you already depend ongroovy (the full runtime). The concurrent classes are included in core.
[[concurrent-java-scope]]
== Structured Concurrency
[source,java]
import groovy.concurrent.AsyncScope;
import groovy.concurrent.Pool;
import org.apache.groovy.runtime.async.AsyncSupport;
// Basic scope โ waits for all children before returning
var result = AsyncScope.withScope(scope -> {
var a = scope.async(() -> fetchUser(id));
var b = scope.async(() -> fetchOrders(id));
return Map.of(
"user", AsyncSupport.await(a),
"orders", AsyncSupport.await(b)
);
});
// With a pool for executor isolation
try (var pool = Pool.cpu()) {
AsyncScope.withScope(pool, scope -> {
var task = scope.async(() -> computeResult());
return AsyncSupport.await(task);
});
}
[[concurrent-java-actors]]
== Actors
[source,java]
import groovy.concurrent.Actor;
import org.apache.groovy.runtime.async.AsyncSupport;
// Reactor โ stateless, each message produces a reply
var doubler = Actor.reactor(n -> n * 2);
int result = AsyncSupport.await(doubler.sendAndGet(21)); // 42
doubler.stop();
// Stateful โ maintains state across messages
var counter = Actor.<String, Integer>stateful(0, (state, msg) -> {
if ("increment".equals(msg)) return state + 1;
return state;
});
counter.send("increment");
counter.send("increment");
int count = AsyncSupport.await(counter.sendAndGet("increment")); // 3
counter.stop();
[[concurrent-java-agents]]
== Agents
[source,java]
import groovy.concurrent.Agent;
import org.apache.groovy.runtime.async.AsyncSupport;
var counter = Agent.create(0);
counter.send(n -> n + 1);
counter.send(n -> n + 1);
counter.send(n -> n + 1);
int result = AsyncSupport.await(counter.getAsync()); // 3 counter.shutdown();
[[concurrent-java-dataflow]]
== Dataflow Variables
[source,java]
import groovy.concurrent.Awaitable;
import groovy.concurrent.DataflowVariable;
import org.apache.groovy.runtime.async.AsyncSupport;
var x = new DataflowVariable();
var y = new DataflowVariable();
// z depends on x and y โ blocks until both are bound
var z = Awaitable.go(() ->
AsyncSupport.await(x) + AsyncSupport.await(y)
);
// Bind in any order
AsyncSupport.getExecutor().execute(() -> x.bind(10));
AsyncSupport.getExecutor().execute(() -> y.bind(5));
int result = AsyncSupport.await(z); // 15
[[concurrent-java-pools]]
== Pools
[source,java]
import groovy.concurrent.ParallelScope;
import groovy.concurrent.Pool;
import org.apache.groovy.runtime.async.AsyncSupport;
var result = ParallelScope.withPool(4, scope -> {
var tasks = new java.util.ArrayList<groovy.concurrent.Awaitable>();
for (int i = 1; i <= 4; i++) {
int n = i;
tasks.add(scope.async(() -> n * 10));
}
int sum = 0;
for (var task : tasks) {
sum += (int) AsyncSupport.await(task);
}
return sum;
}); // 100
[[concurrent-java-not-included]]
== Features not available in the Java module
The following features require the Groovy runtime and are only
available when using the full groovy dependency:
[cols="1,2"]
|===
|Feature |Reason
|Dataflows class
|Uses Groovy's propertyMissing for dynamic property access
|async { } / await keywords
|Groovy language syntax, transformed by the Groovy compiler
|defer { } / yield return
|Groovy language syntax
|for await (x in channel)
|Groovy compiler-generated iteration
|@ActiveObject / @ActiveMethod
|Groovy AST transformation
|Parallel collection methods (collectParallel, etc.)
|Registered as Groovy extension methods on Collection
|AsyncClosureUtils (wrapAsync, wrapAsyncGenerator)
|Return Closure subclasses
|Closure-based APIs (e.g., scope.async { })
|Groovy SAM coercion from Closure to Supplier/Function
|===
Java users have full access to the underlying APIs usingjava.util.function types (Supplier, Function, Consumer,Predicate, etc.) and explicit AsyncSupport.await() calls
instead of the await keyword.
Src Spec Doc Core Differences Java (`src/spec/doc/core-differences-java.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
ifndef::core-semantics[]
:core-semantics: core-semantics.adoc
endif::[]
ifndef::core-syntax[]
:core-syntax: core-syntax.adoc
endif::[]
= Differences with Java
Groovy tries to be as natural as possible for Java developers. Weโve
tried to follow the principle of least surprise when designing Groovy,
particularly for developers learning Groovy whoโve come from a Java
background.
Here we list all the major differences between Java and Groovy.
== Default imports
All these packages and classes are imported by default, i.e. you do not
have to use an explicit import statement to use them:
- java.io.*
- java.lang.*
- java.math.BigDecimal
- java.math.BigInteger
- java.net.*
- java.time.*
- java.util.*
- groovy.lang.*
- groovy.util.*
== Multi-methods
In Groovy, the methods which will be invoked are chosen at runtime. This is called runtime dispatch or multi-methods. It
means that the method will be chosen based on the types of the arguments at runtime. In Java, this is the opposite: methods
are chosen at compile time, based on the declared types.
The following code, written as Java code, can be compiled in both Java and Groovy, but it will behave differently:
[source,java]
include::../test/DifferencesFromJavaTest.groovy[tags=multimethods,indent=0]
In Java, you would have:
[source,java]
include::../test/DifferencesFromJavaTest.groovy[tags=multimethods_java,indent=0]
Whereas in Groovy:
[source,java]
include::../test/DifferencesFromJavaTest.groovy[tags=multimethods_groovy,indent=0]
That is because Java will use the static information type, which is that o is declared as an Object, whereas
Groovy will choose at runtime, when the method is actually called. Since it is called with a String, then theString version is called.
== Array initializers
In Java, array initializers take either of these two forms:
[source,java]
include::../test/DifferencesFromJavaTest.groovy[tags=arraycreate_fail,indent=0]
In Groovy, the { ... } block is reserved for closures.
That means that you cannot create array literals using Java's array initializer shorthand syntax.
You instead borrow Groovy's literal list notation like this:
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=arraycreate_success,indent=0]
For Groovy 3+, you can optionally use the Java's array initializer long syntax:
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=arraycreate3_success,indent=0]
== Package scope visibility
In Groovy, omitting a modifier on a field doesn't result in a package-private field like in Java:
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=packageprivate_property,indent=0]
Instead, it is used to create a property, that is to say a private field, an associated getter and an associated
setter.
It is possible to create a package-private field by annotating it with @PackageScope:
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=packageprivate_field,indent=0]
== ARM blocks
Java 7 introduced ARM (Automatic Resource Management) blocks (also know as try-with-resources) blocks like this:
[source,java]
Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
Such blocks are supported from Groovy 3+.
However, Groovy provides various methods relying on closures, which have the same effect while being more idiomatic. For example:
[source,groovy]
new File('/path/to/file').eachLine('UTF-8') { println it }
or, if you want a version closer to Java:
[source,groovy]
new File('/path/to/file').withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}
== Inner classes
WARNING: The implementation of anonymous inner classes and nested classes follow Java closely,
but there are some differences, e.g.
local variables accessed from within such classes don't have to be final.
We piggyback on some implementation details we use for groovy.lang.Closure
when generating inner class bytecode.
=== Static inner classes
Here's an example of static inner class:
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_1,indent=0]
The usage of static inner classes is the best supported one. If you
absolutely need an inner class, you should make it a static one.
=== Anonymous Inner Classes
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_2,indent=0]
=== Creating Instances of Non-Static Inner Classes
In Java you can do this:
[source,java]
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_3_java,indent=0]
Before 3.0.0, Groovy doesn't support the y.new X() syntax. Instead, you have to write new X(y), like in the code below:
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_3,indent=0]
[WARNING]
Caution though, Groovy supports calling methods with one
parameter without giving an argument. The parameter will then have the
value null. Basically the same rules apply to calling a constructor.
There is a danger that you will write new X() instead of new X(this) for
example. Since this might also be the regular way we have not yet found
a good way to prevent this problem.
[NOTE]
Groovy 3.0.0 supports Java style syntax for creating instances of non-static inner classes.
== Lambda expressions and the method reference operator
Java 8+ supports lambda expressions and the method reference operator (::):
[source,java]
Runnable run = () -> System.out.println("Run"); // Java list.forEach(System.out::println);
Groovy 3 and above also support these within the Parrot parser.
In earlier versions of Groovy you should use closures instead:
[source,groovy]
Runnable run = { println 'run' } list.each { println it } // or list.each(this.&println)
Since Groovy 6.0, intersection-type casts (https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.16[JLS ยง15.16])
are supported on lambdas and method references, so a lambda can opt into Serializable
or other marker interfaces, just like in Java:
[source,groovy]
Runnable r = (Runnable & Serializable) () -> println('hi') // serializable lambda
Groovy additionally accepts the as form, where parentheses are required around
the intersection:
[source,groovy]
def r = { -> println 'hi' } as (Runnable & Serializable)
See the <<{core-semantics}#intersection-cast,Intersection-type cast and coercion>> section in the semantics guide.
== GStrings
As double-quoted string literals are interpreted as GString values, Groovy may fail
with compile error or produce subtly different code if a class with String literal
containing a dollar character is compiled with Groovy and Java compiler.
While typically, Groovy will auto-cast between GString and String if an API declares
the type of a parameter, beware of Java APIs that accept an Object parameter and then
check the actual type.
== String and Character literals
Singly-quoted literals in Groovy are used for String, and double-quoted result inString or GString, depending whether there is interpolation in the literal.
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=type_depends_on_quoting_AND_whether_we_actually_interpolate,indent=0]
Groovy will automatically cast a single-character String to char only when assigning to
a variable of type char. When calling methods with arguments of type char we need
to either cast explicitly or make sure the value has been cast in advance.
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=single_char_strings_are_autocasted,indent=0]
Groovy supports two styles of casting and in the case of casting to char there
are subtle differences when casting a multi-char strings. The Groovy style cast is
more lenient and will take the first character, while the C-style cast will fail
with exception.
[source,groovy]
include::../test/DifferencesFromJavaTest.groovy[tags=chars_c_vs_groovy_cast,indent=0]
== Behaviour of ==
In Java, == means equality of primitive types or identity for objects.
In Groovy, == means equality in all places.
For non-primitives, it translates to a.compareTo(b) == 0,
when evaluating equality for Comparable objects,
and a.equals(b) otherwise.
To check for identity (reference equality), use the is method: a.is(b).
From Groovy 3, you can also use the === operator (or negated version): a === b (or c !== d).
== Primitives and wrappers
In a pure object-oriented language, everything would be an object.
Java takes the stance that primitive types, such as int, boolean and double,
are used very frequently and worthy of special treatment.
Primitives can be efficiently stored and manipulated but can't be used in all contexts where an object could be used.
Luckily, Java auto boxes and unboxes primitives when they are passed as parameters or used as return types:
[source,java]
public class Main { // Java
float f1 = 1.0f;
Float f2 = 2.0f;
float add(Float a1, float a2) { return a1 + a2; }
Float calc() { return add(f1, f2); } // <1>
public static void main(String[] args) {
Float calcResult = new Main().calc();
System.out.println(calcResult); // => 3.0
}}
<1> The add method expects wrapper then primitive type arguments,
but we are supplying parameters with a primitive then wrapper type.
Similarly, the return type from add is primitive, but we need the wrapper type.
Groovy does the same:
[source,groovy]
class Main {
float f1 = 1.0f
Float f2 = 2.0f
float add(Float a1, float a2) { a1 + a2 }
Float calc() { add(f1, f2) }}
assert new Main().calc() == 3.0
Groovy, also supports primitives and object types, however, it goes a little further
in pushing OO purity; it tries hard to treat everything as an object.
Any primitive typed variable or field can be treated like an object, and it
will be link:core-object-orientation.html#_primitive_types[auto-wrapped] as needed.
While primitive types might be used under the covers,
their use should be indistinguishable from normal object use whenever possible,
and they will be boxed/unboxed as needed.
Here is a little example using Java trying to (incorrectly for Java) dereference a primitive float:
[source,java]
public class Main { // Java
public float z1 = 0.0f;
public static void main(String[] args){
new Main().z1.equals(1.0f); // DOESN'T COMPILE, error: float cannot be dereferenced
}}
The same example using Groovy compiles and runs successfully:
[source,groovy]
class Main {
float z1 = 0.0f
}
assert !(new Main().z1.equals(1.0f))
Because of Groovy's additional use of un/boxing, it does not follow
Java's behavior of widening taking priority over boxing.
Here's an example using int
[source,groovy]
include::../test/PrimitiveTest.groovy[tags=widening_vs_boxing,indent=0]
<1> This is the method that Java would call, since widening has precedence over unboxing.
<2> This is the method Groovy actually calls, since all primitive references use their wrapper class.
[[primitive-optimisation-numbers]]
=== Numeric Primitive Optimisation with @CompileStatic
Since Groovy converts to wrapper classes in more places, you might wonder
whether it produces less efficient bytecode for numeric expressions.
Groovy has a highly optimised set of classes for doing math computations.
When using @CompileStatic, expressions involving only primitives
uses the same bytecode that Java would use.
[[zero-edge-case]]
=== Positive/Negative zero edge case
Java float/double operations for both primitives and wrapper classes follow the IEEE 754 standard
but there is an interesting edge case involving positive and negative zero.
The standard supports distinguishing between these two cases and while in many scenarios
programmers may not care about the difference, in some mathematical or data science scenarios
it is important to cater for the distinction.
For primitives, Java maps down onto a special
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dcmp_op[bytecode instruction]
when comparing such values which has the property that
"Positive zero and negative zero are considered equal".
[source,ruby]
jshell> float f1 = 0.0f
f1 ==> 0.0
jshell> float f2 = -0.0f
f2 ==> -0.0
jshell> f1 == f2 $3 ==> true
For the wrapper classes, e.g. jdk:java.base/java.lang.Float#equals(java.lang.Object)[Float#equals],
the result is false for this same case.
[source,ruby]
jshell> Float f1 = 0.0f
f1 ==> 0.0
jshell> Float f2 = -0.0f
f2 ==> -0.0
jshell> f1.equals(f2) $3 ==> false
Groovy on the one hand tries to follow Java behavior closely, but on the other
switches automatically between primitives and wrapped equivalents in more places.
To avoid confusion we recommend the following guidelines:
- If you wish to distinguish between positive and negative zero, use the
equals
method directly or cast any primitives to their wrapper equivalent before using==. - If you wish to ignore the difference between positive and negative zero, use the
equalsIgnoreZeroSign
method directly or cast any non-primitives to their primitive equivalent before using==.
These guidelines are illustrated in the following example:
[source,groovy]
include::../test/PrimitiveTest.groovy[tags=pos_neg_zero,indent=0]
<1> Recall that for non-primitives, == maps to .equals()
== Conversions
Java does automatic widening and narrowing
https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html[conversions].
[cols=">2s,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a"]
.Java Conversions
|============================================================================================
| 8+|Converts to
| Converts from | boolean | byte | short | char | int | long | float | double
| boolean | - | N | N | N | N | N | N | N
| byte | N | - | Y | C | Y | Y | Y | Y
| short | N | C | - | C | Y | Y | Y | Y
| char | N | C | C | - | Y | Y | Y | Y
| int | N | C | C | C | - | Y | T | Y
| long | N | C | C | C | C | - | T | T
| float | N | C | C | C | C | C | - | Y
| double | N | C | C | C | C | C | C | -
|============================================================================================
- 'Y' indicates a conversion Java can make +
- 'C' indicates a conversion Java can make when there is an explicit cast +
- 'T` indicates a conversion Java can make but data is truncated +
- 'N' indicates a conversion Java can't make
Groovy expands greatly on this.
[%autowidth]
[cols=">2s,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a"]
.Groovy Conversions
|=================================================================================================================================================================================================================
| 18+|Converts to
| Converts from |[.rotate]boolean |[.rotate]Boolean |[.rotate]byte |[.rotate]Byte |[.rotate]short |[.rotate]Short |[.rotate]char |[.rotate]Character |[.rotate]int |[.rotate]Integer |[.rotate]long |[.rotate]Long |[.rotate]BigInteger |[.rotate]float |[.rotate]Float |[.rotate]double |[.rotate]Double |[.rotate]BigDecimal
| boolean | - | B | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N
| Boolean | B | - | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N
| byte | T | T | - | B | Y | Y | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| Byte | T | T | B | - | Y | Y | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| short | T | T | D | D | - | B | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| Short | T | T | D | T | B | - | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| char | T | T | Y | D | Y | D | - | D | Y | D | Y | D | D | Y | D | Y | D | D
| Character | T | T | D | D | D | D | D | - | D | D | D | D | D | D | D | D | D | D
| int | T | T | D | D | D | D | Y | D | - | B | Y | Y | Y | Y | Y | Y | Y | Y
| Integer | T | T | D | D | D | D | Y | D | B | - | Y | Y | Y | Y | Y | Y | Y | Y
| long | T | T | D | D | D | D | Y | D | D | D | - | B | Y | T | T | T | T | Y
| Long | T | T | D | D | D | T | Y | D | D | T | B | - | Y | T | T | T | T | Y
| BigInteger | T | T | D | D | D | D | D | D | D | D | D | D | - | D | D | D | D | T
| float | T | T | D | D | D | D | T | D | D | D | D | D | D | - | B | Y | Y | Y
| Float | T | T | D | T | D | T | T | D | D | T | D | T | D | B | - | Y | Y | Y
| double | T | T | D | D | D | D | T | D | D | D | D | D | D | D | D | - | B | Y
| Double | T | T | D | T | D | T | T | D | D | T | D | T | D | D | T | B | - | Y
| BigDecimal | T | T | D | D | D | D | D | D | D | D | D | D | D | T | D | T | D | -
|=================================================================================================================================================================================================================
- 'Y' indicates a conversion Groovy can make +
- 'D' indicates a conversion Groovy can make when compiled dynamically or explicitly cast +
- 'T' indicates a conversion Groovy can make but data is truncated +
- 'B' indicates a boxing/unboxing operation +
- 'N' indicates a conversion Groovy can't make.
The truncation uses <<{core-semantics}#the-groovy-truth,Groovy Truth>> when converting to boolean/Boolean. Converting
from a number to a character casts the Number.intvalue() to char. Groovy constructs BigInteger and BigDecimal
using Number.doubleValue() when converting from a Float or Double, otherwise it constructs using toString().
Other conversions have their behavior defined by java.lang.Number.
== Extra keywords
Groovy has many of the same keywords as Java and Groovy 3 and above also has the same var reserved type as Java.
In addition, Groovy has the following keywords:
asdefintraitit// within closures
Groovy is less stringent than Java in that it allows some keywords to appear in places that would be illegal in Java,
e.g. the following is valid: var var = [def: 1, as: 2, in: 3, trait: 4].
Never-the-less, you are discouraged from using the above keywords in places that might cause confusion even when
the compiler might be happy. In particular, avoid using them for variable, method and class names,
so our previous var var example would be considered poor style.
Additional documentation is available for <<{core-syntax}#_keywords,keywords>>.
Src Spec Doc Core Domain Specific Languages (`src/spec/doc/core-domain-specific-languages.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
ifndef::reldir_root[]
:reldir_root: .
endif::[]
ifndef::core-semantics[]
:core-semantics: core-semantics.adoc
endif::[]
ifndef::xml-userguide[]
:xml-userguide: xml-userguide.adoc
endif::[]
ifndef::jmx[]
:jmx: jmx.adoc
endif::[]
= Domain-Specific Languages
== Command chains
Groovy lets you omit parentheses around the arguments of a
method call for top-level statements. "command chain" feature extends this by allowing us to chain such
parentheses-free method calls, requiring neither parentheses around arguments, nor dots between the chained calls.
The general idea is that a call like a b c d will actually be equivalent to a(b).c(d). This
also works with multiple arguments, closure arguments, and even named arguments. Furthermore, such command chains can
also appear on the right-hand side of assignments. Letโs have a look at some examples
supported by this new syntax:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_1,indent=0]
include::../test/CommandChainsTest.groovy[tags=commandchain_2,indent=0]
include::../test/CommandChainsTest.groovy[tags=commandchain_3,indent=0]
include::../test/CommandChainsTest.groovy[tags=commandchain_4,indent=0]
include::../test/CommandChainsTest.groovy[tags=commandchain_5,indent=0]
It is also possible to use methods in the chain which take no arguments,
but in that case, the parentheses are needed:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_6,indent=0]
If your command chain contains an odd number of elements, the chain will
be composed of method / arguments, and will finish by a final property
access:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_7,indent=0]
This command chain approach opens up interesting possibilities in terms of the much wider range of DSLs which
can now be written in Groovy.
The above examples illustrate using a command chain based DSL but not how to create one. There are various strategies
that you can use, but to illustrate creating such a DSL, we will show a couple of examples - first using maps and Closures:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_impl1,indent=0]
As a second example, consider how you might write a DSL for simplifying
one of your existing APIs. Maybe you need to put this code in front of
customers, business analysts or testers who might be not hard-core Java
developers. Weโll use the Splitter from the Google
https://github.com/google/guava[Guava libraries] project as it
already has a nice Fluent API. Here is how we might use it out of the
box:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_impl2,indent=0] include::../test/CommandChainsTest.groovy[tags=commandchain_impl2_assert,indent=0]
It reads fairly well for a Java developer but if that is not your target
audience or you have many such statements to write, it could be
considered a little verbose. Again, there are many options for writing a
DSL. Weโll keep it simple with Maps and Closures. Weโll first write a
helper method:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_impl3,indent=0]
now instead of this line from our original example:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_impl2_assert,indent=0]
we can write this:
[source,groovy]
include::../test/CommandChainsTest.groovy[tags=commandchain_impl3_assert,indent=0]
== Operator overloading
Various operators in Groovy are mapped onto regular method calls on objects.
This allows you to provide your own Java or Groovy objects which can take advantage of operator overloading. The following table describes the operators supported in Groovy and the methods they map to.
[options="header"]
|======================
|Operator | Method
| a + b | a.plus(b)
| a - b | a.minus(b)
| a * b | a.multiply(b)
| a ** b | a.power(b)
| a / b | a.div(b)
| a % b | a.mod(b)
| a \| b | a.or(b)
| a & b | a.and(b)
| a ^ b | a.xor(b)
| pass:[a++] or pass:[++a] | a.next()
| a-- or --a | a.previous()
| a[b] | a.getAt(b)
| a[b] = c | a.putAt(b, c)
| a << b | a.leftShift(b)
| a >> b | a.rightShift(b)
| a >>> b | a.rightShiftUnsigned(b)
| switch(a) { case(b) : } | b.isCase(a)
| if(a) | a.asBoolean()
| ~a | a.bitwiseNegate()
| -a | a.negative()
| +a | a.positive()
| a as b | a.asType(b)
| a == b | a.equals(b)
| a != b | ! a.equals(b)
| a \<\=> b | a.compareTo(b)
| a > b | a.compareTo(b) > 0
| a >= b | a.compareTo(b) >= 0
| a \< b | a.compareTo(b) < 0
| a \<= b | a.compareTo(b) <= 0
|======================
== Script base classes
=== The Script class
Groovy scripts are always compiled to classes. For example, a script as simple as:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=simple_script,indent=0]
is compiled to a class extending the abstract gapi:groovy.lang.Script[] class. This class contains a single abstract
method called run. When a script is compiled, then its body will become the run method, while the other methods
found in the script are found in the implementing class. The Script class provides base support for integration
with your application through the Binding object, as illustrated in this example:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=integ_binding,indent=0]
<1> a binding is used to share data between the script and the calling class
<2> a GroovyShell can be used with this binding
<3> input variables are set from the calling class inside the binding
<4> then the script is evaluated
<5> and the z variable has been "exported" into the binding
This is a very practical way to share data between the caller and the script, however it may be insufficient or not
practical in some cases. For that purpose, Groovy allows you to set your own base script class. A base script class
has to extend gapi:groovy.lang.Script[] and be a single abstract method type:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=baseclass_def,indent=0]
Then the custom script base class can be declared in the compiler configuration, for example:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=use_custom_conf,indent=0]
<1> create a custom compiler configuration
<2> set the base script class to our custom base script class
<3> then create a GroovyShell using that configuration
<4> the script will then extend the base script class, giving direct access to the name property and greet method
[[dsl-basescript]]
=== The @BaseScript annotation
As an alternative, it is also possible to use the @BaseScript annotation directly into a script:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=use_basescript,indent=0]
where @BaseScript should annotate a variable which type is the class of the base script. Alternatively, you can set
the base script class as a member of the @BaseScript annotation itself:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=use_basescript_alt,indent=0]
When using the special no-args run method, you can even annotate
that method as shown here:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=jep445_example,indent=0]
=== Alternate abstract method
We have seen that the base script class is a single abstract method type that needs to implement the run method. Therun method is executed by the script engine automatically. In some circumstances it may be interesting to have a base
class which implements the run method, but provides an alternative abstract method to be used for the script body.
For example, the base script run method might perform some initialization before the run method is executed. This
is possible by doing this:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=custom_run_method,indent=0]
<1> the base script class should define one (and only one) abstract method
<2> the run method can be overridden and perform a task before executing the script body
<3> run calls the abstract scriptBody method which will delegate to the user script
<4> then it can return something else than the value from the script
If you execute this code:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=custom_run_eval,indent=0]
Then you will see that the script is executed, but the result of the evaluation is 1 as returned by the run
method of the base class. It is even clearer if you use parse instead of evaluate, because it would allow you to
execute the run method several times on the same script instance:
[source,groovy]
include::../test/BaseScriptSpecTest.groovy[tags=custom_run_parse,indent=0]
== Adding properties to numbers
In Groovy number types are considered equal to any other types. As such, it is possible to enhance numbers by adding
properties or methods to them. This can be very handy when dealing with measurable quantities for example. Details about
how existing classes can be enhanced in Groovy are found in the link:core-metaprogramming.html#extension-modules[extension
modules] section or the link:core-metaprogramming.html#categories[categories] section.
An illustration of this can be found in Groovy using the TimeCategory:
[source,groovy]
include::../test/metaprogramming/CategoryTest.groovy[tags=time_category,indent=0]
<1> using the TimeCategory, a property minute is added to the Integer class
<2> similarly, the months method returns a groovy.time.DatumDependentDuration which can be used in calculus
Categories are lexically bound, making them a great fit for internal DSLs.
[[section-delegatesto]]
== @DelegatesTo
[[TheDelegatesToannotation-DSLsmadeeasy]]
=== Explaining delegation strategy at compile time
@groovy.lang.DelegatesTo is a documentation and compile-time annotation aimed at:
- documenting APIs that use closures as arguments
- providing type information for the static type checker and compiler
The Groovy language is a platform of choice for building DSLs. Using
closures, itโs quite easy to create custom control structures, as well
as it is simple to create builders. Imagine that you have the following
code:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=email_builder_usage,indent=0]
One way of implementing this is using the builder strategy, which
implies a method, named emailย which accepts a closure as an argument.
The method may delegate subsequent calls to an object that implements
theย from,ย to,ย subject andย body methods. Again,ย body is a
method which accepts a closure as an argument and that uses the builder
strategy.
Implementing such a builder is usually done the following way:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=email_method_no_delegatesto,indent=0]
the EmailSpec class implements theย from,ย to, โฆ methods. By
callingย rehydrate, weโre creating a copy of the closure for which we
set theย delegate,ย owner andย thisObject values. Setting the owner
and the this object is not very important here since we will use theDELEGATE_ONLY strategy which says that the method calls will be
resolved only against the delegate of the closure.
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=emailspec_no_delegatesto,indent=0]
The EmailSpec class has itself a body method accepting a closure that is cloned and executed. This is what
we call the builder pattern in Groovy.
One of the problems with the code that weโve shown is that the user of
the email method doesnโt have any information about the methods that
heโs allowed to call inside the closure. The only possible information
is from the method documentation. There are two issues with this: first
of all, documentation is not always written, and if it is, itโs not
always available (javadoc not downloaded, for example). Second, it
doesnโt help IDEs. What would be really interesting, here, is for IDEs
to help the developer by suggesting, once they are in the closure body,
methods that exist on the email class.
Moreover, if the user calls a method in the closure which is not defined
by theย EmailSpec class, the IDE should at least issue a warning (because
itโs very likely that it will break at runtime).
One more problem with the code above is that it is not compatible with static type checking. Type checking would let
the user know if a method call is authorized at compile time instead of runtime, but if you try to perform type
checking on this code:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=email_builder_usage,indent=0]
Then the type checker will know that thereโs anย email method accepting
aย Closure, but it will complain about every method callย inside the
closure, becauseย from, for example, is not a method which is defined
in the class. Indeed, itโs defined in theย EmailSpec class and it has
absolutely no hint to help it knowing that the closure delegate will, at
runtime, be of typeย EmailSpec:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=sendmail_typechecked_nodelegatesto,indent=0]
will fail compilation with errors like this one:
[Static type checking] - Cannot find matching method MyScript#from(java.lang.String). Please check if the declared type is correct and if the method exists.
@ line 31, column 21.
from '[email protected]'
[[TheDelegatesToannotation-DelegatesTo]]
=== @DelegatesTo
For those reasons, Groovy 2.1 introduced a new annotation
namedย @DelegatesTo. The goal of this annotation is to solve both the
documentation issue, that will let your IDE know about the expected
methods in the closure body, and it will also solve the type checking
issue, by giving hints to the compiler about what are the potential
receivers of method calls in the closure body.
The idea is to annotate theย Closure parameter of theย email method:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=email_method_delegatesto,indent=0]
What weโve done here is telling the compiler (or the IDE) that when the
method will be called with a closure, the delegate of this closure will
be set to an object of typeย email. But there is still a problem: the
default delegation strategy is not the one which is used in our method.
So we will give more information and tell the compiler (or the IDE) that
the delegation strategy is also changed:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=email_method_delegatesto_strategy,indent=0]
Now, both the IDE and the type checker (if you are using @TypeChecked)
will be aware of the delegate and the delegation strategy. This is very
nice because it will both allow the IDE to provide smart completion, but
it will also remove errors at compile time that exist only because the
behaviour of the program is normally only known at runtime!
The following code will now pass compilation:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=sendmail_typechecked_pass,indent=0]
[[TheDelegatesToannotation-DelegatesTomodes]]
=== DelegatesTo modes
@DelegatesTo supports multiple modes that we will describe with examples
in this section.
[[TheDelegatesToannotation-Simpledelegation]]
==== Simple delegation
In this mode, the only mandatory parameter is theย value which says to
which class we delegate calls. Nothing more. Weโre telling the compiler
that the type of the delegate willย always be of the type documented
byย @DelegatesTo (note that it can be a subclass, but if it is, the
methods defined by the subclass will not be visible to the type
checker).
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=simple_delegation,indent=0]
[[TheDelegatesToannotation-Delegationstrategy]]
==== Delegation strategy
In this mode, you must specify both the delegate classย and a
delegation strategy. This must be used if the closure will not be called
with the default delegation strategy, which isย Closure.OWNER_FIRST.
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=delegation_with_strategy,indent=0]
[[TheDelegatesToannotation-Delegatetoparameter]]
==== Delegate to parameter
In this variant, we will tell the compiler that we are delegating to
another parameter of the method. Take the following code:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=exec_method_no_delegatesto,indent=0]
Here, the delegate which will be used isย not created inside theย exec
method. In fact, we take an argument of the method and delegate to it.
Usage may look like this:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=exec_usage,indent=0]
Each of the method calls are delegated to theย email parameter. This is
a widely used pattern which is also supported byย @DelegatesTo using a
companion annotation:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=exec_method_with_delegatesto,indent=0]
A closure is annotated withย @DelegatesTo, but this time, without
specifying any class. Instead, weโre annotating another parameter
withย @DelegatesTo.Target. The type of the delegate is then determined
at compile time. One could think that we are using the parameter type,
which in this case isย Object but this is not true. Take this code:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=delegatesto_flow_typing_header,indent=0] include::../test/DelegatesToSpecTest.groovy[tags=delegatesto_flow_typing_footer,indent=0]
Remember that this works out of the boxย without having to annotate
withย @DelegatesTo. However, to make the IDE aware of the delegate
type, or theย type checker aware of it, we need to addย @DelegatesTo.
And in this case, it will know that theย Greeter variable is of
typeย Greeter, so it will not report errors on theย sayHello
methodย even if the exec method doesnโt explicitly define the target as
of type Greeter. This is a very powerful feature, because it prevents
you from writing multiple versions of the sameย exec method for
different receiver types!
In this mode, theย @DelegatesTo annotation also supports theย strategy
parameter that weโve described upper.
[[TheDelegatesToannotation-Multipleclosures]]
==== Multiple closures
In the previous example, theย exec method accepted only one closure,
but you may have methods that take multiple closures:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=foobarbaz_method_no_delegatesto,indent=0]
Then nothing prevents you from annotating each closure
withย @DelegatesTo:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=foobarbaz_classes,indent=0]
include::../test/DelegatesToSpecTest.groovy[tags=foobarbaz_method_header,indent=0] ... include::../test/DelegatesToSpecTest.groovy[tags=foobarbaz_method_footer,indent=0]
But more importantly, if you have multiple closuresย and multiple
arguments, you can use several targets:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=foobarbaz_multitarget,indent=0]
include::../test/DelegatesToSpecTest.groovy[tags=multitarget_test,indent=0]
NOTE: At this point, you may wonder why we donโt use the parameter names as
references. The reason is that the information (the parameter name) is
not always available (itโs a debug-only information), so itโs a
limitation of the JVM.
==== Delegating to a generic type
In some situations, it is interesting to instruct the IDE or the compiler that the delegate type will not be a parameter
but a generic type. Imagine a configurator that runs on a list of elements:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=configure_list_method,indent=0]
Then this method can be called with any list like this:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=configure_list_usage,indent=0]
To let the type checker and the IDE know that the configure method calls the closure on each element of the list, you
need to use @DelegatesTo differently:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=configure_list_with_delegatesto,indent=0]
@DelegatesTo takes an optional genericTypeIndex argument that tells what is the index of the generic type that will
be used as the delegate type. This must be used in conjunction with @DelegatesTo.Target and the index starts at 0. In
the example above, that means that the delegate type is resolved against List<T>, and since the generic type at index
0 is T and inferred as a Realm, the type checker infers that the delegate type will be of type Realm.
NOTE: We're using a genericTypeIndex instead of a placeholder (T) because of JVM limitations.
==== Delegating to an arbitrary type
It is possible that none of the options above can represent the type you want to delegate to. For example, let's define
a mapper class which is parametrized with an object and defines a map method which returns an object of another type:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=delegatestotype_mapper,indent=0]
<1> The mapper class takes two generic type arguments: the source type and the target type
<2> The source object is stored in a final field
<3> The map method asks to convert the source object to a target object
As you can see, the method signature from map does not give any information about what object will
be manipulated by the closure. Reading the method body, we know that it will be the value which is
of type T, but T is not found in the method signature, so we are facing a case where none of the
available options for @DelegatesTo is suitable. For example, if we try to statically compile this code:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=delegatestotype_mapper_test,indent=0]
Then the compiler will fail with:
Static type checking] - Cannot find matching method TestScript0#length()
In that case, you can use the type member of the @DelegatesTo annotation to reference T as a type token:
[source,groovy]
include::../test/DelegatesToSpecTest.groovy[tags=delegatestotype_mapper_fixed,indent=0]
<1> The @DelegatesTo annotation references a generic type which is not found in the method signature
Note that you are not limited to generic type tokens. The type member can be used to represent complex types, such
as List<T> or Map<T,List>. The reason why you should use that in last resort is that the type is only checked
when the type checker finds usage of @DelegatesTo, not when the annotated method itself is compiled. This means that
type safety is only ensured at the call site. Additionally, compilation will be slower (though probably unnoticeable for
most cases).
[[compilation-customizers]]
== Compilation customizers
=== Introduction
Whether you are usingย groovyc to compile classes or aย GroovyShell,
for example, to execute scripts, under the hood, a compiler configuration is used. This configuration holds information
like the source encoding or the classpath but it can also be used to perform more operations like adding imports by
default, applying AST transformations transparently or disabling global AST transformations.
The goal of compilation customizers is to make those common tasks easy to implement. For that, theย CompilerConfiguration
class is the entry point. The general schema will always be based on the following code:
[source,groovy]
import org.codehaus.groovy.control.CompilerConfiguration // create a configuration def config = new CompilerConfiguration() // tweak the configuration config.addCompilationCustomizers(...) // run your script def shell = new GroovyShell(config) shell.evaluate(script)
Compilation customizers must extend theย org.codehaus.groovy.control.customizers.CompilationCustomizer class. A customizer works:
- on a specific compilation phase
- onย every class node being compiled
You can implement your own compilation customizer but Groovy includes some of the most common operations.
=== Import customizer
Using this compilation customizer, your code will have imports added
transparently. This is in particular useful for scripts implementing a
DSL where you want to avoid users from having to write imports. The
import customizer will let you add all the variants of imports the
Groovy language allows, that is:
- class imports, optionally aliased
- star imports
- static imports, optionally aliased
- static star imports
[source,groovy]
import org.codehaus.groovy.control.customizers.ImportCustomizer
include::../test/CustomizersTest.groovy[tags=import_cz,indent=0]
A detailed description of all shortcuts can be found in gapi:org.codehaus.groovy.control.customizers.ImportCustomizer[]
=== AST transformation customizer
The AST transformation customizer is meant to apply AST transformations
transparently. Unlike global AST transformations that apply on every
class being compiled as long as the transform is found on classpath
(which has drawbacks like increasing the compilation time or side
effects due to transformations applied where they should not), the
customizer will allow you to selectively apply a transform only for
specific scripts or classes.
As an example, letโs say you want to be able to useย @Log in a script.
The problem is thatย @Log is normally applied on a class node and a
script, by definition, doesnโt require one. But implementation wise,
scripts are classes, itโs just that you cannot annotate this implicit
class node withย @Log. Using the AST customizer, you have a workaround
to do it:
[source,groovy]
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import groovy.util.logging.Log
include::../test/CustomizersTest.groovy[tags=ast_cz_simple,indent=0]
Thatโs all! Internally, theย @Log AST transformation is applied to
every class node in the compilation unit. This means that it will be
applied to the script, but also to classes defined within the script.
If the AST transformation that you are using accepts parameters, you can
use parameters in the constructor too:
[source,groovy]
include::../test/CustomizersTest.groovy[tags=ast_cz_customname,indent=0]
As the AST transformation customizers works with objects instead of AST
nodes, not all values can be converted to AST transformation parameters.
For example, primitive types are converted toย ConstantExpression (that
is LOGGER is converted toย new ConstantExpression('LOGGER'), but if
your AST transformation takes a closure as an argument, then you have to
give it aย ClosureExpression, like in the following example:
[source,groovy]
include::../../../subprojects/groovy-astbuilder/src/spec/test/CustomizersTest.groovy[tags=ast_cz_closure,indent=0]
For a complete list of options, please refer to gapi:org.codehaus.groovy.control.customizers.ASTTransformationCustomizer[]
=== Secure AST customizer
CAUTION: Despite its name, this customizer is not a sandbox and not a security
boundary. It secures the grammar not the execution - it is a best-effort restriction of the grammar available to a DSL, and nothing
more. It cannot constrain what otherwise-permitted code does at runtime, its coverage of
the source is deliberately partial (see <<secure-ast-limitations,Limitations>> below), and
an author of the source being compiled can work around it. The Apache Groovy
https://github.com/apache/groovy/blob/master/THREAT_MODEL.md[threat model] treats it as a
hardening aid rather than a security boundary: a demonstrated bypass is expected
behaviour, not a vulnerability. If you must run partially-trusted scripts, layer real
isolation outside the language โ a separate process or JVM, an OS or container sandbox,
least-privilege credentials, and network egress controls โ and treat this customizer as
one layer within that, never the perimeter.
This customizer will allow the developer of a DSL to restrict the
grammar of the language, for example, to prevent users from using particular constructs.
It is only ``secure'' in that one aspect, i.e. limiting the allowable constructs within a DSL.
It doesย not replace other aspects of security which might additionally
be needed.
The only reason for it to exist is to limit the expressiveness of the
language. This customizer only works at the AST (abstract syntax tree)
level, not at runtime! It can be strange at first glance, but it makes
much more sense if you think of Groovy as a platform to build DSLs. You
may not want a user to have a complete language at hand. In the example
below, we will demonstrate it using an example of language that only
allows arithmetic operations, but this customizer allows you to:
- allow/disallow creation of closures
- allow/disallow imports
- allow/disallow package definition
- allow/disallow definition of methods
- restrict the receivers of method calls
- restrict the kind of AST expressions a user can use
- restrict the tokens (grammar-wise) a user can use
- restrict the types of the constants that can be used in code
For all those features, the secure AST customizer works using either an
allowed list (list of elements that are permitted)ย or a disallowed list (list of
elements that are not permitted). For each type of feature (imports,
tokens, โฆ) you have the choice to use either an allowed or disallowed list,
but you can mix dis/allowed lists for distinct features. Typically,
you will choose allowed lists (which permits only the constructs listed and disallows all others).
[source,groovy]
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
import static org.codehaus.groovy.syntax.Types.* <1>
include::../test/CustomizersTest.groovy[tags=secure_cz,indent=0]
<1> use for token types from gapi:org.codehaus.groovy.syntax.Types[]
<2> you can use class literals here
If what the secure AST customizer provides out of the box isnโt enough
for your needs, before creating your own compilation customizer, you
might be interested in the expression and statement checkers that the
AST customizer supports. Basically, it allows you to add custom checks
on the AST tree, on expressions (expression checkers) or statements
(statement checkers). For this, you must
implementย org.codehaus.groovy.control.customizers.SecureASTCustomizer.StatementChecker
orย org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker.
Those interfaces define a single method calledย isAuthorized, returning
a boolean, and taking aย Statement (orย Expression) as a parameter. It
allows you to perform complex logic over expressions or statements to
tell if a user is allowed to do it or not.
For example, there's no predefined configuration flag in the customizer which
will let you prevent people from using an attribute expression. Using a custom
checker, it is trivial:
[source,groovy]
include::../test/CustomizersTest.groovy[tags=secure_cz_custom,indent=0]
Then we can make sure that this works by evaluating a simple script:
[source,groovy]
new GroovyShell(config).evaluate ''' include::../test/CustomizersTest.groovy[tags=secure_cz_custom_assert,indent=4] '''
<1> will fail compilation
Statements can be checked using gapi:org.codehaus.groovy.control.customizers.SecureASTCustomizer.StatementChecker[]
Expressions can be checked using gapi:org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker[]
[[secure-ast-limitations]]
==== Limitations of the secure AST customizer
Coverage is partial by design, so it is worth knowing where the checks do and do not
reach. The customizer visits the script statement block, method and constructor bodies,
static and instance initializer blocks, and field initializer expressions. It does not
visit the following, so restrictions such as disallowedReceivers, the statement and
expression allow/disallow lists, and your own custom checkers do not apply to code
appearing there:
- annotation members, including closure arguments to annotations
- code carrying no source position, which is how constructors, initializers and fields
added by the compiler or by an AST transformation are told apart from those written by
the author of the source being secured โ a script class always has generated
constructors, for example, and checking those would reject valid programs
A generated member may still contain code you wrote, because a transformation can move it
there: @TupleConstructor(pre=...) and @MapConstructor(pre=...) relocate the supplied
closure body into the constructor they generate, and @ConditionalInterrupt relocates its
condition into a synthetic method. Such code keeps its original source position and is
checked wherever it was moved to.
Note that a constructor is not a ``method definition'' as far as methodDefinitionAllowed
is concerned: its body is checked, but declaring one remains permitted.
Import restrictions apply to actual import statements, so they have no effect on a
fully-qualified reference such as new java.lang.ProcessBuilder(...). TheindirectImportCheckEnabled flag exists to catch some of those, but only within the code
the customizer visits.
More fundamentally, compiling Groovy source is itself code execution, whether or not you
subsequently run the result: global AST transforms found on the compile classpath,@groovy.transform.ASTTest, @Grab dependency resolution and static initializers all run
during compilation, before and independently of any customizer you configure. Restricting
the grammar of source that you nevertheless compile does not change this. When compiling
source you do not fully trust, you should also set the groovy.grape.enable system
property to false, keep the compile classpath under your control, and apply the
out-of-language isolation described above.
=== Source aware customizer
This customizer may be used as a filter on other customizers. The
filter, in that case, is theย org.codehaus.groovy.control.SourceUnit.
For this, the source aware customizer takes another customizer as a
delegate, and it will apply customization of that delegate only and only
if predicates on the source unit match.
SourceUnit gives you access to multiple things but in particular the
file being compiled (if compiling from a file, of course). It gives
you the potential to perform operation based on the file name, for
example. Here is how you would create a source aware customizer:
[source,groovy]
import org.codehaus.groovy.control.customizers.SourceAwareCustomizer
import org.codehaus.groovy.control.customizers.ImportCustomizer
include::../test/CustomizersTest.groovy[tags=source_cz,indent=0]
Then you can use predicates on the source aware customizer:
[source,groovy]
include::../test/CustomizersTest.groovy[tags=source_cz_predicates,indent=0]
=== Customizer builder
If you are using compilation customizers in Groovy code (like the
examples above) then you can use an alternative syntax to customize compilation.
A builderย (org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder)
simplifies the creation of customizers using a hierarchical DSL.
[source,groovy]
import org.codehaus.groovy.control.CompilerConfiguration
import static org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder.withConfig <1>
include::../test/CustomizersTest.groovy[tags=customizer_withconfig,indent=0]
<1> static import of the builder method
<2> configuration goes here
The code sample above shows how to use the builder. A static
method,ย withConfig, takes a closure corresponding to the builder code,
and automatically registers compilation customizers to the
configuration. Every compilation customizer available in the distribution
can be configured this way:
==== Import customizer
[source,groovy]
withConfig(configuration) {
imports { // imports customizer
normal 'my.package.MyClass' // a normal import
alias 'AI', 'java.util.concurrent.atomic.AtomicInteger' // an aliased import
star 'java.util.concurrent' // star imports
staticMember 'java.lang.Math', 'PI' // static import
staticMember 'pi', 'java.lang.Math', 'PI' // aliased static import
}
}
==== AST transformation customizer
[source,groovy]
withConfig(conf) {
ast(Log) <1>
}
withConfig(conf) { ast(Log, value: 'LOGGER') <2> }
<1> apply @Log transparently
<2> apply @Log with a different name for the logger
==== Secure AST customizer
[source,groovy]
withConfig(conf) {
secureAst {
closuresAllowed = false
methodDefinitionAllowed = false
}
}
==== Source aware customizer
[source,groovy]
withConfig(configuration){
source(extension: 'sgroovy') {
ast(CompileStatic) <1>
}
}
withConfig(configuration){
source(extensions: ['sgroovy','sg']) {
ast(CompileStatic) <2>
}
}
withConfig(configuration) {
source(extensionValidator: { it.name in ['sgroovy','sg']}) {
ast(CompileStatic) <2>
}
}
withConfig(configuration) {
source(basename: 'foo') {
ast(CompileStatic) <3>
}
}
withConfig(configuration) {
source(basenames: ['foo', 'bar']) {
ast(CompileStatic) <4>
}
}
withConfig(configuration) {
source(basenameValidator: { it in ['foo', 'bar'] }) {
ast(CompileStatic) <4>
}
}
withConfig(configuration) {
source(unitValidator: { unit -> !unit.AST.classes.any { it.name == 'Baz' } }) {
ast(CompileStatic) <5>
}
}
<1> apply CompileStatic AST annotation on .sgroovy files
<2> apply CompileStatic AST annotation on .sgroovy or .sg files
<3> apply CompileStatic AST annotation on files whose name is 'foo'
<4> apply CompileStatic AST annotation on files whose name is 'foo' or 'bar'
<5> apply CompileStatic AST annotation on files that do not contain a class named 'Baz'
==== Inlining a customizer
Inlined customizer allows you to write a compilation customizer
directly, without having to create a class for it.
[source,groovy]
withConfig(configuration) {
inline(phase:'CONVERSION') { source, context, classNode -> <1>
println "visiting $classNode" <2>
}
}
<1> define an inlined customizer which will execute at the CONVERSION phase
<2> prints the name of the class node being compiled
==== Multiple customizers
Of course, the builder allows you to define multiple customizers at
once:
[source,groovy]
withConfig(configuration) { ast(ToString) ast(EqualsAndHashCode) }
=== The configscript commandline parameter
So far, we have described how you can customize compilation using
aย CompilationConfiguration class, but this is only possible if you
embed Groovy and that you create your own instances
ofย CompilerConfiguration (then use it to create aGroovyShell, GroovyScriptEngine, โฆ).
If you want it to be applied on the classes you compile with the normal
Groovy compiler (that is to say with groovyc, ant or gradle,
for example), it is possible to use a commandline parameter named configscript
that takes a Groovy configuration script as argument.
This script gives you access to the CompilerConfiguration instanceย before
the files are compiled (exposed into the configuration script as a variable named configuration),
so that you can tweak it.
It also transparently integrates the compiler configuration builder above. As an example, let's see
how you would activate static compilation by default on all classes.
==== Configscript example: Static compilation by default
Normally, classes in Groovy are compiled with a dynamic runtime. You can activate static compilation
by placing an annotation named @CompileStatic on any class. Some people would like to have this
mode activated by default, that is to say not having to annotate (potentially many) classes.
Using configscript, makes this possible.
First of all, you need to create a file named config.groovy into say src/conf with
the following contents:
[source,groovy]
withConfig(configuration) { <1> ast(groovy.transform.CompileStatic) }
<1> configuration references a CompilerConfiguration instance
That is actually all you need. You donโt have to import the builder, itโs automatically
exposed in the script. Then, compile your files using the following command line:
groovyc -configscript src/conf/config.groovy src/main/groovy/MyClass.groovy
We strongly recommend you to separate configuration files from classes,
hence why we suggest using the src/main and src/conf directories above.
==== Configscript example: Setting system properties
In a configuration script you can also set system properties, e.g.:
[source,groovy]
System.setProperty('spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'true')
If you have numerous system properties to set, then using a configuration file
will reduce the need to set a bunch of system properties with a long command line
or appropriately defined environment variable.
You can also share all the settings by simply sharing the config file.
=== AST transformations
If:
- runtime metaprogramming doesn't allow you to do what you want
- you need to improve the performance of the execution of your DSLs
- you want to leverage the same syntax as Groovy but with different semantics
- you want to improve support for type checking in your DSLs
Then AST transformations are the way to go. Unlike the techniques used so far, AST transformations are meant to
change or generate code before it is compiled to bytecode. AST transformations are capable of adding new methods at
compile time for example, or totally changing the body of a method based on your needs. They are a very powerful tool
but also come at the price of not being easy to write. For more information about AST transformations, please take
a look at the https://docs.groovy-lang.org/latest/html/documentation/index.html#_compile_time_metaprogramming[compile-time
metaprogramming] section of this manual.
== Custom type checking extensions
It may be interesting, in some circumstances, to provide feedback about wrong code to the user as soon as possible,
that is to say when the DSL script is compiled, rather than having to wait for the execution of the script. However,
this is not often possible with dynamic code. Groovy actually provides a practical answer to this known as
link:core-semantics.html#type-checking-extensions[type checking extensions].
== Builders
Many tasks require building things and the builder pattern is one technique
used by developers to make building things easier, especially building
of structures which are hierarchical in nature.
This pattern is so ubiquitous that Groovy has special built-in support.
Firstly, there are many built-in builders. Secondly, there are
classes which make it easier to write your own builders.
=== Existing builders
Groovy comes with many built-in builders. Let's look at some of them.
==== MarkupBuilder
See <<{xml-userguide}#_markupbuilder,Creating Xml - MarkupBuilder>>.
==== StreamingMarkupBuilder
See <<{xml-userguide}#_streamingmarkupbuilder,Creating Xml - StreamingMarkupBuilder>>.
include::../../../subprojects/groovy-xml/src/spec/doc/_sax-builder.adoc[leveloffset=+3]
include::../../../subprojects/groovy-xml/src/spec/doc/_stax-builder.adoc[leveloffset=+3]
include::../../../subprojects/groovy-xml/src/spec/doc/_dom-builder.adoc[leveloffset=+3]
==== NodeBuilder
NodeBuilder is used for creating nested trees of gapi:groovy.util.Node[Node] objects for handling arbitrary data.
To create a simple user list you use a NodeBuilder like this:
[source,groovy]
include::../test/builder/NodeBuilderTest.groovy[tags=node_builder_example,indent=0]
Now you can process the data further, e.g. by using <<{core-semantics}#gpath_expressions,GPath expressions>>:
[source,groovy]
include::../test/builder/NodeBuilderTest.groovy[tags=node_builder_gpath_assert,indent=0]
include::../../../subprojects/groovy-json/src/spec/doc/_json-builder.adoc[leveloffset=+3]
include::../../../subprojects/groovy-json/src/spec/doc/_streaming-jason-builder.adoc[leveloffset=+3]
include::../../../subprojects/groovy-swing/src/spec/doc/_swing-builder.adoc[leveloffset=+3]
include::../../../subprojects/groovy-ant/src/spec/doc/_ant-builder.adoc[leveloffset=+3]
==== CliBuilder
CliBuilder provides a compact way to specify the available options for a commandline application and then
automatically parse the application's commandline parameters according to that specification. By convention,
a distinction is made between option commandline parameters and any remaining parameters which are passed
to an application as its arguments. Typically, several types of options might be supported such as -V or--tabsize=4. CliBuilder removes the burden of developing lots of code for commandline processing.
Instead, it supports a somewhat declarative approach to declaring your options and then provides a single call
to parse the commandline parameters with a simple mechanism to interrogate the options (you can think of this
as a simple model for your options).
Even though the details of each commandline you create could be quite different, the same main steps are
followed each time. First, a CliBuilder instance is created. Then, allowed commandline options are defined.
This can be done using a dynamic api style or an annotation style.
The commandline parameters are then parsed according to the options specification resulting in a
collection of options which are then interrogated.
Here is a simple example Greeter.groovy script illustrating usage:
[source,groovy]
// import of CliBuilder not shown <1>
// specify parameters
def cli = new CliBuilder(usage: 'groovy Greeter [option]') <2>
cli.a(longOpt: 'audience', args: 1, 'greeting audience') <3>
cli.h(longOpt: 'help', 'display usage') <4>
// parse and process parameters def options = cli.parse(args) <5> if (options.h) cli.usage() <6> else println "Hello ${options.a ? options.a : 'World'}" <7>
<1> Earlier versions of Groovy had a CliBuilder in the groovy.util package and no import was necessary.
In Groovy 2.5, this approach became deprecated: applications should instead choose the groovy.cli.picocli or groovy.cli.commons version.
The groovy.util version in Groovy 2.5 points to the commons-cli version for backwards compatibility but has been removed in Groovy 3.0.
<2> define a new CliBuilder instance specifying an optional usage string
<3> specify a -a option taking a single argument with an optional long variant --audience
<4> specify a -h option taking no arguments with an optional long variant --help
<5> parse the commandline parameters supplied to the script
<6> if the h option is found display a usage message
<7> display a standard greeting or, if the a option is found, a customized greeting
Running this script with no commandline parameters, i.e.:
[source,shell]
groovy Greeter
results in the following output:
Hello World
Running this script with -h as the single commandline parameter, i.e.:
[source,shell]
groovy Greeter -h
results in the following output:
usage: groovy Greeter [option]
-a,--audience greeting audience
-h,--help display usage
Running this script with --audience Groovologist as the commandline parameters, i.e.:
[source,shell]
groovy Greeter --audience Groovologist
results in the following output:
Hello Groovologist
When creating the CliBuilder instance in the above example, we set the optional usage property
within the constructor call. This follows Groovy's normal ability to set additional properties
of the instance during construction. There are numerous other properties which can be set
such as header and footer. For the complete set of available properties, see the
available properties for the gapi:groovy.util.CliBuilder[CliBuilder] class.
When defining an allowed commandline option, both a short name (e.g. "h" for the help option shown previously)
and a short description (e.g. "display usage" for the help option) must be supplied.
In our example above, we also set some
additional properties such as longOpt and args. The following additional
properties are supported when specifying an allowed commandline option:
[options="header"]
|======================
| Name | Description | Type
| argName | the name of the argument for this option used in output | String
| longOpt | the long representation or long name of the option | String
| args | the number of argument values | int or String {nbsp} {nbsp} (1)
| optionalArg | whether the argument value is optional | boolean
| required | whether the option is mandatory | boolean
| type | the type of this option | Class
| valueSeparator | the character that is the value separator | char {nbsp} {nbsp} (2)
| defaultValue | a default value | String
| convert | converts the incoming String to the required type | Closure {nbsp} {nbsp} (1)
|======================
(1) More details later +
(2) Single character Strings are coerced to chars in special cases in Groovy
If you have an option with only a longOpt variant, you can use the special shortname of 'pass:[_]'
to specify the option, e.g. : cli.pass:[_](longOpt: 'verbose', 'enable verbose logging').
Some of the remaining named parameters should be fairly self-explanatory while others deserve
a bit more explanation. But before further explanations, let's look at ways of usingCliBuilder with annotations.
===== Using Annotations and an interface
Rather than making a series of method calls (albeit in a very declarative mini-DSL form)
to specify the allowable options, you can provide an interface specification of the allowable options where
annotations are used to indicate and provide details for those options and for how unprocessed
parameters are handled. Two annotations are used: gapi:groovy.cli.Option[] and gapi:groovy.cli.Unparsed[].
Here is how such a specification can be defined:
[source,groovy]
include::../../testFixtures/groovy/cli/GreeterI.groovy[tags=annotationInterfaceSpec,indent=0]
<1> Specify a Boolean option set using -h or --help
<2> Specify a String option set using -a or --audience
<3> Specify where any remaining parameters will be stored
Note how the long name is automatically determined from the interface method name.
You can use the longName annotation attribute to override that behavior and specify
a custom long name if you wish or use a longName of 'pass:[_]' to indicate that no long name
is to be provided. You will need to specify a shortName in such a case.
Here is how you could use the interface specification:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=annotationInterface,indent=0]
<1> Create a CliBuilder instance as before with optional properties
<2> Parse parameters using the interface specification
<3> Interrogate options using the methods from the interface
<4> Parse a different set of parameters
<5> Interrogate the remaining parameters
When parseFromSpec is called, CliBuilder automatically creates an instance implementing the interface
and populates it. You simply call the interface methods to interrogate the option values.
===== Using Annotations and an instance
Alternatively, perhaps you already have a domain class containing the option information.
You can simply annotate properties or setters from that class to enable CliBuilder to appropriately
populate your domain object. Each annotation both describes that option's properties through the annotation
attributes and indicates the setter the CliBuilder will use to populate that option in your domain object.
Here is how such a specification can be defined:
[source,groovy]
include::../../testFixtures/groovy/cli/GreeterC.groovy[tags=annotationClassSpec,indent=0]
<1> Indicate that a Boolean property is an option
<2> Indicate that a String property (with explicit setter) is an option
<3> Specify where any remaining args will be stored
And here is how you could use the specification:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=annotationClass,indent=0]
<1> Create a CliBuilder instance as before with optional parameters
<2> Create an instance for CliBuilder to populate
<3> Parse arguments populating the supplied instance
<4> Interrogate the String option property
<5> Interrogate the remaining arguments property
When parseFromInstance is called, CliBuilder automatically populates your instance.
You simply interrogate the instance properties (or whatever accessor methods you have provided
in your domain object) to access the option values.
===== Using Annotations and a script
Finally, there are two additional convenience annotation aliases specifically for scripts. They
simply combine the previously mentioned annotations and gapi:groovy.transform.Field[].
The groovydoc for those annotations reveals the details: gapi:groovy.cli.OptionField[] and
gapi:groovy.cli.UnparsedField[].
Here is an example using those annotations in a self-contained script that would be called
with the same arguments as shown for the instance example earlier:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=annotationScript,indent=0]
===== Options with arguments
We saw in our initial example that some options act like flags, e.g. Greeter -h but
others take an argument, e.g. Greeter --audience Groovologist. The simplest cases
involve options which act like flags or have a single (potentially optional) argument.
Here is an example involving those cases:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withArgument,indent=0]
<1> An option that is simply a flag - the default; setting args to 0 is allowed but not needed.
<2> An option with exactly one argument
<3> An option with an optional argument; it acts like a flag if the option is left out
<4> An example using this spec where an argument is supplied to the 'c' option
<5> An example using this spec where no argument is supplied to the 'c' option; it's just a flag
Note: when an option with an optional argument is encountered, it will (somewhat) greedily consume the
next parameter from the supplied commandline parameters. If however, the next parameter matches a known long or short
option (with leading single or double hyphens), that will take precedence, e.g. -b in the above example.
Option arguments may also be specified using the annotation style. Here is an interface option specification
illustrating such a definition:
[source,groovy]
include::../../testFixtures/groovy/cli/WithArgsI.groovy[tags=withArgumentInterfaceSpec,indent=0]
And here is how it is used:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withArgumentInterface,indent=0]
This example makes use of an array-typed option specification. We cover this in more detail shortly when we discuss
multiple arguments.
===== Specifying a type
Arguments on the commandline are by nature Strings (or arguably can be considered Booleans for flags) but can be
converted to richer types automatically by supplying additional typing information. For the
annotation-based argument definition style, these types are supplied using the field types for annotation
properties or return types of annotated methods (or the setter argument type for setter methods).
For the dynamic method style of argument definition a special 'type' property is supported
which allows you to specify a Class name.
When an explicit type is defined, the args named-parameter is assumed to be 1 (except for Boolean-typed
options where it is 0 by default). An explicit args parameter can still be provided if needed.
Here is an example using types with the dynamic api argument definition style:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withType,indent=0]
Primitives, numeric types, files, enums and arrays thereof, are supported (they are converted using
gapi:org.codehaus.groovy.runtime.StringGroovyMethods#asType[StringGroovyMethods#asType(String, Class)]).
===== Custom parsing of the argument String
If the supported types aren't sufficient, you can supply a closure to handle the String to rich type conversion
for you. Here is a sample using the dynamic api style:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withConvert,indent=0]
Alternatively, you can use the annotation style by supplying the conversion closure as an annotation parameter.
Here is an example specification:
[source,groovy]
include::../../testFixtures/groovy/cli/WithConvertI.groovy[tags=withConvertInterfaceSpec,indent=0]
And an example using that specification:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withConvertInterface,indent=0]
===== Options with multiple arguments
Multiple arguments are also supported using an args value greater than 1. There is a special named parameter,valueSeparator, which can also be optionally used when processing multiple arguments. It allows some additional
flexibility in the syntax supported when supplying such argument lists on the commandline. For example,
supplying a value separator of ',' allows a comma-delimited list of values to be passed on the commandline.
The args value is normally an integer. It can be optionally supplied as a String. There are two special
String symbols: + and \*.
The * value means 0 or more. The + value means 1 or more.
The * value is the same as using + and also setting the optionalArg value to true.
Accessing the multiple arguments follows a special convention. Simply add an 's' to the normal property
you would use to access the argument option and you will retrieve all the supplied arguments as a list.
So, for a short option named 'a', you access the first 'a' argument using options.a and the list of
all arguments using options.as. It's fine to have a shortname or longname ending in 's' so long as you
don't also have the singular variant without the 's'. So, if name is one of your options with multiple arguments
and guess is another with a single argument, there will be no confusion using options.names and options.guess.
Here is an excerpt highlighting the use of multiple arguments:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=multipleArgs,indent=0]
<1> Args value supplied as a String and comma value separator specified
<2> One or more arguments are allowed
<3> Two commandline parameters will be supplied as the 'b' option's list of arguments
<4> Access the 'a' option's first argument
<5> Access the 'a' option's list of arguments
<6> An alternative syntax for specifying two arguments for the 'a' option
<7> The arguments to the 'b' option supplied as a comma-separated value
As an alternative to accessing multiple arguments using the plural name approach, you can use an
array-based type for the option. In this case, all options will always be returned via the array
which is accessed via the normal singular name. We'll see an example of this next when discussing
types.
Multiple arguments are also supported using the annotation style of option definition by using an
array type for the annotated class member (method or property) as this example shows:
[source,groovy]
include::../../testFixtures/groovy/cli/ValSepI.groovy[tags=multipleArgsInterfaceSpec,indent=0]
And used as follows:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=multipleArgsInterface,indent=0]
===== Types and multiple arguments
Here is an example using types and multiple arguments with the dynamic api argument definition style:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withTypeMultiple,indent=0]
<1> For an array type, the trailing 's' can be used but isn't needed
===== Setting a default value
Groovy makes it easy using the Elvis operator to provide a default value at the point of usage of some variable,
e.g. String x = someVariable ?: 'some default'. But sometimes you wish to make such a default part of the
options specification to minimise the interrogators work in later stages. CliBuilder supports the defaultValue
property to cater for this scenario.
Here is how you could use it using the dynamic api style:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withDefaultValue,indent=0]
Similarly, you might want such a specification using the annotation style. Here is an example using an interface
specification:
[source,groovy]
include::../../testFixtures/groovy/cli/WithDefaultValueI.groovy[tags=withDefaultValueInterfaceSpec,indent=0]
Which would be used like this:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withDefaultValueInterface,indent=0]
You can also use the defaultValue annotation attribute when using annotations with an instance,
though it's probably just as easy to provide an initial value for the property (or backing field).
===== Use with TypeChecked
The dynamic api style of using CliBuilder is inherently dynamic but you have a few options
should you want to make use of Groovy's static type checking capabilities. Firstly, consider using the
annotation style, for example, here is an interface option specification:
[source,groovy]
include::../../testFixtures/groovy/cli/TypeCheckedI.groovy[tags=withTypeCheckedInterfaceSpec,indent=0]
And it can be used in combination with @TypeChecked as shown here:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withTypeCheckedInterface,indent=0]
Secondly, there is a feature of the dynamic api style which offers some support. The definition statements
are inherently dynamic but actually return a value which we have ignored in earlier examples.
The returned value is in fact a TypedOption<Type> and special getAt support allows the options
to be interrogated using the typed option, e.g. options[savedTypeOption]. So, if you have statements
similar to these in a non type checked part of your code:
[source,groovy]
def cli = new CliBuilder() TypedOption age = cli.a(longOpt: 'age', type: Integer, 'some age option')
Then, the following statements can be in a separate part of your code which is type checked:
[source,groovy]
def args = '--age 21'.split() def options = cli.parse(args) int a = options[age] assert a == 21
Finally, there is one additional convenience method offered by CliBuilder to even allow the
definition part to be type checked. It is a slightly more verbose method call. Instead of using
the short name (the opt name) in the method call, you use a fixed name of option and
supply the opt value as a property. You must also specify the type directly as shown in
the following example:
[source,groovy]
include::../../testFixtures/groovy/cli/CliBuilderTestCase.groovy[tags=withTypeChecked,indent=0]
===== Advanced CLI Usage
[NOTE]
NOTE Advanced CLI features
`CliBuilder` can be thought of as a Groovy friendly wrapper on top of either https://github.com/remkop/picocli[picocli] or https://commons.apache.org/proper/commons-cli/[Apache Commons CLI]. If there is a feature not provided by `CliBuilder` that you know is supported in the underlying library, the current `CliBuilder` implementation (and various Groovy language features) make it easy for you to call the underlying library methods directly. Doing so is a pragmatic way to leverage the Groovy-friendly syntax offered by `CliBuilder` and yet still access some of the underlying library's advanced features. A word of caution however; future versions of `CliBuilder` could potentially use another underlying library and in that event, some porting work may be required for your Groovy classes and/or scripts.
====== Apache Commons CLI
As an example, here is some code for making use of Apache Commons CLI's grouping mechanism:
[source,groovy]
import org.apache.commons.cli.*
def cli = new CliBuilder() cli.f longOpt: 'from', 'f option' cli.u longOpt: 'until', 'u option' def optionGroup = new OptionGroup() optionGroup.with { addOption cli.option('o', [longOpt: 'output'], 'o option') addOption cli.option('d', [longOpt: 'directory'], 'd option') } cli.options.addOptionGroup optionGroup assert !cli.parse('-d -o'.split()) // <1>
<1> The parse will fail since only one option from a group can be used at a time.
====== Picocli
Below are some features available in the picocli version of CliBuilder.
New property: errorWriter
When users of your application give invalid command line arguments,
CliBuilder writes an error message and the usage help message to the stderr output stream.
It doesn't use the stdout stream to prevent the error message from being parsed when your program's
output is used as input for another process.
You can customize the destination by setting the errorWriter to a different value.
On the other hand, CliBuilder.usage() prints the usage help message to the stdout stream.
This way, when users request help (e.g. with a --help parameter),
they can pipe the output to a utility like less or grep.
You can specify different writers for testing.
Be aware that for backwards compatibility, setting the writer property to a different value
will set both the writer and the errorWriter to the specified writer.
ANSI colors
The picocli version of CliBuilder renders the usage help message in ANSI colors on supported platforms automatically.
If desired you can http://picocli.info/#_usage_help_with_styles_and_colors[customize] this.
(An example follows below.)
New property: name
As before, you can set the synopsis of the usage help message with the usage property.
You may be interested in a small improvement:
if you only set the command name, a synopsis will be generated automatically,
with repeating elements followed by ... and optional elements surrounded with [ and ].
(An example follows below.)
New property: usageMessage
This property exposes a UsageMessageSpec object from the underlying picocli library,
which gives fine-grained control over various sections of the usage help message. For example:
[source,groovy]
include::../../../subprojects/groovy-cli-picocli/src/spec/test/builder/CliBuilderTest.groovy[tags=withUsageMessageSpec,indent=0]
Gives this output:
image:{reldir_root}/assets/img/usageMessageSpec.png[]
Property: parser
The parser property gives access to the picocli ParserSpec object that can be used to customize the parser behavior.
This can be useful when the CliBuilder options to control the parser are not fine-grained enough.
For example, for backward compatibility with the Commons CLI implementation of CliBuilder, by default CliBuilder stops looking for options when an unknown option is encountered, and subsequent command line arguments are treated as positional parameters.CliBuilder provides a stopAtNonOption property, and by setting this to false you can make the parser more strict, so an unknown option results in error: Unknown option: '-x'.
But what if you want to treat unknown options as positional parameters, and still process subsequent command line arguments as options?
This can be accomplished with the parser property.
For example:
[source,groovy]
def cli = new CliBuilder() cli.parser.stopAtPositional(false) cli.parser.unmatchedOptionsArePositionalParams(true) // ... def opts = cli.parse(args) // ...
See the http://picocli.info/apidocs/picocli/CommandLine.Model.ParserSpec.html[documentation] for details.
Map options
Finally, if your application has options that are key-value pairs, you may be interested in picocli's support for maps. For example:
[source,groovy]
include::../../../subprojects/groovy-cli-picocli/src/spec/test/builder/CliBuilderTest.groovy[tags=mapOptionImports,indent=0]
include::../../../subprojects/groovy-cli-picocli/src/spec/test/builder/CliBuilderTest.groovy[tags=mapOption,indent=0]
<1> Previously, key=value pairs were split up into parts and added to a list
<2> Picocli map support: simply specify Map as the type of the option
<3> You can even specify the type of the map elements
<4> To compare, let's specify two key-value pairs for each option
<5> Previously, all key-value pairs end up in a list and it is up to the application to work with this list
<6> Picocli returns the key-value pairs as a Map
<7> Both keys and values of the map can be strongly typed
Controlling the Picocli version
To use a specific version of picocli, add a dependency to that version in your build configuration.
If running scripts using a pre-installed version of Groovy, use the @Grab annotation to control the version of picocli to use in CliBuilder.
[source,groovy]
@GrabConfig(systemClassLoader=true)
@Grab('info.picocli:picocli:4.2.0')
import groovy.cli.picocli.CliBuilder
def cli = new CliBuilder()
==== ObjectGraphBuilder
ObjectGraphBuilder is a builder for an arbitrary graph of beans that
follow the JavaBean convention. It is in particular useful for creating test data.
Let's start with a list of classes that belong to your domain:
[source,groovy]
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=domain_classes,indent=0]
Then using ObjectGraphBuilder building a Company with three employees is as
easy as:
[source,groovy]
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=builder_example,indent=0]
<1> creates a new object graph builder
<2> sets the classloader where the classes will be resolved
<3> sets the base package name for classes to be resolved
<4> creates a Company instance
<5> with 3 Employee instances
<6> each of them having a distinct Address
Behind the scenes, the object graph builder:
- will try to match a node name into a
Class, using a defaultClassNameResolverstrategy that requires a package name - then will create an instance of the appropriate class using a default
NewInstanceResolverstrategy that calls a no-arg constructor - resolves the parent/child relationship for nested nodes, involving two other strategies:
**RelationNameResolverwill yield the name of the child property in the parent, and the name of the parent property
in the child (if any, in this case,Employeehas a parent property aptly namedcompany)
**ChildPropertySetterwill insert the child into the parent taking into account if the child belongs to aCollection
or not (in this caseemployeesshould be a list ofEmployeeinstances inCompany).
All 4 strategies have a default implementation that work as expected if
the code follows the usual conventions for writing JavaBeans. In case any of your beans or objects do not follow the convention
you may plug your own implementation of each strategy. For example imagine that you need to build a class which is
immutable:
[source,groovy]
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=immutable_class,indent=0]
Then if you try to create a Person with the builder:
[source,groovy]
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=immutable_fail_runtime,indent=0]
It will fail at runtime with:
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=expected_error_immutable,indent=0]
Fixing this can be done by changing the new instance strategy:
[source,groovy]
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=newinstanceresolver,indent=0]
ObjectGraphBuilder supports ids per node, meaning
that you can store a reference to a node in the builder. This is
useful when multiple objects reference the same instance. Because a
property named id may be of business meaning in some domain modelsObjectGraphBuilder has a strategy named IdentifierResolver that you
may configure to change the default name value. The same may
happen with the property used for referencing a previously saved
instance, a strategy named ReferenceResolver will yield the
appropriate value (default is `refId'):
[source,groovy]
include::../test/builder/ObjectGraphBuilderTest.groovy[tags=test_id,indent=0]
<1> an address can be created with an id
<2> an employee can reference the address directly with its id
<3> or use the refId attribute corresponding to the id of the corresponding address
Its worth mentioning that you cannot modify the properties of a
referenced bean.
==== JmxBuilder
See <<{jmx}#jmx_jmxbuilder,Working with JMX - JmxBuilder>> for details.
==== FileTreeBuilder
gapi:groovy.util.FileTreeBuilder[FileTreeBuilder] is a builder for generating a file directory structure from a specification. For example, to create the following tree:
src/
|--- main
| |--- groovy
| |--- Foo.groovy
|--- test
|--- groovy
|--- FooTest.groovy
You can use a FileTreeBuilder like this:
[source,groovy]
include::../test/builder/FileTreeBuilderTest.groovy[tags=example,indent=0]
To check that everything worked as expected we use the following asserts:
[source,groovy]
include::../test/builder/FileTreeBuilderTest.groovy[tags=example_assert,indent=0]
FileTreeBuilder also supports a shorthand syntax:
[source,groovy]
include::../test/builder/FileTreeBuilderTest.groovy[tags=shorthand_syntax,indent=0]
This produces the same directory structure as above, as shown by these asserts:
[source,groovy]
include::../test/builder/FileTreeBuilderTest.groovy[tags=shorthand_syntax_assert,indent=0]
=== Creating a builder
While Groovy has many built-in builders, the builder pattern is so common,
you will no doubt eventually come across a building requirement that
hasn't been catered for by those built-in builders.
The good news is that you can build your own.
You can do everything from scratch by relying on Groovy's
metaprogramming capabilities. Alternatively, the BuilderSupport
and FactoryBuilderSupport classes make designing your own
builders much easier.
==== BuilderSupport
One approach to building a builder is to subclass BuilderSupport.
With this approach, the general idea is to override one or more of a number of
lifecycle methods including setParent, nodeCompleted and some or
all of the createNode methods from the BuilderSupport abstract class.
As an example, suppose we want to create a builder of
tracking athletic training programs. Each program is made
up of a number of sets and each set has its own steps.
A step might itself be a set of smaller steps.
For each set or step, we might wish to record
the distance required (or time), whether to repeat
the steps a certain number of times, whether to take a break
between each step and so forth.
For the simplicity of this example, we'll capture the training
programming using maps and lists. A set has a list of steps.
Information like repeat count or distance will be tracked
in a map of attributes for each step and set.
The builder implementation is as follows:
- Override a couple of the
createNodemethods.
We'll create a map capturing the set name, an empty list of steps,
and potentially some attributes. - Whenever we complete a node we'll add the node to the list of steps
for the parent (if any).
The code looks like this:
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=define_builder1,indent=0]
Next, we'll write a little helper method which recursively
adds up the distances of all substeps, accounting for repeated steps
as needed.
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=define_total_helper1,indent=0]
Finally, we can now use our builder and helper method to create a
swimming training program and check its total distance:
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=use_builder1,indent=0]
==== FactoryBuilderSupport
A second approach to building a builder is to subclass FactoryBuilderSupport.
This builder has similar goals to BuilderSupport but with extra features
to simplify domain class construction.
With this approach, the general idea is to override one or more of a number of
lifecycle methods including resolveFactory, nodeCompleted and postInstantiate methods from the FactoryBuilderSupport abstract class.
We'll use the same example as for the previous BuilderSupport example;
a builder of tracking athletic training programs.
For this example, rather than capturing the training
programming using maps and lists, we'll use some simple domain classes.
The builder implementation is as follows:
- Override the
resolveFactorymethod to return a special
factory which returns classes by capitalizing the names used in our mini DSL. - Whenever we complete a node we'll add the node to the list of steps
for the parent (if any).
The code, including the code for the special factory class, looks like this:
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=define_builder2,indent=0]
Rather than using lists and maps, we'll have some simple domain classes
and related traits:
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=define_domain_classes2,indent=0]
Just like for the BuilderSupport example, it is useful to have a helper method
to calculate the total distance covered during the training session.
The implementation is very similar to our earlier example, but is adjusted
to work well with our newly defined traits.
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=define_total_helper2,indent=0]
Finally, we can now use our new builder and helper methods to create a
cycling training program and check its total distance:
[source,groovy]
include::../test/builder/BuilderSpecTest.groovy[tags=use_builder2,indent=0]
Src Spec Doc Core Gdk (`src/spec/doc/core-gdk.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
= Groovy Development Kit
include::_working-with-io.adoc[leveloffset=+1]
include::_working-with-collections.adoc[leveloffset=+1]
include::_working-with-arrays.adoc[leveloffset=+1]
include::../../../subprojects/groovy-dateutil/src/spec/doc/_working-with-dateutil-types.adoc[leveloffset=+1]
include::../../../subprojects/groovy-datetime/src/spec/doc/_working-with-datetime-types.adoc[leveloffset=+1]
== Handy utilities
=== ConfigSlurper
ConfigSlurper is a utility class for reading configuration files defined in the form of Groovy scripts. Like it is
the case with Java *.properties files, ConfigSlurper allows a dot notation. But in addition, it allows for Closure scoped
configuration values and arbitrary object types.
[source,groovy]
include::../test/gdk/ConfigSlurperTest.groovy[tags=arbitrary_types,indent=0]
<1> Usage of the dot notation
<2> Usage of Closure scopes as an alternative to the dot notation
As can be seen in the above example, the parse method can be used to retrieve groovy.util.ConfigObject instances. TheConfigObject is a specialized java.util.Map implementation that either returns the configured value or a new ConfigObject
instance but never null.
[source,groovy]
include::../test/gdk/ConfigSlurperTest.groovy[tags=never_null,indent=0]
<1> config.test has not been specified yet it returns a ConfigObject when being called.
In the case of a dot being part of a configuration variable name, it can be escaped by using single or double quotes.
[source,groovy]
include::../test/gdk/ConfigSlurperTest.groovy[tags=escape_dot,indent=0]
In addition, ConfigSlurper comes with support for environments. The environments method can be used to hand over
a Closure instance that itself may consist of a several sections. Let's say we wanted to create a particular configuration
value for the development environment. When creating the ConfigSlurper instance we can use the ConfigSlurper(String)
constructor to specify the target environment.
[source,groovy]
include::../test/gdk/ConfigSlurperTest.groovy[tags=environments,indent=0]
[NOTE]
The ConfigSlurper environments aren't restricted to any particular environment names. It solely depends on theConfigSlurper client code what value are supported and interpreted accordingly.
The environments method is built-in but the registerConditionalBlock method can be used to register other method names
in addition to the environments name.
[source,groovy]
include::../test/gdk/ConfigSlurperTest.groovy[tags=custom_environments,indent=0]
<1> Once the new block is registered ConfigSlurper can parse it.
For Java integration purposes the toProperties method can be used to convert the ConfigObject to a java.util.Properties
object that might be stored to a *.properties text file. Be aware though that the configuration values are converted toString instances during adding them to the newly created Properties instance.
[source,groovy]
include::../test/gdk/ConfigSlurperTest.groovy[tags=properties,indent=0]
=== Expando
The Expando class can be used to create a dynamically expandable object. Despite its name it does not use theExpandoMetaClass underneath. Each Expando object represents a standalone, dynamically-crafted instance that can be
extended with properties (or methods) at runtime.
[source,groovy]
include::../test/gdk/ExpandoTest.groovy[tags=expando_property,indent=0]
A special case occurs when a dynamic property registers a Closure code block. Once being registered it can be invoked
as it would be done with a method call.
[source,groovy]
include::../test/gdk/ExpandoTest.groovy[tags=expando_method,indent=0]
=== Observable list, map and set
Groovy comes with observable lists, maps and sets. Each of these collections trigger java.beans.PropertyChangeEvent events when elements
are added, removed or changed. Note that a PropertyChangeEvent is not only signalling that a certain event has
occurred, moreover, it holds information on the property name and the old/new value a certain property has been changed to.
Depending on the type of change that has happened, observable collections might fire more specialized PropertyChangeEvent
types. For example, adding an element to an observable list fires an ObservableList.ElementAddedEvent event.
[source,groovy]
include::../test/gdk/ObservableTest.groovy[tags=observable_list,indent=0]
<1> Declares a PropertyChangeEventListener that is capturing the fired events
<2> ObservableList.ElementEvent and its descendant types are relevant for this listener
<3> Creates an ObservableList from the given list
<4> Registers the listener
<5> Triggers an ObservableList.ElementAddedEvent event
[NOTE]
Be aware that adding an element in fact causes two events to be triggered. The first is of type ObservableList.ElementAddedEvent,
the second is a plain PropertyChangeEvent that informs listeners about the change of property size.
The ObservableList.ElementClearedEvent event type is another interesting one. Whenever multiple
elements are removed, for example when calling clear(), it holds the elements being removed from the list.
[source,groovy]
include::../test/gdk/ObservableTest.groovy[tags=observable_list_clear,indent=0]
To get an overview of all the supported event types the reader is encouraged to have a look at the JavaDoc documentation
or the source code of the observable collection in use.
ObservableMap and ObservableSet come with the same concepts as we have seen for ObservableList in this section.
Src Spec Doc Core Getting Started (`src/spec/doc/core-getting-started.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
ifndef::groovy-console[]
:groovy-console: groovy-console.adoc
endif::[]
= Getting started
== Download
From the https://groovy.apache.org/download.html[download] page,
you will be able to download the distribution (binary and source),
the Windows installer (a community artifact) and the documentation for Groovy.
For a quick and effortless start on Mac OSX, Linux, WSL2 or Cygwin, you can use https://sdkman.io/[SDKMAN!]
(The Software Development Kit Manager) to download and configure any Groovy version of your choice.
Basic <<SDKMAN,instructions>> can be found below.
=== Stable
- Download zip: https://groovy.jfrog.io/artifactory/dist-release-local/groovy-zips/apache-groovy-binary-{groovy-full-version}.zip[**Binary Release**] | https://groovy.jfrog.io/artifactory/dist-release-local/groovy-zips/apache-groovy-src-{groovy-full-version}.zip[Source Release]
- Download documentation: https://groovy.jfrog.io/artifactory/dist-release-local/groovy-zips/apache-groovy-docs-{groovy-full-version}.zip[**JavaDoc and zipped online documentation**]
- Combined binary / source / documentation bundle: https://groovy.jfrog.io/artifactory/dist-release-local/groovy-zips/apache-groovy-sdk-{groovy-full-version}.zip[**Distribution bundle**]
You can learn more about this version in the https://groovy-lang.org/releasenotes/groovy-{groovy-short-version}.html[release notes] or in the https://groovy-lang.org/changelogs/changelog-{groovy-full-version}.html[changelog].
If you plan on using invokedynamic support, link:invokedynamic-support.html[read those notes].
=== Snapshots
For those who want to test the very latest versions of Groovy and live on the bleeding edge, you can use our https://repository.apache.org/content/groups/snapshots/org/apache/groovy[snapshot builds].
As soon as a build succeeds on our continuous integration server a snapshot is deployed to this repository.
These snapshots are not official releases and are intended for integration testing by the development
community prior to official versions being released. We welcome any feedback.
=== Prerequisites
Groovy {groovy-short-version} requires Java 17+ with support for up to Java 25.
Various Groovy CI servers run the test suite (with more than 10000 tests) across numerous versions of Java.
Those servers are also useful to look at to confirm supported Java versions for different Groovy releases.
== Maven Repository
If you wish to embed Groovy in your application, you may just prefer to point your build
to your favourite maven repository or the Groovy https://groovy.jfrog.io/artifactory/libs-release-local/[artifactory instance].
Please see the https://groovy.apache.org/download.html#buildtools[download page] for available modules for each Groovy version.
[[SDKMAN]]
== SDKMAN! (The Software Development Kit Manager)
This tool makes installing Groovy on any Bash platform (Mac OSX, Linux, Cygwin, Solaris or FreeBSD) very easy.
Simply open a new terminal and enter:
[source,shell]
$ curl -s "https://get.sdkman.io" | bash
Follow the instructions on-screen to complete installation.
Open a new terminal or type the command:
[source,shell]
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
Then install the latest stable Groovy:
[source,shell]
$ sdk install groovy
After installation is complete and you've made it your default version, test it with:
[source,shell]
$ groovy -version
That's all there is to it!
== Other ways to get Groovy
=== Installation on Mac OS X
==== MacPorts
If you're on macOS and have https://www.macports.org[MacPorts] installed, you can run:
[source,shell]
sudo port install groovy
==== Homebrew
If you're on macOS and have https://brew.sh/[Homebrew] installed, you can run:
[source,shell]
brew install groovy
=== Installation on Windows
If you're on Windows, you can also use the https://groovy.jfrog.io/artifactory/dist-release-local/groovy-windows-installer/[Windows installer].
=== Other Distributions
You may download other distributions of Groovy from the ASF
https://archive.apache.org/dist/groovy/[archive repository]
or from the Groovy
https://groovy.jfrog.io/artifactory/dist-release-local/groovy-zips/[artifactory instance] (also includes pre-ASF versions).
=== Source Code
If you prefer to live on the bleeding edge, you can also grab the https://github.com/apache/groovy[source code from GitHub].
=== IDE plugin
If you are an IDE user, you can just grab the latest link:tools-ide.html[IDE plugin] and follow the plugin installation instructions.
== Install Binary
These instructions describe how to install a binary distribution of Groovy:
- https://groovy.apache.org/download.html[Download] a binary distribution of Groovy and unpack it into some folder on your local file system.
- Set your
GROOVY_HOMEenvironment variable to the directory where you unpacked the distribution. - Add
GROOVY_HOME/binto yourPATHenvironment variable. - Set your
JAVA_HOMEenvironment variable to point to your JDK. On OS X this is/Library/Java/Home, on other unixes its often/usr/javaetc. If you've already installed tools like Ant or Maven you've probably already done this step.
You should now have Groovy installed properly. You can test this by typing the following in a command shell:
[source,shell]
groovysh
Which should create an interactive groovy shell where you can type Groovy statements. Or to run the <<{groovy-console}#title-heading,Swing interactive console>> type:
[source,shell]
groovyConsole
To run a specific Groovy script type:
[source,shell]
groovy SomeScript
Src Spec Doc Core Grammar (`src/spec/doc/core-grammar.adoc`)
//////////////////////////////////////////
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.
//////////////////////////////////////////
ifndef::core-syntax[]
:core-syntax: core-syntax.adoc
endif::[]
ifndef::core-operators[]
:core-operators: core-operators.adoc
endif::[]
ifndef::core-program-structure[]
:core-program-structure: core-program-structure.adoc
endif::[]
ifndef::core-semantics[]
:core-semantics: core-semantics.adoc
endif::[]
[[groovy-grammar]]
= Grammar
This chapter is a complete specification of the Groovy language syntax
recognised at compilation phase PARSING. It is written for readers who
need a closed, citable grammar -- language researchers, tool authors,
and implementers. Informal syntax, examples, and runtime meaning live
in <<{core-syntax}#_syntax,Syntax>>,
<<{core-operators}#groovy-operators,Operators>>,
<<{core-program-structure}#_program_structure,Program structure>>, and
<<{core-semantics}#_semantics,Semantics>>.
The executable grammar is src/antlr/GroovyLexer.g4 andsrc/antlr/GroovyParser.g4. This chapter is a complete EBNF projection
of those files: every parser rule, lexer token, fragment, and mode
appears. The .g4 files remain the implementation source of truth --
if a production here and a rule there disagree, the rule wins until
this chapter is updated.
== Status and scope
Groovy's surface syntax is not a pure context-free grammar. The
specification has three layers:
. a lexical context machine (lexer modes, grouping stack, newline
hiding, token-type rewrites);
. a CFG skeleton in Groovy Spec EBNF, using the ANTLR parser rule
names as nonterminals;
. a finite list of restrictions R1, R2, ... that filter or
disambiguate that skeleton (the analogue of JLS static restrictions /
ECMA-262 early errors).
Lexer alternatives are ordered (first match among those whose
predicates hold, then longest match). Parser alternatives ofexpression are ordered by precedence (earlier binds tighter).
Conversion of the parse tree to AST (AstBuilder, compilation phaseCONVERSION), semantic analysis, type checking, and AST transforms are
out of scope. The CFG is an intentional over-approximation: some
programs that match it are rejected later. Those cases are listed
under <
productions.
This chapter does not re-introduce parser error alternatives for
missing ), ], or } (GROOVY-9588). Recognition failures share the
diagnostic path described in ARCHITECTURE.md.
[[grammar-notation]]
== Notation
[[grammar-ebnf-legend]]
=== Groovy Spec EBNF
Productions are written in [source,ebnf] blocks. The operator set is
W3C EBNF; the names are ANTLR 4 rule names.
[cols="1,2,2", options="header"]
|===
| Construct
| Meaning
| Example
| N ::= ...
| Production for nonterminal N
| sep ::= (NL \| ';')+
| A \| B
| Alternation (one of)
| 'class' \| 'interface'
| ( ... )
| Grouping
| 'static'? qualifiedName
| X?
| Optional (zero or one)
| packageDeclaration?
| X*
| Kleene star (zero or more)
| NL*
| X+
| One or more
| (NL \| ';')+
| 'foo'
| Terminal with that exact spelling (see <
| 'import'
| Identifier
| Terminal with no unique spelling (token name)
| GStringBegin
| /* R12 */
| This alternative is subject to restriction R12. Lexer fragments with
such marks are ordered: try earlier alternatives first.
| '$' /* R2 fails */
| fragment F
| Lexer fragment: not a token, only used by other lexer productions
| fragment Digits
| <MODE>
| Lexer start condition (mode). The production applies only in that mode
| <DQ_GSTRING_MODE>
|===
Concatenation is juxtaposition (W3C, not ISO 14977 comma). Square
brackets and braces are Groovy terminals (lists, closures), so
ISO 14977 optional/repetition brackets are not used.
A bare A | B in a parser production is unordered alternation (the
CFG skeleton). Where the g4 is ordered -- lexer rules, lexer
fragments with predicates, and predicated parser alternatives -- this
chapter annotates the restricted alternative with /* R */.
Reconstructing a lexer from the unordered | of a fragment without
those comments yields a different language.
There is no nls nonterminal in GroovyParser.g4. Optional
newlines are written NL* in situ. The statement separator is sep.
Left-recursive expression is a CFG production. Associativity that
ANTLR marks with <assoc=right> is recorded in the precedence table,
not with a special EBNF operator. Earlier alternatives ofexpression bind tighter (ANTLR 4 left-recursion).
ANTLR parameterized rules (classBody[int t],expressionList[boolean canSpread]) are not parameterized EBNF.
Syntactic parameters become named specialisations plus
<
CST for AstBuilder are notes, not syntax.
Parser alternative labels (#castExprAlt, ...) are visitor names, not
nonterminals.
[[grammar-vocabulary]]
== Terminal vocabulary
=== Keywords and identifier sets
The lexer always emits a dedicated token for a keyword spelling (exceptval when R5 disables it). The parser then re-accepts some of those
tokens as names.
Four concentric sets:
identifier-- unquoted names, labels, type names, unquoted method
names.keywords-- after.(namePart), as map keys, as annotation
element names.qualifiedNameElement-- package / import pieces, additionally'def''in''as''trait'.methodName--identifierorstringLiteral(quoted names of any
spelling, including reserved words).
className is only CapitalizedIdentifier.
[cols="1,1,1,1,2", options="header"]
|===
| Lexeme
| Token
| In identifier?
| In keywords?
| Notes
| abstract assert break case catch class continue default do else enum extends final finally for if implements import instanceof interface native new package return static strictfp super switch synchronized this throw throws transient try volatile while
| matching keyword token
| no
| yes
| reserved
| public protected private
| PUBLIC PROTECTED PRIVATE
| no
| yes
| reserved
| void and primitives
| VOID / BuiltInPrimitiveType
| no
| yes
| primitives are one token type (see below)
| true false null
| BooleanLiteral / NullLiteral
| no
| yes
| reserved words, not identifier
| def
| DEF
| no
| yes
| also qualifiedNameElement; a modifier, not a type production
| const goto threadsafe
| CONST GOTO THREADSAFE
| no
| yes
| reserved and unused. Not in identifier: a bare use is a parse
error. In keywords, so legal after ., as a map key, and as an
annotation element name.
| non-sealed
| NON_SEALED
| no
| yes
| one token, including the hyphen
| as in trait record sealed permits var yield async await defer module
| matching token
| yes
| module: no; others: yes
| contextual. module is in identifier only (for import module)
| val
| VAL or Identifier (R5)
| yes (always: token VAL or Identifier)
| yes only when the token is VAL
| feature flag groovy.val.enabled (default true). When false, val
is Identifier and is therefore not a variableModifier.
|===
BuiltInPrimitiveType is a single token type whose spellings are
the fragments boolean char byte short int long floatdouble. Those fragments are not independent tokens.
it is not a token. Implicit closure parameters are semantic.
The tutorial keyword tables in <<{core-syntax}#_syntax,Syntax>> are a
user-facing cut. This chapter follows the g4. In particular the grammar
has yield (not yields), and async / await / defer / module /val as tokens.
=== Operators and separators
Lexer tokens with unique spelling (parser productions quote the
lexeme):
[cols="2,1,3", options="header"]
|===
| Lexeme
| Token
| Role
| .. <.. ..< <..<
| RANGE_INCLUSIVE RANGE_EXCLUSIVE_LEFT RANGE_EXCLUSIVE_RIGHT RANGE_EXCLUSIVE_FULL
| ranges (shift precedence)
| *. ?. ??. .& ::
| SPREAD_DOT SAFE_DOT SAFE_CHAIN_DOT METHOD_POINTER METHOD_REFERENCE
| path operators. '??.' is syntax even if omitted from the operators tutorial
| ?[
| SAFE_INDEX
| safe index; pushes grouping (R9)
| ?: ?=
| ELVIS ELVIS_ASSIGN
| elvis / elvis-assign
| =~ ==~
| REGEX_FIND REGEX_MATCH
| regex find / match
| ** **=
| POWER POWER_ASSIGN
| power
| <=> === !== ==>
| SPACESHIP IDENTICAL NOT_IDENTICAL IMPLIES
|
| ->
| ARROW
| closure / lambda / switch-expression arrow
| !instanceof !in
| NOT_INSTANCEOF NOT_IN
| R6
| ( ) { } [ ]
| LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK
| grouping; each opener pushMode(DEFAULT_MODE)
| ; , . @ ...
| SEMI COMMA DOT AT ELLIPSIS
|
| = > < ! ~ ? : == \<= >= != && \|\| ++ -- + - * / & \| ^ %
| ASSIGN GT LT NOT BITNOT QUESTION COLON EQUAL LE GE NOTEQUAL AND OR INC DEC ADD SUB MUL DIV BITAND BITOR XOR MOD
| / as an operator is DIV, never written '/' in parser EBNF (R1)
| += -= *= /= &= \|= ^= %= <<= >>= >>>=
| matching *_ASSIGN tokens
| shift assignments are tokens; shift operators are not
|===
There are no LSHIFT / RSHIFT / URSHIFT lexer tokens. The
parser composes '<' '<', '>' '>', '>' '>' '>' soList<List<T>> can close.
=== Literals as token names
IntegerLiteral, FloatingPointLiteral, BooleanLiteral,NullLiteral, StringLiteral, GStringBegin, GStringPart,GStringEnd, GStringPathPart, Identifier, CapitalizedIdentifier,NL.
Quote style of an interpolating string is not a parser token:TdqGStringBegin, SlashyGStringBegin, andDollarSlashyGStringBegin are rewritten to GStringBegin (similarlyGStringPart / GStringEnd).
[[grammar-lexical]]
== Lexical structure
=== Input elements
The lexer produces a stream of default-channel tokens for the parser,
plus skipped or hidden trivia. Successful tokenisation never takes theUNEXPECTED_CHAR / unclosed-comment / invalid-octal paths
(AbstractLexer); those are diagnostics, not language
(<
=== White space and line escapes
[source,ebnf]
WS ::= ([ \t]+ | LineEscape+) /* skip */
fragment LineEscape ::= '\\' LineTerminator fragment LineTerminator ::= '\r'? '\n' | '\r'
Spaces and tabs are skipped. A backslash immediately followed by a line
terminator is also skipped (line continuation) and is not NL.
=== Line terminators, comments, shebang
[source,ebnf]
NL ::= LineTerminator /* then R9 */
SL_COMMENT ::=
'//' ~[\r\n\uFFFF]* /* -> type(NL); then R9 */
ML_COMMENT ::=
'/' .? ('/' | EOF) / -> type(NL); then R10.
unclosed: diagnostic at opener */
SH_COMMENT ::= /* skip; R11 /
'#!' ShCommand (LineTerminator '#!' ShCommand)
fragment ShCommand ::= ~[\r\n\uFFFF]*
There is no comment channel. // and /* ... */ are rewritten to
token type NL, so a comment can act as a statement separator when it
stays on the default channel.
Groovydoc (/** ... */ and runtime /**@ ... */) is not a
distinct token. It is ML_COMMENT; GroovydocManager attaches it
after parse. Groovydoc tag semantics are excluded.
SH_COMMENT is skipped when R11 holds (tokenIndex == 0). Leading
skipped WS (spaces, tabs, line-escapes) does not incrementtokenIndex, so a shebang after leading spaces is still skipped. A
prior default-channel token -- including a leading NL -- is not
allowed. Extra #! lines immediately after a line terminator are
part of the same SH_COMMENT token.
=== The grouping stack and newline significance
(/[/{/?[ record a grouping frame (enterParen) andpushMode(DEFAULT_MODE). The matching closer pops both.
Restriction R9: NL (and SL_COMMENT rewritten to NL) is moved to
the hidden channel when the stack top is:
'('whose preceding default-channel token is not'try', or'[', or'?['.
Newlines inside { ... } are significant. Try-with-resourcestry ( ... ) does not hide newlines (TRY on the ( frame).
Angle brackets '<' / '>' do not push a grouping frame, which
is why typeArguments writes NL* explicitly.
$\{ in a GString also calls enterParen() (see
<'{' -- not an
enclosing '(' / '[' -- is the stack top while the closure body is
tokenised.
Restriction R10: /* ... */ remains a visible NL only when it is
not inside grouping (R9) and only whitespace remains until end
of line or file. So a /*c*/ b is not two statements; a /*c*/ then
newline then b is.
Inside grouping, hidden NL is absent from the parser token stream.
Parser productions still write NL* / sep; those match only
visible NL.
lastTokenType (used by slashy-string vs DIV, R1) is updated only
for default-channel tokens. Hidden newlines do not change it.
=== Identifiers
[source,ebnf]
CapitalizedIdentifier ::=
[A-Z] JavaLetterOrDigit*
| ~[\u0000-\u007F\uD800-\uDBFF] /* R7 BMP, uppercase /
JavaLetterOrDigit
| [\uD800-\uDBFF] [\uDC00-\uDFFF] /* R7 supplementary, uppercase /
JavaLetterOrDigit
Identifier ::=
[a-z$_] JavaLetterOrDigit*
| ~[\u0000-\u007F\uD800-\uDBFF] /* R7 BMP, not uppercase /
JavaLetterOrDigit
| [\uD800-\uDBFF] [\uDC00-\uDFFF] /* R7 supplementary, not uppercase /
JavaLetterOrDigit
fragment JavaLetter ::=
[a-zA-Z$_]
| ~[\u0000-\u007F\uD800-\uDBFF] /* R7 BMP start, not ignorable /
| [\uD800-\uDBFF] [\uDC00-\uDFFF] / supplementary start (code point) */
fragment JavaLetterOrDigit ::=
[a-zA-Z0-9$_]
| ~[\u0000-\u007F\uD800-\uDBFF] /* R7 BMP part, not ignorable /
| [\uD800-\uDBFF] [\uDC00-\uDFFF] / supplementary part (code point) */
fragment JavaLetterInGString ::=
JavaLetter /* R8: last consumed char is not '$' */
fragment JavaLetterOrDigitInGString ::=
JavaLetterOrDigit /* R8 */
fragment IdentifierInGString ::=
JavaLetterInGString JavaLetterOrDigitInGString*
ASCII is a predicate-free DFA split ([A-Z] vs [a-z$_]).
Supplementary-plane letters classify capitalized vs not from the
decoded code point, not LA(-1) (a low surrogate is never uppercase;
GROOVY-12398). Identifier-ignorable start and continuation characters
are rejected on the BMP path (R7); the supplementary-plane path
tests Character.isJavaIdentifierStart / Part and case only.
GString identifiers cannot contain $ (R8).
$ and _ are allowed in Identifier. A leading $ or _ is
always Identifier, never CapitalizedIdentifier.
This is stricter and more precise than the identifier ranges in the
syntax tutorial.
=== !in / !instanceof / val
See R5 and R6.
=== Numeric literals
[source,ebnf]
IntegerLiteral ::=
(DecimalIntegerLiteral | HexIntegerLiteral
| OctalIntegerLiteral | BinaryIntegerLiteral)
Underscore? /* trailing '_' is then a diagnostic */
fragment DecimalIntegerLiteral ::= DecimalNumeral IntegerTypeSuffix?
fragment HexIntegerLiteral ::= HexNumeral IntegerTypeSuffix?
fragment OctalIntegerLiteral ::= OctalNumeral IntegerTypeSuffix?
fragment BinaryIntegerLiteral ::= BinaryNumeral IntegerTypeSuffix?
fragment IntegerTypeSuffix ::= [lLiIgG]
fragment DecimalNumeral ::= '0' | [1-9] (Digits? | Underscores Digits)
fragment Digits ::= [0-9] ([0-9_]* [0-9])?
fragment Underscores ::= ''+
fragment Underscore ::= ''
fragment HexNumeral ::= '0' [xX] HexDigits
fragment HexDigits ::= HexDigit ([0-9a-fA-F_]* HexDigit)?
fragment HexDigit ::= [0-9a-fA-F]
fragment OctalNumeral ::= '0' ''* OctalDigits
fragment OctalDigits ::= [0-7] ([0-7]* [0-7])?
fragment BinaryNumeral ::= '0' [bB] BinaryDigits
fragment BinaryDigits ::= [01] ([01_]* [01])?
FloatingPointLiteral ::=
(DecimalFloatingPointLiteral | HexadecimalFloatingPointLiteral)
Underscore?
fragment DecimalFloatingPointLiteral ::=
Digits? '.' Digits ExponentPart? FloatTypeSuffix?
| Digits ExponentPart FloatTypeSuffix?
| Digits FloatTypeSuffix
fragment ExponentPart ::= [eE] [+-]? Digits
fragment FloatTypeSuffix ::= [fFdDgG]
fragment HexadecimalFloatingPointLiteral ::=
HexSignificand BinaryExponent FloatTypeSuffix?
fragment HexSignificand ::= HexNumeral '.'? | '0' [xX] HexDigits? '.' HexDigits
fragment BinaryExponent ::= [pP] [+-]? Digits
Suffix G/g is BigInteger / BigDecimal at conversion, not a
distinct token. Invalid octal (08, 09, ...) and trailing _ are
lexer diagnostics, not language (<
=== Ordinary string literals (StringLiteral)
A string with no interpolating $ is StringLiteral:
[source,ebnf]
StringLiteral ::=
'"' DqStringCharacter* '"'
| "'" SqStringCharacter* "'"
| '/' SlashyStringCharacter+ '/' /* R1; at least one inner char /
| '"""' TdqStringCharacter '"""'
| "'''" TsqStringCharacter* "'''"
| '$/' DollarSlashyStringCharacter+ '/$'
fragment DqStringCharacter ::= ~["\r\n\$] | EscapeSequence
fragment SqStringCharacter ::= ~['\r\n\] | EscapeSequence
fragment TdqStringCharacter ::=
~["\$]
| '"' /* R3 /
| EscapeSequence
fragment TsqStringCharacter ::=
~['\]
| "'" / R3 /
| EscapeSequence
fragment SlashyStringCharacter ::=
'\' '/'
| '$' / R2 fails /
| ~[/$\u0000]
fragment DollarSlashyStringCharacter ::=
'$$'
| '$/$' / R4: LA(-4) != '$' /
| '$/' / R4: LA(1) != '$' /
| '/' / R4: LA(1) != '$' /
| '$' / R2 fails */
| ~[/$\u0000]
fragment EscapeSequence ::=
'\' [btnfrs"'\]
| OctalEscape
| UnicodeEscape /* '' 'u' HexDigit{4} -- exactly one u */
| '\' '$'
| LineEscape
fragment OctalEscape ::=
'\' [0-7]
| '\' [0-7] [0-7]
| '\' [0-3] [0-7] [0-7]
fragment UnicodeEscape ::=
'\' 'u' HexDigit HexDigit HexDigit HexDigit
Empty // is SL_COMMENT, not an empty slashy string (the slashy
alternative requires SlashyStringCharacter+). Empty $/$ is notStringLiteral either.
The g4 comment "Groovy allows 1 or more u's after the backslash" does
not match the production: UnicodeEscape is exactly one u.
[[grammar-gstring-lexical]]
=== GString lexical contexts
An interpolating string is not `