001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.annotation;
008:
009: import com.thoughtworks.qdox.JavaDocBuilder;
010: import com.thoughtworks.qdox.model.DocletTag;
011: import com.thoughtworks.qdox.model.JavaClass;
012: import com.thoughtworks.qdox.model.JavaField;
013: import com.thoughtworks.qdox.model.JavaMethod;
014:
015: import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
016: import org.codehaus.aspectwerkz.exception.DefinitionException;
017: import org.codehaus.aspectwerkz.util.Strings;
018: import org.codehaus.aspectwerkz.annotation.expression.AnnotationVisitor;
019: import org.codehaus.aspectwerkz.annotation.expression.ast.ParseException;
020:
021: import java.io.File;
022: import java.io.FileNotFoundException;
023: import java.io.IOException;
024: import java.io.ObjectInputStream;
025: import java.io.Serializable;
026: import java.util.ArrayList;
027: import java.util.Collection;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import java.lang.reflect.Proxy;
033: import java.lang.reflect.InvocationHandler;
034: import java.lang.reflect.Method;
035:
036: /**
037: * Parses and retrieves annotations.
038: *
039: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
040: * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
041: */
042: public class AnnotationManager {
043:
044: private static final String JAVA_LANG_OBJECT_CLASS_NAME = "java.lang.Object";
045:
046: /**
047: * The JavaDoc parser.
048: */
049: private final JavaDocBuilder m_parser = new JavaDocBuilder();
050:
051: /**
052: * Map with the registered annotations mapped to their interface implementation classes.
053: */
054: private final Map m_registeredAnnotations = new HashMap();
055:
056: /**
057: * Constructs a new annotation manager and had the given ClassLoader to the
058: * search path
059: *
060: * @param loader
061: */
062: public AnnotationManager(ClassLoader loader) {
063: m_parser.getClassLibrary().addClassLoader(loader);
064: }
065:
066: /**
067: * Adds a source tree to the builder.
068: *
069: * @param srcDirs the source trees
070: */
071: public void addSourceTrees(final String[] srcDirs) {
072: for (int i = 0; i < srcDirs.length; i++) {
073: m_parser.addSourceTree(new File(srcDirs[i]));
074: }
075: }
076:
077: /**
078: * Adds a source file.
079: *
080: * @param srcFile the source file
081: */
082: public void addSource(final String srcFile) {
083: try {
084: m_parser.addSource(new File(srcFile));
085: } catch (Exception e) {
086: throw new WrappedRuntimeException(e);
087: }
088: }
089:
090: /**
091: * Register an annotation together with its proxy implementation under
092: * a doclet name
093: *
094: * @param proxyClass the proxy class
095: * @param docletName the name of the doclet. The annotation name is the proxy FQN.
096: */
097: public void registerAnnotationProxy(final Class proxyClass,
098: final String docletName) {
099: 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,
130: final JavaClass clazz) {
131: DocletTag[] tags = clazz.getTags();
132: List annotations = new ArrayList();
133: for (int i = 0; i < tags.length; i++) {
134: DocletTag tag = tags[i];
135: RawAnnotation rawAnnotation = getRawAnnotation(name, tag);
136: if (rawAnnotation != null) {
137: annotations.add(instantiateAnnotation(rawAnnotation));
138: }
139: }
140: return (Annotation[]) annotations.toArray(new Annotation[] {});
141: }
142:
143: /**
144: * Returns the annotations with a specific name for a specific method.
145: *
146: * @param name
147: * @param method
148: * @return an array with the annotations
149: */
150: public Annotation[] getAnnotations(final String name,
151: final JavaMethod method) {
152: DocletTag[] tags = method.getTags();
153: List annotations = new ArrayList();
154: for (int i = 0; i < tags.length; i++) {
155: DocletTag tag = tags[i];
156: RawAnnotation rawAnnotation = getRawAnnotation(name, tag);
157: if (rawAnnotation != null) {
158: annotations.add(instantiateAnnotation(rawAnnotation));
159: }
160: }
161: return (Annotation[]) annotations.toArray(new Annotation[] {});
162: }
163:
164: /**
165: * Returns the annotations with a specific name for a specific field.
166: *
167: * @param name
168: * @param field
169: * @return an array with the annotations
170: */
171: public Annotation[] getAnnotations(final String name,
172: final JavaField field) {
173: DocletTag[] tags = field.getTags();
174: List annotations = new ArrayList();
175: for (int i = 0; i < tags.length; i++) {
176: DocletTag tag = tags[i];
177: RawAnnotation rawAnnotation = getRawAnnotation(name, tag);
178: if (rawAnnotation != null) {
179: annotations.add(instantiateAnnotation(rawAnnotation));
180: }
181: }
182: return (Annotation[]) annotations.toArray(new Annotation[] {});
183: }
184:
185: /**
186: * Instantiate the given annotation based on its name, and initialize it by passing the given value (may be parsed
187: * or not, depends on type/untyped)
188: *
189: * @param rawAnnotation
190: * @return
191: */
192: private Annotation instantiateAnnotation(
193: final RawAnnotation rawAnnotation) {
194: final Class proxyClass = (Class) m_registeredAnnotations
195: .get(rawAnnotation.name);
196:
197: if (!proxyClass.isInterface()) {
198: throw new RuntimeException(
199: "Annotation class is not defined as an interface for "
200: + rawAnnotation.name
201: + ". Use of AspectWerkz 1.x Annotation proxies is not anymore supported.");
202: }
203:
204: try {
205: InvocationHandler handler = new Java14AnnotationInvocationHander(
206: proxyClass, rawAnnotation.name, rawAnnotation.value);
207: Object annotationProxy = Proxy.newProxyInstance(proxyClass
208: .getClassLoader(), new Class[] { Annotation.class,
209: proxyClass }, handler);
210: return (Annotation) annotationProxy;
211: } catch (Throwable e) {
212: throw new DefinitionException(
213: "Unable to parse annotation @" + rawAnnotation.name
214: + '(' + " " + rawAnnotation.value + ')', e);
215: }
216: }
217:
218: /**
219: * Instantiate an annotation given its interface and elements
220: * It is used only for nested annotation hence requires typed annotation
221: * without nicknames.
222: *
223: * TODO: Note: should we support nicked name nested ?
224: * If so grammar needs to track annotation name
225: *
226: * @return
227: */
228: public static Annotation instantiateNestedAnnotation(
229: final Class annotationClass, final Map elements) {
230: if (!annotationClass.isInterface()) {
231: throw new RuntimeException(
232: "Annotation class is not defined as an interface for "
233: + annotationClass.getName()
234: + ". Use of AspectWerkz 1.x Annotation proxies is not anymore supported.");
235: }
236:
237: try {
238: InvocationHandler handler = new Java14AnnotationInvocationHander(
239: annotationClass, elements);
240: Object annotationProxy = Proxy.newProxyInstance(
241: annotationClass.getClassLoader(), new Class[] {
242: Annotation.class, annotationClass },
243: handler);
244: return (Annotation) annotationProxy;
245: } catch (Throwable e) {
246: throw new DefinitionException(
247: "Unable to parse nested annotation @"
248: + annotationClass.getName(), e);
249: }
250: }
251:
252: /**
253: * Extract the raw information (name + unparsed value without optional parenthesis) from a Qdox doclet Note:
254: * StringBuffer.append(null<string>) sucks and produce "null" string..
255: * Note: when using untyped annotation, then the first space character(s) in the value part will be
256: * resumed to only one space (untyped type -> untyped type), due to QDox doclet handling.
257: *
258: * @param annotationName
259: * @param tag
260: * @return RawAnnotation or null if not found
261: */
262: private RawAnnotation getRawAnnotation(String annotationName,
263: DocletTag tag) {
264: String asIs = tag.getName() + " " + tag.getValue();
265: asIs = asIs.trim();
266: Strings.removeFormattingCharacters(asIs);
267:
268: // filter out if annotationName cannot be found
269: if (!asIs.startsWith(annotationName)) {
270: return null;
271: }
272:
273: String name = null;
274: String value = null;
275:
276: // try untyped split
277: if (asIs.indexOf(' ') > 0) {
278: name = asIs.substring(0, asIs.indexOf(' '));
279: }
280: if (annotationName.equals(name)) {
281: // untyped
282: value = asIs
283: .substring(asIs.indexOf(' ') + 1, asIs.length());
284: if (value.startsWith("(") && value.endsWith(")")) {
285: if (value.length() > 2) {
286: value = value.substring(1, value.length() - 1);
287: } else {
288: value = "";
289: }
290: }
291: } else {
292: // try typed split
293: if (asIs.indexOf('(') > 0) {
294: name = asIs.substring(0, asIs.indexOf('('));
295: }
296: if (annotationName.equals(name)) {
297: value = asIs.substring(asIs.indexOf('(') + 1, asIs
298: .length());
299: if (value.endsWith(")")) {
300: if (value.length() > 1) {
301: value = value.substring(0, value.length() - 1);
302: } else {
303: value = "";
304: }
305: }
306: } else if (annotationName.equals(asIs)) {
307: value = "";
308: }
309: }
310:
311: // found one
312: if (value != null) {
313: RawAnnotation annotation = new RawAnnotation();
314: annotation.name = annotationName;
315: annotation.value = value;
316: return annotation;
317: } else {
318: return null;
319: }
320: }
321:
322: /**
323: * Raw info about an annotation: Do(foo) ==> Do + foo [unless untyped then ==> Do(foo) + null Do foo ==> Do + foo
324: * etc
325: */
326: private static class RawAnnotation implements Serializable {
327: String name;
328: String value;
329: }
330: }
|