001    package groovy.lang;
002    
003    import org.codehaus.groovy.runtime.InvokerHelper;
004    
005    import java.beans.IntrospectionException;
006    
007    /**
008     * As subclass of MetaClass, ProxyMetaClass manages calls from Groovy Objects to POJOs.
009     * It enriches MetaClass with the feature of making method invokations interceptable by
010     * an Interceptor. To this end, it acts as a decorator (decorator pattern) allowing
011     * to add or withdraw this feature at runtime.
012     * See groovy/lang/InterceptorTest.groovy for details.
013     * @author Dierk Koenig
014     */
015    public class ProxyMetaClass extends MetaClass {
016    
017        protected MetaClass adaptee = null;
018        protected Interceptor interceptor = null;
019    
020        /**
021         * convenience factory method for the most usual case.
022         */
023        public static ProxyMetaClass getInstance(Class theClass) throws IntrospectionException {
024            MetaClassRegistry metaRegistry = InvokerHelper.getInstance().getMetaRegistry();
025            MetaClass meta = metaRegistry.getMetaClass(theClass);
026            return new ProxyMetaClass(metaRegistry, theClass, meta);
027        }
028        /**
029         * @param adaptee   the MetaClass to decorate with interceptability
030         */
031        public ProxyMetaClass(MetaClassRegistry registry, Class theClass, MetaClass adaptee) throws IntrospectionException {
032            super(registry, theClass);
033            this.adaptee = adaptee;
034            if (null == adaptee) throw new IllegalArgumentException("adaptee must not be null");
035        }
036    
037        /**
038         * Make this ProxyMetaClass the funnel for all method calls, thus enabling interceptions.
039         */
040        public void register() {
041            registry.setMetaClass(theClass, this);
042        }
043    
044        /**
045         * Reset to using the decorated adaptee, disable interception.
046         */
047        public void unRegister() {
048            registry.setMetaClass(theClass, adaptee);
049        }
050    
051        /**
052         * @return the interceptor in use or null if no interceptor is used
053         */
054        public Interceptor getInterceptor() {
055            return interceptor;
056        }
057    
058        /**
059         * @param interceptor may be null to reset any interception
060         */
061        public void setInterceptor(Interceptor interceptor) {
062            this.interceptor = interceptor;
063        }
064    
065        /**
066         * Call invokeMethod on adaptee with logic like in MetaClass unless we have an Interceptor.
067         * With Interceptor the call is nested in its beforeInvoke and afterInvoke methods.
068         * The method call is suppressed if Interceptor.doInvoke() returns false.
069         * See Interceptor for details.
070         */
071        public Object invokeMethod(final Object object, final String methodName, final Object[] arguments) {
072            return doCall(object, methodName, arguments, new Callable(){
073                public Object call() {
074                    return adaptee.invokeMethod(object, methodName, arguments);
075                }
076            });
077        }
078        /**
079         * Call invokeStaticMethod on adaptee with logic like in MetaClass unless we have an Interceptor.
080         * With Interceptor the call is nested in its beforeInvoke and afterInvoke methods.
081         * The method call is suppressed if Interceptor.doInvoke() returns false.
082         * See Interceptor for details.
083         */
084        public Object invokeStaticMethod(final Object object, final String methodName, final Object[] arguments) {
085            return doCall(object, methodName, arguments, new Callable(){
086                public Object call() {
087                    return adaptee.invokeStaticMethod(object, methodName, arguments);
088                }
089            });
090        }
091    
092        /**
093         * Call invokeConstructor on adaptee with logic like in MetaClass unless we have an Interceptor.
094         * With Interceptor the call is nested in its beforeInvoke and afterInvoke methods.
095         * The method call is suppressed if Interceptor.doInvoke() returns false.
096         * See Interceptor for details.
097         */
098        public Object invokeConstructor(final Object[] arguments) {
099            return doCall(theClass, "ctor", arguments, new Callable(){
100                public Object call() {
101                    return adaptee.invokeConstructor(arguments);
102                }
103            });
104        }
105    
106        // since Java has no Closures...
107        private interface Callable{
108            Object call();
109        }
110        private Object doCall(Object object, String methodName, Object[] arguments, Callable howToInvoke) {
111            if (null == interceptor) {
112                return howToInvoke.call();
113            }
114            Object result = interceptor.beforeInvoke(object, methodName, arguments);
115            if (interceptor.doInvoke()) {
116                result = howToInvoke.call();
117            }
118            result = interceptor.afterInvoke(object, methodName, arguments, result);
119            return result;
120        }
121    }