...
Initially
...
developed
...
under
...
the
...
Grailsumbrella
...
and
...
integrated
...
back
...
into
...
Groovy
...
1.5,
...
ExpandoMetaClass
...
is
...
a
...
very
...
handy
...
way
...
for
...
changing
...
the
...
runtime
...
behavior
...
of
...
your
...
objects
...
and
...
classes,
...
instead
...
of
...
writing
...
full-blow
...
MetaClass
...
classes.
...
Each
...
time,
...
we
...
want
...
to
...
add
...
/
...
change
...
several
...
properties
...
or
...
methods
...
of
...
an
...
existing
...
type,
...
there
...
is
...
too
...
much
...
of
...
a
...
repetition
...
of
...
Type.metaClass.xxx.
...
Take
...
for
...
example
...
this
...
extract
...
of
...
a
...
Unit
...
manipulation
...
DSL
...
dealing
...
with
...
operator
...
overloading:
...
| Code Block |
|---|
Number.metaClass.multiply = { Amount amount -> amount.times(delegate) }
Number.metaClass.div = { Amount amount -> amount.inverse().times(delegate) }
Amount.metaClass.div = { Number factor -> delegate.divide(factor) }
Amount.metaClass.div = { Amount factor -> delegate.divide(factor) }
Amount.metaClass.multiply = { Number factor -> delegate.times(factor) }
Amount.metaClass.power = { Number factor -> delegate.pow(factor) }
Amount.metaClass.negative = { -> delegate.opposite() }
{code}
|
The
...
repetition,
...
here,
...
looks
...
obvious.
...
But
...
with
...
the
...
ExpandoMetaClass
...
DSL,
...
we
...
can
...
streamline
...
the
...
code
...
by
...
regrouping
...
the
...
operators
...
per
...
type:
...
| Code Block |
|---|
Number.metaClass {
multiply { Amount amount -> amount.times(delegate) }
div { Amount amount -> amount.inverse().times(delegate) }
}
Amount.metaClass {
div << { Number factor -> delegate.divide(factor) }
div << { Amount factor -> delegate.divide(factor) }
multiply { Number factor -> delegate.times(factor) }
power { Number factor -> delegate.pow(factor) }
negative { -> delegate.opposite() }
}
{code}
|
A
...
metaClass()
...
method
...
takes
...
a
...
closure
...
as
...
single
...
argument,
...
containing
...
the
...
various
...
definitions
...
of
...
the
...
methods
...
and
...
properties,
...
instead
...
of
...
repeating
...
the
...
Type.metaClass
...
on
...
each
...
line.
...
When
...
there
...
is
...
just
...
one
...
method
...
of
...
a
...
given
...
name,
...
use
...
the
...
pattern
...
methodName
...
{
...
/*
...
closure
...
*/
...
},
...
but
...
when
...
there
...
are
...
several,
...
you
...
should
...
use
...
the
...
append
...
operator
...
and
...
follow
...
the
...
patten
...
methodName
...
<<
...
{
...
/*
...
closure
...
*/
...
}.
...
Static
...
methods
...
can
...
also
...
be
...
added
...
through
...
this
...
mechanism,
...
so
...
instead
...
of
...
the
...
classical
...
approach:
...
| Code Block |
|---|
// add a fqn() method to Class to get the fully
// qualified name of the class (ie. simply Class#getName)
Class.metaClass.static.fqn = { delegate.name }
assert String.fqn() == "java.lang.String"
{code}
|
You
...
can
...
now
...
do:
...
| Code Block |
|---|
Class.metaClass {
'static' {
fqn { delegate.name }
}
}
{code}
|
Note
...
here
...
that
...
you
...
have
...
to
...
quote
...
the
...
statickeyword,
...
to
...
avoid
...
this
...
construct
...
to
...
look
...
like
...
a
...
static
...
initializer.
...
For
...
one
...
off
...
method
...
addition,
...
the
...
classical
...
approach
...
is
...
obviously
...
more
...
concise,
...
but
...
when
...
you
...
have
...
several
...
methods
...
to
...
add,
...
the
...
EMC
...
DSL
...
makes
...
sense.
...
The
...
usual
...
approach
...
for
...
adding
...
properties
...
to
...
existing
...
classes
...
through
...
ExpandoMetaClass
...
is
...
to
...
add
...
a
...
getter
...
and
...
a
...
setter
...
as
...
methods.
...
For
...
instance,
...
say
...
you
...
want
...
to
...
add
...
a
...
method
...
that
...
counts
...
the
...
number
...
of
...
words
...
in
...
a
...
text
...
file,
...
you
...
could
...
try
...
this:
...
| Code Block |
|---|
File.metaClass.getWordCount = {
delegate.text.split(/\w/).size()
}
new File('myFile.txt').wordCount
{code}
|
When
...
there
...
is
...
some
...
logic
...
inside
...
the
...
getter,
...
this
...
is
...
certainly
...
the
...
best
...
approach,
...
but
...
when
...
you
...
just
...
want
...
to
...
have
...
new
...
properties
...
holding
...
simple
...
values,
...
through
...
the
...
ExpandoMetaClass
...
DSL,
...
it
...
is
...
possible
...
to
...
define
...
them.
...
In
...
the
...
following
...
example,
...
a
...
lastAccessed
...
property
...
is
...
added
...
to
...
a
...
Car
...
class
...
— each
...
instance
...
will
...
have
...
its
...
property.
...
Whenever
...
a
...
method
...
is
...
called
...
on
...
that
...
car,
...
this
...
property
...
is
...
updated
...
with
...
a
...
newer
...
timestamp.
...
| Code Block |
|---|
class Car {
void turnOn() {}
void drive() {}
void turnOff() {}
}
Car.metaClass {
lastAccessed = null
invokeMethod = { String name, args ->
def metaMethod = delegate.metaClass.getMetaMethod(name, args)
if (metaMethod) {
delegate.lastAccessed = new Date()
metaMethod.doMethodInvoke(delegate, args)
} else {
throw new MissingMethodException(name, delegate.class, args)
}
}
}
def car = new Car()
println "Last accessed: ${car.lastAccessed ?: 'Never'}"
car.turnOn()
println "Last accessed: ${car.lastAccessed ?: 'Never'}"
car.drive()
sleep 1000
println "Last accessed: ${car.lastAccessed ?: 'Never'}"
sleep 1000
car.turnOff()
println "Last accessed: ${car.lastAccessed ?: 'Never'}"
{code}
|
In
...
our
...
example,
...
in
...
the
...
DSL,
...
we
...
access
...
that
...
property
...
through
...
the
...
delegate
...
of
...
the
...
closure,
...
with
...
delegate.lastAccessed
...
=
...
new
...
Date().
...
And
...
we
...
intercept
...
any
...
method
...
call
...
thanks
...
to
...
invokeMethod(),
...
delegating
...
to
...
the
...
original
...
method
...
for
...
the
...
call,
...
and
...
throwing
...
an
...
exception
...
in
...
case
...
the
...
method
...
doesn't
...
exist.
...
Later
...
on,
...
you
...
can
...
see
...
by
...
executing
...
this
...
script
...
that
...
lastAccessed
...
is
...
updated
...
as
...
soon
...
as
...
we
...
call
...
a
...
method
...
on
...
our
...
instance.
...