/* * The contents of this file are subject to the terms of the Common Development * and Distribution License (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at http://www.sun.com/cddl/cddl.html * or http://www.netbeans.org/cddl.txt. * * When distributing Covered Code, include this CDDL Header Notice in each file * and include the License file at http://www.netbeans.org/cddl.txt. * If applicable, add the following below the CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * The Original Software is SezPoz. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 2006-2007 Sun * Microsystems, Inc. All Rights Reserved. */ package net.java.sezpoz; import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.java.sezpoz.impl.SerAnnConst; import net.java.sezpoz.impl.SerAnnotatedElement; import net.java.sezpoz.impl.SerEnumConst; import net.java.sezpoz.impl.SerTypeConst; import org.sonatype.guice.bean.reflect.ClassSpace; /** * One index item. * May be associated with a class, method, or field. * Caches result of {@link #element} and {@link #instance} after first call. * Not thread-safe. * @param A the type of annotation being loaded * @param I the type of instance being loaded */ public final class SpaceIndexItem<A extends Annotation,I> { private static final Logger LOGGER = Logger.getLogger(SpaceIndexItem.class.getName()); private final SerAnnotatedElement structure; private final Class<A> annotationType; private final Class<I> instanceType; private final ClassSpace space; private final URL resource; private AnnotatedElement element; private Object instance; SpaceIndexItem(SerAnnotatedElement structure, Class<A> annotationType, Class<I> instanceType, ClassSpace space, URL resource) throws IOException { this.structure = structure; this.annotationType = annotationType; this.instanceType = instanceType; this.space = space; this.resource = resource; LOGGER.log(Level.FINE, "Loaded index item {0}", structure); } /** * Get the annotation itself. * A lightweight proxy will be returned which obeys the * {@link Annotation} contract and should be equal to (but not identical * to) the "real" annotation available from {@link AnnotatedElement#getAnnotation} * on {@link #element} * (if in fact it has runtime retention, which is encouraged but not required). * @return a live or proxy annotation */ public A annotation() { return proxy(annotationType, structure.values); } /** * Determine what kind of element is annotated. * @return one of {@link ElementType#TYPE}, {@link ElementType#METHOD}, or {@link ElementType#FIELD} */ public ElementType kind() { return structure.isMethod ? ElementType.METHOD : structure.memberName != null ? ElementType.FIELD : ElementType.TYPE; } /** * Get the name of the class which is the annotated element or of which the annotated element is a member. * @return the class name (format e.g. "x.y.Z$I") */ public String className() { return structure.className; } /** * Get the name of the annotated member element. * @return a method or field name, or null if the annotated element is a class */ public String memberName() { return structure.memberName; } /** * Get the live annotated element. * @return a {@link Class}, {@link Method}, or {@link Field} * @throws InstantiationException if the class cannot be loaded or there is some other reflective problem */ public AnnotatedElement element() throws InstantiationException { if (element == null) { try { Class<?> impl = space.loadClass(className()); if (structure.isMethod) { element = impl.getMethod(structure.memberName); } else if (structure.memberName != null) { element = impl.getField(structure.memberName); } else { element = impl; } LOGGER.log(Level.FINER, "Loaded annotated element: {0}", element); } catch (Exception x) { throw (InstantiationException) new InstantiationException(labelFor(resource) + " might need to be rebuilt: " + x).initCause(x); } catch (LinkageError x) { throw (InstantiationException) new InstantiationException(x.toString()).initCause(x); } } return element; } private static String labelFor(URL resource) { String u = resource.toString(); Matcher m = Pattern.compile("jar:(file:.+)!/.+").matcher(u); if (m.matches()) { return new File(URI.create(m.group(1))).getAbsolutePath(); } else { return u; } } /** * Get an instance referred to by the element. * This instance is cached by the item object. * The element must always be public. * <ol> * <li>In case of a class, the class will be instantiated by a public no-argument constructor. * <li>In case of a method, it must be static and have no arguments; it will be called. * <li>In case of a field, it must be static and final; its value will be used. * </ol> * @return an object guaranteed to be assignable to the {@link Indexable#type} if specified * (or may be null, in the case of a method or field) * @throws InstantiationException for the same reasons as {@link #element}, * or if creating the object fails */ public I instance() throws InstantiationException { if (instance == null) { AnnotatedElement e = element(); try { if (e instanceof Class<?>) { instance = ((Class<?>) e).newInstance(); } else if (e instanceof Method) { instance = ((Method) e).invoke(null); } else { instance = ((Field) e).get(null); } LOGGER.log(Level.FINER, "Loaded instance: {0}", instance); } catch (InstantiationException x) { throw x; } catch (Exception x) { throw (InstantiationException) new InstantiationException(x.toString()).initCause(x); } catch (LinkageError x) { throw (InstantiationException) new InstantiationException(x.toString()).initCause(x); } } return instanceType.cast(instance); } @Override public int hashCode() { return className().hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof SpaceIndexItem<?,?>)) { return false; } SpaceIndexItem<? extends Annotation,?> o = (SpaceIndexItem<?,?>) obj; return structure.equals(o.structure) && annotationType == o.annotationType && space == o.space; } @Override public String toString() { return "@" + annotationType.getName() + ":" + structure; } private static <T extends Annotation> T proxy(Class<T> type, Map<String,Object> data) { return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {type}, new AnnotationProxy(type, data))); } /** * Manages a proxy for the live annotation. */ private static final class AnnotationProxy implements InvocationHandler { /** type of the annotation */ private final Class<? extends Annotation> type; /** (non-default) annotation method values; value may be wrapped in Ser*Const objects or ArrayList */ private final Map<String,Object> data; public AnnotationProxy(Class<? extends Annotation> type, Map<String,Object> data) { this.type = type; this.data = data; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if (name.equals("annotationType") && method.getParameterTypes().length == 0) { return type; } else if (name.equals("hashCode") && method.getParameterTypes().length == 0) { // See Annotation#hashCode for explanation of algorithm. int x = 0; for (Method m : type.getDeclaredMethods()) { Object val = annCall(m); int valhash = val.hashCode(); Class<?> arrClazz; if (val instanceof Object[]) { arrClazz = Object[].class; } else { arrClazz = val.getClass(); } try { Method arraysHashCode = Arrays.class.getMethod("hashCode", arrClazz); valhash = (Integer) arraysHashCode.invoke(null, val); } catch (NoSuchMethodException nsme) { // fine, not an array object } x += (127 * m.getName().hashCode()) ^ valhash; } return x; } else if (name.equals("equals") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == Object.class) { // All annotation values have to be equal (even if defaulted). if (!(args[0] instanceof Annotation)) { return false; } Annotation o = (Annotation) args[0]; if (type != o.annotationType()) { return false; } for (Method m : type.getDeclaredMethods()) { Object myval = annCall(m); Object other = m.invoke(o); Class<?> arrClazz; if (myval instanceof Object[]) { arrClazz = Object[].class; } else { arrClazz = myval.getClass(); } try { Method arraysEquals = Arrays.class.getMethod("equals", arrClazz, arrClazz); if (!((Boolean) arraysEquals.invoke(null, myval, other))) { return false; } } catch (NoSuchMethodException nsme) { // fine, not an array object if (!myval.equals(other)) { return false; } } } return true; } else if (name.equals("toString") && method.getParameterTypes().length == 0) { // No firm contract, just for debugging. return "@" + type.getName() + data; } else { // Anything else is presumed to be one of the annotation methods. return annCall(method); } } /** * Invoke an annotation method. */ private Object annCall(Method m) throws Exception { assert m.getParameterTypes().length == 0; String name = m.getName(); if (data.containsKey(name)) { return evaluate(data.get(name), m.getReturnType()); } else { Object o = m.getDefaultValue(); assert o != null; return o; } } /** * Unwrap a value to a live type. */ private Object evaluate(Object o, Class<?> expectedType) throws Exception { if (o instanceof SerAnnConst) { SerAnnConst a = (SerAnnConst) o; return proxy(type.getClassLoader().loadClass(a.name).asSubclass(Annotation.class), a.values); } else if (o instanceof SerTypeConst) { return type.getClassLoader().loadClass(((SerTypeConst) o).name); } else if (o instanceof SerEnumConst) { SerEnumConst e = (SerEnumConst) o; return type.getClassLoader().loadClass(e.enumName).getField(e.constName).get(null); } else if (o instanceof ArrayList<?>) { List<?> l = (List<?>) o; Class<?> compType = expectedType.getComponentType(); int size = l.size(); Object arr = Array.newInstance(compType, size); for (int i = 0; i < size; i++) { Array.set(arr, i, evaluate(l.get(i), compType)); } return arr; } else { return o; } } } }