/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.mule.devkit.model.code;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* Dynamically implements the typed annotation writer interfaces.
*
* @author Kohsuke Kawaguchi
*/
class TypedAnnotationWriter<A extends Annotation, W extends AnnotationWriter<A>>
implements InvocationHandler, AnnotationWriter<A> {
/**
* This is what we are writing to.
*/
private final AnnotationUse use;
/**
* The annotation that we are writing.
*/
private final Class<A> annotation;
/**
* The type of the writer.
*/
private final Class<W> writerType;
/**
* Keeps track of writers for array members.
* Lazily created.
*/
private Map<String, AnnotationArrayMember> arrays;
public TypedAnnotationWriter(Class<A> annotation, Class<W> writer, AnnotationUse use) {
this.annotation = annotation;
this.writerType = writer;
this.use = use;
}
public AnnotationUse getAnnotationUse() {
return use;
}
public Class<A> getAnnotationType() {
return annotation;
}
@SuppressWarnings("unchecked")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == AnnotationWriter.class) {
try {
return method.invoke(this, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
String name = method.getName();
Object arg = null;
if (args != null && args.length > 0) {
arg = args[0];
}
// check how it's defined on the annotation
Method m = annotation.getDeclaredMethod(name);
Class<?> rt = m.getReturnType();
// array value
if (rt.isArray()) {
return addArrayValue(proxy, name, rt.getComponentType(), method.getReturnType(), arg);
}
// sub annotation
if (Annotation.class.isAssignableFrom(rt)) {
Class<? extends Annotation> r = (Class<? extends Annotation>) rt;
return new TypedAnnotationWriter(
r, method.getReturnType(), use.annotationParam(name, r)).createProxy();
}
// scalar value
if (arg instanceof Type) {
Type targ = (Type) arg;
checkType(Class.class, rt);
if (m.getDefaultValue() != null) {
// check the default
if (targ.equals(targ.owner().ref((Class) m.getDefaultValue()))) {
return proxy; // defaulted
}
}
use.param(name, targ);
return proxy;
}
// other Java built-in types
checkType(arg.getClass(), rt);
if (m.getDefaultValue() != null && m.getDefaultValue().equals(arg))
// defaulted. no need to write out.
{
return proxy;
}
if (arg instanceof String) {
use.param(name, (String) arg);
return proxy;
}
if (arg instanceof Boolean) {
use.param(name, (Boolean) arg);
return proxy;
}
if (arg instanceof Integer) {
use.param(name, (Integer) arg);
return proxy;
}
if (arg instanceof Class) {
use.param(name, (Class) arg);
return proxy;
}
if (arg instanceof Enum) {
use.param(name, (Enum) arg);
return proxy;
}
throw new IllegalArgumentException("Unable to handle this method call " + method.toString());
}
@SuppressWarnings("unchecked")
private Object addArrayValue(Object proxy, String name, Class itemType, Class expectedReturnType, Object arg) {
if (arrays == null) {
arrays = new HashMap<String, AnnotationArrayMember>();
}
AnnotationArrayMember m = arrays.get(name);
if (m == null) {
m = use.paramArray(name);
arrays.put(name, m);
}
// sub annotation
if (Annotation.class.isAssignableFrom(itemType)) {
Class<? extends Annotation> r = (Class<? extends Annotation>) itemType;
if (!AnnotationWriter.class.isAssignableFrom(expectedReturnType)) {
throw new IllegalArgumentException("Unexpected return type " + expectedReturnType);
}
return new TypedAnnotationWriter(r, expectedReturnType, m.annotate(r)).createProxy();
}
// primitive
if (arg instanceof Type) {
checkType(Class.class, itemType);
m.param((Type) arg);
return proxy;
}
checkType(arg.getClass(), itemType);
if (arg instanceof String) {
m.param((String) arg);
return proxy;
}
if (arg instanceof Boolean) {
m.param((Boolean) arg);
return proxy;
}
if (arg instanceof Integer) {
m.param((Integer) arg);
return proxy;
}
if (arg instanceof Class) {
m.param((Class) arg);
return proxy;
}
// TODO: enum constant. how should we handle it?
throw new IllegalArgumentException("Unable to handle this method call ");
}
/**
* Check if the type of the argument matches our expectation.
* If not, report an error.
*/
private void checkType(Class<?> actual, Class<?> expected) {
if (expected == actual || expected.isAssignableFrom(actual)) {
return; // no problem
}
if (expected == CodeModel.boxToPrimitive.get(actual)) {
return; // no problem
}
throw new IllegalArgumentException("Expected " + expected + " but found " + actual);
}
/**
* Creates a proxy and returns it.
*/
@SuppressWarnings("unchecked")
private W createProxy() {
return (W) Proxy.newProxyInstance(
writerType.getClassLoader(), new Class[]{writerType}, this);
}
/**
* Creates a new typed annotation writer.
*/
@SuppressWarnings("unchecked")
static <W extends AnnotationWriter<?>> W create(Class<W> w, Annotable annotatable) {
Class<? extends Annotation> a = findAnnotationType(w);
return (W) new TypedAnnotationWriter(a, w, annotatable.annotate(a)).createProxy();
}
private static Class<? extends Annotation> findAnnotationType(Class<?> clazz) {
for (java.lang.reflect.Type t : clazz.getGenericInterfaces()) {
if (t instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) t;
if (p.getRawType() == AnnotationWriter.class) {
return (Class<? extends Annotation>) p.getActualTypeArguments()[0];
}
}
if (t instanceof Class<?>) {
// recursive search
Class<? extends Annotation> r = findAnnotationType((Class<?>) t);
if (r != null) {
return r;
}
}
}
return null;
}
}