KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > compiler > apt > model > AnnotationMirrorImpl


1 /*******************************************************************************
2  * Copyright (c) 2005, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.internal.compiler.apt.model;
12
13 import java.lang.reflect.Array JavaDoc;
14 import java.lang.reflect.Field JavaDoc;
15 import java.lang.reflect.InvocationHandler JavaDoc;
16 import java.lang.reflect.Method JavaDoc;
17 import java.lang.reflect.Proxy JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.util.Collections JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Map JavaDoc;
23
24 import javax.lang.model.element.AnnotationMirror;
25 import javax.lang.model.element.AnnotationValue;
26 import javax.lang.model.element.ExecutableElement;
27 import javax.lang.model.type.DeclaredType;
28 import javax.lang.model.type.MirroredTypeException;
29 import javax.lang.model.type.MirroredTypesException;
30 import javax.lang.model.type.TypeMirror;
31
32 import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl;
33 import org.eclipse.jdt.internal.compiler.impl.Constant;
34 import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
35 import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
36 import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
37 import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
38 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
39 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
40 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
41 import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
42
43 public class AnnotationMirrorImpl implements AnnotationMirror, InvocationHandler JavaDoc {
44     
45     public final BaseProcessingEnvImpl _env;
46     public final AnnotationBinding _binding;
47     
48     /* package */ AnnotationMirrorImpl(BaseProcessingEnvImpl env, AnnotationBinding binding) {
49         _env = env;
50         _binding = binding;
51     }
52     
53     @Override JavaDoc
54     public boolean equals(Object JavaDoc obj) {
55         if (obj instanceof AnnotationMirrorImpl) {
56             if (this._binding == null) {
57                 return ((AnnotationMirrorImpl) obj)._binding == null;
58             }
59             return equals(this._binding, ((AnnotationMirrorImpl) obj)._binding);
60         }
61         return false;
62     }
63
64     private static boolean equals(AnnotationBinding annotationBinding, AnnotationBinding annotationBinding2) {
65         if (annotationBinding.getAnnotationType() != annotationBinding2.getAnnotationType()) return false;
66         final ElementValuePair[] elementValuePairs = annotationBinding.getElementValuePairs();
67         final ElementValuePair[] elementValuePairs2 = annotationBinding2.getElementValuePairs();
68         final int length = elementValuePairs.length;
69         if (length != elementValuePairs2.length) return false;
70         loop: for (int i = 0; i < length; i++) {
71             ElementValuePair pair = elementValuePairs[i];
72             // loop on the given pair to make sure one will match
73
for (int j = 0; j < length; j++) {
74                 ElementValuePair pair2 = elementValuePairs2[j];
75                 if (pair.binding == pair2.binding) {
76                     if (pair.value == null) {
77                         if (pair2.value == null) {
78                             continue loop;
79                         }
80                         return false;
81                     } else {
82                         if (pair2.value == null
83                                 || !pair2.value.equals(pair.value)) {
84                             return false;
85                         }
86                     }
87                     continue loop;
88                 }
89             }
90             return false;
91         }
92         return true;
93     }
94
95     public DeclaredType getAnnotationType() {
96         if (this._binding == null) {
97             return _env.getFactory().getErrorType();
98         }
99         ReferenceBinding annoType = _binding.getAnnotationType();
100         return _env.getFactory().newDeclaredType(annoType);
101     }
102     
103     /**
104      * @return all the members of this annotation mirror that have explicit values.
105      * Default values are not included.
106      */

107     public Map JavaDoc<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() {
108         if (this._binding == null) {
109             return Collections.emptyMap();
110         }
111         ElementValuePair[] pairs = _binding.getElementValuePairs();
112         Map JavaDoc<ExecutableElement, AnnotationValue> valueMap =
113             new HashMap JavaDoc<ExecutableElement, AnnotationValue>(pairs.length);
114         for (ElementValuePair pair : pairs) {
115             MethodBinding method = pair.getMethodBinding();
116             if (method == null) {
117                 // ideally we should be able to create a fake ExecuableElementImpl
118
continue;
119             }
120             ExecutableElement e = new ExecutableElementImpl(_env, method);
121             AnnotationValue v = new AnnotationValueImpl(_env, pair.getValue(), method.returnType);
122             valueMap.put(e, v);
123         }
124         return Collections.unmodifiableMap(valueMap);
125     }
126     
127     /**
128      * {@see Elements#getElementValuesWithDefaults()}
129      * @return all the members of this annotation mirror that have explicit or default
130      * values.
131      */

132     public Map JavaDoc<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults() {
133         if (this._binding == null) {
134             return Collections.emptyMap();
135         }
136         ElementValuePair[] pairs = _binding.getElementValuePairs();
137         ReferenceBinding annoType = _binding.getAnnotationType();
138         Map JavaDoc<ExecutableElement, AnnotationValue> valueMap =
139             new HashMap JavaDoc<ExecutableElement, AnnotationValue>();
140         for (MethodBinding method : annoType.methods()) {
141             // if binding is in ElementValuePair list, then get value from there
142
boolean foundExplicitValue = false;
143             for (int i = 0; i < pairs.length; ++i) {
144                 MethodBinding explicitBinding = pairs[i].getMethodBinding();
145                 if (method == explicitBinding) {
146                     ExecutableElement e = new ExecutableElementImpl(_env, explicitBinding);
147                     AnnotationValue v = new AnnotationValueImpl(_env, pairs[i].getValue(), explicitBinding.returnType);
148                     valueMap.put(e, v);
149                     foundExplicitValue = true;
150                     break;
151                 }
152             }
153             // else get default value if one exists
154
if (!foundExplicitValue) {
155                 Object JavaDoc defaultVal = method.getDefaultValue();
156                 if (null != defaultVal) {
157                     ExecutableElement e = new ExecutableElementImpl(_env, method);
158                     AnnotationValue v = new AnnotationValueImpl(_env, defaultVal, method.returnType);
159                     valueMap.put(e, v);
160                 }
161             }
162         }
163         return Collections.unmodifiableMap(valueMap);
164     }
165     
166     public int hashCode() {
167         if (this._binding == null) return this._env.hashCode();
168         return this._binding.hashCode();
169     }
170
171     /*
172      * Used by getAnnotation(), which returns a reflective proxy of the annotation class. When processors then
173      * invoke methods such as value() on the annotation proxy, this method is called.
174      * <p>
175      * A challenge here is that the processor was not necessarily compiled against the same annotation
176      * definition that the compiler is looking at right now, not to mention that the annotation itself
177      * may be defective in source. So the actual type of the value may be quite different than the
178      * type expected by the caller, which will result in a ClassCastException, which is ugly for the
179      * processor to try to catch. So we try to catch and correct this type mismatch where possible.
180      * <p>
181      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
182      */

183     @Override JavaDoc
184     public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc
185     {
186         if (this._binding == null) return null;
187         final String JavaDoc methodName = method.getName();
188         if ( args == null || args.length == 0 ) {
189             if( methodName.equals("hashCode") ) { //$NON-NLS-1$
190
return new Integer JavaDoc( hashCode() );
191             }
192             else if( methodName.equals("toString") ) { //$NON-NLS-1$
193
return toString();
194             }
195             else if( methodName.equals("annotationType")) { //$NON-NLS-1$
196
return proxy.getClass().getInterfaces()[0];
197             }
198         }
199         else if ( args.length == 1 && methodName.equals("equals") ) { //$NON-NLS-1$
200
return new Boolean JavaDoc( equals( args[0] ) );
201         }
202         
203         // If it's not one of the above methods, it must be an annotation member, so it cannot take any arguments
204
if ( args != null && args.length != 0 ) {
205             throw new NoSuchMethodException JavaDoc("method " + method.getName() + formatArgs(args) + " does not exist on annotation " + toString()); //$NON-NLS-1$ //$NON-NLS-2$
206
}
207         final MethodBinding methodBinding = getMethodBinding(methodName);
208         if ( methodBinding == null ) {
209             throw new NoSuchMethodException JavaDoc("method " + method.getName() + "() does not exist on annotation" + toString()); //$NON-NLS-1$ //$NON-NLS-2$
210
}
211
212         Object JavaDoc actualValue = null;
213         boolean foundMethod = false;
214         ElementValuePair[] pairs = _binding.getElementValuePairs();
215         for (ElementValuePair pair : pairs) {
216             if (methodName.equals(new String JavaDoc(pair.getName()))) {
217                 actualValue = pair.getValue();
218                 foundMethod = true;
219                 break;
220             }
221         }
222         if (!foundMethod) {
223             // couldn't find explicit value; see if there's a default
224
actualValue = methodBinding.getDefaultValue();
225         }
226         Class JavaDoc<?> expectedType = method.getReturnType();
227         TypeBinding actualType = methodBinding.returnType;
228         return getReflectionValue(actualValue, actualType, expectedType);
229     }
230
231     /*
232      * (non-Javadoc)
233      * Sun implementation shows the values. We avoid that here,
234      * because getting the values is not idempotent.
235      */

236     @Override JavaDoc
237     public String JavaDoc toString() {
238         if (this._binding == null) {
239             return "@any()"; //$NON-NLS-1$
240
}
241         return "@" + _binding.getAnnotationType().debugName(); //$NON-NLS-1$
242
}
243     
244     /**
245      * Used for constructing exception message text.
246      * @return a string like "(a, b, c)".
247      */

248     private String JavaDoc formatArgs(final Object JavaDoc[] args)
249     {
250         // estimate that each class name (plus the separators) is 10 characters long plus 2 for "()".
251
final StringBuilder JavaDoc builder = new StringBuilder JavaDoc(args.length * 8 + 2 );
252         builder.append('(');
253         for( int i=0; i<args.length; i++ )
254         {
255             if( i > 0 )
256                 builder.append(", "); //$NON-NLS-1$
257
builder.append(args[i].getClass().getName());
258         }
259         builder.append(')');
260         return builder.toString();
261     }
262     
263     /**
264      * Find a particular annotation member by name.
265      * @return a compiler method binding, or null if no member was found.
266      */

267     private MethodBinding getMethodBinding(String JavaDoc name) {
268         ReferenceBinding annoType = _binding.getAnnotationType();
269         MethodBinding[] methods = annoType.getMethods(name.toCharArray());
270         for (MethodBinding method : methods) {
271             // annotation members have no parameters
272
if (method.parameters.length == 0) {
273                 return method;
274             }
275         }
276         return null;
277     }
278
279     /**
280      * Convert an annotation member value from JDT into Reflection, and from whatever its actual type
281      * is into whatever type the reflective invoker of a method is expecting.
282      * <p>
283      * Only certain types are permitted as member values. Specifically, a member must be a constant,
284      * and must be either a primitive type, String, Class, an enum constant, an annotation, or an
285      * array of any of those. Multidimensional arrays are not permitted.
286      *
287      * @param annoValue the value as represented by {@link ElementValuePair#getValue()}
288      * @param actualType the return type of the corresponding {@link MethodBinding}
289      * @param expectedType the type that the reflective method invoker is expecting
290      * @return an object of the expected type representing the annotation member value,
291      * or an appropriate dummy value (such as null) if no value is available
292      */

293     private Object JavaDoc getReflectionValue(Object JavaDoc actualValue, TypeBinding actualType, Class JavaDoc<?> expectedType)
294     {
295         if (null == expectedType) {
296             // With no expected type, we can't even guess at a conversion
297
return null;
298         }
299         if (null == actualValue) {
300             // Return a type-appropriate equivalent of null
301
return Factory.getMatchingDummyValue(expectedType);
302         }
303         if (expectedType.isArray()) {
304             if (Class JavaDoc.class.equals(expectedType.getComponentType())) {
305                 // package Class[]-valued return as a MirroredTypesException
306
if (actualType.isArrayType() && actualValue instanceof Object JavaDoc[] &&
307                         ((ArrayBinding)actualType).leafComponentType.erasure().id == TypeIds.T_JavaLangClass) {
308                     Object JavaDoc[] bindings = (Object JavaDoc[])actualValue;
309                     List JavaDoc<TypeMirror> mirrors = new ArrayList JavaDoc<TypeMirror>(bindings.length);
310                     for (int i = 0; i < bindings.length; ++i) {
311                         if (bindings[i] instanceof TypeBinding) {
312                             mirrors.add(_env.getFactory().newTypeMirror((TypeBinding)bindings[i]));
313                         }
314                     }
315                     throw new MirroredTypesException(mirrors);
316                 }
317                 // TODO: actual value is not a TypeBinding[]. Should we return a TypeMirror[] around an ErrorType?
318
return null;
319             }
320             // Handle arrays of types other than Class, e.g., int[], MyEnum[], ...
321
return convertJDTArrayToReflectionArray(actualValue, actualType, expectedType);
322         }
323         else if (Class JavaDoc.class.equals(expectedType)) {
324             // package the Class-valued return as a MirroredTypeException
325
if (actualValue instanceof TypeBinding) {
326                 TypeMirror mirror = _env.getFactory().newTypeMirror((TypeBinding)actualValue);
327                 throw new MirroredTypeException(mirror);
328             }
329             else {
330                 // TODO: actual value is not a TypeBinding. Should we return a TypeMirror around an ErrorType?
331
return null;
332             }
333         }
334         else {
335             // Handle unitary values of type other than Class, e.g., int, MyEnum, ...
336
return convertJDTValueToReflectionType(actualValue, actualType, expectedType);
337         }
338     }
339
340     /**
341      * Convert an array of JDT types as obtained from ElementValuePair.getValue()
342      * (e.g., an Object[] containing IntConstant elements) to the type expected by
343      * a reflective method invocation (e.g., int[]).
344      * <p>
345      * This does not handle arrays of Class, but it does handle primitives, enum constants,
346      * and types such as String.
347      * @param jdtValue the actual value returned by ElementValuePair.getValue() or MethodBinding.getDefault()
348      * @param jdtType the return type of the annotation method binding
349      * @param expectedType the type that the invoker of the method is expecting; must be an array type
350      * @return an Object which is, e.g., an int[]; or null, if an array cannot be created.
351      */

352     private Object JavaDoc convertJDTArrayToReflectionArray(Object JavaDoc jdtValue, TypeBinding jdtType, Class JavaDoc<?> expectedType)
353     {
354         assert null != expectedType && expectedType.isArray();
355         if (!jdtType.isArrayType() || !(jdtValue instanceof Object JavaDoc[])) {
356             // TODO: wrap solo element into one-length array
357
return null;
358         }
359         TypeBinding jdtLeafType = jdtType.leafComponentType();
360         Object JavaDoc[] jdtArray = (Object JavaDoc[])jdtValue;
361         Class JavaDoc<?> expectedLeafType = expectedType.getComponentType();
362         final int length = jdtArray.length;
363         final Object JavaDoc returnArray = Array.newInstance(expectedLeafType, length);
364         for (int i = 0; i < length; ++i) {
365             Object JavaDoc jdtElementValue = jdtArray[i];
366             if (expectedLeafType.isPrimitive() || String JavaDoc.class.equals(expectedLeafType)) {
367                 if (jdtElementValue instanceof Constant) {
368                     if (boolean.class.equals(expectedLeafType)) {
369                         Array.setBoolean(returnArray, i, ((Constant)jdtElementValue).booleanValue());
370                     }
371                     else if (byte.class.equals(expectedLeafType)) {
372                         Array.setByte(returnArray, i, ((Constant)jdtElementValue).byteValue());
373                     }
374                     else if (char.class.equals(expectedLeafType)) {
375                         Array.setChar(returnArray, i, ((Constant)jdtElementValue).charValue());
376                     }
377                     else if (double.class.equals(expectedLeafType)) {
378                         Array.setDouble(returnArray, i, ((Constant)jdtElementValue).doubleValue());
379                     }
380                     else if (float.class.equals(expectedLeafType)) {
381                         Array.setFloat(returnArray, i, ((Constant)jdtElementValue).floatValue());
382                     }
383                     else if (int.class.equals(expectedLeafType)) {
384                         Array.setInt(returnArray, i, ((Constant)jdtElementValue).intValue());
385                     }
386                     else if (long.class.equals(expectedLeafType)) {
387                         Array.setLong(returnArray, i, ((Constant)jdtElementValue).longValue());
388                     }
389                     else if (short.class.equals(expectedLeafType)) {
390                         Array.setShort(returnArray, i, ((Constant)jdtElementValue).shortValue());
391                     }
392                     else if (String JavaDoc.class.equals(expectedLeafType)) {
393                         Array.set(returnArray, i, ((Constant)jdtElementValue).stringValue());
394                     }
395                 }
396                 else {
397                     // Primitive or string is expected, but our actual value cannot be coerced into one.
398
// TODO: if the actual value is an array of primitives, should we unpack the first one?
399
Factory.setArrayMatchingDummyValue(returnArray, i, expectedLeafType);
400                 }
401             }
402             else if (expectedLeafType.isEnum()) {
403                 Object JavaDoc returnVal = null;
404                 if (jdtLeafType != null && jdtLeafType.isEnum() && jdtElementValue instanceof FieldBinding) {
405                     FieldBinding binding = (FieldBinding)jdtElementValue;
406                     try {
407                         Field JavaDoc returnedField = null;
408                         returnedField = expectedLeafType.getField( new String JavaDoc(binding.name) );
409                         if (null != returnedField) {
410                             returnVal = returnedField.get(null);
411                         }
412                     }
413                     catch (NoSuchFieldException JavaDoc nsfe) {
414                         // return null
415
}
416                     catch (IllegalAccessException JavaDoc iae) {
417                         // return null
418
}
419                 }
420                 Array.set(returnArray, i, returnVal);
421             }
422             else if (expectedLeafType.isAnnotation()) {
423                 // member value is expected to be an annotation type. Wrap it in an Annotation proxy.
424
Object JavaDoc returnVal = null;
425                 if (jdtLeafType.isAnnotationType() && jdtElementValue instanceof AnnotationBinding) {
426                     AnnotationMirrorImpl annoMirror =
427                         (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror((AnnotationBinding)jdtElementValue);
428                     returnVal = Proxy.newProxyInstance(expectedLeafType.getClassLoader(),
429                             new Class JavaDoc[]{ expectedLeafType }, annoMirror );
430                 }
431                 Array.set(returnArray, i, returnVal);
432             }
433             else {
434                 Array.set(returnArray, i, null);
435             }
436         }
437         return returnArray;
438     }
439
440     /**
441      * Convert a JDT annotation value as obtained from ElementValuePair.getValue()
442      * (e.g., IntConstant, FieldBinding, etc.) to the type expected by a reflective
443      * method invocation (e.g., int, an enum constant, etc.).
444      * @return a value of type {@code expectedType}, or a dummy value of that type if
445      * the actual value cannot be converted.
446      */

447     private Object JavaDoc convertJDTValueToReflectionType(Object JavaDoc jdtValue, TypeBinding actualType, Class JavaDoc<?> expectedType) {
448         if (expectedType.isPrimitive() || String JavaDoc.class.equals(expectedType)) {
449             if (jdtValue instanceof Constant) {
450                 if (boolean.class.equals(expectedType)) {
451                     return ((Constant)jdtValue).booleanValue();
452                 }
453                 else if (byte.class.equals(expectedType)) {
454                     return ((Constant)jdtValue).byteValue();
455                 }
456                 else if (char.class.equals(expectedType)) {
457                     return ((Constant)jdtValue).charValue();
458                 }
459                 else if (double.class.equals(expectedType)) {
460                     return ((Constant)jdtValue).doubleValue();
461                 }
462                 else if (float.class.equals(expectedType)) {
463                     return ((Constant)jdtValue).floatValue();
464                 }
465                 else if (int.class.equals(expectedType)) {
466                     return ((Constant)jdtValue).intValue();
467                 }
468                 else if (long.class.equals(expectedType)) {
469                     return ((Constant)jdtValue).longValue();
470                 }
471                 else if (short.class.equals(expectedType)) {
472                     return ((Constant)jdtValue).shortValue();
473                 }
474                 else if (String JavaDoc.class.equals(expectedType)) {
475                     return ((Constant)jdtValue).stringValue();
476                 }
477             }
478             // Primitive or string is expected, but our actual value cannot be coerced into one.
479
// TODO: if the actual value is an array of primitives, should we unpack the first one?
480
return Factory.getMatchingDummyValue(expectedType);
481         }
482         else if (expectedType.isEnum()) {
483             Object JavaDoc returnVal = null;
484             if (actualType != null && actualType.isEnum() && jdtValue instanceof FieldBinding) {
485                 
486                 FieldBinding binding = (FieldBinding)jdtValue;
487                 try {
488                     Field JavaDoc returnedField = null;
489                     returnedField = expectedType.getField( new String JavaDoc(binding.name) );
490                     if (null != returnedField) {
491                         returnVal = returnedField.get(null);
492                     }
493                 }
494                 catch (NoSuchFieldException JavaDoc nsfe) {
495                     // return null
496
}
497                 catch (IllegalAccessException JavaDoc iae) {
498                     // return null
499
}
500             }
501             return null == returnVal ? Factory.getMatchingDummyValue(expectedType) : returnVal;
502         }
503         else if (expectedType.isAnnotation()) {
504             // member value is expected to be an annotation type. Wrap it in an Annotation proxy.
505
if (actualType.isAnnotationType() && jdtValue instanceof AnnotationBinding) {
506                 AnnotationMirrorImpl annoMirror =
507                     (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror((AnnotationBinding)jdtValue);
508                 return Proxy.newProxyInstance(expectedType.getClassLoader(),
509                         new Class JavaDoc[]{ expectedType }, annoMirror );
510             }
511             else {
512                 // No way to cast a non-annotation value to an annotation type; return null to caller
513
return null;
514             }
515         }
516         else {
517             return Factory.getMatchingDummyValue(expectedType);
518         }
519     }
520
521 }
522
Popular Tags