~hudson-ubuntu/+junk/sezpoz

« back to all changes in this revision

Viewing changes to sezpoz/src/main/java/net/java/sezpoz/IndexItem.java

  • Committer: James Page
  • Date: 2010-11-24 13:57:41 UTC
  • Revision ID: james.page@canonical.com-20101124135741-8ntgqh7ltywqy3x2
InitialĀ releaseĀ 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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]"
 
16
 *
 
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.
 
20
 *
 
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.
 
31
 */
 
32
 
 
33
package net.java.sezpoz;
 
34
 
 
35
import java.io.File;
 
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;
 
45
import java.net.URI;
 
46
import java.net.URL;
 
47
import java.util.ArrayList;
 
48
import java.util.Arrays;
 
49
import java.util.List;
 
50
import java.util.Map;
 
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;
 
59
 
 
60
/**
 
61
 * One index item.
 
62
 * May be associated with a class, method, or field.
 
63
 * Caches result of {@link #element} and {@link #instance} after first call.
 
64
 * Not thread-safe.
 
65
 * @param A the type of annotation being loaded
 
66
 * @param I the type of instance being loaded
 
67
 */
 
68
public final class IndexItem<A extends Annotation,I> {
 
69
 
 
70
    private static final Logger LOGGER = Logger.getLogger(IndexItem.class.getName());
 
71
 
 
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;
 
79
 
 
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;
 
84
        this.loader = loader;
 
85
        this.resource = resource;
 
86
        LOGGER.log(Level.FINE, "Loaded index item {0}", structure);
 
87
    }
 
88
 
 
89
    /**
 
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}
 
94
     * on {@link #element}
 
95
     * (if in fact it has runtime retention, which is encouraged but not required).
 
96
     * @return a live or proxy annotation
 
97
     */
 
98
    public A annotation() {
 
99
        return proxy(annotationType, structure.values);
 
100
    }
 
101
 
 
102
    /**
 
103
     * Determine what kind of element is annotated.
 
104
     * @return one of {@link ElementType#TYPE}, {@link ElementType#METHOD}, or {@link ElementType#FIELD}
 
105
     */
 
106
    public ElementType kind() {
 
107
        return structure.isMethod ? ElementType.METHOD : structure.memberName != null ? ElementType.FIELD : ElementType.TYPE;
 
108
    }
 
109
 
 
110
    /**
 
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")
 
113
     */
 
114
    public String className() {
 
115
        return structure.className;
 
116
    }
 
117
 
 
118
    /**
 
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
 
121
     */
 
122
    public String memberName() {
 
123
        return structure.memberName;
 
124
    }
 
125
 
 
126
    /**
 
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
 
130
     */
 
131
    public AnnotatedElement element() throws InstantiationException {
 
132
        if (element == null) {
 
133
            try {
 
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);
 
139
                } else {
 
140
                    element = impl;
 
141
                }
 
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);
 
147
            }
 
148
        }
 
149
        return element;
 
150
    }
 
151
 
 
152
    private static String labelFor(URL resource) {
 
153
        String u = resource.toString();
 
154
        Matcher m = Pattern.compile("jar:(file:.+)!/.+").matcher(u);
 
155
        if (m.matches()) {
 
156
            return new File(URI.create(m.group(1))).getAbsolutePath();
 
157
        } else {
 
158
            return u;
 
159
        }
 
160
    }
 
161
 
 
162
    /**
 
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.
 
166
     * <ol>
 
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.
 
170
     * </ol>
 
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
 
175
     */
 
176
    public I instance() throws InstantiationException {
 
177
        if (instance == null) {
 
178
            AnnotatedElement e = element();
 
179
            try {
 
180
                if (e instanceof Class<?>) {
 
181
                    instance = ((Class<?>) e).newInstance();
 
182
                } else if (e instanceof Method) {
 
183
                    instance = ((Method) e).invoke(null);
 
184
                } else {
 
185
                    instance = ((Field) e).get(null);
 
186
                }
 
187
                LOGGER.log(Level.FINER, "Loaded instance: {0}", instance);
 
188
            } catch (InstantiationException x) {
 
189
                throw 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);
 
194
            }
 
195
        }
 
196
        return instanceType.cast(instance);
 
197
    }
 
198
 
 
199
    @Override
 
200
    public int hashCode() {
 
201
        return className().hashCode();
 
202
    }
 
203
 
 
204
    @Override
 
205
    public boolean equals(Object obj) {
 
206
        if (!(obj instanceof IndexItem<?,?>)) {
 
207
            return false;
 
208
        }
 
209
        IndexItem<? extends Annotation,?> o = (IndexItem<?,?>) obj;
 
210
        return structure.equals(o.structure) && annotationType == o.annotationType && loader == o.loader;
 
211
    }
 
212
 
 
213
    @Override
 
214
    public String toString() {
 
215
        return "@" + annotationType.getName() + ":" + structure;
 
216
    }
 
217
 
 
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)));
 
222
    }
 
223
 
 
224
    /**
 
225
     * Manages a proxy for the live annotation.
 
226
     */
 
227
    private static final class AnnotationProxy implements InvocationHandler {
 
228
 
 
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;
 
233
 
 
234
        public AnnotationProxy(Class<? extends Annotation> type, Map<String,Object> data) {
 
235
            this.type = type;
 
236
            this.data = data;
 
237
        }
 
238
 
 
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) {
 
242
                return type;
 
243
            } else if (name.equals("hashCode") && method.getParameterTypes().length == 0) {
 
244
                // See Annotation#hashCode for explanation of algorithm.
 
245
                int x = 0;
 
246
                for (Method m : type.getDeclaredMethods()) {
 
247
                    Object val = annCall(m);
 
248
                    int valhash = val.hashCode();
 
249
                    Class<?> arrClazz;
 
250
                    if (val instanceof Object[]) {
 
251
                        arrClazz = Object[].class;
 
252
                    } else {
 
253
                        arrClazz = val.getClass();
 
254
                    }
 
255
                    try {
 
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
 
260
                    }
 
261
                    x += (127 * m.getName().hashCode()) ^ valhash;
 
262
                }
 
263
                return x;
 
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)) {
 
267
                    return false;
 
268
                }
 
269
                Annotation o = (Annotation) args[0];
 
270
                if (type != o.annotationType()) {
 
271
                    return false;
 
272
                }
 
273
                for (Method m : type.getDeclaredMethods()) {
 
274
                    Object myval = annCall(m);
 
275
                    Object other = m.invoke(o);
 
276
                    Class<?> arrClazz;
 
277
                    if (myval instanceof Object[]) {
 
278
                        arrClazz = Object[].class;
 
279
                    } else {
 
280
                        arrClazz = myval.getClass();
 
281
                    }
 
282
                    try {
 
283
                        Method arraysEquals = Arrays.class.getMethod("equals", arrClazz, arrClazz);
 
284
                        if (!((Boolean) arraysEquals.invoke(null, myval, other))) {
 
285
                            return false;
 
286
                        }
 
287
                    } catch (NoSuchMethodException nsme) {
 
288
                        // fine, not an array object
 
289
                        if (!myval.equals(other)) {
 
290
                            return false;
 
291
                        }
 
292
                    }
 
293
                }
 
294
                return true;
 
295
            } else if (name.equals("toString") && method.getParameterTypes().length == 0) {
 
296
                // No firm contract, just for debugging.
 
297
                return "@" + type.getName() + data;
 
298
            } else {
 
299
                // Anything else is presumed to be one of the annotation methods.
 
300
                return annCall(method);
 
301
            }
 
302
        }
 
303
 
 
304
        /**
 
305
         * Invoke an annotation method.
 
306
         */
 
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());
 
312
            } else {
 
313
                Object o = m.getDefaultValue();
 
314
                assert o != null;
 
315
                return o;
 
316
            }
 
317
        }
 
318
 
 
319
        /**
 
320
         * Unwrap a value to a live type.
 
321
         */
 
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();
 
334
                int size = l.size();
 
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));
 
338
                }
 
339
                return arr;
 
340
            } else {
 
341
                return o;
 
342
            }
 
343
        }
 
344
 
 
345
    }
 
346
 
 
347
}