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