View Javadoc

1   /***************************************************************************************
2    * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
3    * http://aspectwerkz.codehaus.org                                                    *
4    * ---------------------------------------------------------------------------------- *
5    * The software in this package is published under the terms of the LGPL license      *
6    * a copy of which has been included with this distribution in the license.txt file.  *
7    **************************************************************************************/
8   package org.codehaus.aspectwerkz.aspect;
9   
10  import org.codehaus.aspectwerkz.DeploymentModel;
11  import org.codehaus.aspectwerkz.definition.IntroductionDefinition;
12  import org.codehaus.aspectwerkz.exception.DefinitionException;
13  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
14  import org.codehaus.aspectwerkz.transform.ReflectHelper;
15  import org.codehaus.aspectwerkz.transform.TransformationUtil;
16  
17  import java.lang.reflect.InvocationTargetException;
18  import java.lang.reflect.Method;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.WeakHashMap;
24  import java.util.ArrayList;
25  
26  /***
27   * Container for Introductions.
28   * 
29   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
30   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
31   */
32  public class IntroductionContainer {
33      /***
34       * Holds a reference to the sole per JVM introduction.
35       */
36      protected Introduction m_perJvm;
37  
38      /***
39       * Holds references to the per class introductions.
40       */
41      protected Map m_perClass = new WeakHashMap();
42  
43      /***
44       * Holds references to the per instance introductions.
45       */
46      protected Map m_perInstance = new WeakHashMap();
47  
48      /***
49       * Holds references to the per thread introductions.
50       */
51      protected Map m_perThread = new WeakHashMap();
52  
53      /***
54       * The introduction prototype.
55       */
56      protected Introduction m_prototype;
57  
58      /***
59       * The methods repository.
60       */
61      protected Method[] m_methodRepository = new Method[0];
62  
63      /***
64       * The aspect container for the introduction.
65       */
66      protected AspectContainer m_definingAspectContainer;
67  
68      /***
69       * Creates a new container strategy.
70       * 
71       * @param definingAspectContainer the aspect container
72       */
73      public IntroductionContainer(final Introduction prototype, final AspectContainer definingAspectContainer) {
74          if (prototype == null) {
75              throw new IllegalArgumentException("introduction prototype can not be null");
76          }
77          m_definingAspectContainer = definingAspectContainer;
78          m_prototype = prototype;
79          createMethodRepository();
80      }
81  
82      /***
83       * Invokes the method on a per JVM basis.
84       * 
85       * @param methodIndex the method index
86       * @param parameters the parameters for the invocation
87       * @return the result from the method invocation
88       */
89      public Object invokeIntroductionPerJvm(final int methodIndex, final Object[] parameters) throws Throwable {
90          Object result = null;
91          try {
92              if (m_perJvm == null) {
93                  // only compatible aspect deployment is perJVM
94                  m_perJvm = Introduction.newInstance(m_prototype, m_prototype.getCrossCuttingInfo());
95                  m_perJvm.createMixin();
96              }
97              result = m_methodRepository[methodIndex].invoke(m_perJvm.getImplementation(), parameters);
98          } catch (InvocationTargetException e) {
99              if (e.getTargetException() instanceof ClassCastException) {
100                 System.err
101                         .println("WARNING: ClassCastException has been thrown from introduced method - this can occur if you cast 'this' to CrossCutting instead of casting 'OuterAspectClass.this'");
102             }
103             throw e.getTargetException();
104         } catch (Exception e) {
105             throw new WrappedRuntimeException(e);
106         }
107         return result;
108     }
109 
110     /***
111      * Invokes the method on a per class basis.
112      * 
113      * @param targetInstance a reference to the calling object
114      * @param methodIndex the method index
115      * @param parameters the parameters for the invocation
116      * @return the result from the method invocation
117      */
118     public Object invokeIntroductionPerClass(
119         final Object targetInstance,
120         final int methodIndex,
121         final Object[] parameters) throws Throwable {
122         final Class targetClass = targetInstance.getClass();
123         Object result = null;
124         try {
125             if (!m_perClass.containsKey(targetClass)) {
126                 synchronized (m_perClass) {
127                     // only compatible aspect deployments are perJVM and perClass
128                     Introduction perClassIntroduction = Introduction.newInstance(m_prototype, m_prototype
129                             .getCrossCuttingInfo());
130                     m_perClass.put(targetClass, perClassIntroduction);
131                     perClassIntroduction.createMixin();
132                 }
133             }
134             result = m_methodRepository[methodIndex].invoke(((Introduction) m_perClass.get(targetClass))
135                     .getImplementation(), parameters);
136         } catch (InvocationTargetException e) {
137             if (e.getTargetException() instanceof ClassCastException) {
138                 System.err
139                         .println("WARNING: ClassCastException has been thrown from introduced method - this can occur if you cast 'this' to CrossCutting instead of casting 'OuterAspectClass.this'");
140             }
141             throw e.getTargetException();
142         } catch (Exception e) {
143             throw new WrappedRuntimeException(e);
144         }
145         return result;
146     }
147 
148     /***
149      * Invokes the method on a per instance basis.
150      * 
151      * @param targetInstance a reference to the target instance
152      * @param methodIndex the method index
153      * @param parameters the parameters for the invocation
154      * @return the result from the method invocation
155      */
156     public Object invokeIntroductionPerInstance(
157         final Object targetInstance,
158         final int methodIndex,
159         final Object[] parameters) throws Throwable {
160         Object result = null;
161         try {
162             if (!m_perInstance.containsKey(targetInstance)) {
163                 synchronized (m_perInstance) {
164                     // only compatible aspect deployments are perJVM and perClass
165                     Introduction perInstanceIntroduction = Introduction.newInstance(m_prototype, m_prototype
166                             .getCrossCuttingInfo());
167                     m_perInstance.put(targetInstance, perInstanceIntroduction);
168 
169                     //TODO
170                     //AW-207
171                     //even with createMixin called after this is not fixed
172                     // System.out.println(" added " + targetInstance + " / " +
173                     // perInstanceIntroduction.getImplementation());
174                     // since getImplementation is not yet set (since createMixin not yet called)
175                     // and the getTargetInstance call is looping thru based on matching on
176                     // getImplementation
177                     // ie cannot be used as long as the mixin instance is not created
178                     // ..
179                     perInstanceIntroduction.createMixin();
180                 }
181             }
182             result = m_methodRepository[methodIndex].invoke(((Introduction) m_perInstance.get(targetInstance))
183                     .getImplementation(), parameters);
184         } catch (InvocationTargetException e) {
185             if (e.getTargetException() instanceof ClassCastException) {
186                 System.err
187                         .println("WARNING: ClassCastException has been thrown from introduced method - this can occur if you cast 'this' to CrossCutting instead of casting 'OuterAspectClass.this'");
188             }
189             throw e.getTargetException();
190         } catch (Exception e) {
191             throw new WrappedRuntimeException(e);
192         }
193         return result;
194     }
195 
196     /***
197      * Invokes the method on a per thread basis.
198      * 
199      * @param methodIndex the method index
200      * @param parameters the parameters for the invocation
201      * @return the result from the method invocation
202      */
203     public Object invokeIntroductionPerThread(final int methodIndex, final Object[] parameters) throws Throwable {
204         Object result;
205         try {
206             final Thread currentThread = Thread.currentThread();
207             if (!m_perThread.containsKey(currentThread)) {
208                 synchronized (m_perThread) {
209                     // only compatible aspect deployments is perThread
210                     Introduction perThreadIntroduction = Introduction.newInstance(m_prototype, m_prototype
211                             .getCrossCuttingInfo());
212                     m_perThread.put(currentThread, perThreadIntroduction);
213                     perThreadIntroduction.createMixin();
214                 }
215             }
216             result = m_methodRepository[methodIndex].invoke(((Introduction) m_perThread.get(currentThread))
217                     .getImplementation(), parameters);
218         } catch (InvocationTargetException e) {
219             if (e.getTargetException() instanceof ClassCastException) {
220                 System.err
221                         .println("WARNING: ClassCastException has been thrown from introduced method - this can occur if you cast 'this' to CrossCutting instead of casting 'OuterAspectClass.this'");
222             }
223             throw e.getTargetException();
224         } catch (Exception e) {
225             throw new WrappedRuntimeException(e);
226         }
227         return result;
228     }
229 
230     /***
231      * Swaps the current mixin implementation.
232      * 
233      * @param newImplementationClass the class of the new implementation to use
234      */
235     public void swapImplementation(final Class newImplementationClass) {
236         if (newImplementationClass == null) {
237             throw new IllegalArgumentException("new implementation class class can not be null");
238         }
239 
240         // check compatibility
241         IntroductionDefinition def = m_prototype.getIntroductionDefinition();
242         for (Iterator intfs = def.getInterfaceClassNames().iterator(); intfs.hasNext();) {
243             if (!findInterfaceInHierarchy(newImplementationClass, (String) intfs.next())) {
244                 throw new DefinitionException("new implementation class is not compatible");
245             }
246         }
247         synchronized (this) {
248             try {
249                 // create the new introduction to replace the current one
250                 m_prototype.swapImplementation(newImplementationClass);
251                 createMethodRepository();
252 
253                 // clear the current introduction storages
254                 m_perJvm = null;
255                 m_perClass = new HashMap(m_perClass.size());
256                 m_perInstance = new WeakHashMap(m_perClass.size());
257                 m_perThread = new WeakHashMap(m_perClass.size());
258             } catch (Exception e) {
259                 throw new WrappedRuntimeException(e);
260             }
261         }
262     }
263 
264     /***
265      * Recursively traverse the interface hierarchy implemented by the given root class in order to find one that
266      * matches the given name. Looks in the class hierarchy as well.
267      * 
268      * @param root is the class or interface to start the search at.
269      * @param requiredInterface that we are looking for.
270      * @return <code>true</code> if we found the interface, <code>false</code> otherwise.
271      */
272     private static boolean findInterfaceInHierarchy(final Class root, final String requiredInterface) {
273         if (root == null) {
274             return false;
275         }
276 
277         // looks in directly implemented interface first
278         Class[] interfaces = root.getInterfaces();
279         for (int i = 0; i < interfaces.length; i++) {
280             Class implemented = interfaces[i];
281             if (implemented.getName().equals(requiredInterface)
282                 || findInterfaceInHierarchy(implemented, requiredInterface)) {
283                 return true;
284             }
285         }
286         return findInterfaceInHierarchy(root.getSuperclass(), requiredInterface);
287     }
288 
289     /***
290      * Creates a method repository for the introduced methods.
291      */
292     private void createMethodRepository() {
293         synchronized (m_methodRepository) {
294             // grab method defined in parent interfaces only
295             // See IntroductionDefinition.new()
296             List interfaceDeclaredMethods = collectInterfaceMethods(m_prototype.getImplementationClass());
297             List methodList = ReflectHelper.createInterfaceDefinedSortedMethodList(m_prototype.getImplementationClass(), interfaceDeclaredMethods);
298             m_methodRepository = new Method[methodList.size()];
299             for (int i = 0; i < m_methodRepository.length; i++) {
300                 Method method = (Method) methodList.get(i);
301                 method.setAccessible(true);
302                 m_methodRepository[i] = method;
303             }
304         }
305     }
306 
307     /***
308      * Collects the methods from all the mixin interfaces.
309      *
310      * @param mixinClass
311      * @return list of methods declared in given class interfaces
312      */
313     private List collectInterfaceMethods(final Class mixinClass) {
314         List interfaceDeclaredMethods = new ArrayList();
315         Class[] interfaces = mixinClass.getInterfaces();
316         for (int i = 0; i < interfaces.length; i++) {
317             interfaceDeclaredMethods.addAll(ReflectHelper.createCompleteSortedMethodList(interfaces[i]));
318         }
319         Class superClass = mixinClass.getSuperclass();
320         if (superClass != null) {
321             interfaceDeclaredMethods.addAll(collectInterfaceMethods(superClass));
322         }
323         return interfaceDeclaredMethods;
324     }
325 
326     /***
327      * Returns the target instance from an introduction
328      * 
329      * @param mixinImpl aka "this" from the mixin impl
330      * @return the target instance or null (if not perInstance deployed mixin)
331      */
332     public Object getTargetInstance(Object mixinImpl) {
333         Object targetInstance = null;
334         if (m_prototype.getDeploymentModel() == DeploymentModel.PER_INSTANCE) {
335             for (Iterator i = m_perInstance.entrySet().iterator(); i.hasNext();) {
336                 Map.Entry entry = (Map.Entry) i.next();
337                 Object mixin = ((Introduction) entry.getValue()).getImplementation();
338                 if (mixinImpl.equals(mixin)) {
339                     targetInstance = entry.getKey();
340                     break;
341                 }
342             }
343         }
344         return targetInstance;
345     }
346 
347     /***
348      * Returns the target class from an introduction
349      * 
350      * @param mixinImpl aka "this" from the mixin impl
351      * @return the target instance or null (if not perInstance or perClas deployed mixin)
352      */
353     public Class getTargetClass(Object mixinImpl) {
354         Class targetClass = null;
355         if (m_prototype.getDeploymentModel() == DeploymentModel.PER_INSTANCE) {
356             Object instance = getTargetInstance(mixinImpl);
357             if (instance != null) {
358                 targetClass = instance.getClass();
359             }
360         } else if (m_prototype.getDeploymentModel() == DeploymentModel.PER_CLASS) {
361             for (Iterator i = m_perClass.entrySet().iterator(); i.hasNext();) {
362                 Map.Entry entry = (Map.Entry) i.next();
363                 Object mixin = ((Introduction) entry.getValue()).getImplementation();
364                 if (mixinImpl.equals(mixin)) {
365                     targetClass = (Class) entry.getKey();
366                     break;
367                 }
368             }
369         }
370         return targetClass;
371     }
372 }