Package groovy.lang

Class 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
    • Constructor Detail

      • ExpandoMetaClass

        public ExpandoMetaClass​(Class theClass,
                                boolean register,
                                boolean allowChangesAfterInit,
                                MetaMethod[] add)
      • ExpandoMetaClass

        public ExpandoMetaClass​(MetaClassRegistry registry,
                                Class theClass,
                                boolean register,
                                boolean allowChangesAfterInit,
                                MetaMethod[] add)
      • ExpandoMetaClass

        public ExpandoMetaClass​(Class theClass)
        Constructs a new ExpandoMetaClass instance for the given class
        Parameters:
        theClass - The class that the MetaClass applies to
      • ExpandoMetaClass

        public ExpandoMetaClass​(Class theClass,
                                MetaMethod[] add)
      • ExpandoMetaClass

        public ExpandoMetaClass​(Class theClass,
                                boolean register)
        Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically
        Parameters:
        theClass - The class that the MetaClass applies to
        register - True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changed
      • ExpandoMetaClass

        public ExpandoMetaClass​(Class theClass,
                                boolean register,
                                MetaMethod[] add)
      • ExpandoMetaClass

        public ExpandoMetaClass​(Class theClass,
                                boolean register,
                                boolean allowChangesAfterInit)
        Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass in the MetaClassRegistry automatically
        Parameters:
        theClass - The class that the MetaClass applies to
        register - True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changed
        allowChangesAfterInit - Should the meta class be modifiable after initialization. Default is false.
    • Method Detail

      • getExpandoSubclassMethods

        public Collection getExpandoSubclassMethods()
      • isModified

        public boolean isModified()
        Description copied from class: MetaClassImpl
        Returns whether this MetaClassImpl has been modified. Since MetaClassImpl is not designed for modification this method always returns false
        Specified by:
        isModified in interface MutableMetaClass
        Overrides:
        isModified in class MetaClassImpl
        Returns:
        false
      • registerSubclassInstanceMethod

        public void registerSubclassInstanceMethod​(String name,
                                                   Class klazz,
                                                   Closure closure)
      • registerSubclassInstanceMethod

        public void registerSubclassInstanceMethod​(MetaMethod metaMethod)
      • enableGlobally

        public static void enableGlobally()
        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
      • disableGlobally

        public static void disableGlobally()
        Call to disable the global use of ExpandoMetaClass
      • initialize

        public void initialize()
        Description copied from class: MetaClassImpl
        Complete the initialisation process. After this method is called no methods should be added to the meta class. Invocation of methods or access to fields/properties is forbidden unless this method is called. This method should contain any initialisation code, taking a longer time to complete. An example is the creation of the Reflector. It is suggested to synchronize this method.
        Specified by:
        initialize in interface MetaClass
        Overrides:
        initialize in class MetaClassImpl
      • setInitialized

        protected void setInitialized​(boolean b)
      • invokeConstructor

        public Object invokeConstructor​(Object[] arguments)
        Description copied from interface: MetaObjectProtocol
        Invokes a constructor for the given arguments. The MetaClass will attempt to pick the best argument which matches the types of the objects passed within the arguments array
        Specified by:
        invokeConstructor in interface MetaObjectProtocol
        Overrides:
        invokeConstructor in class MetaClassImpl
        Parameters:
        arguments - The arguments to the constructor
        Returns:
        An instance of the java.lang.Class that this MetaObjectProtocol object applies to
      • getMetaClass

        public MetaClass getMetaClass()
        Description copied from interface: GroovyObject
        Returns the metaclass for a given class.
        Specified by:
        getMetaClass in interface GroovyObject
        Returns:
        the metaClass of this instance
      • getProperty

        public Object getProperty​(String property)
        Description copied from interface: GroovyObject
        Retrieves a property value.
        Specified by:
        getProperty in interface GroovyObject
        Parameters:
        property - the name of the property of interest
        Returns:
        the given property
      • isValidExpandoProperty

        public static boolean isValidExpandoProperty​(String property)
      • invokeMethod

        public Object invokeMethod​(String name,
                                   Object args)
        Description copied from interface: GroovyObject
        Invokes the given method.
        Specified by:
        invokeMethod in interface GroovyObject
        Parameters:
        name - the name of the method to call
        args - the arguments to use for the method call
        Returns:
        the result of invoking the method
      • setMetaClass

        public void setMetaClass​(MetaClass metaClass)
        Description copied from interface: GroovyObject
        Allows the MetaClass to be replaced with a derived implementation.
        Specified by:
        setMetaClass in interface GroovyObject
        Parameters:
        metaClass - the new metaclass
      • setProperty

        public void setProperty​(String property,
                                Object newValue)
        Description copied from interface: GroovyObject
        Sets the given property to the new value.
        Specified by:
        setProperty in interface GroovyObject
        Parameters:
        property - the name of the property of interest
        newValue - the new value for the property
      • performOperationOnMetaClass

        protected void performOperationOnMetaClass​(groovy.lang.ExpandoMetaClass.Callable c)
      • checkInitalised

        protected void checkInitalised()
        Description copied from class: MetaClassImpl
        checks if the initialisation of the class id complete. This method should be called as a form of assert, it is no way to test if there is still initialisation work to be done. Such logic must be implemented in a different way.
        Overrides:
        checkInitalised in class MetaClassImpl
      • registerBeanProperty

        public void registerBeanProperty​(String property,
                                         Object newValue)
        Registers a new bean property
        Parameters:
        property - The property name
        newValue - The properties initial value
      • registerInstanceMethod

        public void registerInstanceMethod​(MetaMethod metaMethod)
        Registers a new instance method for the given method name and closure on this MetaClass
        Parameters:
        metaMethod -
      • registerInstanceMethod

        public void registerInstanceMethod​(String name,
                                           Closure closure)
      • registerStaticMethod

        protected void registerStaticMethod​(String name,
                                            Closure callable)
      • registerStaticMethod

        protected void registerStaticMethod​(String name,
                                            Closure callable,
                                            Class[] paramTypes)
        Registers a new static method for the given method name and closure on this MetaClass
        Parameters:
        name - The method name
        callable - The callable Closure
      • getJavaClass

        public Class getJavaClass()
        Returns:
        The Java class enhanced by this MetaClass
      • refreshInheritedMethods

        public void refreshInheritedMethods​(Set modifiedSuperExpandos)
        Called from ExpandoMetaClassCreationHandle in the registry if it exists to set up inheritance handling
        Parameters:
        modifiedSuperExpandos - A list of modified super ExpandoMetaClass
      • getExpandoMethods

        public List<MetaMethod> getExpandoMethods()
        Returns a list of expando MetaMethod instances added to this ExpandoMetaClass
        Returns:
        the expandoMethods
      • getExpandoProperties

        public Collection<MetaProperty> getExpandoProperties()
        Returns a list of MetaBeanProperty instances added to this ExpandoMetaClass
        Returns:
        the expandoProperties
      • invokeMethod

        public 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
        Specified by:
        invokeMethod in interface MetaClass
        Overrides:
        invokeMethod in class MetaClassImpl
        Parameters:
        sender - The java.lang.Class instance that invoked the method
        object - The object which the method was invoked on
        methodName - The name of the method
        originalArguments - The arguments to the method
        isCallToSuper - Whether the method is a call to a super class method
        fromInsideClass - Whether the call was invoked from the inside or the outside of the class
        Returns:
        The return value of the method
        See Also:
        MetaClassImpl.invokeMethod(Class, Object, String, Object[], boolean, boolean)
      • getProperty

        public 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
        Specified by:
        getProperty in interface MetaClass
        Overrides:
        getProperty in class MetaClassImpl
        Parameters:
        sender - The java.lang.Class instance that requested the property
        object - The Object which the property is being retrieved from
        name - The name of the property
        useSuper - Whether the call is to a super class property
        fromInsideClass - ??
        Returns:
        the given property's value on the object
        See Also:
        MetaClassImpl.getProperty(Class, Object, String, boolean, boolean)
      • setProperty

        public void setProperty​(Class sender,
                                Object object,
                                String name,
                                Object newValue,
                                boolean useSuper,
                                boolean fromInsideClass)
        Overrides default implementation just in case setProperty method has been overridden by ExpandoMetaClass
        Specified by:
        setProperty in interface MetaClass
        Overrides:
        setProperty in class MetaClassImpl
        Parameters:
        sender - The java.lang.Class instance that is mutating the property
        object - The Object which the property is being set on
        name - The name of the property
        newValue - The new value of the property to set
        useSuper - Whether the call is to a super class property
        fromInsideClass - Whether the call was invoked from the inside or the outside of the class.
        See Also:
        MetaClassImpl.setProperty(Class, Object, String, Object, boolean, boolean)
      • hasMetaProperty

        public boolean hasMetaProperty​(String name)
        Returns true if the MetaClass has the given property
        Parameters:
        name - The name of the MetaProperty
        Returns:
        True it exists as a MetaProperty
      • hasMetaMethod

        public boolean hasMetaMethod​(String name,
                                     Class[] args)
        Checks whether a MetaMethod for the given name and arguments exists
        Parameters:
        name - The name of the MetaMethod
        args - The arguments to the meta method
        Returns:
        True if the method exists otherwise null
      • getPropertyForSetter

        public String getPropertyForSetter​(String setterName)
        Returns a property name equivalent for the given setter name or null if it is not a getter
        Parameters:
        setterName - The setter name
        Returns:
        The property name equivalent
      • hasCustomStaticInvokeMethod

        public boolean hasCustomStaticInvokeMethod()
        Description copied from class: MetaClassImpl
        indicates is the meta class method invocation for static methods is done through a custom invoker object.
        Overrides:
        hasCustomStaticInvokeMethod in class MetaClassImpl
        Returns:
        true - if the method invocation is not done by the meta class itself