View Javadoc

1   /*
2    * Copyright 2010-2013 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.InvocationHandler;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Proxy;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.IdentityHashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.ListIterator;
31  import java.util.Map;
32  import org.callbackparams.ext.CallbackControlPanel;
33  import org.callbackparams.combine.reflect.Combined;
34  
35  /**
36   * @author Henrik Kaipe
37   */
38  public class TestrunCallbacks {
39  
40      private static final List/*CallbackRef*/ callbackMethodParams =
41              new ArrayList();
42      private static final Map/*Field,CallbackRef*/ callbackInjectionFields =
43              new IdentityHashMap();
44      private static final Map/*Method,Object[]*/ valuesCache =
45              new IdentityHashMap();
46  
47      /**
48       * This piece of information has been saved for exceptional circumstances
49       * when the test-classes has been rebyted/reloaded once more by the
50       * wrapped runner from third-party.
51       */
52      private static final List/*Method*/ callbackMethods = new ArrayList();
53      private static Collection/*Combined*/ currentCombinedRecord;
54  
55      /**
56       * To have the class-loader as a final field makes debugging easier.
57       */
58  //    private final ClassLoader cl = getClass().getClassLoader();
59  
60      boolean currentlyWrappedWithinAdaptiveRules = false;
61  
62      private Collection/*Combined*/ refBackupOfCurrentCombinedRecord;
63      private CallbackRecord callbackRecord;
64      protected Object[] callbackMethodArguments;
65  
66      public static int addMethodParams(Method m) {
67          callbackMethods.add(m);
68          int startIndex = callbackMethodParams.size();
69          for (int i = 0, size = m.getParameterTypes().length ; i < size ; ++i) {
70              callbackMethodParams.add(CallbackRef.newInstance(m, i));
71          }
72          return startIndex;
73      }
74  
75      public static void addFieldToInject(Field f) {
76          try {
77              f.setAccessible(true);
78          } catch (SecurityException ignore) {
79              /* We will try to access the field anyway ... */
80          }
81          callbackInjectionFields.put(f, CallbackRef.newInstance(f));
82      }
83  
84      public static void setCallbackRecord(Collection testRunCombinedRecord) {
85          currentCombinedRecord = testRunCombinedRecord;
86      }
87  
88      public static Iterator iterateFields() {
89          return callbackInjectionFields.keySet().iterator();
90      }
91  
92      public static Iterator iterateMethods() {
93          return callbackMethods.iterator();
94      }
95  
96      public static Collection getCurrentCallbackRecord() {
97          return currentCombinedRecord;
98      }
99  
100     private void parseCurrentCombinedRecord() {
101         this.refBackupOfCurrentCombinedRecord = currentCombinedRecord;
102         List callbacks = new ArrayList(currentCombinedRecord.size());
103         for (Iterator i = currentCombinedRecord.iterator(); i.hasNext();) {
104             final Combined combined = Combined
105                     .resurrectFromOtherClassLoader(i.next());
106             Object callback = combined.retrieve(this, valuesCache);
107             if (null != callback) {
108                 callbacks.add(callback);
109             }
110         }
111         this.callbackRecord = new CallbackRecord(callbacks);
112     }
113 
114     private CallbackRecord getCallbackRecord() {
115         if (this.refBackupOfCurrentCombinedRecord != currentCombinedRecord) {
116             parseCurrentCombinedRecord();
117         }
118         return this.callbackRecord;
119     }
120 
121     private void setupCallbackMethodArguments() {
122         callbackMethodArguments = callbackMethodParams.toArray(
123                 new Object[callbackMethodParams.size()]);
124 
125         ListIterator li = Arrays.asList(callbackMethodArguments).listIterator();
126         while (li.hasNext()) {
127             final CallbackRef methodParam = (CallbackRef) li.next();
128             li.set(createCallbackProxy(methodParam));
129         }
130     }
131 
132     private void injectCallbackFields() {
133         for (Iterator i = callbackInjectionFields.entrySet().iterator()
134                 ; i.hasNext() ;) {
135             Map.Entry/*Field,CallbackRef*/ fieldEntr = (Map.Entry) i.next();
136             Field f = (Field) fieldEntr.getKey();
137             if (f.getDeclaringClass().isInstance(this)) {
138                 try {
139                     f.set(this, createCallbackProxy(
140                             (CallbackRef) fieldEntr.getValue()));
141                 } catch (Exception ex) {
142                     throw new Error("Callback injection failed for the field "
143                             + f.getName() + ": " + ex.getClass().getName()
144                             + " - " + ex.getMessage(), ex);
145                 }
146             }
147         }
148     }
149 
150     private Object createCallbackProxy(final CallbackRef ref) {
151         final Class paramClass = ref.getParamClass();
152         final Class[] interfaces = new Class[] {paramClass};
153 
154         if (CallbackControlPanel.class == paramClass
155                 || org.callbackparams.CallbackControlPanel.class == paramClass) {
156             return Proxy.newProxyInstance(
157                     getClass().getClassLoader(), interfaces,
158                     new InvocationHandler() {
159                 public Object invoke(Object proxy, Method method, Object[] args)
160                 throws Throwable {
161                     try {
162                         return method.invoke(
163                                 getCallbackRecord().getCallbackControlPanel(),
164                                 args);
165                     } catch (InvocationTargetException x) {
166                         throw x.getTargetException();
167                     }
168                 }
169             });
170             
171 
172         } else {
173             return Proxy.newProxyInstance(
174                     paramClass.getClassLoader(), interfaces,
175                     new InvocationHandler() {
176                 CallbackRecord record;
177                 InvocationHandler wrappedInvokeHandler;
178                 public Object invoke(Object proxy, Method method, Object[] args)
179                 throws Throwable {
180                     if (getCallbackRecord() != record) {
181                         record = getCallbackRecord();
182                         wrappedInvokeHandler = record.newCallbackInvoker(ref);
183                     }
184                     return wrappedInvokeHandler.invoke(proxy, method, args);
185                 }
186             });
187         }
188     }
189 
190     private void init() throws Exception {
191         if (null == currentCombinedRecord) {
192             ThirdPartyReloadingWorkaround.getInstance()
193                     .resurrectSetup(getClass());
194         }
195 
196         parseCurrentCombinedRecord();
197         setupCallbackMethodArguments();
198         injectCallbackFields();
199     }
200 
201     public TestrunCallbacks() throws Exception {
202         init();
203     }
204 }