View Javadoc

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.TypedAnnotationProxy;
11  import org.codehaus.aspectwerkz.annotation.Annotation;
12  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTAnnotation;
13  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTArray;
14  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTBoolean;
15  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTChar;
16  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTFloat;
17  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTHex;
18  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTIdentifier;
19  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTInteger;
20  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTKeyValuePair;
21  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTOct;
22  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTRoot;
23  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTString;
24  import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParserVisitor;
25  import org.codehaus.aspectwerkz.annotation.expression.ast.SimpleNode;
26  
27  import java.lang.reflect.Field;
28  import java.lang.reflect.Method;
29  
30  /***
31   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
32   * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
33   */
34  public class AnnotationVisitor implements AnnotationParserVisitor {
35      protected ASTRoot m_root;
36  
37      protected TypedAnnotationProxy m_annotationProxy;
38  
39      /***
40       * Creates a new visitor.
41       * 
42       * @param root the AST root
43       */
44      public AnnotationVisitor(final ASTRoot root, final TypedAnnotationProxy annotationProxy) {
45          m_root = root;
46          m_annotationProxy = annotationProxy;
47      }
48  
49      public static void parse(final TypedAnnotationProxy annotation, final ASTRoot root) {
50          new AnnotationVisitor(root, annotation).visit(root, annotation);
51      }
52  
53      public Object visit(SimpleNode node, Object data) {
54          return node.jjtGetChild(0).jjtAccept(this, data);
55      }
56  
57      public Object visit(ASTRoot node, Object data) {
58          return node.jjtGetChild(0).jjtAccept(this, data);
59      }
60  
61      public Object visit(ASTAnnotation node, Object data) {
62          int nr = node.jjtGetNumChildren();
63          if (nr == 1 && !(node.jjtGetChild(0) instanceof ASTKeyValuePair)) {
64              // single "value" default
65              Object value = node.jjtGetChild(0).jjtAccept(this, data);
66              MethodInfo valueMethodInfo = getMethodInfo("value");
67              invokeSetterMethod(valueMethodInfo, value, "default value");
68          } else {
69              for (int i = 0; i < nr; i++) {
70                  node.jjtGetChild(i).jjtAccept(this, data);
71              }
72          }
73          return null;
74      }
75  
76      public Object visit(ASTKeyValuePair node, Object data) {
77          String valueName = node.getKey();
78          MethodInfo methodInfo = getMethodInfo(valueName);
79          Object typedValue = node.jjtGetChild(0).jjtAccept(this, methodInfo);
80  
81          invokeSetterMethod(methodInfo, typedValue, valueName);
82          return null;
83      }
84  
85      public Object visit(ASTArray node, Object data) {
86          MethodInfo methodInfo = (MethodInfo) data;
87          Class valueType = methodInfo.valueType;
88          if (!valueType.isArray()) {
89              throw new RuntimeException("parameter type to setter method ["
90                  + methodInfo.setterMethod.getName()
91                  + "] is not of type array");
92          }
93          Class componentType = valueType.getComponentType();
94          if (componentType.isArray()) {
95              throw new UnsupportedOperationException(
96                  "multidimensional arrays are not supported, required for for setter method ["
97                      + methodInfo.setterMethod.getName()
98                      + "]");
99          }
100         return createTypedArray(node, data, node.jjtGetNumChildren(), componentType);
101     }
102 
103     public Object visit(ASTIdentifier node, Object data) {
104         String identifier = node.getValue();
105         if (identifier.endsWith(".class")) {
106             return handleClassIdentifier(identifier, data.getClass().getClassLoader());
107         } else if (isJavaReferenceType(identifier)) {
108             return handleReferenceIdentifier(identifier);
109         } else {
110             throw new RuntimeException("unsupported format for java type or reference [" + identifier + "]");
111         }
112     }
113 
114     public Object visit(ASTBoolean node, Object data) {
115         return Boolean.valueOf(node.getValue());
116     }
117 
118     public Object visit(ASTChar node, Object data) {
119         return new Character(node.getValue().charAt(0));
120     }
121 
122     public Object visit(ASTString node, Object data) {
123         // the node contains the  \" string escapes
124         if (node.getValue().length()>=2) {
125             return node.getValue().substring(1, node.getValue().length()-1);
126         } else {
127             return node.getValue();
128         }
129     }
130 
131     public Object visit(ASTInteger node, Object data) {
132         String value = node.getValue();
133         char lastChar = value.charAt(value.length() - 1);
134         if ((lastChar == 'L') || (lastChar == 'l')) {
135             return new Long(value.substring(0, value.length() - 1));
136         } else if (value.length() > 9) {
137             return new Long(value);
138         } else {
139             return new Integer(value);
140         }
141     }
142 
143     public Object visit(ASTFloat node, Object data) {
144         String value = node.getValue();
145         char lastChar = value.charAt(value.length() - 1);
146         if ((lastChar == 'D') || (lastChar == 'd')) {
147             return new Double(value.substring(0, value.length() - 1));
148         } else if ((lastChar == 'F') || (lastChar == 'f')) {
149             return new Float(value.substring(0, value.length() - 1));
150         } else {
151             return new Double(value);
152         }
153     }
154 
155     public Object visit(ASTHex node, Object data) {
156         throw new UnsupportedOperationException("hex numbers not yet supported");
157     }
158 
159     public Object visit(ASTOct node, Object data) {
160         throw new UnsupportedOperationException("octal numbers not yet supported");
161     }
162 
163     /***
164      * For a typed annotation, there should be
165      * - a setter method setx or setX
166      * - a getter method x or getx or getX
167      *
168      * @param valueName
169      * @return
170      */
171     private MethodInfo getMethodInfo(final String valueName) {
172         StringBuffer javaBeanMethodPostfix = new StringBuffer();
173         javaBeanMethodPostfix.append(valueName.substring(0, 1).toUpperCase());
174         if (valueName.length() > 1) {
175             javaBeanMethodPostfix.append(valueName.substring(1));
176         }
177 
178         MethodInfo methodInfo = new MethodInfo();
179         try {
180             Class clazz = m_annotationProxy.getClass();
181             Method[] methods = clazz.getMethods();
182             // look for getter method
183             for (int i = 0; i < methods.length; i++) {
184                 Method getterMethod = methods[i];
185                 if (getterMethod.getName().equals(valueName) || getterMethod.getName().equalsIgnoreCase("get"+valueName)) {
186                     methodInfo.getterMethod = getterMethod;
187                     methodInfo.valueType = getterMethod.getReturnType();
188                     // look for setter method
189                     try {
190                         methodInfo.setterMethod = clazz.getMethod("set" + javaBeanMethodPostfix, new Class[]{methodInfo.valueType});
191                     } catch (NoSuchMethodException e) {
192                         methodInfo.setterMethod = clazz.getMethod("set" + valueName, new Class[]{methodInfo.valueType});
193                     }
194                     break;
195                 }
196             }
197         } catch (NoSuchMethodException e) {
198             throw new RuntimeException("could not find setter method for value ["
199                 + valueName
200                 + "] due to: "
201                 + e.toString());
202         }
203         if (methodInfo.getterMethod == null) {
204             throw new RuntimeException("setter method with the name [set"
205                 + valueName
206                 + "] can not be found in annotation proxy ["
207                 + m_annotationProxy.getClass().getName()
208                 + "]");
209         }
210         return methodInfo;
211     }
212 
213     private void invokeSetterMethod(final MethodInfo methodInfo, final Object typedValue, final String valueName) {
214         try {
215             methodInfo.setterMethod.invoke(m_annotationProxy, new Object[] {
216                 typedValue
217             });
218         } catch (Exception e) {
219             throw new RuntimeException("could not invoke setter method for named value ["
220                 + valueName
221                 + "] due to: "
222                 + e.toString());
223         }
224     }
225 
226     private boolean isJavaReferenceType(final String valueAsString) {
227         int first = valueAsString.indexOf('.');
228         int last = valueAsString.lastIndexOf('.');
229         int comma = valueAsString.indexOf(',');
230         if ((first > 0) && (last > 0) && (first != last) && (comma < 0)) {
231             return true;
232         } else {
233             return false;
234         }
235     }
236 
237     private Object createTypedArray(
238         final ASTArray node,
239         final Object data,
240         final int nrOfElements,
241         final Class componentType) {
242         if (componentType.equals(String.class)) {
243             String[] array = new String[nrOfElements];
244             for (int i = 0; i < nrOfElements; i++) {
245                 String value = (String) node.jjtGetChild(i).jjtAccept(this, data);
246                 array[i] = value;
247 //                if ((value.charAt(0) == '"') && (value.charAt(value.length() - 1) == '"')) {
248 //                    array[i] = value.substring(1, value.length() - 1);
249 //                } else {
250 //                    throw new RuntimeException("badly formatted string [" + value + "]");
251 //                }
252             }
253             return array;
254         } else if (componentType.equals(long.class)) {
255             long[] array = new long[nrOfElements];
256             for (int i = 0; i < nrOfElements; i++) {
257                 array[i] = ((Long) node.jjtGetChild(i).jjtAccept(this, data)).longValue();
258             }
259             return array;
260         } else if (componentType.equals(int.class)) {
261             int[] array = new int[nrOfElements];
262             for (int i = 0; i < nrOfElements; i++) {
263                 array[i] = ((Integer) node.jjtGetChild(i).jjtAccept(this, data)).intValue();
264             }
265             return array;
266         } else if (componentType.equals(short.class)) {
267             short[] array = new short[nrOfElements];
268             for (int i = 0; i < nrOfElements; i++) {
269                 array[i] = ((Short) node.jjtGetChild(i).jjtAccept(this, data)).shortValue();
270             }
271             return array;
272         } else if (componentType.equals(double.class)) {
273             double[] array = new double[nrOfElements];
274             for (int i = 0; i < nrOfElements; i++) {
275                 array[i] = ((Double) node.jjtGetChild(i).jjtAccept(this, data)).doubleValue();
276             }
277             return array;
278         } else if (componentType.equals(float.class)) {
279             float[] array = new float[nrOfElements];
280             for (int i = 0; i < nrOfElements; i++) {
281                 array[i] = ((Float) node.jjtGetChild(i).jjtAccept(this, data)).floatValue();
282             }
283             return array;
284         } else if (componentType.equals(byte.class)) {
285             byte[] array = new byte[nrOfElements];
286             for (int i = 0; i < nrOfElements; i++) {
287                 array[i] = ((Byte) node.jjtGetChild(i).jjtAccept(this, data)).byteValue();
288             }
289             return array;
290         } else if (componentType.equals(char.class)) {
291             char[] array = new char[nrOfElements];
292             for (int i = 0; i < nrOfElements; i++) {
293                 array[i] = ((Character) node.jjtGetChild(i).jjtAccept(this, data)).charValue();
294             }
295             return array;
296         } else if (componentType.equals(boolean.class)) {
297             boolean[] array = new boolean[nrOfElements];
298             for (int i = 0; i < nrOfElements; i++) {
299                 array[i] = ((Boolean) node.jjtGetChild(i).jjtAccept(this, data)).booleanValue();
300             }
301             return array;
302         } else if (componentType.equals(Class.class)) {
303             Class[] array = new Class[nrOfElements];
304             for (int i = 0; i < nrOfElements; i++) {
305                 array[i] = (Class) node.jjtGetChild(i).jjtAccept(this, data);
306             }
307             return array;
308         } else { // reference type
309             Object[] array = new Object[nrOfElements];
310             for (int i = 0; i < nrOfElements; i++) {
311                 array[i] = node.jjtGetChild(i).jjtAccept(this, data);
312             }
313             return array;
314         }
315     }
316 
317     /***
318      * FIXME handle array types
319      */
320     private Object handleClassIdentifier(String identifier, ClassLoader loader) {
321         int index = identifier.lastIndexOf('.');
322         String className = identifier.substring(0, index);
323         if (className.endsWith("[]")) {
324             throw new UnsupportedOperationException("does currently not support array types [" + identifier + "]");
325         }
326         if (className.equals("long")) {
327             return long.class;
328         } else if (className.equals("int")) {
329             return int.class;
330         } else if (className.equals("short")) {
331             return short.class;
332         } else if (className.equals("double")) {
333             return double.class;
334         } else if (className.equals("float")) {
335             return float.class;
336         } else if (className.equals("byte")) {
337             return byte.class;
338         } else if (className.equals("char")) {
339             return char.class;
340         } else if (className.equals("boolean")) {
341             return boolean.class;
342         } else {
343             try {
344                 return (loader!=null)?loader.loadClass(className):Class.forName(className);
345             } catch (Exception e) {
346                 throw new RuntimeException("could not load class [" + className + "] due to: " + e.toString());
347             }
348         }
349     }
350 
351     private Object handleReferenceIdentifier(String identifier) {
352         int index = identifier.lastIndexOf('.');
353         String className = identifier.substring(0, index);
354         String fieldName = identifier.substring(index + 1, identifier.length());
355         try {
356             Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
357             Field field = clazz.getDeclaredField(fieldName);
358             return field.get(null);
359         } catch (Exception e) {
360             throw new RuntimeException("could not access reference field [" + identifier + "] due to: " + e.toString());
361         }
362     }
363 
364     /***
365      * Holds the setter, getter methods and the value type.
366      */
367     private static class MethodInfo {
368         public Method setterMethod;
369 
370         public Method getterMethod;
371 
372         public Class valueType;
373     }
374 }