1 /***************************************************************************************
2 * Copyright (c) Jonas Bonr, 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;
34 import java.lang.reflect.Method;
35 import java.lang.reflect.Array;
36 import java.util.Map;
37 import java.util.HashMap;
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 m_annotationElementValueHoldersByName;
61
62 /***
63 * We reference class at parse time. We don't need to avoid reflection.
64 */
65 protected Class m_annotationClass;
66
67 /***
68 * Creates a new visitor.
69 */
70 public AnnotationVisitor(final Map annotationElementValueHoldersByName, final Class 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 annotationElements, final String annotationRepresentation,
84 final Class 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 visit(SimpleNode node, Object data) {
94 return node.jjtGetChild(0).jjtAccept(this, data);
95 }
96
97 public Object visit(ASTRoot node, Object data) {
98 return node.jjtGetChild(0).jjtAccept(this, data);
99 }
100
101 public Object visit(ASTAnnotation node, Object data) {
102 int nr = node.jjtGetNumChildren();
103
104 if (nr == 1 && !(node.jjtGetChild(0) instanceof ASTKeyValuePair)) {
105
106 Object value = node.jjtGetChild(0).jjtAccept(this, data);
107
108 if(!(node.jjtGetChild(0) instanceof ASTAnnotation)) {
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 visit(ASTKeyValuePair node, Object data) {
121 String elementName = node.getKey();
122
123
124 MethodInfo elementMethod = getMethodInfo(elementName);
125
126
127 if (node.jjtGetChild(0) instanceof ASTAnnotation) {
128 Map nestedAnnotationElementValueHoldersByName = new HashMap();
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 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 visit(ASTArray node, Object data) {
146 MethodInfo methodInfo = (MethodInfo) data;
147 Class elementType = methodInfo.elementType;
148 if (!elementType.isArray()) {
149 throw new RuntimeException(
150 "type for element ["
151 + methodInfo.elementMethod.getName()
152 + "] is not of type array"
153 );
154 }
155 Class componentType = elementType.getComponentType();
156 if (componentType.isArray()) {
157 throw new UnsupportedOperationException(
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 visit(ASTIdentifier node, Object data) {
167 String 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("unsupported format for java type or reference [" + identifier + "]");
174 }
175 }
176
177 public Object visit(ASTBoolean node, Object data) {
178 return Boolean.valueOf(node.getValue());
179 }
180
181 public Object visit(ASTChar node, Object data) {
182 return new Character(node.getValue().charAt(0));
183 }
184
185 public Object visit(ASTString node, Object data) {
186
187 if (node.getValue().length() >= 2) {
188 String 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 visit(ASTInteger node, Object data) {
196 String value = node.getValue();
197 char lastChar = value.charAt(value.length() - 1);
198 if ((lastChar == 'L') || (lastChar == 'l')) {
199 return new Long(value.substring(0, value.length() - 1));
200 } else if (value.length() > 9) {
201 return new Long(value);
202 } else {
203 return new Integer(value);
204 }
205 }
206
207 public Object visit(ASTFloat node, Object data) {
208 String value = node.getValue();
209 char lastChar = value.charAt(value.length() - 1);
210 if ((lastChar == 'D') || (lastChar == 'd')) {
211 return new Double(value.substring(0, value.length() - 1));
212 } else if ((lastChar == 'F') || (lastChar == 'f')) {
213 return new Float(value.substring(0, value.length() - 1));
214 } else {
215 return new Double(value);
216 }
217 }
218
219 public Object visit(ASTHex node, Object data) {
220 throw new UnsupportedOperationException("hex numbers not yet supported");
221 }
222
223 public Object visit(ASTOct node, Object data) {
224 throw new UnsupportedOperationException("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 elementName) {
236 StringBuffer javaBeanMethodPostfix = new StringBuffer();
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[] methods = m_annotationClass.getDeclaredMethods();
244
245 for (int i = 0; i < methods.length; i++) {
246 Method 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(
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 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 createTypedArray(final ASTArray node,
277 final Object data,
278 final int nrOfElements,
279 final Class componentType) {
280 if (componentType.equals(String.class)) {
281 String[] array = new String[nrOfElements];
282 for (int i = 0; i < nrOfElements; i++) {
283 String value = (String) 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) 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) 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) 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) 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) 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) 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) 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) node.jjtGetChild(i).jjtAccept(this, data)).booleanValue();
333 }
334 return array;
335 } else if (componentType.equals(Class.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
344 Object[] nestedTyped = (Object[])Array.newInstance(componentType, nrOfElements);
345 for (int i = 0; i < nrOfElements; i++) {
346 Map nestedAnnotationElementValueHoldersByName = new HashMap();
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
357 Object[] array = new Object[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 handleClassIdentifier(String identifier) {
367 int index = identifier.lastIndexOf('.');
368 String className = identifier.substring(0, index);
369
370 int dimension = 0;
371 String componentClassName = className;
372 while (componentClassName.endsWith("[]")) {
373 dimension++;
374 componentClassName = componentClassName.substring(0, componentClassName.length()-2);
375 }
376
377 Class 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 e) {
400 throw new RuntimeException("could not load class [" + className + "] due to: " + e.toString());
401 }
402 }
403
404
405 if (isComponentPrimitive) {
406 if (dimension <= 0) {
407 return componentClass;
408 } else {
409 return Array.newInstance(componentClass, dimension);
410 }
411 } else {
412 String 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 handleReferenceIdentifier(String identifier) {
422 int index = identifier.lastIndexOf('.');
423 String className = identifier.substring(0, index);
424 String fieldName = identifier.substring(index + 1, identifier.length());
425 try {
426
427 Class clazz = Class.forName(className, false, m_annotationClass.getClassLoader());
428 Field field = clazz.getDeclaredField(fieldName);
429 return field.get(null);
430 } catch (Exception e) {
431 throw new RuntimeException(
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 elementMethod;
443
444 public Class elementType;
445 }
446 }