Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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.

...