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 com.thoughtworks.qdox.JavaDocBuilder;
    11  import com.thoughtworks.qdox.model.DocletTag;
    12  import com.thoughtworks.qdox.model.JavaClass;
    13  import com.thoughtworks.qdox.model.JavaField;
    14  import com.thoughtworks.qdox.model.JavaMethod;
    15
    16  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
    17  import org.codehaus.aspectwerkz.exception.DefinitionException;
    18  import org.codehaus.aspectwerkz.util.Strings;
    19  import org.codehaus.aspectwerkz.annotation.expression.AnnotationVisitor;
    20  import org.codehaus.aspectwerkz.annotation.expression.ast.ParseException;
    21
    22  import java.io.File;
    23  import java.io.FileNotFoundException;
    24  import java.io.IOException;
    25  import java.io.ObjectInputStream;
    26  import java.io.Serializable;
    27  import java.util.ArrayList;
    28  import java.util.Collection;
    29  import java.util.HashMap;
    30  import java.util.Iterator;
    31  import java.util.List;
    32  import java.util.Map;
    33  import java.lang.reflect.Proxy;
    34  import java.lang.reflect.InvocationHandler;
    35  import java.lang.reflect.Method;
    36
    37  /***
    38   * Parses and retrieves annotations.
    39   *
    40   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    41   * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
    42   */
    43  public class AnnotationManager {
    44
    45      private static final String JAVA_LANG_OBJECT_CLASS_NAME = "java.lang.Object";
    46
    47      /***
    48       * The JavaDoc parser.
    49       */
    50      private final JavaDocBuilder m_parser = new JavaDocBuilder();
    51
    52      /***
    53       * Map with the registered annotations mapped to their interface implementation classes.
    54       */
    55      private final Map m_registeredAnnotations = new HashMap();
    56
    57      /***
    58       * Constructs a new annotation manager and had the given ClassLoader to the
    59       * search path
    60       *
    61       * @param loader
    62       */
    63      public AnnotationManager(ClassLoader loader) {
    64          m_parser.getClassLibrary().addClassLoader(loader);
    65      }
    66
    67      /***
    68       * Adds a source tree to the builder.
    69       *
    70       * @param srcDirs the source trees
    71       */
    72      public void addSourceTrees(final String[] srcDirs) {
    73          for (int i = 0; i < srcDirs.length; i++) {
    74              m_parser.addSourceTree(new File(srcDirs[i]));
    75          }
    76      }
    77
    78      /***
    79       * Adds a source file.
    80       *
    81       * @param srcFile the source file
    82       */
    83      public void addSource(final String srcFile) {
    84          try {
    85              m_parser.addSource(new File(srcFile));
    86          } catch (Exception e) {
    87              throw new WrappedRuntimeException(e);
    88          }
    89      }
    90
    91      /***
    92       * Register an annotation together with its proxy implementation under
    93       * a doclet name
    94       *
    95       * @param proxyClass     the proxy class
    96       * @param docletName     the name of the doclet. The annotation name is the proxy FQN.
    97       */
    98      public void registerAnnotationProxy(final Class proxyClass, final String docletName) {
    99          m_registeredAnnotations.put(docletName, proxyClass);
    100     }
    101
    102     /***
    103      * Returns all classes.
    104      *
    105      * @return an array with all classes
    106      */
    107     public JavaClass[] getAllClasses() {
    108         Collection classes = m_parser.getClassLibrary().all();
    109         Collection javaClasses = new ArrayList();
    110         String className;
    111         for (Iterator it = classes.iterator(); it.hasNext();) {
    112             className = (String) it.next();
    113             if (JAVA_LANG_OBJECT_CLASS_NAME.equals(className)) {
    114                 continue;
    115             }
    116             JavaClass clazz = m_parser.getClassByName(className);
    117             javaClasses.add(clazz);
    118         }
    119         return (JavaClass[]) javaClasses.toArray(new JavaClass[]{});
    120     }
    121
    122     /***
    123      * Returns the annotations with a specific name for a specific class.
    124      *
    125      * @param name
    126      * @param clazz
    127      * @return an array with the annotations
    128      */
    129     public Annotation[] getAnnotations(final String name, final JavaClass clazz) {
    130         DocletTag[] tags = clazz.getTags();
    131         List annotations = new ArrayList();
    132         for (int i = 0; i < tags.length; i++) {
    133             DocletTag tag = tags[i];
    134             RawAnnotation rawAnnotation = getRawAnnotation(name, tag);
    135             if (rawAnnotation != null) {
    136                 annotations.add(instantiateAnnotation(rawAnnotation));
    137             }
    138         }
    139         return (Annotation[]) annotations.toArray(new Annotation[]{});
    140     }
    141
    142     /***
    143      * Returns the annotations with a specific name for a specific method.
    144      *
    145      * @param name
    146      * @param method
    147      * @return an array with the annotations
    148      */
    149     public Annotation[] getAnnotations(final String name, final JavaMethod method) {
    150         DocletTag[] tags = method.getTags();
    151         List annotations = new ArrayList();
    152         for (int i = 0; i < tags.length; i++) {
    153             DocletTag tag = tags[i];
    154             RawAnnotation rawAnnotation = getRawAnnotation(name, tag);
    155             if (rawAnnotation != null) {
    156                 annotations.add(instantiateAnnotation(rawAnnotation));
    157             }
    158         }
    159         return (Annotation[]) annotations.toArray(new Annotation[]{});
    160     }
    161
    162     /***
    163      * Returns the annotations with a specific name for a specific field.
    164      *
    165      * @param name
    166      * @param field
    167      * @return an array with the annotations
    168      */
    169     public Annotation[] getAnnotations(final String name, final JavaField field) {
    170         DocletTag[] tags = field.getTags();
    171         List annotations = new ArrayList();
    172         for (int i = 0; i < tags.length; i++) {
    173             DocletTag tag = tags[i];
    174             RawAnnotation rawAnnotation = getRawAnnotation(name, tag);
    175             if (rawAnnotation != null) {
    176                 annotations.add(instantiateAnnotation(rawAnnotation));
    177             }
    178         }
    179         return (Annotation[]) annotations.toArray(new Annotation[]{});
    180     }
    181
    182
    183     /***
    184      * Instantiate the given annotation based on its name, and initialize it by passing the given value (may be parsed
    185      * or not, depends on type/untyped)
    186      *
    187      * @param rawAnnotation
    188      * @return
    189      */
    190     private Annotation instantiateAnnotation(final RawAnnotation rawAnnotation) {
    191         final Class proxyClass = (Class) m_registeredAnnotations.get(rawAnnotation.name);
    192
    193         if (!proxyClass.isInterface()) {
    194             throw new RuntimeException("Annotation class is not defined as an interface for " + rawAnnotation.name
    195                 + ". Use of AspectWerkz 1.x Annotation proxies is not anymore supported.");
    196         }
    197
    198         try {
    199             InvocationHandler handler = new Java14AnnotationInvocationHander(
    200                     proxyClass, rawAnnotation.name, rawAnnotation.value
    201             );
    202             Object annotationProxy = Proxy.newProxyInstance(
    203                     proxyClass.getClassLoader(), new Class[]{Annotation.class, proxyClass}, handler
    204             );
    205             return (Annotation) annotationProxy;
    206         } catch (Throwable e) {
    207             throw new DefinitionException(
    208                     "Unable to parse annotation @" + rawAnnotation.name + '(' +
    209                     " " + rawAnnotation.value + ')', e
    210             );
    211         }
    212     }
    213
    214     /***
    215      * Instantiate an annotation given its interface and elements
    216      * It is used only for nested annotation hence requires typed annotation
    217      * without nicknames.
    218      *
    219      * TODO: Note: should we support nicked name nested ?
    220      * If so grammar needs to track annotation name
    221      *
    222      * @return
    223      */
    224     public static Annotation instantiateNestedAnnotation(final Class annotationClass, final Map elements) {
    225         if (!annotationClass.isInterface()) {
    226             throw new RuntimeException("Annotation class is not defined as an interface for " + annotationClass.getName()
    227                 + ". Use of AspectWerkz 1.x Annotation proxies is not anymore supported.");
    228         }
    229
    230         try {
    231             InvocationHandler handler = new Java14AnnotationInvocationHander(
    232                     annotationClass, elements
    233             );
    234             Object annotationProxy = Proxy.newProxyInstance(
    235                     annotationClass.getClassLoader(), new Class[]{Annotation.class, annotationClass}, handler
    236             );
    237             return (Annotation) annotationProxy;
    238         } catch (Throwable e) {
    239             throw new DefinitionException(
    240                     "Unable to parse nested annotation @" + annotationClass.getName(), e
    241             );
    242         }
    243     }
    244
    245     /***
    246      * Extract the raw information (name + unparsed value without optional parenthesis) from a Qdox doclet Note:
    247      * StringBuffer.append(null<string>) sucks and produce "null" string..
    248      * Note: when using untyped annotation, then the first space character(s) in the value part will be
    249      * resumed to only one space (untyped     type -> untyped type), due to QDox doclet handling.
    250      *
    251      * @param annotationName
    252      * @param tag
    253      * @return RawAnnotation or null if not found
    254      */
    255     private RawAnnotation getRawAnnotation(String annotationName, DocletTag tag) {
    256         String asIs = tag.getName() + " " + tag.getValue();
    257         asIs = asIs.trim();
    258         Strings.removeFormattingCharacters(asIs);
    259
    260         // filter out if annotationName cannot be found
    261         if (!asIs.startsWith(annotationName)) {
    262             return null;
    263         }
    264
    265         String name = null;
    266         String value = null;
    267
    268         // try untyped split
    269         if (asIs.indexOf(' ') > 0) {
    270             name = asIs.substring(0, asIs.indexOf(' '));
    271         }
    272         if (annotationName.equals(name)) {
    273             // untyped
    274             value = asIs.substring(asIs.indexOf(' ') + 1, asIs.length());
    275             if (value.startsWith("(") && value.endsWith(")")) {
    276                 if (value.length() > 2) {
    277                     value = value.substring(1, value.length()-1);
    278                 } else {
    279                     value = "";
    280                 }
    281             }
    282         } else {
    283             // try typed split
    284             if (asIs.indexOf('(') > 0) {
    285                 name = asIs.substring(0, asIs.indexOf('('));
    286             }
    287             if (annotationName.equals(name)) {
    288                 value = asIs.substring(asIs.indexOf('(') + 1, asIs.length());
    289                 if (value.endsWith(")")) {
    290                     if (value.length() > 1) {
    291                         value = value.substring(0, value.length() - 1);
    292                     } else {
    293                         value = "";
    294                     }
    295                 }
    296             } else if (annotationName.equals(asIs)) {
    297                 value = "";
    298             }
    299         }
    300
    301         // found one
    302         if (value != null) {
    303             RawAnnotation annotation = new RawAnnotation();
    304             annotation.name = annotationName;
    305             annotation.value = value;
    306             return annotation;
    307         } else {
    308             return null;
    309         }
    310     }
    311
    312     /***
    313      * Raw info about an annotation: Do(foo) ==> Do + foo [unless untyped then ==> Do(foo) + null Do foo  ==> Do + foo
    314      * etc
    315      */
    316     private static class RawAnnotation implements Serializable {
    317         String name;
    318         String value;
    319     }
    320 }