KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > codehaus > aspectwerkz > annotation > expression > AnnotationVisitor


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.expression;
9
10 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTAnnotation;
11 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTArray;
12 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTBoolean;
13 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTChar;
14 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTFloat;
15 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTHex;
16 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTIdentifier;
17 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTInteger;
18 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTKeyValuePair;
19 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTOct;
20 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTRoot;
21 import org.codehaus.aspectwerkz.annotation.expression.ast.ASTString;
22 import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParserVisitor;
23 import org.codehaus.aspectwerkz.annotation.expression.ast.SimpleNode;
24 import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParser;
25 import org.codehaus.aspectwerkz.annotation.expression.ast.ParseException;
26 import org.codehaus.aspectwerkz.annotation.AnnotationElement;
27 import org.codehaus.aspectwerkz.annotation.AnnotationManager;
28 import org.codehaus.aspectwerkz.annotation.Annotation;
29 import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
30 import org.codehaus.aspectwerkz.util.Strings;
31 import org.objectweb.asm.Type;
32
33 import java.lang.reflect.Field JavaDoc;
34 import java.lang.reflect.Method JavaDoc;
35 import java.lang.reflect.Array JavaDoc;
36 import java.util.Map JavaDoc;
37 import java.util.HashMap JavaDoc;
38
39 /**
40  * Parse a source-like annotation representation to feed a map of AnnotationElement which
41  * contain holder to actual values. Class and type referenced are holded behind lazy
42  * wrapper that won't load them unless used.
43  * <p/>
44  * Note that this parser will trigger class loading to ensure type consistency
45  * [change to ASMClassInfo instead of reflect if embedded parsing needed]
46  * <p/>
47  * Note: the loader used here is the one from the annotation class and not the one from annotated element
48  * That does not matter since parse time is a build time operation for now.
49  *
50  * @author <a HREF="mailto:jboner@codehaus.org">Jonas Bonér </a>
51  * @author <a HREF="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
52  */

53 public class AnnotationVisitor implements AnnotationParserVisitor {
54
55     /**
56      * The one and only annotation parser.
57      */

58     protected static final AnnotationParser PARSER = new AnnotationParser(System.in);
59
60     protected Map JavaDoc m_annotationElementValueHoldersByName;
61
62     /**
63      * We reference class at parse time. We don't need to avoid reflection.
64      */

65     protected Class JavaDoc m_annotationClass;
66
67     /**
68      * Creates a new visitor.
69      */

70     public AnnotationVisitor(final Map JavaDoc annotationElementValueHoldersByName, final Class JavaDoc annotationClass) {
71         m_annotationElementValueHoldersByName = annotationElementValueHoldersByName;
72         m_annotationClass = annotationClass;
73     }
74
75     /**
76      * Parse the given annotationRepresentation (@XXX(...)) to feed the given annotationElements map,
77      * based on the annotationClass annotation interface.
78      *
79      * @param annotationElements
80      * @param annotationRepresentation
81      * @param annotationClass
82      */

83     public static void parse(final Map JavaDoc annotationElements, final String JavaDoc annotationRepresentation,
84                              final Class JavaDoc annotationClass) {
85         try {
86             ASTRoot root = PARSER.parse(annotationRepresentation);
87             new AnnotationVisitor(annotationElements, annotationClass).visit(root, null);
88         } catch (ParseException e) {
89             throw new WrappedRuntimeException("cannot parse annotation [" + annotationRepresentation + "]", e);
90         }
91     }
92
93     public Object JavaDoc visit(SimpleNode node, Object JavaDoc data) {
94         return node.jjtGetChild(0).jjtAccept(this, data);
95     }
96
97     public Object JavaDoc visit(ASTRoot node, Object JavaDoc data) {
98         return node.jjtGetChild(0).jjtAccept(this, data);
99     }
100
101     public Object JavaDoc visit(ASTAnnotation node, Object JavaDoc data) {
102         int nr = node.jjtGetNumChildren();
103
104         if (nr == 1 && !(node.jjtGetChild(0) instanceof ASTKeyValuePair)) {
105             // single "value" default
106
Object JavaDoc value = node.jjtGetChild(0).jjtAccept(this, data);
107
108                 if(!(node.jjtGetChild(0) instanceof ASTAnnotation)) { // child already set the value
109
m_annotationElementValueHoldersByName.put("value",
110                      new AnnotationElement("value", value));
111                 }
112         } else {
113             for (int i = 0; i < nr; i++) {
114                 node.jjtGetChild(i).jjtAccept(this, data);
115             }
116         }
117         return null;
118     }
119
120     public Object JavaDoc visit(ASTKeyValuePair node, Object JavaDoc data) {
121         String JavaDoc elementName = node.getKey();
122
123         // get the methodInfo for this elementName to access its type from its name
124
MethodInfo elementMethod = getMethodInfo(elementName);
125
126         // nested annotation
127
if (node.jjtGetChild(0) instanceof ASTAnnotation) {
128             Map JavaDoc nestedAnnotationElementValueHoldersByName = new HashMap JavaDoc();
129             AnnotationVisitor nestedAnnotationVisitor = new AnnotationVisitor(
130                     nestedAnnotationElementValueHoldersByName,
131                     elementMethod.elementType
132                     );
133             nestedAnnotationVisitor.visit((ASTAnnotation)node.jjtGetChild(0), data);
134             m_annotationElementValueHoldersByName.put(elementName,
135                     new AnnotationElement(elementName,
136                             AnnotationManager.instantiateNestedAnnotation(elementMethod.elementType, nestedAnnotationElementValueHoldersByName)));
137         } else {
138             Object JavaDoc typedValue = node.jjtGetChild(0).jjtAccept(this, elementMethod);
139             m_annotationElementValueHoldersByName.put(elementName,
140                     new AnnotationElement(elementName, typedValue));
141         }
142         return null;
143     }
144
145     public Object JavaDoc visit(ASTArray node, Object JavaDoc data) {
146         MethodInfo methodInfo = (MethodInfo) data;
147         Class JavaDoc elementType = methodInfo.elementType;
148         if (!elementType.isArray()) {
149             throw new RuntimeException JavaDoc(
150                     "type for element ["
151                     + methodInfo.elementMethod.getName()
152                     + "] is not of type array"
153             );
154         }
155         Class JavaDoc componentType = elementType.getComponentType();
156         if (componentType.isArray()) {
157             throw new UnsupportedOperationException JavaDoc(
158                     "multidimensional arrays are not supported for element type, was required method ["
159                     + methodInfo.elementMethod.getName()
160                     + "]"
161             );
162         }
163         return createTypedArray(node, data, node.jjtGetNumChildren(), componentType);
164     }
165
166     public Object JavaDoc visit(ASTIdentifier node, Object JavaDoc data) {
167         String JavaDoc identifier = node.getValue();
168         if (identifier.endsWith(".class")) {
169             return handleClassIdentifier(identifier);
170         } else if (isJavaReferenceType(identifier)) {
171             return handleReferenceIdentifier(identifier);
172         } else {
173             throw new RuntimeException JavaDoc("unsupported format for java type or reference [" + identifier + "]");
174         }
175     }
176
177     public Object JavaDoc visit(ASTBoolean node, Object JavaDoc data) {
178         return Boolean.valueOf(node.getValue());
179     }
180
181     public Object JavaDoc visit(ASTChar node, Object JavaDoc data) {
182         return new Character JavaDoc(node.getValue().charAt(0));
183     }
184
185     public Object JavaDoc visit(ASTString node, Object JavaDoc data) {
186         // the node contains the \" string escapes
187
if (node.getValue().length() >= 2) {
188             String JavaDoc escaped = node.getValue().substring(1, node.getValue().length() - 1);
189             return Strings.replaceSubString(escaped, "\\\"", "\"");
190         } else {
191             return node.getValue();
192         }
193     }
194
195     public Object JavaDoc visit(ASTInteger node, Object JavaDoc data) {
196         String JavaDoc value = node.getValue();
197         char lastChar = value.charAt(value.length() - 1);
198         if ((lastChar == 'L') || (lastChar == 'l')) {
199             return new Long JavaDoc(value.substring(0, value.length() - 1));
200         } else if (value.length() > 9) {
201             return new Long JavaDoc(value);
202         } else {
203             return new Integer JavaDoc(value);
204         }
205     }
206
207     public Object JavaDoc visit(ASTFloat node, Object JavaDoc data) {
208         String JavaDoc value = node.getValue();
209         char lastChar = value.charAt(value.length() - 1);
210         if ((lastChar == 'D') || (lastChar == 'd')) {
211             return new Double JavaDoc(value.substring(0, value.length() - 1));
212         } else if ((lastChar == 'F') || (lastChar == 'f')) {
213             return new Float JavaDoc(value.substring(0, value.length() - 1));
214         } else {
215             return new Double JavaDoc(value);
216         }
217     }
218
219     public Object JavaDoc visit(ASTHex node, Object JavaDoc data) {
220         throw new UnsupportedOperationException JavaDoc("hex numbers not yet supported");
221     }
222
223     public Object JavaDoc visit(ASTOct node, Object JavaDoc data) {
224         throw new UnsupportedOperationException JavaDoc("octal numbers not yet supported");
225     }
226
227     /**
228      * For a typed annotation, there should be
229      * - a setter method setx or setX
230      * - a getter method x or getx or getX
231      *
232      * @param elementName
233      * @return
234      */

235     private MethodInfo getMethodInfo(final String JavaDoc elementName) {
236         StringBuffer JavaDoc javaBeanMethodPostfix = new StringBuffer JavaDoc();
237         javaBeanMethodPostfix.append(elementName.substring(0, 1).toUpperCase());
238         if (elementName.length() > 1) {
239             javaBeanMethodPostfix.append(elementName.substring(1));
240         }
241
242         MethodInfo methodInfo = new MethodInfo();
243         Method JavaDoc[] methods = m_annotationClass.getDeclaredMethods();
244         // look for element methods
245
for (int i = 0; i < methods.length; i++) {
246             Method JavaDoc elementMethod = methods[i];
247             if (elementMethod.getName().equals(elementName)) {
248                 methodInfo.elementMethod = elementMethod;
249                 methodInfo.elementType = elementMethod.getReturnType();
250                 break;
251             }
252         }
253         if (methodInfo.elementMethod == null) {
254             throw new RuntimeException JavaDoc(
255                     "method for the annotation element ["
256                     + elementName
257                     + "] can not be found in annotation interface ["
258                     + m_annotationClass.getName()
259                     + "]"
260             );
261         }
262         return methodInfo;
263     }
264
265     private boolean isJavaReferenceType(final String JavaDoc valueAsString) {
266         int first = valueAsString.indexOf('.');
267         int last = valueAsString.lastIndexOf('.');
268         int comma = valueAsString.indexOf(',');
269         if ((first > 0) && (last > 0) && (first != last) && (comma < 0)) {
270             return true;
271         } else {
272             return false;
273         }
274     }
275
276     private Object JavaDoc createTypedArray(final ASTArray node,
277                                     final Object JavaDoc data,
278                                     final int nrOfElements,
279                                     final Class JavaDoc componentType) {
280         if (componentType.equals(String JavaDoc.class)) {
281             String JavaDoc[] array = new String JavaDoc[nrOfElements];
282             for (int i = 0; i < nrOfElements; i++) {
283                 String JavaDoc value = (String JavaDoc) node.jjtGetChild(i).jjtAccept(this, data);
284                 array[i] = value;
285             }
286             return array;
287         } else if (componentType.equals(long.class)) {
288             long[] array = new long[nrOfElements];
289             for (int i = 0; i < nrOfElements; i++) {
290                 array[i] = ((Long JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).longValue();
291             }
292             return array;
293         } else if (componentType.equals(int.class)) {
294             int[] array = new int[nrOfElements];
295             for (int i = 0; i < nrOfElements; i++) {
296                 array[i] = ((Integer JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).intValue();
297             }
298             return array;
299         } else if (componentType.equals(short.class)) {
300             short[] array = new short[nrOfElements];
301             for (int i = 0; i < nrOfElements; i++) {
302                 array[i] = ((Short JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).shortValue();
303             }
304             return array;
305         } else if (componentType.equals(double.class)) {
306             double[] array = new double[nrOfElements];
307             for (int i = 0; i < nrOfElements; i++) {
308                 array[i] = ((Double JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).doubleValue();
309             }
310             return array;
311         } else if (componentType.equals(float.class)) {
312             float[] array = new float[nrOfElements];
313             for (int i = 0; i < nrOfElements; i++) {
314                 array[i] = ((Float JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).floatValue();
315             }
316             return array;
317         } else if (componentType.equals(byte.class)) {
318             byte[] array = new byte[nrOfElements];
319             for (int i = 0; i < nrOfElements; i++) {
320                 array[i] = ((Byte JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).byteValue();
321             }
322             return array;
323         } else if (componentType.equals(char.class)) {
324             char[] array = new char[nrOfElements];
325             for (int i = 0; i < nrOfElements; i++) {
326                 array[i] = ((Character JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).charValue();
327             }
328             return array;
329         } else if (componentType.equals(boolean.class)) {
330             boolean[] array = new boolean[nrOfElements];
331             for (int i = 0; i < nrOfElements; i++) {
332                 array[i] = ((Boolean JavaDoc) node.jjtGetChild(i).jjtAccept(this, data)).booleanValue();
333             }
334             return array;
335         } else if (componentType.equals(Class JavaDoc.class)) {
336             AnnotationElement.LazyClass[] array = new AnnotationElement.LazyClass[nrOfElements];
337             for (int i = 0; i < nrOfElements; i++) {
338                 array[i] = (AnnotationElement.LazyClass) node.jjtGetChild(i).jjtAccept(this, data);
339             }
340             return array;
341         } else {
342             if (nrOfElements > 1 && node.jjtGetChild(0) instanceof ASTAnnotation) {
343                 // nested array of annotation
344
Object JavaDoc[] nestedTyped = (Object JavaDoc[])Array.newInstance(componentType, nrOfElements);
345                 for (int i = 0; i < nrOfElements; i++) {
346                     Map JavaDoc nestedAnnotationElementValueHoldersByName = new HashMap JavaDoc();
347                     AnnotationVisitor nestedAnnotationVisitor = new AnnotationVisitor(
348                             nestedAnnotationElementValueHoldersByName,
349                             componentType
350                             );
351                     nestedAnnotationVisitor.visit((ASTAnnotation)node.jjtGetChild(i), data);
352                     nestedTyped[i] = AnnotationManager.instantiateNestedAnnotation(componentType, nestedAnnotationElementValueHoldersByName);
353                 }
354                 return nestedTyped;
355             } else {
356                 // reference type
357
Object JavaDoc[] array = new Object JavaDoc[nrOfElements];
358                 for (int i = 0; i < nrOfElements; i++) {
359                     array[i] = node.jjtGetChild(i).jjtAccept(this, data);
360                 }
361                 return array;
362             }
363         }
364     }
365
366     private Object JavaDoc handleClassIdentifier(String JavaDoc identifier) {
367         int index = identifier.lastIndexOf('.');
368         String JavaDoc className = identifier.substring(0, index);
369
370         int dimension = 0;
371         String JavaDoc componentClassName = className;
372         while (componentClassName.endsWith("[]")) {
373             dimension++;
374             componentClassName = componentClassName.substring(0, componentClassName.length()-2);
375         }
376
377         Class JavaDoc componentClass = null;
378         boolean isComponentPrimitive = true;
379         if (componentClassName.equals("long")) {
380             componentClass = long.class;
381         } else if (componentClassName.equals("int")) {
382             componentClass = int.class;
383         } else if (componentClassName.equals("short")) {
384             componentClass = short.class;
385         } else if (componentClassName.equals("double")) {
386             componentClass = double.class;
387         } else if (componentClassName.equals("float")) {
388             componentClass = float.class;
389         } else if (componentClassName.equals("byte")) {
390             componentClass = byte.class;
391         } else if (componentClassName.equals("char")) {
392             componentClass = char.class;
393         } else if (componentClassName.equals("boolean")) {
394             componentClass = boolean.class;
395         } else {
396             isComponentPrimitive = false;
397             try {
398                 componentClass = Class.forName(componentClassName, false, m_annotationClass.getClassLoader());
399             } catch (ClassNotFoundException JavaDoc e) {
400                 throw new RuntimeException JavaDoc("could not load class [" + className + "] due to: " + e.toString());
401             }
402         }
403
404         // primitive types are not wrapped in a LazyClass
405
if (isComponentPrimitive) {
406             if (dimension <= 0) {
407                 return componentClass;
408             } else {
409                 return Array.newInstance(componentClass, dimension);
410             }
411         } else {
412             String JavaDoc componentType = Type.getType(componentClass).getDescriptor();
413             for (int i = 0; i < dimension; i++) {
414                 componentType = "[" + componentType;
415             }
416             Type type = Type.getType(componentType);
417             return new AnnotationElement.LazyClass(type.getClassName());
418         }
419     }
420
421     private Object JavaDoc handleReferenceIdentifier(String JavaDoc identifier) {
422         int index = identifier.lastIndexOf('.');
423         String JavaDoc className = identifier.substring(0, index);
424         String JavaDoc fieldName = identifier.substring(index + 1, identifier.length());
425         try {
426             // TODO m_annotationClass might be higher in the CL than a referenced identifier
427
Class JavaDoc clazz = Class.forName(className, false, m_annotationClass.getClassLoader());
428             Field JavaDoc field = clazz.getDeclaredField(fieldName);
429             return field.get(null);
430         } catch (Exception JavaDoc e) {
431             throw new RuntimeException JavaDoc(
432                     "could not access reference field [" + identifier + "] due to: " + e.toString()
433             );
434         }
435     }
436
437     /**
438      * Holds the element method and type.
439      */

440     private static class MethodInfo {
441
442         public Method JavaDoc elementMethod;
443
444         public Class JavaDoc elementType;
445     }
446 }
Popular Tags