Package groovy.lang

Class ExpandoMetaClass

java.lang.Object
groovy.lang.MetaClassImpl
groovy.lang.ExpandoMetaClass
All Implemented Interfaces:
GroovyObject, MetaClass, MetaObjectProtocol, MutableMetaClass

public class ExpandoMetaClass
extends MetaClassImpl
implements GroovyObject
ExpandoMetaClass is a MetaClass that behaves like an Expando, allowing the addition or replacement of methods, properties and constructors on the fly.

Some examples of usage:

 // defines or replaces instance method:
 metaClass.myMethod = { args -> }

 // defines a new instance method
 metaClass.myMethod << { args -> }

 // creates multiple overloaded methods of the same name
 metaClass.myMethod << { String s -> } << { Integer i -> }

 // defines or replaces a static method with the 'static' qualifier
 metaClass.'static'.myMethod = { args ->  }

 // defines a new static method with the 'static' qualifier
 metaClass.'static'.myMethod << { args ->  }

 // defines a new constructor
 metaClass.constructor << { String arg -> }

 // defines or replaces a constructor
 metaClass.constructor = { String arg -> }

 // defines a new property with an initial value of "blah"
 metaClass.myProperty = "blah"
 

ExpandoMetaClass also supports a DSL/builder like notation to combine multiple definitions together. So instead of this:

 Number.metaClass.multiply = { Amount amount -> amount.times(delegate) }
 Number.metaClass.div =      { Amount amount -> amount.inverse().times(delegate) }
 
You can also now do this:
 Number.metaClass {
     multiply { Amount amount -> amount.times(delegate) }
     div      { Amount amount -> amount.inverse().times(delegate) }
 }
 

ExpandoMetaClass also supports runtime mixins. While @Mixin allows you to mix in new behavior to classes you own and are designing, you can not easily mixin anything to types you didn't own, e.g. from third party libraries or from JDK library classes. Runtime mixins let you add a mixin on any type at runtime.

 interface Vehicle {
     String getName()
 }

 // Category annotation style
 @Category(Vehicle) class FlyingAbility {
     def fly() { "I'm the ${name} and I fly!" }
 }

 // traditional category style
 class DivingAbility {
     static dive(Vehicle self) { "I'm the ${self.name} and I dive!" }
 }

 // provided by a third-party, so can't augment using Mixin annotation
 class JamesBondVehicle implements Vehicle {
     String getName() { "James Bond's vehicle" }
 }

 // Can be added via metaClass, e.g.:
 // JamesBondVehicle.metaClass.mixin DivingAbility, FlyingAbility
 // Or using shorthand through DGM method on Class
 JamesBondVehicle.mixin DivingAbility, FlyingAbility

 assert new JamesBondVehicle().fly() ==
        "I'm the James Bond's vehicle and I fly!"
 assert new JamesBondVehicle().dive() ==
        "I'm the James Bond's vehicle and I dive!"
 
As another example, consider the following class definitions:
 class Student {
     List schedule = []
     def addLecture(String lecture) { schedule << lecture }
 }

 class Worker {
     List schedule = []
     def addMeeting(String meeting) { schedule << meeting }
 }
 
We can mimic a form of multiple inheritance as follows:
 class CollegeStudent {
     static { mixin Student, Worker }
 }
 new CollegeStudent().with {
     addMeeting('Performance review with Boss')
     addLecture('Learn about Groovy Mixins')
     println schedule
     println mixedIn[Student].schedule
     println mixedIn[Worker].schedule
 }
 
Which outputs these lines when run:
 [Performance review with Boss]
 [Learn about Groovy Mixins]
 [Performance review with Boss]
 
Perhaps some explanation is required here. The methods and properties of Student and Worker are added to CollegeStudent. Worker is added last, so for overlapping methods, its methods will be used, e.g. when calling schedule, it will be the schedule property (getSchedule method) from Worker that is used. The schedule property from Student will be shadowed but the mixedIn notation allows us to get to that too if we need as the last two lines show.

We can also be a little more dynamic and not require the CollegeStudent class to be defined at all, e.g.:

 def cs = new Object()
 cs.metaClass {
     mixin Student, Worker
     getSchedule {
         mixedIn[Student].schedule + mixedIn[Worker].schedule
     }
 }
 cs.with {
     addMeeting('Performance review with Boss')
     addLecture('Learn about Groovy Mixins')
     println schedule
 }
 
Which outputs this line when run:
 [Learn about Groovy Mixins, Performance review with Boss]
 
As another example, we can also define a no dup queue by mixing in some Queue and Set functionality as follows:
 def ndq = new Object()
 ndq.metaClass {
     mixin ArrayDeque
     mixin HashSet
     leftShift = { Object o  -> 
         if (!mixedIn[Set].contains(o)) {
             mixedIn[Queue].push(o)
             mixedIn[Set].add(o)
         }
     }
 }
 ndq << 1
 ndq << 2
 ndq << 1
 assert ndq.size() == 2
 
As a final example, we sometimes need to pass such mixed in classes or objects into Java methods which require a given static type but the ExpandoMetaClass mixin approach uses a very dynamic approach based on duck typing rather than static interface definitions, so doesn't by default produce objects matching the required static type. Luckily, there is a mixins capability within ExpandoMetaClass which supports the use of Groovy's common 'as StaticType' notation to produce an object having the correct static type so that it can be passed to the Java method call in question. A slightly contrived example illustrating this feature:
 class CustomComparator implements Comparator {
     int compare(Object a, b) { return a.size() - b.size() }
 }

 class CustomCloseable implements Closeable {
     void close() { println 'Lights out - I am closing' }
 }

 import static mypackage.IOUtils.closeQuietly
 import static java.util.Collections.sort
 def o = new Object()
 o.metaClass.mixin CustomComparator, CustomCloseable
 def items = ['a', 'bbb', 'cc']
 sort(items, o as Comparator)
 println items                // => [a, cc, bbb]
 closeQuietly(o as Closeable) // => Lights out - I am closing
 

Further details

When using the default implementations of MetaClass, methods are only allowed to be added before initialize() is called. In other words you create a new MetaClass, add some methods and then call initialize(). If you attempt to add new methods after initialize() has been called, an error will be thrown. This is to ensure that the MetaClass can operate appropriately in multi-threaded environments as it forces you to do all method additions at the beginning, before using the MetaClass.

ExpandoMetaClass differs here from the default in that it allows you to add methods after initialize has been called. This is done by setting the initialize flag internally to false and then add the methods. Since this is not thread safe it has to be done in a synchronized block. The methods to check for modification and initialization are therefore synchronized as well. Any method call done through this meta class will first check if the it is synchronized. Should this happen during a modification, then the method cannot be selected or called unless the modification is completed.

Since:
1.5