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 }