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.
| Modifier and Type | Class and Description |
|---|---|
| private static interface | ExpandoMetaClass.Callable
For simulating closures in Java |
| private class | |
| protected class | ExpandoMetaClass.ExpandoMetaConstructor
Handles the ability to use the left shift operator to append new constructors |
| protected class | ExpandoMetaClass.ExpandoMetaProperty
Instances of this class are returned when using the |
| private static class | |
| private class | |
| private class |
| Modifier and Type | Field and Description |
|---|---|
| private final boolean | |
| private final Map | |
| private static final String | |
| private static final String | |
| public static final String | |
| private static final Class | |
| private final Map | |
| private final Map | |
| private final ConcurrentHashMap | |
| private static final String | |
| private final Set | |
| private boolean | |
| private volatile boolean | |
| public boolean | |
| private ClosureStaticMetaMethod | |
| private static final String | |
| private static final String | |
| private static final String | |
| private static final String | |
| private final Set | |
| private volatile boolean | |
| private MetaClass | |
| private static final String | |
| private final Lock | |
| private final ReentrantReadWriteLock | |
| public static final String | |
| private final Map | |
| private final Lock |
| Access | Constructor and Description |
|---|---|
| public | ExpandoMetaClass(Class<T> theClass, boolean register, boolean allowChangesAfterInit, MetaMethod[] add)
|
| public | ExpandoMetaClass(MetaClassRegistry registry, Class<T> theClass, boolean register, boolean allowChangesAfterInit, MetaMethod[] add)
|
| public | ExpandoMetaClass(Class<T>
The class that the MetaClass applies to theClass)Constructs a new ExpandoMetaClass instance for the given class |
| public | |
| public | ExpandoMetaClass(Class<T>
The class that the MetaClass applies to theClass, boolean True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changed register)Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically |
| public | |
| public | ExpandoMetaClass(Class<T>
The class that the MetaClass applies to theClass, boolean True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changed register, boolean Should the meta class be modifiable after initialization. Default is false. allowChangesAfterInit)Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically |
| Modifier and Type | Method and Description |
|---|---|
| public void | |
| private void | |
| public Object | |
| protected void | checkInitalised()
Overrides groovy. |
| public CallSite | createConstructorSite(CallSite site, Object[] args)
Create a CallSite
|
| public CallSite | |
| public CallSite | createPogoCallSite(CallSite site, Object[] args)
Overrides groovy. |
| public CallSite | createPojoCallSite(CallSite site, Object receiver, Object[] args)
Overrides groovy. |
| public CallSite | createStaticSite(CallSite site, Object[] args)
Overrides groovy. |
| public ExpandoMetaClass | |
| public static void | |
| public static void | |
| public MetaMethod | findMixinMethod(String methodName, Class<T>[] arguments)
Overrides groovy. |
| public List | Returns: the expandoMethodsReturns a list of expando MetaMethod instances added to this ExpandoMetaClass |
| public Collection | Returns: the expandoPropertiesReturns a list of MetaBeanProperty instances added to this ExpandoMetaClass |
| public Collection | |
| public Class | |
| public MetaClass | getMetaClass()
Implements groovy. |
| public MetaProperty | Returns: The MetaProperty or null if it doesn't existThe name of the MetaProperty name)Overrides groovy. Implements groovy. |
| public List | Returns: A list of MetaMethodsOverrides groovy. Implements groovy. |
| public List | getProperties()
Overrides groovy. Implements groovy. |
| public Object | getProperty(String
the name of the property of interest property)Overrides default groovy. |
| public Object | getProperty(Class<T>
The java.lang.Class instance that requested the property sender, Object The Object which the property is being retrieved from object, String The name of the property name, boolean Whether the call is to a super class property useSuper, boolean ?? fromInsideClass)Overrides groovy. Implements groovy. |
| public Object | getProperty(Object
The Object which the property is being retrieved from object, String The name of the property name)Overrides groovy. Implements groovy. |
| private String | Returns: The property name equivalentThe getter name getterName)Returns a property name equivalent for the given getter name or null if it is not a getter |
| public String | Returns: The property name equivalentThe setter name setterName)Returns a property name equivalent for the given setter name or null if it is not a getter |
| protected Object | getSubclassMetaMethods(String methodName)
|
| public boolean | hasCustomStaticInvokeMethod()
Overrides groovy. |
| public boolean | Returns: True if the method exists otherwise nullThe name of the MetaMethod name, Class<T>[] The arguments to the meta method args)Checks whether a MetaMethod for the given name and arguments exists |
| public boolean | Returns: True it exists as a MetaPropertyThe name of the MetaProperty name)Returns true if the MetaClass has the given property |
| private boolean | |
| public void | initialize()
Overrides groovy. Implements groovy. |
| public Object | invokeConstructor(Object[]
The arguments to the constructor arguments)Overrides groovy. Implements groovy. |
| public Object | invokeMethod(String
the name of the method to call name, Object the arguments to use for the method call args)Overrides default groovy. |
| public Object | invokeMethod(Class<T>
The java.lang.Class instance that invoked the method sender, Object The object which the method was invoked on object, String The name of the method methodName, Object[] The arguments to the method originalArguments, boolean Whether the method is a call to a super class method isCallToSuper, boolean Whether the call was invoked from the inside or the outside of the class fromInsideClass)Overrides groovy. Implements groovy. |
| public Object | invokeStaticMethod(Object
An instance of the class returned by the getTheClass() method or the class itself object, String The name of the method methodName, Object[] The arguments to the method arguments)Overrides groovy. Implements groovy. |
| private boolean | Returns: True if it is a javabean property methodTrue if its a Javabean property name, CachedClass[] The arguments args)Returns true if the name of the method specified and the number of arguments make it a javabean property |
| protected boolean | isInitialized()
Overrides groovy. |
| public boolean | isModified()
Overrides groovy. Implements groovy. |
| private static boolean | |
| public boolean | |
| public static boolean | |
| protected void | |
| protected void | |
| protected void | |
| protected void | |
| protected void | |
| protected synchronized void | |
| private void | |
| public void | refreshInheritedMethods(Set<E>
A list of modified super ExpandoMetaClass modifiedSuperExpandos)Called from ExpandoMetaClassCreationHandle in the registry if it exists to set up inheritance handling |
| private void | |
| public void | registerBeanProperty(final String
The property name property, final Object The properties initial value newValue)Registers a new bean property |
| private void | registerBeanPropertyForMethod(MetaMethod metaMethod, String propertyName, boolean getter, boolean isStatic)
|
| public void | registerInstanceMethod(final MetaMethod metaMethod)
Registers a new instance method for the given method name and closure on this MetaClass |
| public void | |
| protected void | |
| protected void | registerStaticMethod(final String
The method name name, final Closure<V> The callable Closure callable, final Class<T>[] paramTypes)Registers a new static method for the given method name and closure on this MetaClass |
| public void | |
| public void | |
| public MetaMethod | retrieveConstructor(Object[] args)
This is a helper method added in Groovy 2.1.0, which is used only by indy.
|
| protected void | |
| public void | setMetaClass(MetaClass
the new metaclass metaClass)Implements groovy. |
| public void | setProperty(String
the name of the property of interest property, Object the new value for the property newValue)Overrides default groovy. |
| public void | setProperty(Class<T>
The java.lang.Class instance that is mutating the property sender, Object The Object which the property is being set on object, String The name of the property name, Object The new value of the property to set newValue, boolean Whether the call is to a super class property useSuper, boolean Whether the call was invoked from the inside or the outside of the class. fromInsideClass)Overrides groovy. Implements groovy. |