MethodDelegate.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
1/*
2 * Copyright 2003,2004 The Apache Software Foundation
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockito.cglib.reflect;
17
18import java.lang.reflect.*;
19
20import org.mockito.asm.ClassVisitor;
21import org.mockito.asm.Type;
22import org.mockito.cglib.*;
23import org.mockito.cglib.core.*;
24
25// TODO: don't require exact match for return type
26
27/**
28 * <b>DOCUMENTATION FROM APACHE AVALON DELEGATE CLASS</b>
29 *
30 * <p>
31 * Delegates are a typesafe pointer to another method.  Since Java does not
32 * have language support for such a construct, this utility will construct
33 * a proxy that forwards method calls to any method with the same signature.
34 * This utility is inspired in part by the C# delegate mechanism.  We
35 * implemented it in a Java-centric manner.
36 * </p>
37 *
38 * <h2>Delegate</h2>
39 * <p>
40 *   Any interface with one method can become the interface for a delegate.
41 *   Consider the example below:
42 * </p>
43 *
44 * <pre>
45 *   public interface MainDelegate {
46 *       int main(String[] args);
47 *   }
48 * </pre>
49 *
50 * <p>
51 *   The interface above is an example of an interface that can become a
52 *   delegate.  It has only one method, and the interface is public.  In
53 *   order to create a delegate for that method, all we have to do is
54 *   call <code>MethodDelegate.create(this, "alternateMain", MainDelegate.class)</code>.
55 *   The following program will show how to use it:
56 * </p>
57 *
58 * <pre>
59 *   public class Main {
60 *       public static int main( String[] args ) {
61 *           Main newMain = new Main();
62 *           MainDelegate start = (MainDelegate)
63 *               MethodDelegate.create(newMain, "alternateMain", MainDelegate.class);
64 *           return start.main( args );
65 *       }
66 *
67 *       public int alternateMain( String[] args ) {
68 *           for (int i = 0; i < args.length; i++) {
69 *               System.out.println( args[i] );
70 *           }
71 *           return args.length;
72 *       }
73 *   }
74 * </pre>
75 *
76 * <p>
77 *   By themselves, delegates don't do much.  Their true power lies in the fact that
78 *   they can be treated like objects, and passed to other methods.  In fact that is
79 *   one of the key building blocks of building Intelligent Agents which in tern are
80 *   the foundation of artificial intelligence.  In the above program, we could have
81 *   easily created the delegate to match the static <code>main</code> method by
82 *   substituting the delegate creation call with this:
83 *   <code>MethodDelegate.createStatic(getClass(), "main", MainDelegate.class)</code>.
84 * </p>
85 * <p>
86 *   Another key use for Delegates is to register event listeners.  It is much easier
87 *   to have all the code for your events separated out into methods instead of individual
88 *   classes.  One of the ways Java gets around that is to create anonymous classes.
89 *   They are particularly troublesome because many Debuggers do not know what to do
90 *   with them.  Anonymous classes tend to duplicate alot of code as well.  We can
91 *   use any interface with one declared method to forward events to any method that
92 *   matches the signature (although the method name can be different).
93 * </p>
94 *
95 * <h3>Equality</h3>
96 *  The criteria that we use to test if two delegates are equal are:
97 *   <ul>
98 *     <li>
99 *       They both refer to the same instance.  That is, the <code>instance</code>
100 *       parameter passed to the newDelegate method was the same for both. The
101 *       instances are compared with the identity equality operator, <code>==</code>.
102 *     </li>
103 *     <li>They refer to the same method as resolved by <code>Method.equals</code>.</li>
104 *   </ul>
105 *
106 * @version $Id: MethodDelegate.java,v 1.25 2006/03/05 02:43:19 herbyderby Exp $
107 */
108abstract public class MethodDelegate {
109    private static final MethodDelegateKey KEY_FACTORY =
110      (MethodDelegateKey)KeyFactory.create(MethodDelegateKey.class, KeyFactory.CLASS_BY_NAME);
111
112    protected Object target;
113    protected String eqMethod;
114
115    interface MethodDelegateKey {
116        Object newInstance(Class delegateClass, String methodName, Class iface);
117    }
118
119    public static MethodDelegate createStatic(Class targetClass, String methodName, Class iface) {
120        Generator gen = new Generator();
121        gen.setTargetClass(targetClass);
122        gen.setMethodName(methodName);
123        gen.setInterface(iface);
124        return gen.create();
125    }
126
127    public static MethodDelegate create(Object target, String methodName, Class iface) {
128        Generator gen = new Generator();
129        gen.setTarget(target);
130        gen.setMethodName(methodName);
131        gen.setInterface(iface);
132        return gen.create();
133    }
134
135    public boolean equals(Object obj) {
136        MethodDelegate other = (MethodDelegate)obj;
137        return target == other.target && eqMethod.equals(other.eqMethod);
138    }
139
140    public int hashCode() {
141        return target.hashCode() ^ eqMethod.hashCode();
142    }
143
144    public Object getTarget() {
145        return target;
146    }
147
148    abstract public MethodDelegate newInstance(Object target);
149
150    public static class Generator extends AbstractClassGenerator {
151        private static final Source SOURCE = new Source(MethodDelegate.class.getName());
152        private static final Type METHOD_DELEGATE =
153          TypeUtils.parseType("org.mockito.cglib.reflect.MethodDelegate");
154        private static final Signature NEW_INSTANCE =
155          new Signature("newInstance", METHOD_DELEGATE, new Type[]{ Constants.TYPE_OBJECT });
156
157        private Object target;
158        private Class targetClass;
159        private String methodName;
160        private Class iface;
161
162        public Generator() {
163            super(SOURCE);
164        }
165
166        public void setTarget(Object target) {
167            this.target = target;
168            this.targetClass = target.getClass();
169        }
170
171        public void setTargetClass(Class targetClass) {
172            this.targetClass = targetClass;
173        }
174
175        public void setMethodName(String methodName) {
176            this.methodName = methodName;
177        }
178
179        public void setInterface(Class iface) {
180            this.iface = iface;
181        }
182
183        protected ClassLoader getDefaultClassLoader() {
184            return targetClass.getClassLoader();
185        }
186
187        public MethodDelegate create() {
188            setNamePrefix(targetClass.getName());
189            Object key = KEY_FACTORY.newInstance(targetClass, methodName, iface);
190            return (MethodDelegate)super.create(key);
191        }
192
193        protected Object firstInstance(Class type) {
194            return ((MethodDelegate)ReflectUtils.newInstance(type)).newInstance(target);
195        }
196
197        protected Object nextInstance(Object instance) {
198            return ((MethodDelegate)instance).newInstance(target);
199        }
200
201        public void generateClass(ClassVisitor v) throws NoSuchMethodException {
202            Method proxy = ReflectUtils.findInterfaceMethod(iface);
203            final Method method = targetClass.getMethod(methodName, proxy.getParameterTypes());
204            if (!proxy.getReturnType().isAssignableFrom(method.getReturnType())) {
205                throw new IllegalArgumentException("incompatible return types");
206            }
207
208            MethodInfo methodInfo = ReflectUtils.getMethodInfo(method);
209
210            boolean isStatic = TypeUtils.isStatic(methodInfo.getModifiers());
211            if ((target == null) ^ isStatic) {
212                throw new IllegalArgumentException("Static method " + (isStatic ? "not " : "") + "expected");
213            }
214
215            ClassEmitter ce = new ClassEmitter(v);
216            CodeEmitter e;
217            ce.begin_class(Constants.V1_2,
218                           Constants.ACC_PUBLIC,
219                           getClassName(),
220                           METHOD_DELEGATE,
221                           new Type[]{ Type.getType(iface) },
222                           Constants.SOURCE_FILE);
223            ce.declare_field(Constants.PRIVATE_FINAL_STATIC, "eqMethod", Constants.TYPE_STRING, null);
224            EmitUtils.null_constructor(ce);
225
226            // generate proxied method
227            MethodInfo proxied = ReflectUtils.getMethodInfo(iface.getDeclaredMethods()[0]);
228            e = EmitUtils.begin_method(ce, proxied, Constants.ACC_PUBLIC);
229            e.load_this();
230            e.super_getfield("target", Constants.TYPE_OBJECT);
231            e.checkcast(methodInfo.getClassInfo().getType());
232            e.load_args();
233            e.invoke(methodInfo);
234            e.return_value();
235            e.end_method();
236
237            // newInstance
238            e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null);
239            e.new_instance_this();
240            e.dup();
241            e.dup2();
242            e.invoke_constructor_this();
243            e.getfield("eqMethod");
244            e.super_putfield("eqMethod", Constants.TYPE_STRING);
245            e.load_arg(0);
246            e.super_putfield("target", Constants.TYPE_OBJECT);
247            e.return_value();
248            e.end_method();
249
250            // static initializer
251            e = ce.begin_static();
252            e.push(methodInfo.getSignature().toString());
253            e.putfield("eqMethod");
254            e.return_value();
255            e.end_method();
256
257            ce.end_class();
258        }
259    }
260}
261