001: /**************************************************************************************
002: * Copyright (c) Jonas Bonr, 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.expression;
008:
009: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTAnnotation;
010: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTArray;
011: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTBoolean;
012: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTChar;
013: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTFloat;
014: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTHex;
015: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTIdentifier;
016: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTInteger;
017: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTKeyValuePair;
018: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTOct;
019: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTRoot;
020: import org.codehaus.aspectwerkz.annotation.expression.ast.ASTString;
021: import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParserVisitor;
022: import org.codehaus.aspectwerkz.annotation.expression.ast.SimpleNode;
023: import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParser;
024: import org.codehaus.aspectwerkz.annotation.expression.ast.ParseException;
025: import org.codehaus.aspectwerkz.annotation.AnnotationElement;
026: import org.codehaus.aspectwerkz.annotation.AnnotationManager;
027: import org.codehaus.aspectwerkz.annotation.Annotation;
028: import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
029: import org.codehaus.aspectwerkz.util.Strings;
030: import org.objectweb.asm.Type;
031:
032: import java.lang.reflect.Field;
033: import java.lang.reflect.Method;
034: import java.lang.reflect.Array;
035: import java.util.Map;
036: import java.util.HashMap;
037:
038: /**
039: * Parse a source-like annotation representation to feed a map of AnnotationElement which
040: * contain holder to actual values. Class and type referenced are holded behind lazy
041: * wrapper that won't load them unless used.
042: * <p/>
043: * Note that this parser will trigger class loading to ensure type consistency
044: * [change to ASMClassInfo instead of reflect if embedded parsing needed]
045: * <p/>
046: * Note: the loader used here is the one from the annotation class and not the one from annotated element
047: * That does not matter since parse time is a build time operation for now.
048: *
049: * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
050: * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
051: */
052: public class AnnotationVisitor implements AnnotationParserVisitor {
053:
054: /**
055: * The one and only annotation parser.
056: */
057: protected static final AnnotationParser PARSER = new AnnotationParser(
058: System.in);
059:
060: protected Map m_annotationElementValueHoldersByName;
061:
062: /**
063: * We reference class at parse time. We don't need to avoid reflection.
064: */
065: protected Class m_annotationClass;
066:
067: /**
068: * Creates a new visitor.
069: */
070: public AnnotationVisitor(
071: final Map annotationElementValueHoldersByName,
072: final Class annotationClass) {
073: m_annotationElementValueHoldersByName = annotationElementValueHoldersByName;
074: m_annotationClass = annotationClass;
075: }
076:
077: /**
078: * Parse the given annotationRepresentation (@XXX(...)) to feed the given annotationElements map,
079: * based on the annotationClass annotation interface.
080: *
081: * @param annotationElements
082: * @param annotationRepresentation
083: * @param annotationClass
084: */
085: public static void parse(final Map annotationElements,
086: final String annotationRepresentation,
087: final Class annotationClass) {
088: try {
089: ASTRoot root = PARSER.parse(annotationRepresentation);
090: new AnnotationVisitor(annotationElements, annotationClass)
091: .visit(root, null);
092: } catch (ParseException e) {
093: throw new WrappedRuntimeException(
094: "cannot parse annotation ["
095: + annotationRepresentation + "]", e);
096: }
097: }
098:
099: public Object visit(SimpleNode node, Object data) {
100: return node.jjtGetChild(0).jjtAccept(this , data);
101: }
102:
103: public Object visit(ASTRoot node, Object data) {
104: return node.jjtGetChild(0).jjtAccept(this , data);
105: }
106:
107: public Object visit(ASTAnnotation node, Object data) {
108: int nr = node.jjtGetNumChildren();
109:
110: if (nr == 1
111: && !(node.jjtGetChild(0) instanceof ASTKeyValuePair)) {
112: // single "value" default
113: Object value = node.jjtGetChild(0).jjtAccept(this , data);
114:
115: if (!(node.jjtGetChild(0) instanceof ASTAnnotation)) { // child already set the value
116: m_annotationElementValueHoldersByName.put("value",
117: new AnnotationElement("value", value));
118: }
119: } else {
120: for (int i = 0; i < nr; i++) {
121: node.jjtGetChild(i).jjtAccept(this , data);
122: }
123: }
124: return null;
125: }
126:
127: public Object visit(ASTKeyValuePair node, Object data) {
128: String elementName = node.getKey();
129:
130: // get the methodInfo for this elementName to access its type from its name
131: MethodInfo elementMethod = getMethodInfo(elementName);
132:
133: // nested annotation
134: if (node.jjtGetChild(0) instanceof ASTAnnotation) {
135: Map nestedAnnotationElementValueHoldersByName = new HashMap();
136: AnnotationVisitor nestedAnnotationVisitor = new AnnotationVisitor(
137: nestedAnnotationElementValueHoldersByName,
138: elementMethod.elementType);
139: nestedAnnotationVisitor.visit((ASTAnnotation) node
140: .jjtGetChild(0), data);
141: m_annotationElementValueHoldersByName
142: .put(
143: elementName,
144: new AnnotationElement(
145: elementName,
146: AnnotationManager
147: .instantiateNestedAnnotation(
148: elementMethod.elementType,
149: nestedAnnotationElementValueHoldersByName)));
150: } else {
151: Object typedValue = node.jjtGetChild(0).jjtAccept(this ,
152: elementMethod);
153: m_annotationElementValueHoldersByName.put(elementName,
154: new AnnotationElement(elementName, typedValue));
155: }
156: return null;
157: }
158:
159: public Object visit(ASTArray node, Object data) {
160: MethodInfo methodInfo = (MethodInfo) data;
161: Class elementType = methodInfo.elementType;
162: if (!elementType.isArray()) {
163: throw new RuntimeException("type for element ["
164: + methodInfo.elementMethod.getName()
165: + "] is not of type array");
166: }
167: Class componentType = elementType.getComponentType();
168: if (componentType.isArray()) {
169: throw new UnsupportedOperationException(
170: "multidimensional arrays are not supported for element type, was required method ["
171: + methodInfo.elementMethod.getName() + "]");
172: }
173: return createTypedArray(node, data, node.jjtGetNumChildren(),
174: componentType);
175: }
176:
177: public Object visit(ASTIdentifier node, Object data) {
178: String identifier = node.getValue();
179: if (identifier.endsWith(".class")) {
180: return handleClassIdentifier(identifier);
181: } else if (isJavaReferenceType(identifier)) {
182: return handleReferenceIdentifier(identifier);
183: } else {
184: throw new RuntimeException(
185: "unsupported format for java type or reference ["
186: + identifier + "]");
187: }
188: }
189:
190: public Object visit(ASTBoolean node, Object data) {
191: return Boolean.valueOf(node.getValue());
192: }
193:
194: public Object visit(ASTChar node, Object data) {
195: return new Character(node.getValue().charAt(0));
196: }
197:
198: public Object visit(ASTString node, Object data) {
199: // the node contains the \" string escapes
200: if (node.getValue().length() >= 2) {
201: String escaped = node.getValue().substring(1,
202: node.getValue().length() - 1);
203: return Strings.replaceSubString(escaped, "\\\"", "\"");
204: } else {
205: return node.getValue();
206: }
207: }
208:
209: public Object visit(ASTInteger node, Object data) {
210: String value = node.getValue();
211: char lastChar = value.charAt(value.length() - 1);
212: if ((lastChar == 'L') || (lastChar == 'l')) {
213: return new Long(value.substring(0, value.length() - 1));
214: } else if (value.length() > 9) {
215: return new Long(value);
216: } else {
217: return new Integer(value);
218: }
219: }
220:
221: public Object visit(ASTFloat node, Object data) {
222: String value = node.getValue();
223: char lastChar = value.charAt(value.length() - 1);
224: if ((lastChar == 'D') || (lastChar == 'd')) {
225: return new Double(value.substring(0, value.length() - 1));
226: } else if ((lastChar == 'F') || (lastChar == 'f')) {
227: return new Float(value.substring(0, value.length() - 1));
228: } else {
229: return new Double(value);
230: }
231: }
232:
233: public Object visit(ASTHex node, Object data) {
234: throw new UnsupportedOperationException(
235: "hex numbers not yet supported");
236: }
237:
238: public Object visit(ASTOct node, Object data) {
239: throw new UnsupportedOperationException(
240: "octal numbers not yet supported");
241: }
242:
243: /**
244: * For a typed annotation, there should be
245: * - a setter method setx or setX
246: * - a getter method x or getx or getX
247: *
248: * @param elementName
249: * @return
250: */
251: private MethodInfo getMethodInfo(final String elementName) {
252: StringBuffer javaBeanMethodPostfix = new StringBuffer();
253: javaBeanMethodPostfix.append(elementName.substring(0, 1)
254: .toUpperCase());
255: if (elementName.length() > 1) {
256: javaBeanMethodPostfix.append(elementName.substring(1));
257: }
258:
259: MethodInfo methodInfo = new MethodInfo();
260: Method[] methods = m_annotationClass.getDeclaredMethods();
261: // look for element methods
262: for (int i = 0; i < methods.length; i++) {
263: Method elementMethod = methods[i];
264: if (elementMethod.getName().equals(elementName)) {
265: methodInfo.elementMethod = elementMethod;
266: methodInfo.elementType = elementMethod.getReturnType();
267: break;
268: }
269: }
270: if (methodInfo.elementMethod == null) {
271: throw new RuntimeException(
272: "method for the annotation element ["
273: + elementName
274: + "] can not be found in annotation interface ["
275: + m_annotationClass.getName() + "]");
276: }
277: return methodInfo;
278: }
279:
280: private boolean isJavaReferenceType(final String valueAsString) {
281: int first = valueAsString.indexOf('.');
282: int last = valueAsString.lastIndexOf('.');
283: int comma = valueAsString.indexOf(',');
284: if ((first > 0) && (last > 0) && (first != last) && (comma < 0)) {
285: return true;
286: } else {
287: return false;
288: }
289: }
290:
291: private Object createTypedArray(final ASTArray node,
292: final Object data, final int nrOfElements,
293: final Class componentType) {
294: if (componentType.equals(String.class)) {
295: String[] array = new String[nrOfElements];
296: for (int i = 0; i < nrOfElements; i++) {
297: String value = (String) node.jjtGetChild(i).jjtAccept(
298: this , data);
299: array[i] = value;
300: }
301: return array;
302: } else if (componentType.equals(long.class)) {
303: long[] array = new long[nrOfElements];
304: for (int i = 0; i < nrOfElements; i++) {
305: array[i] = ((Long) node.jjtGetChild(i).jjtAccept(this ,
306: data)).longValue();
307: }
308: return array;
309: } else if (componentType.equals(int.class)) {
310: int[] array = new int[nrOfElements];
311: for (int i = 0; i < nrOfElements; i++) {
312: array[i] = ((Integer) node.jjtGetChild(i).jjtAccept(
313: this , data)).intValue();
314: }
315: return array;
316: } else if (componentType.equals(short.class)) {
317: short[] array = new short[nrOfElements];
318: for (int i = 0; i < nrOfElements; i++) {
319: array[i] = ((Short) node.jjtGetChild(i).jjtAccept(this ,
320: data)).shortValue();
321: }
322: return array;
323: } else if (componentType.equals(double.class)) {
324: double[] array = new double[nrOfElements];
325: for (int i = 0; i < nrOfElements; i++) {
326: array[i] = ((Double) node.jjtGetChild(i).jjtAccept(
327: this , data)).doubleValue();
328: }
329: return array;
330: } else if (componentType.equals(float.class)) {
331: float[] array = new float[nrOfElements];
332: for (int i = 0; i < nrOfElements; i++) {
333: array[i] = ((Float) node.jjtGetChild(i).jjtAccept(this ,
334: data)).floatValue();
335: }
336: return array;
337: } else if (componentType.equals(byte.class)) {
338: byte[] array = new byte[nrOfElements];
339: for (int i = 0; i < nrOfElements; i++) {
340: array[i] = ((Byte) node.jjtGetChild(i).jjtAccept(this ,
341: data)).byteValue();
342: }
343: return array;
344: } else if (componentType.equals(char.class)) {
345: char[] array = new char[nrOfElements];
346: for (int i = 0; i < nrOfElements; i++) {
347: array[i] = ((Character) node.jjtGetChild(i).jjtAccept(
348: this , data)).charValue();
349: }
350: return array;
351: } else if (componentType.equals(boolean.class)) {
352: boolean[] array = new boolean[nrOfElements];
353: for (int i = 0; i < nrOfElements; i++) {
354: array[i] = ((Boolean) node.jjtGetChild(i).jjtAccept(
355: this , data)).booleanValue();
356: }
357: return array;
358: } else if (componentType.equals(Class.class)) {
359: AnnotationElement.LazyClass[] array = new AnnotationElement.LazyClass[nrOfElements];
360: for (int i = 0; i < nrOfElements; i++) {
361: array[i] = (AnnotationElement.LazyClass) node
362: .jjtGetChild(i).jjtAccept(this , data);
363: }
364: return array;
365: } else {
366: if (nrOfElements > 1
367: && node.jjtGetChild(0) instanceof ASTAnnotation) {
368: // nested array of annotation
369: Object[] nestedTyped = (Object[]) Array.newInstance(
370: componentType, nrOfElements);
371: for (int i = 0; i < nrOfElements; i++) {
372: Map nestedAnnotationElementValueHoldersByName = new HashMap();
373: AnnotationVisitor nestedAnnotationVisitor = new AnnotationVisitor(
374: nestedAnnotationElementValueHoldersByName,
375: componentType);
376: nestedAnnotationVisitor.visit((ASTAnnotation) node
377: .jjtGetChild(i), data);
378: nestedTyped[i] = AnnotationManager
379: .instantiateNestedAnnotation(componentType,
380: nestedAnnotationElementValueHoldersByName);
381: }
382: return nestedTyped;
383: } else {
384: // reference type
385: Object[] array = new Object[nrOfElements];
386: for (int i = 0; i < nrOfElements; i++) {
387: array[i] = node.jjtGetChild(i)
388: .jjtAccept(this , data);
389: }
390: return array;
391: }
392: }
393: }
394:
395: private Object handleClassIdentifier(String identifier) {
396: int index = identifier.lastIndexOf('.');
397: String className = identifier.substring(0, index);
398:
399: int dimension = 0;
400: String componentClassName = className;
401: while (componentClassName.endsWith("[]")) {
402: dimension++;
403: componentClassName = componentClassName.substring(0,
404: componentClassName.length() - 2);
405: }
406:
407: Class componentClass = null;
408: boolean isComponentPrimitive = true;
409: if (componentClassName.equals("long")) {
410: componentClass = long.class;
411: } else if (componentClassName.equals("int")) {
412: componentClass = int.class;
413: } else if (componentClassName.equals("short")) {
414: componentClass = short.class;
415: } else if (componentClassName.equals("double")) {
416: componentClass = double.class;
417: } else if (componentClassName.equals("float")) {
418: componentClass = float.class;
419: } else if (componentClassName.equals("byte")) {
420: componentClass = byte.class;
421: } else if (componentClassName.equals("char")) {
422: componentClass = char.class;
423: } else if (componentClassName.equals("boolean")) {
424: componentClass = boolean.class;
425: } else {
426: isComponentPrimitive = false;
427: try {
428: componentClass = Class.forName(componentClassName,
429: false, m_annotationClass.getClassLoader());
430: } catch (ClassNotFoundException e) {
431: throw new RuntimeException("could not load class ["
432: + className + "] due to: " + e.toString());
433: }
434: }
435:
436: // primitive types are not wrapped in a LazyClass
437: if (isComponentPrimitive) {
438: if (dimension <= 0) {
439: return componentClass;
440: } else {
441: return Array.newInstance(componentClass, dimension);
442: }
443: } else {
444: String componentType = Type.getType(componentClass)
445: .getDescriptor();
446: for (int i = 0; i < dimension; i++) {
447: componentType = "[" + componentType;
448: }
449: Type type = Type.getType(componentType);
450: return new AnnotationElement.LazyClass(type.getClassName());
451: }
452: }
453:
454: private Object handleReferenceIdentifier(String identifier) {
455: int index = identifier.lastIndexOf('.');
456: String className = identifier.substring(0, index);
457: String fieldName = identifier.substring(index + 1, identifier
458: .length());
459: try {
460: // TODO m_annotationClass might be higher in the CL than a referenced identifier
461: Class clazz = Class.forName(className, false,
462: m_annotationClass.getClassLoader());
463: Field field = clazz.getDeclaredField(fieldName);
464: return field.get(null);
465: } catch (Exception e) {
466: throw new RuntimeException(
467: "could not access reference field [" + identifier
468: + "] due to: " + e.toString());
469: }
470: }
471:
472: /**
473: * Holds the element method and type.
474: */
475: private static class MethodInfo {
476:
477: public Method elementMethod;
478:
479: public Class elementType;
480: }
481: }
|