View Javadoc

1   /*
2    * Copyright 2010 the original author or authors.
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   */
16  
17  package org.callbackparams.internal.template;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import org.apache.bcel.Constants;
29  import org.apache.bcel.generic.ArrayType;
30  import org.apache.bcel.generic.ClassGen;
31  import org.apache.bcel.generic.ConstantPoolGen;
32  import org.apache.bcel.generic.IFNULL;
33  import org.apache.bcel.generic.IfInstruction;
34  import org.apache.bcel.generic.InstructionFactory;
35  import org.apache.bcel.generic.InstructionList;
36  import org.apache.bcel.generic.MethodGen;
37  import org.apache.bcel.generic.Type;
38  import org.callbackparams.AdaptiveRule;
39  import org.callbackparams.AdaptiveRule.TestRun;
40  import org.callbackparams.support.MethodHashKey;
41  
42  /**
43   * @author Henrik Kaipe
44   */
45  public class AdaptiveRulesPackager
46  implements java.lang.reflect.InvocationHandler, Constants {
47  
48      /**
49       * Specifies details of a Visitor super-interface for
50       * {@link org.callbackparams.internal.CallbackMethodProxyingRebyter}.
51       * @see 
52       */
53      public static class VisitorParentGenerator {
54          private final String interfaceName;
55          private Type enclosingType;
56          private List/*Field*/ adaptiveRuleFields;
57          private List/*Method*/ testMethods;
58  
59          public VisitorParentGenerator(Class enclosingClass,
60                  List/*Field*/ adaptiveRuleFields, List/*Method*/ testMethods) {
61              this.enclosingType = Type.getType(enclosingClass);
62              this.adaptiveRuleFields = adaptiveRuleFields;
63              this.testMethods = testMethods;
64              this.interfaceName =
65                      defineInterfaceNameOfNestedVisitorParent(enclosingClass);
66          }
67  
68          public String getInterfaceName() {
69              return interfaceName;
70          }
71  
72          public byte[] generateByteCode() {
73              return defineNestedVisitorParent(interfaceName, enclosingType,
74                      adaptiveRuleFields, testMethods);
75          }
76      }
77  
78      /**
79       * Will be rebyted when classes are reloaded for the CallbackParams testrun!
80       * As super-interfaces it will get the nested (artificial)
81       * VisitorParent_x interfaces of the test-class and its super-classes.
82       * It will itself get a single method, which return-type will be the
83       * (rebyted) test-class.
84       */
85      public interface Visitor {}
86  
87      private static final Map/*Method,Method*/ testMethodMap = new HashMap();
88      private static final List/*Field*/ adaptiveRuleFields = new ArrayList();
89      private static final Class[] proxiedInterfaces = {Visitor.class};
90      private static final Class testClass;
91  
92      private final TestrunCallbacks testInstance;
93  
94      public AdaptiveRulesPackager(TestrunCallbacks testInstance) {
95          this.testInstance = testInstance;
96      }
97  
98      static {
99          Class tempTestClass = null;
100         final Method[] visitorMethods = Visitor.class.getMethods();
101 
102         Map/*MethodHashKey,Method*/ hashedVisitorMethods = new HashMap();
103         for (int i = 0; i < visitorMethods.length; ++i) {
104             final Method m = visitorMethods[i];
105             final Class[] params = m.getParameterTypes();
106 
107             if (0 == params.length) {
108                 tempTestClass = m.getReturnType();
109 
110             } else if (params[0].isArray()) {//<-Array is code for rule-field
111                 try {
112                     Field f = params[0].getComponentType()
113                             .getDeclaredField(m.getName());
114                     try {
115                         f.setAccessible(true);
116                     } catch (SecurityException ignoreAndHopeForTheBest) {}
117                     adaptiveRuleFields.add(f);
118                 } catch (Exception x) {
119                     throw new Error(x.getMessage());
120                 }
121 
122             } else {
123                 MethodHashKey key = new MethodHashKey(
124                         params[0].getName(),
125                         m.getName(),
126                         asTestMethodSignature(m.getReturnType(), params));
127                 hashedVisitorMethods.put(key, m);
128             }
129         }
130         testClass = tempTestClass;
131         while (false == hashedVisitorMethods.isEmpty()) {
132             final Method[] testClassMethods =
133                     tempTestClass.getDeclaredMethods();
134             for (int i = 0; i < testClassMethods.length; ++i) {
135                 final Method m = testClassMethods[i];
136                 final Method visitorMethod = (Method)
137                         hashedVisitorMethods.remove(MethodHashKey.getHashKey(m));
138                 if (null != visitorMethod) {
139                     try {
140                         m.setAccessible(true);
141                     } catch (SecurityException ignoreAndHopeForTheBest) {}
142                     testMethodMap.put(
143                             MethodHashKey.getHashKey(visitorMethod), m);
144                 }
145             }
146             tempTestClass = tempTestClass.getSuperclass();
147         }
148     }
149 
150     private static String asTestMethodSignature(
151             Class returnType, Class[] visitorMethodParams) {
152         final Type[] argTypes = new Type[visitorMethodParams.length - 1];
153         for (int i = 0; i < argTypes.length; ++i) {
154             argTypes[i] = Type.getType(visitorMethodParams[i + 1]);
155         }
156         return Type.getMethodSignature(Type.getType(returnType), argTypes);
157     }
158 
159     private static String defineInterfaceNameOfNestedVisitorParent(
160             Class enclosingClass) {
161         String interfNamePrefix = enclosingClass.getName() + "$VisitorParent_";
162         String interfName = null;
163         try {
164             for (int suffix = 0; true; ++suffix) {
165                 interfName = interfNamePrefix + suffix;
166                 enclosingClass.getClassLoader().loadClass(interfName);
167             }
168         } catch (ClassNotFoundException x) {
169             return interfName;
170         }
171     }
172 
173     private static byte[] defineNestedVisitorParent(
174             String interfName, Type enclosingType,
175             List/*Field*/ adaptiveRules, List/*Method*/ testMethods) {
176         ClassGen cg = new ClassGen(interfName, Object.class.getName(),
177                 "<generated>", ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT, null);
178         ConstantPoolGen cp = cg.getConstantPool();
179         if (false == adaptiveRules.isEmpty()) {
180             Type[] arg_types = {new ArrayType(enclosingType, 1)};
181             for (Iterator/*Field*/ i = adaptiveRules.iterator(); i.hasNext();) {
182                 final Field ruleField = (Field) i.next();
183                 cg.addMethod(new MethodGen(ACC_PUBLIC | ACC_ABSTRACT,
184                         Type.VOID, arg_types, null,
185                         ruleField.getName(), interfName, null, cp).getMethod());
186             }
187         }
188         if (false == testMethods.isEmpty()) {
189             VisitorMethodArgtypesFactory argsFactory =
190                     new VisitorMethodArgtypesFactory(enclosingType);
191             for (Iterator/*Method*/ i = testMethods.iterator(); i.hasNext();) {
192                 final Method m = (Method) i.next();
193                 MethodGen mg = new MethodGen(ACC_PUBLIC | ACC_ABSTRACT,
194                         Type.getType(m.getReturnType()),
195                         argsFactory.asVisitorMethodArgtypes(m.getParameterTypes()),
196                         null, m.getName(), interfName, null, cp);
197                 if (0 < m.getExceptionTypes().length) {
198                     mg.addException(Throwable.class.getName());
199                 }
200                 cg.addMethod(mg.getMethod());
201             }
202         }
203         return cg.getJavaClass().getBytes();
204     }
205 
206     public static InstructionList defineTestMethodDetourCall(
207             InstructionFactory factory, Method m) {
208         final InstructionList il = new InstructionList();
209 
210         /* Invoke static method Visitor fetchTestrunVisitor(TestrunCallbacks):*/
211         il.append(factory.ALOAD_0);
212         il.append(factory.createInvoke(
213                 AdaptiveRulesPackager.class.getName(),
214                 "fetchTestrunVisitor",
215                 Type.getType(Visitor.class),
216                 new Type[] {Type.getType(TestrunCallbacks.class)},
217                 INVOKESTATIC));
218 
219         /*
220          * Duplicate Visitor instance on stack so that
221          * it willbe available for both the "ifnull"-check and the
222          * following method-invocation.
223          */
224         il.append(factory.DUP);
225 
226         /* Jump to the end of this instruction-list in case the
227          * fetchTestrunVisitor-invocation returned null.
228          */
229         IfInstruction ifNull = new IFNULL(null);
230         il.append(ifNull);
231 
232         /*
233          * Invoke the proper visitor-method.
234          */
235         Type declaringClassType = Type.getType(m.getDeclaringClass());
236         Type[] argTypes = new VisitorMethodArgtypesFactory(declaringClassType)
237                 .asVisitorMethodArgtypes(m.getParameterTypes());
238         il.append(factory.ACONST_NULL);//Detour already knows of test-instance
239         for (int i = 1; i < argTypes.length; ++i) {
240             il.append(factory.createLoad(argTypes[i], i));
241         }
242         il.append(factory.createInvoke(
243                 Visitor.class.getName(), m.getName(),
244                 Type.getType(m.getReturnType()), argTypes,
245                 INVOKEINTERFACE));
246 
247         /*
248          * Return from method - returning the return-value of the
249          * preceding interface-invocation (or just return for void-methods).
250          */
251         il.append(factory.createReturn(Type.getType(m.getReturnType())));
252 
253         /*
254          * The ifnull-target ...
255          * When the ifnull-target reaches here there will be an unused
256          * null-reference on the stack because of the Visitor-duplication
257          * above, so it could as well be removed ...
258          */
259         ifNull.setTarget(il.append(factory.POP));
260 
261         return il;
262     }
263 
264     public static Visitor fetchTestrunVisitor(TestrunCallbacks testInstance) {
265         if (testInstance.currentlyWrappedWithinAdaptiveRules
266                 || false == testClass.isInstance(testInstance)
267                 || adaptiveRuleFields.isEmpty()) {
268             return null;
269         } else {
270             return (Visitor) java.lang.reflect.Proxy.newProxyInstance(
271                     Visitor.class.getClassLoader(),
272                     proxiedInterfaces,
273                     new AdaptiveRulesPackager(testInstance));
274         }
275     }
276 
277     private TestRun wrapTestrunWithRule(
278             final AdaptiveRule rule, final TestRun testRun) {
279         return new TestRun() {
280             public Object executeTestMethod() throws Throwable {
281                 return rule.evaluate(testRun);
282             }
283         };
284     }
285 
286     private Object executeWithAdaptiveRules(TestRun testRun) throws Throwable {
287         for (Iterator i = adaptiveRuleFields.iterator(); i.hasNext();) {
288             final Field ruleField = (Field) i.next();
289             AdaptiveRule rule = (AdaptiveRule) ruleField.get(testInstance);
290             if (null != rule) {
291                 testRun = wrapTestrunWithRule(rule, testRun);
292             }
293         }
294         return testRun.executeTestMethod();
295     }
296 
297     public Object invoke(Object proxy,
298             final Method m, final Object[] args)
299     throws Throwable {
300         testInstance.currentlyWrappedWithinAdaptiveRules = true;
301         try {
302             return executeWithAdaptiveRules(new AdaptiveRule.TestRun() {
303                 public Object executeTestMethod() throws Throwable {
304                     try {
305                         Method testMethod = (Method)
306                                 testMethodMap.get(MethodHashKey.getHashKey(m));
307                         return testMethod.invoke(
308                                 testInstance, asTestMethodArgs(args));
309                     } catch (InvocationTargetException ite) {
310                         throw ite.getTargetException();
311                     }
312                 }
313             });
314         } finally {
315             testInstance.currentlyWrappedWithinAdaptiveRules = false;
316         }
317     }
318 
319     private static Object[] asTestMethodArgs(final Object[] args) {
320         if (1 == args.length) {
321             return null;
322         } else {
323             final Object[] testMethodArgs = new Object[args.length - 1];
324             for (int i = 0; i < testMethodArgs.length; ++i) {
325                 testMethodArgs[i] = args[i + 1];
326             }
327             return testMethodArgs;
328         }
329     }
330 
331     public static byte[] generateRebytedVisitor(
332             Class testClass, Set superInterfaceNames) {
333         String[] superInterfaces = (String[]) superInterfaceNames.toArray(
334                 new String[superInterfaceNames.size()]);
335         ClassGen cg = new ClassGen(
336                 Visitor.class.getName(), Object.class.getName(),
337                 "<regenerated>", ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT,
338                 superInterfaces);
339         MethodGen mg = new MethodGen(ACC_PUBLIC | ACC_ABSTRACT,
340                 Type.getType(testClass), new Type[]{}, null, "testClass",
341                 Visitor.class.getName(), null, cg.getConstantPool());
342         cg.addMethod(mg.getMethod());
343         return cg.getJavaClass().getBytes();
344     }
345 
346     private static class VisitorMethodArgtypesFactory {
347         final Type enclosingType;
348 
349         VisitorMethodArgtypesFactory(Type enclosingType) {
350             this.enclosingType = enclosingType;
351         }
352 
353         Type[] asVisitorMethodArgtypes(final Class[] testMethodParams) {
354             final Type[] argTypes = new Type[testMethodParams.length + 1];
355             argTypes[0] = enclosingType;
356             for (int i = 0; i < testMethodParams.length; ++i) {
357                 argTypes[i + 1] = Type.getType(testMethodParams[i]);
358             }
359             return argTypes;
360         }
361     }
362 }