001: /**************************************************************************************
002: * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.annotation;
008:
009: import org.codehaus.aspectwerkz.reflect.MethodInfo;
010: import org.codehaus.aspectwerkz.reflect.ClassInfo;
011: import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
012: import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
013: import org.objectweb.asm.attrs.*;
014: import org.objectweb.asm.attrs.Annotation;
015: import org.objectweb.asm.Type;
016:
017: import java.lang.reflect.InvocationHandler;
018: import java.lang.reflect.Method;
019: import java.lang.reflect.Proxy;
020: import java.lang.reflect.Field;
021: import java.lang.reflect.Array;
022: import java.util.List;
023: import java.util.Collection;
024: import java.util.ArrayList;
025: import java.util.Iterator;
026: import java.util.Map;
027: import java.util.HashMap;
028: import java.util.Arrays;
029: import java.io.Serializable;
030:
031: /**
032: * Dynamic proxy handler for ASM Annotations we extract
033: * The handler resolve the LazyClass to a concrete Class so that the proxy creation does not trigger
034: * any class loading.
035: * <p/>
036: *
037: * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
038: */
039: public class Java5AnnotationInvocationHandler implements
040: InvocationHandler {
041:
042: /**
043: * The annotation class name
044: */
045: private final String m_annotationClassName;
046:
047: /**
048: * A list of AnnotationElement containing the annotation instance element values
049: * (including the defaulted value)
050: */
051: private final List m_annotationElements;
052:
053: /**
054: * private ctor - see getAnnotationProxy()
055: *
056: * @param annotationClassName
057: * @param annotationElements
058: */
059: private Java5AnnotationInvocationHandler(
060: String annotationClassName, Collection annotationElements) {
061: m_annotationClassName = annotationClassName;
062: m_annotationElements = new ArrayList(annotationElements.size());
063: for (Iterator iterator = annotationElements.iterator(); iterator
064: .hasNext();) {
065: m_annotationElements.add(iterator.next());
066: }
067: }
068:
069: /**
070: * Dynamic proxy based implementation
071: * toString(), annotationType() and value() have a specific behavior
072: *
073: * @param proxy
074: * @param method
075: * @param args
076: * @return
077: * @throws Throwable
078: */
079: public Object invoke(Object proxy, Method method, Object[] args)
080: throws Throwable {
081: String name = method.getName();
082: if ("toString".equals(name)) {
083: StringBuffer sb = new StringBuffer();
084: sb.append('@').append(m_annotationClassName);
085: sb.append("(");
086: String sep = "";
087: for (Iterator iterator = m_annotationElements.iterator(); iterator
088: .hasNext();) {
089: AnnotationElement annotationElement = (AnnotationElement) iterator
090: .next();
091: sb.append(sep).append(
092: annotationElement.name + "="
093: + annotationElement.toString());
094: sep = ", ";
095: }
096: sb.append(")");
097: return sb.toString();
098: } else if ("annotationType".equals(name)) {
099: // funny, may explain why 1.5 Annotation intf has annotationType + getClass
100: // since a dynamic proxy handler cannot hijack getClass() ..
101: return Class.forName(m_annotationClassName, false, proxy
102: .getClass().getClassLoader());
103: } else if ("value".equals(name)) {
104: if (m_annotationElements.isEmpty()) {
105: return null;
106: } else {
107: //FIXME !!value can be there with other elements !
108: // we could check that we don't have more than one element
109: return ((AnnotationElement) m_annotationElements.get(0))
110: .resolveValueHolderFrom(proxy.getClass()
111: .getClassLoader());
112: }
113: } else {
114: for (Iterator iterator = m_annotationElements.iterator(); iterator
115: .hasNext();) {
116: AnnotationElement annotationElement = (AnnotationElement) iterator
117: .next();
118: if (name.equals(annotationElement.name)) {
119: return annotationElement
120: .resolveValueHolderFrom(proxy.getClass()
121: .getClassLoader());
122: }
123: }
124: // element not found for such a name
125: throw new RuntimeException(
126: "No such element on Annotation @"
127: + m_annotationClassName + " : " + name);
128: }
129: }
130:
131: /**
132: * Build and return a dynamic proxy representing the given ASM Annotation.
133: * The proxy implements the AspectWerkz Annotation interface, as well as the user type Annotation.
134: * Each elements of the annotation is proxied if needed or agressively created unless Class types to not trigger
135: * any nested loading.
136: *
137: * Note: JSR-175 does not support Annotation value of N-dimensional array. At most 1 dimension is supported and
138: * only for a subset of Java types.
139: *
140: * @param annotation
141: * @param loader the classloader of the annotatED component (can be different from the one of the annotation class)
142: * @return
143: */
144: public static org.codehaus.aspectwerkz.annotation.Annotation getAnnotationProxy(
145: org.objectweb.asm.attrs.Annotation annotation,
146: ClassLoader loader) {
147: String annotationClassName = Type.getType(annotation.type)
148: .getClassName();
149:
150: // get the ClassInfo for the annoation class to populate the assigned element values
151: // with lazy value holders from the setted value or the default value if defaulted element
152: // has been used in the annotation
153: ClassInfo annotationClassInfo = AsmClassInfo.getClassInfo(
154: annotationClassName, loader);
155: Map annotationElementValueHoldersByName = new HashMap();
156:
157: // populate with the default values (might be then overriden by setted values)
158: MethodInfo[] annotationMethods = annotationClassInfo
159: .getMethods();
160: for (int i = 0; i < annotationMethods.length; i++) {
161: MethodInfo annotationMethod = annotationMethods[i];
162: for (Iterator iterator = annotationMethod.getAnnotations()
163: .iterator(); iterator.hasNext();) {
164: AnnotationInfo annotationInfo = (AnnotationInfo) iterator
165: .next();
166: // handles AnnotationDefault attribute that we have wrapped. See AnnotationDefault.
167: if (annotationInfo.getName().equals(
168: AnnotationDefault.NAME)) {
169: Object value = ((AnnotationDefault) annotationInfo
170: .getAnnotation()).value();
171: Object valueHolder = getAnnotationValueHolder(
172: value, loader);
173: annotationElementValueHoldersByName.put(
174: annotationMethod.getName(),
175: new AnnotationElement(annotationMethod
176: .getName(), valueHolder));
177: }
178: }
179: }
180:
181: // override and populate with the setted values
182: List settedElementValues = annotation.elementValues;
183: for (int i = 0; i < settedElementValues.size(); i++) {
184: Object[] element = (Object[]) settedElementValues.get(i);
185: String name = (String) element[0];
186: Object valueHolder = getAnnotationValueHolder(element[1],
187: loader);
188: annotationElementValueHoldersByName.put(name,
189: new AnnotationElement(name, valueHolder));
190: }
191:
192: // create a dynamic proxy to embody the annotation instance
193: try {
194: Class typeClass = Class.forName(annotationClassName, false,
195: loader);
196: Object proxy = Proxy
197: .newProxyInstance(
198: loader,
199: new Class[] {
200: org.codehaus.aspectwerkz.annotation.Annotation.class,
201: typeClass },
202: new Java5AnnotationInvocationHandler(
203: annotationClassName,
204: annotationElementValueHoldersByName
205: .values()));
206: return (org.codehaus.aspectwerkz.annotation.Annotation) proxy;
207: } catch (ClassNotFoundException e) {
208: throw new WrappedRuntimeException(e);
209: }
210: }
211:
212: /**
213: * Turn an ASM Annotation value into a concrete Java value holder, unless the value is of type
214: * Class, in which case we wrap it behind a LazyClass() object so that actual loading of the class
215: * will be done lazily
216: *
217: * @param value
218: * @param loader
219: * @return
220: */
221: private static Object getAnnotationValueHolder(Object value,
222: ClassLoader loader) {
223: if (value instanceof Annotation.EnumConstValue) {
224: Annotation.EnumConstValue enumAsmValue = (Annotation.EnumConstValue) value;
225: try {
226: Class enumClass = Class.forName(Type.getType(
227: enumAsmValue.typeName).getClassName(), false,
228: loader);
229: Field enumConstValue = enumClass
230: .getField(enumAsmValue.constName);
231: return enumConstValue.get(null);
232: } catch (Exception e) {
233: throw new WrappedRuntimeException(e);
234: }
235: } else if (value instanceof Type) {
236: // TODO may require additional filtering ?
237: return new AnnotationElement.LazyClass(((Type) value)
238: .getClassName());
239: } else if (value instanceof Annotation) {
240: return getAnnotationProxy(((Annotation) value), loader);
241: } else if (value instanceof Object[]) {
242: Object[] values = (Object[]) value;
243: Object[] holders = new Object[values.length];
244: boolean isLazyClass = false;
245: for (int i = 0; i < values.length; i++) {
246: holders[i] = getAnnotationValueHolder(values[i], loader);
247: if (!isLazyClass
248: && holders[i] instanceof AnnotationElement.LazyClass) {
249: isLazyClass = true;
250: }
251: }
252: if (isLazyClass) {
253: // retype the array
254: AnnotationElement.LazyClass[] typedHolders = (AnnotationElement.LazyClass[]) Array
255: .newInstance(AnnotationElement.LazyClass.class,
256: values.length);
257: for (int i = 0; i < holders.length; i++) {
258: typedHolders[i] = (AnnotationElement.LazyClass) holders[i];
259: }
260: return typedHolders;
261: } else {
262: return holders;
263: }
264: }
265: return value;
266: }
267:
268: }
|