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
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
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
165 Introduction perInstanceIntroduction = Introduction.newInstance(m_prototype, m_prototype
166 .getCrossCuttingInfo());
167 m_perInstance.put(targetInstance, perInstanceIntroduction);
168
169
170
171
172
173
174
175
176
177
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
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
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
250 m_prototype.swapImplementation(newImplementationClass);
251 createMethodRepository();
252
253
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
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
295
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 }