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
261 if (!asIs.startsWith(annotationName)) {
262 return null;
263 }
264
265 String name = null;
266 String value = null;
267
268
269 if (asIs.indexOf(' ') > 0) {
270 name = asIs.substring(0, asIs.indexOf(' '));
271 }
272 if (annotationName.equals(name)) {
273
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
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
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 }