2
* The contents of this file are subject to the terms of either the GNU
3
* General Public License Version 2 only ("GPL") or the Common
4
* Development and Distribution License ("CDDL") (collectively, the
5
* "License"). You may not use this file except in compliance with the
6
* License. You can obtain a copy of the License at
7
* http://www.netbeans.org/cddl-gplv2.html. See the License for the
8
* specific language governing permissions and limitations under the
9
* License. When distributing the software, include this License Header
10
* Notice in each file. This particular file is subject to the "Classpath"
11
* exception as provided in the GPL Version 2 section of the License file
12
* that accompanied this code. If applicable, add the following below the
13
* License Header, with the fields enclosed by brackets [] replaced by
14
* your own identifying information:
15
* "Portions Copyrighted [year] [name of copyright owner]"
17
* The Original Software is SezPoz. The Initial Developer of the Original
18
* Software is Sun Microsystems, Inc. Portions Copyright 2006-2010 Sun
19
* Microsystems, Inc. All Rights Reserved.
21
* If you wish your version of this file to be governed by only the CDDL
22
* or only the GPL Version 2, indicate your decision by adding
23
* "[Contributor] elects to include this software in this distribution
24
* under the [CDDL or GPL Version 2] license." If you do not indicate a
25
* single choice of license, a recipient has the option to distribute
26
* your version of this file under either the CDDL, the GPL Version 2 or
27
* to extend the choice of license to its licensees as provided above.
28
* However, if you add GPL Version 2 code and therefore, elected the GPL
29
* Version 2 license, then the option applies only if the new code is
30
* made subject to such option by the copyright holder.
33
package net.java.sezpoz;
36
import java.io.IOException;
37
import java.lang.annotation.Annotation;
38
import java.lang.annotation.ElementType;
39
import java.lang.reflect.AnnotatedElement;
40
import java.lang.reflect.Array;
41
import java.lang.reflect.Field;
42
import java.lang.reflect.InvocationHandler;
43
import java.lang.reflect.Method;
44
import java.lang.reflect.Proxy;
47
import java.util.ArrayList;
48
import java.util.Arrays;
49
import java.util.List;
51
import java.util.logging.Level;
52
import java.util.logging.Logger;
53
import java.util.regex.Matcher;
54
import java.util.regex.Pattern;
55
import net.java.sezpoz.impl.SerAnnConst;
56
import net.java.sezpoz.impl.SerAnnotatedElement;
57
import net.java.sezpoz.impl.SerEnumConst;
58
import net.java.sezpoz.impl.SerTypeConst;
62
* May be associated with a class, method, or field.
63
* Caches result of {@link #element} and {@link #instance} after first call.
65
* @param A the type of annotation being loaded
66
* @param I the type of instance being loaded
68
public final class IndexItem<A extends Annotation,I> {
70
private static final Logger LOGGER = Logger.getLogger(IndexItem.class.getName());
72
private final SerAnnotatedElement structure;
73
private final Class<A> annotationType;
74
private final Class<I> instanceType;
75
private final ClassLoader loader;
76
private final URL resource;
77
private AnnotatedElement element;
78
private Object instance;
80
IndexItem(SerAnnotatedElement structure, Class<A> annotationType, Class<I> instanceType, ClassLoader loader, URL resource) throws IOException {
81
this.structure = structure;
82
this.annotationType = annotationType;
83
this.instanceType = instanceType;
85
this.resource = resource;
86
LOGGER.log(Level.FINE, "Loaded index item {0}", structure);
90
* Get the annotation itself.
91
* A lightweight proxy will be returned which obeys the
92
* {@link Annotation} contract and should be equal to (but not identical
93
* to) the "real" annotation available from {@link AnnotatedElement#getAnnotation}
95
* (if in fact it has runtime retention, which is encouraged but not required).
96
* @return a live or proxy annotation
98
public A annotation() {
99
return proxy(annotationType, structure.values);
103
* Determine what kind of element is annotated.
104
* @return one of {@link ElementType#TYPE}, {@link ElementType#METHOD}, or {@link ElementType#FIELD}
106
public ElementType kind() {
107
return structure.isMethod ? ElementType.METHOD : structure.memberName != null ? ElementType.FIELD : ElementType.TYPE;
111
* Get the name of the class which is the annotated element or of which the annotated element is a member.
112
* @return the class name (format e.g. "x.y.Z$I")
114
public String className() {
115
return structure.className;
119
* Get the name of the annotated member element.
120
* @return a method or field name, or null if the annotated element is a class
122
public String memberName() {
123
return structure.memberName;
127
* Get the live annotated element.
128
* @return a {@link Class}, {@link Method}, or {@link Field}
129
* @throws InstantiationException if the class cannot be loaded or there is some other reflective problem
131
public AnnotatedElement element() throws InstantiationException {
132
if (element == null) {
134
Class<?> impl = loader.loadClass(className());
135
if (structure.isMethod) {
136
element = impl.getMethod(structure.memberName);
137
} else if (structure.memberName != null) {
138
element = impl.getField(structure.memberName);
142
LOGGER.log(Level.FINER, "Loaded annotated element: {0}", element);
143
} catch (Exception x) {
144
throw (InstantiationException) new InstantiationException(labelFor(resource) + " might need to be rebuilt: " + x).initCause(x);
145
} catch (LinkageError x) {
146
throw (InstantiationException) new InstantiationException(x.toString()).initCause(x);
152
private static String labelFor(URL resource) {
153
String u = resource.toString();
154
Matcher m = Pattern.compile("jar:(file:.+)!/.+").matcher(u);
156
return new File(URI.create(m.group(1))).getAbsolutePath();
163
* Get an instance referred to by the element.
164
* This instance is cached by the item object.
165
* The element must always be public.
167
* <li>In case of a class, the class will be instantiated by a public no-argument constructor.
168
* <li>In case of a method, it must be static and have no arguments; it will be called.
169
* <li>In case of a field, it must be static and final; its value will be used.
171
* @return an object guaranteed to be assignable to the {@link Indexable#type} if specified
172
* (or may be null, in the case of a method or field)
173
* @throws InstantiationException for the same reasons as {@link #element},
174
* or if creating the object fails
176
public I instance() throws InstantiationException {
177
if (instance == null) {
178
AnnotatedElement e = element();
180
if (e instanceof Class<?>) {
181
instance = ((Class<?>) e).newInstance();
182
} else if (e instanceof Method) {
183
instance = ((Method) e).invoke(null);
185
instance = ((Field) e).get(null);
187
LOGGER.log(Level.FINER, "Loaded instance: {0}", instance);
188
} catch (InstantiationException x) {
190
} catch (Exception x) {
191
throw (InstantiationException) new InstantiationException(x.toString()).initCause(x);
192
} catch (LinkageError x) {
193
throw (InstantiationException) new InstantiationException(x.toString()).initCause(x);
196
return instanceType.cast(instance);
200
public int hashCode() {
201
return className().hashCode();
205
public boolean equals(Object obj) {
206
if (!(obj instanceof IndexItem<?,?>)) {
209
IndexItem<? extends Annotation,?> o = (IndexItem<?,?>) obj;
210
return structure.equals(o.structure) && annotationType == o.annotationType && loader == o.loader;
214
public String toString() {
215
return "@" + annotationType.getName() + ":" + structure;
218
private static <T extends Annotation> T proxy(Class<T> type, Map<String,Object> data) {
219
return type.cast(Proxy.newProxyInstance(type.getClassLoader(),
220
new Class<?>[] {type},
221
new AnnotationProxy(type, data)));
225
* Manages a proxy for the live annotation.
227
private static final class AnnotationProxy implements InvocationHandler {
229
/** type of the annotation */
230
private final Class<? extends Annotation> type;
231
/** (non-default) annotation method values; value may be wrapped in Ser*Const objects or ArrayList */
232
private final Map<String,Object> data;
234
public AnnotationProxy(Class<? extends Annotation> type, Map<String,Object> data) {
239
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
240
String name = method.getName();
241
if (name.equals("annotationType") && method.getParameterTypes().length == 0) {
243
} else if (name.equals("hashCode") && method.getParameterTypes().length == 0) {
244
// See Annotation#hashCode for explanation of algorithm.
246
for (Method m : type.getDeclaredMethods()) {
247
Object val = annCall(m);
248
int valhash = val.hashCode();
250
if (val instanceof Object[]) {
251
arrClazz = Object[].class;
253
arrClazz = val.getClass();
256
Method arraysHashCode = Arrays.class.getMethod("hashCode", arrClazz);
257
valhash = (Integer) arraysHashCode.invoke(null, val);
258
} catch (NoSuchMethodException nsme) {
259
// fine, not an array object
261
x += (127 * m.getName().hashCode()) ^ valhash;
264
} else if (name.equals("equals") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == Object.class) {
265
// All annotation values have to be equal (even if defaulted).
266
if (!(args[0] instanceof Annotation)) {
269
Annotation o = (Annotation) args[0];
270
if (type != o.annotationType()) {
273
for (Method m : type.getDeclaredMethods()) {
274
Object myval = annCall(m);
275
Object other = m.invoke(o);
277
if (myval instanceof Object[]) {
278
arrClazz = Object[].class;
280
arrClazz = myval.getClass();
283
Method arraysEquals = Arrays.class.getMethod("equals", arrClazz, arrClazz);
284
if (!((Boolean) arraysEquals.invoke(null, myval, other))) {
287
} catch (NoSuchMethodException nsme) {
288
// fine, not an array object
289
if (!myval.equals(other)) {
295
} else if (name.equals("toString") && method.getParameterTypes().length == 0) {
296
// No firm contract, just for debugging.
297
return "@" + type.getName() + data;
299
// Anything else is presumed to be one of the annotation methods.
300
return annCall(method);
305
* Invoke an annotation method.
307
private Object annCall(Method m) throws Exception {
308
assert m.getParameterTypes().length == 0;
309
String name = m.getName();
310
if (data.containsKey(name)) {
311
return evaluate(data.get(name), m.getReturnType());
313
Object o = m.getDefaultValue();
320
* Unwrap a value to a live type.
322
private Object evaluate(Object o, Class<?> expectedType) throws Exception {
323
if (o instanceof SerAnnConst) {
324
SerAnnConst a = (SerAnnConst) o;
325
return proxy(type.getClassLoader().loadClass(a.name).asSubclass(Annotation.class), a.values);
326
} else if (o instanceof SerTypeConst) {
327
return type.getClassLoader().loadClass(((SerTypeConst) o).name);
328
} else if (o instanceof SerEnumConst) {
329
SerEnumConst e = (SerEnumConst) o;
330
return type.getClassLoader().loadClass(e.enumName).getField(e.constName).get(null);
331
} else if (o instanceof ArrayList<?>) {
332
List<?> l = (List<?>) o;
333
Class<?> compType = expectedType.getComponentType();
335
Object arr = Array.newInstance(compType, size);
336
for (int i = 0; i < size; i++) {
337
Array.set(arr, i, evaluate(l.get(i), compType));