public class ExpandoMetaClass extends MetaClassImpl
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 amountYou can also now do this:->
amount.times(delegate) } Number.metaClass.div = { Amount amount->
amount.inverse().times(delegate) }
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 { ListWe can mimic a form of multiple inheritance as follows:schedule = [] def addLecture(String lecture) { schedule <<
lecture } } class Worker { Listschedule = [] def addMeeting(String meeting) { schedule <<
meeting } }
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 oAs 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:->
if (!mixedIn[Set].contains(o)) { mixedIn[Queue].push(o) mixedIn[Set].add(o) } } } ndq<<
1 ndq<<
2 ndq<<
1 assert ndq.size() == 2
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.
Modifiers | Name | Description |
---|---|---|
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 << left shift operator. |
Modifiers | Name | Description |
---|---|---|
static String |
CONSTRUCTOR |
|
static String |
STATIC_QUALIFIER |
|
boolean |
inRegistry |
Fields inherited from class | Fields |
---|---|
class MetaClassImpl |
EMPTY_ARGUMENTS, INVOKE_METHOD_METHOD, METHOD_MISSING, PROPERTY_MISSING, STATIC_METHOD_MISSING, STATIC_PROPERTY_MISSING, getPropertyMethod, invokeMethodMethod, isGroovyObject, isMap, metaMethodIndex, registry, setPropertyMethod, theCachedClass, theClass |
Constructor and description |
---|
ExpandoMetaClass
(Class theClass, boolean register, boolean allowChangesAfterInit, MetaMethod[] add) |
ExpandoMetaClass
(MetaClassRegistry registry, Class theClass, boolean register, boolean allowChangesAfterInit, MetaMethod[] add) |
ExpandoMetaClass
(Class theClass) Constructs a new ExpandoMetaClass instance for the given class |
ExpandoMetaClass
(Class theClass, MetaMethod[] add) |
ExpandoMetaClass
(Class theClass, boolean register) Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically |
ExpandoMetaClass
(Class theClass, boolean register, MetaMethod[] add) |
ExpandoMetaClass
(Class theClass, boolean register, boolean allowChangesAfterInit) Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically |
Type Params | Return Type | Name and description |
---|---|---|
|
void |
addMixinClass(MixinInMetaClass mixin) |
|
void |
call() |
|
Object |
castToMixedType(Object obj, Class type) |
|
protected void |
checkInitalised() |
|
CallSite |
createConstructorSite(CallSite site, Object[] args) |
|
CallSite |
createPogoCallCurrentSite(CallSite site, Class sender, String name, Object[] args) |
|
CallSite |
createPogoCallSite(CallSite site, Object[] args) |
|
CallSite |
createPojoCallSite(CallSite site, Object receiver, Object[] args) |
|
CallSite |
createStaticSite(CallSite site, Object[] args) |
|
ExpandoMetaClass |
define(Closure closure) |
|
static void |
disableGlobally() Call to disable the global use of ExpandoMetaClass |
|
static void |
enableGlobally() Call to enable global use of ExpandoMetaClass within the registry. |
|
MetaMethod |
findMixinMethod(String methodName, Class[] arguments) |
|
List<MetaMethod> |
getExpandoMethods() Returns a list of expando MetaMethod instances added to this ExpandoMetaClass |
|
Collection<MetaProperty> |
getExpandoProperties() Returns a list of MetaBeanProperty instances added to this ExpandoMetaClass |
|
Collection |
getExpandoSubclassMethods() |
|
Class |
getJavaClass()
|
|
MetaClass |
getMetaClass() |
|
MetaProperty |
getMetaProperty(String name) Looks up an existing MetaProperty by name |
|
List<MetaMethod> |
getMethods() Overrides the behavior of parent getMethods() method to make MetaClass aware of added Expando methods |
|
List<MetaProperty> |
getProperties() |
|
Object |
getProperty(String property) |
|
Object |
getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) Overrides default implementation just in case getProperty method has been overridden by ExpandoMetaClass |
|
Object |
getProperty(Object object, String name) Overrides default implementation just in case getProperty method has been overridden by ExpandoMetaClass |
|
String |
getPropertyForSetter(String setterName) Returns a property name equivalent for the given setter name or null if it is not a getter |
|
protected Object |
getSubclassMetaMethods(String methodName) |
|
boolean |
hasCustomStaticInvokeMethod() |
|
boolean |
hasMetaMethod(String name, Class[] args) Checks whether a MetaMethod for the given name and arguments exists |
|
boolean |
hasMetaProperty(String name) Returns true if the MetaClass has the given property |
|
void |
initialize() {@inheritDoc} |
|
Object |
invokeConstructor(Object[] arguments) |
|
Object |
invokeMethod(String name, Object args) |
|
Object |
invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) Overrides default implementation just in case invokeMethod has been overridden by ExpandoMetaClass |
|
Object |
invokeStaticMethod(Object object, String methodName, Object[] arguments) Overrides default implementation just in case a static invoke method has been set on ExpandoMetaClass |
|
protected boolean |
isInitialized() Checks if the meta class is initialized. |
|
boolean |
isModified() |
|
boolean |
isSetter(String name, CachedClass[] args) |
|
static boolean |
isValidExpandoProperty(String property) |
|
protected void |
onGetPropertyFoundInHierarchy(MetaMethod method) |
|
protected void |
onInvokeMethodFoundInHierarchy(MetaMethod method) |
|
protected void |
onSetPropertyFoundInHierarchy(MetaMethod method) |
|
protected void |
onSuperMethodFoundInHierarchy(MetaMethod method) |
|
protected void |
onSuperPropertyFoundInHierarchy(MetaBeanProperty property) |
|
protected void |
performOperationOnMetaClass(ExpandoMetaClass.Callable c) |
|
void |
refreshInheritedMethods(Set modifiedSuperExpandos) Called from ExpandoMetaClassCreationHandle in the registry if it exists to set up inheritance handling |
|
void |
registerBeanProperty(String property, Object newValue) Registers a new bean property |
|
void |
registerInstanceMethod(MetaMethod metaMethod) Registers a new instance method for the given method name and closure on this MetaClass |
|
void |
registerInstanceMethod(String name, Closure closure) |
|
protected void |
registerStaticMethod(String name, Closure callable) |
|
protected void |
registerStaticMethod(String name, Closure callable, Class[] paramTypes) Registers a new static method for the given method name and closure on this MetaClass |
|
void |
registerSubclassInstanceMethod(String name, Class klazz, Closure closure) |
|
void |
registerSubclassInstanceMethod(MetaMethod metaMethod) |
|
MetaMethod |
retrieveConstructor(Object[] args) |
|
protected void |
setInitialized(boolean b) |
|
void |
setMetaClass(MetaClass metaClass) |
|
void |
setProperty(String property, Object newValue) |
|
void |
setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) |
Methods inherited from class | Name |
---|---|
class MetaClassImpl |
getMetaMethod, getMetaProperty, getRegistry, getStaticMetaMethod, getSuperClasses, getTheCachedClass, getTheClass, hasProperty, isGroovyObject, methodNameAction, respondsTo, respondsTo, skipClass |
Constructs a new ExpandoMetaClass instance for the given class
theClass
- The class that the MetaClass applies toConstructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically
theClass
- The class that the MetaClass applies toregister
- True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changedConstructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically
theClass
- The class that the MetaClass applies toregister
- True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changedallowChangesAfterInit
- Should the meta class be modifiable after initialization. Default is false.Call to disable the global use of ExpandoMetaClass
Call to enable global use of ExpandoMetaClass within the registry. This has the advantage that inheritance will function correctly and metaclass modifications will also apply to existing objects, but has a higher memory usage on the JVM than normal Groovy
Returns a list of expando MetaMethod instances added to this ExpandoMetaClass
Returns a list of MetaBeanProperty instances added to this ExpandoMetaClass
Looks up an existing MetaProperty by name
name
- The name of the MetaPropertyOverrides the behavior of parent getMethods() method to make MetaClass aware of added Expando methods
Overrides default implementation just in case getProperty method has been overridden by ExpandoMetaClass
Overrides default implementation just in case getProperty method has been overridden by ExpandoMetaClass
Returns a property name equivalent for the given setter name or null if it is not a getter
setterName
- The setter nameChecks whether a MetaMethod for the given name and arguments exists
name
- The name of the MetaMethodargs
- The arguments to the meta methodReturns true if the MetaClass has the given property
name
- The name of the MetaProperty{@inheritDoc}
Overrides default implementation just in case invokeMethod has been overridden by ExpandoMetaClass
Overrides default implementation just in case a static invoke method has been set on ExpandoMetaClass
Checks if the meta class is initialized.
Called from ExpandoMetaClassCreationHandle in the registry if it exists to set up inheritance handling
modifiedSuperExpandos
- A list of modified super ExpandoMetaClassRegisters a new bean property
property
- The property namenewValue
- The properties initial valueRegisters a new instance method for the given method name and closure on this MetaClass
Registers a new static method for the given method name and closure on this MetaClass
name
- The method namecallable
- The callable ClosureCopyright © 2003-2020 The Apache Software Foundation. All rights reserved.