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
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
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
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
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
248
249
250
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 {
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 }