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 MetaClassImpl {
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         * Use the ProxyMetaClass for the given Closure.
039         * Cares for balanced register/unregister.
040         * @param closure piece of code to be executed with registered ProxyMetaClass
041         */
042        public void use(Closure closure){
043            registry.setMetaClass(theClass, this);
044            
045            try {
046                closure.call();
047            } finally {
048                registry.setMetaClass(theClass, adaptee);
049            }
050        }
051    
052         /**
053         * Use the ProxyMetaClass for the given Closure.
054         * Cares for balanced setting/unsetting ProxyMetaClass.
055         * @param closure piece of code to be executed with ProxyMetaClass
056         */
057        public void use(GroovyObject object, Closure closure){
058            object.setMetaClass(this);
059            
060            try {
061                closure.call();
062            } finally {
063                object.setMetaClass(adaptee);
064            }
065        }
066    
067        /**
068         * @return the interceptor in use or null if no interceptor is used
069         */
070        public Interceptor getInterceptor() {
071            return interceptor;
072        }
073    
074        /**
075         * @param interceptor may be null to reset any interception
076         */
077        public void setInterceptor(Interceptor interceptor) {
078            this.interceptor = interceptor;
079        }
080    
081        /**
082         * Call invokeMethod on adaptee with logic like in MetaClass unless we have an Interceptor.
083         * With Interceptor the call is nested in its beforeInvoke and afterInvoke methods.
084         * The method call is suppressed if Interceptor.doInvoke() returns false.
085         * See Interceptor for details.
086         */
087        public Object invokeMethod(final Object object, final String methodName, final Object[] arguments) {
088            return doCall(object, methodName, arguments, new Callable(){
089                public Object call() {
090                    return adaptee.invokeMethod(object, methodName, arguments);
091                }
092            });
093        }
094        /**
095         * Call invokeStaticMethod on adaptee with logic like in MetaClass unless we have an Interceptor.
096         * With Interceptor the call is nested in its beforeInvoke and afterInvoke methods.
097         * The method call is suppressed if Interceptor.doInvoke() returns false.
098         * See Interceptor for details.
099         */
100        public Object invokeStaticMethod(final Object object, final String methodName, final Object[] arguments) {
101            return doCall(object, methodName, arguments, new Callable(){
102                public Object call() {
103                    return adaptee.invokeStaticMethod(object, methodName, arguments);
104                }
105            });
106        }
107    
108        /**
109         * Call invokeConstructor on adaptee with logic like in MetaClass unless we have an Interceptor.
110         * With Interceptor the call is nested in its beforeInvoke and afterInvoke methods.
111         * The method call is suppressed if Interceptor.doInvoke() returns false.
112         * See Interceptor for details.
113         */
114        public Object invokeConstructor(final Object[] arguments) {
115            return doCall(theClass, "ctor", arguments, new Callable(){
116                public Object call() {
117                    return adaptee.invokeConstructor(arguments);
118                }
119            });
120        }
121    
122        public Object invokeConstructorAt(final Class at, final Object[] arguments) {
123            return doCall(theClass, "ctor", arguments, new Callable() {
124                public Object call() {
125                    return adaptee.invokeConstructorAt(at, arguments);
126                }
127            });
128        }
129    
130        // since Java has no Closures...
131        private interface Callable{
132            Object call();
133        }
134        private Object doCall(Object object, String methodName, Object[] arguments, Callable howToInvoke) {
135            if (null == interceptor) {
136                return howToInvoke.call();
137            }
138            Object result = interceptor.beforeInvoke(object, methodName, arguments);
139            if (interceptor.doInvoke()) {
140                result = howToInvoke.call();
141            }
142            result = interceptor.afterInvoke(object, methodName, arguments, result);
143            return result;
144        }
145    }