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.annotation;
9   
10  import org.codehaus.aspectwerkz.reflect.MethodInfo;
11  import org.codehaus.aspectwerkz.reflect.ClassInfo;
12  import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
13  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
14  import org.objectweb.asm.attrs.*;
15  import org.objectweb.asm.attrs.Annotation;
16  import org.objectweb.asm.Type;
17  
18  import java.lang.reflect.InvocationHandler;
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Proxy;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.Array;
23  import java.util.List;
24  import java.util.Collection;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.HashMap;
29  import java.util.Arrays;
30  import java.io.Serializable;
31  
32  /***
33   * Dynamic proxy handler for ASM Annotations we extract
34   * The handler resolve the LazyClass to a concrete Class so that the proxy creation does not trigger
35   * any class loading.
36   * <p/>
37   *
38   * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
39   */
40  public class Java5AnnotationInvocationHandler implements InvocationHandler {
41  
42      /***
43       * The annotation class name
44       */
45      private final String m_annotationClassName;
46  
47      /***
48       * A list of AnnotationElement containing the annotation instance element values
49       * (including the defaulted value)
50       */
51      private final List m_annotationElements;
52  
53      /***
54       * private ctor - see getAnnotationProxy()
55       *
56       * @param annotationClassName
57       * @param annotationElements
58       */
59      private Java5AnnotationInvocationHandler(String annotationClassName, Collection annotationElements) {
60          m_annotationClassName = annotationClassName;
61          m_annotationElements = new ArrayList(annotationElements.size());
62          for (Iterator iterator = annotationElements.iterator(); iterator.hasNext();) {
63              m_annotationElements.add(iterator.next());
64          }
65      }
66  
67      /***
68       * Dynamic proxy based implementation
69       * toString(), annotationType() and value() have a specific behavior
70       *
71       * @param proxy
72       * @param method
73       * @param args
74       * @return
75       * @throws Throwable
76       */
77      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
78          String name = method.getName();
79          if ("toString".equals(name)) {
80              StringBuffer sb = new StringBuffer();
81              sb.append('@').append(m_annotationClassName);
82              sb.append("(");
83              String sep = "";
84              for (Iterator iterator = m_annotationElements.iterator(); iterator.hasNext();) {
85                  AnnotationElement annotationElement = (AnnotationElement) iterator.next();
86                  sb.append(sep).append(annotationElement.name + "=" + annotationElement.toString());
87                  sep = ", ";
88              }
89              sb.append(")");
90              return sb.toString();
91          } else if ("annotationType".equals(name)) {
92              // funny, may explain why 1.5 Annotation intf has annotationType + getClass
93              // since a dynamic proxy handler cannot hijack getClass() ..
94              return Class.forName(m_annotationClassName, false, proxy.getClass().getClassLoader());
95          } else if ("value".equals(name)) {
96              if (m_annotationElements.isEmpty()) {
97                  return null;
98              } else {
99                  //FIXME !!value can be there with other elements !
100                 // we could check that we don't have more than one element
101                 return ((AnnotationElement) m_annotationElements.get(0)).resolveValueHolderFrom(
102                         proxy.getClass().getClassLoader()
103                 );
104             }
105         } else {
106             for (Iterator iterator = m_annotationElements.iterator(); iterator.hasNext();) {
107                 AnnotationElement annotationElement = (AnnotationElement) iterator.next();
108                 if (name.equals(annotationElement.name)) {
109                     return annotationElement.resolveValueHolderFrom(proxy.getClass().getClassLoader());
110                 }
111             }
112             // element not found for such a name
113             throw new RuntimeException("No such element on Annotation @" + m_annotationClassName + " : " + name);
114         }
115     }
116 
117     /***
118      * Build and return a dynamic proxy representing the given ASM Annotation.
119      * The proxy implements the AspectWerkz Annotation interface, as well as the user type Annotation.
120      * Each elements of the annotation is proxied if needed or agressively created unless Class types to not trigger
121      * any nested loading.
122      *
123      * Note: JSR-175 does not support Annotation value of N-dimensional array. At most 1 dimension is supported and
124      * only for a subset of Java types.
125      *
126      * @param annotation
127      * @param loader the classloader of the annotatED component (can be different from the one of the annotation class)
128      * @return
129      */
130     public static org.codehaus.aspectwerkz.annotation.Annotation getAnnotationProxy(org.objectweb.asm.attrs.Annotation annotation, ClassLoader loader) {
131         String annotationClassName = Type.getType(annotation.type).getClassName();
132 
133         // get the ClassInfo for the annoation class to populate the assigned element values
134         // with lazy value holders from the setted value or the default value if defaulted element
135         // has been used in the annotation
136         ClassInfo annotationClassInfo = AsmClassInfo.getClassInfo(annotationClassName, loader);
137         Map annotationElementValueHoldersByName = new HashMap();
138 
139         // populate with the default values (might be then overriden by setted values)
140         MethodInfo[] annotationMethods = annotationClassInfo.getMethods();
141         for (int i = 0; i < annotationMethods.length; i++) {
142             MethodInfo annotationMethod = annotationMethods[i];
143             for (Iterator iterator = annotationMethod.getAnnotations().iterator(); iterator.hasNext();) {
144                 AnnotationInfo annotationInfo = (AnnotationInfo) iterator.next();
145                 // handles AnnotationDefault attribute that we have wrapped. See AnnotationDefault.
146                 if (annotationInfo.getName().equals(AnnotationDefault.NAME)) {
147                     Object value = ((AnnotationDefault)annotationInfo.getAnnotation()).value();
148                     Object valueHolder = getAnnotationValueHolder(value, loader);
149                     annotationElementValueHoldersByName.put(annotationMethod.getName(),
150                                                             new AnnotationElement(annotationMethod.getName(),
151                                                                                   valueHolder)
152                     );
153                 }
154             }
155         }
156 
157         // override and populate with the setted values
158         List settedElementValues = annotation.elementValues;
159         for (int i = 0; i < settedElementValues.size(); i++) {
160             Object[] element = (Object[]) settedElementValues.get(i);
161             String name = (String) element[0];
162             Object valueHolder = getAnnotationValueHolder(element[1], loader);
163             annotationElementValueHoldersByName.put(name, new AnnotationElement(name, valueHolder));
164         }
165 
166         // create a dynamic proxy to embody the annotation instance
167         try {
168             Class typeClass = Class.forName(annotationClassName, false, loader);
169             Object proxy = Proxy.newProxyInstance(
170                     loader,
171                     new Class[]{org.codehaus.aspectwerkz.annotation.Annotation.class, typeClass},
172                     new Java5AnnotationInvocationHandler(annotationClassName,
173                                                          annotationElementValueHoldersByName.values()
174                     )
175             );
176             return (org.codehaus.aspectwerkz.annotation.Annotation) proxy;
177         } catch (ClassNotFoundException e) {
178             throw new WrappedRuntimeException(e);
179         }
180     }
181 
182     /***
183      * Turn an ASM Annotation value into a concrete Java value holder, unless the value is of type
184      * Class, in which case we wrap it behind a LazyClass() object so that actual loading of the class
185      * will be done lazily
186      *
187      * @param value
188      * @param loader
189      * @return
190      */
191     private static Object getAnnotationValueHolder(Object value, ClassLoader loader) {
192         if (value instanceof Annotation.EnumConstValue) {
193             Annotation.EnumConstValue enumAsmValue = (Annotation.EnumConstValue) value;
194             try {
195                 Class enumClass = Class.forName(Type.getType(enumAsmValue.typeName).getClassName(), false, loader);
196                 Field enumConstValue = enumClass.getField(enumAsmValue.constName);
197                 return enumConstValue.get(null);
198             } catch (Exception e) {
199                 throw new WrappedRuntimeException(e);
200             }
201         } else if (value instanceof Type) {
202             // TODO may require additional filtering ?
203             return new AnnotationElement.LazyClass(((Type) value).getClassName());
204         } else if (value instanceof Annotation) {
205             return getAnnotationProxy(((Annotation) value), loader);
206         } else if (value instanceof Object[]) {
207             Object[] values = (Object[]) value;
208             Object[] holders = new Object[values.length];
209             boolean isLazyClass = false;
210             for (int i = 0; i < values.length; i++) {
211                 holders[i] = getAnnotationValueHolder(values[i], loader);
212                 if (!isLazyClass && holders[i] instanceof AnnotationElement.LazyClass) {
213                     isLazyClass = true;
214                 }
215             }
216             if (isLazyClass) {
217                 // retype the array
218                 AnnotationElement.LazyClass[] typedHolders = (AnnotationElement.LazyClass[]) Array.newInstance(AnnotationElement.LazyClass.class, values.length);
219                 for (int i = 0; i < holders.length; i++) {
220                     typedHolders[i] = (AnnotationElement.LazyClass) holders[i];
221                 }
222                 return typedHolders;
223             } else {
224                 return holders;
225             }
226         }
227         return value;
228     }
229 
230 }
231