325N/A/*
325N/A * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
325N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
325N/A *
325N/A * This code is free software; you can redistribute it and/or modify it
325N/A * under the terms of the GNU General Public License version 2 only, as
325N/A * published by the Free Software Foundation. Oracle designates this
325N/A * particular file as subject to the "Classpath" exception as provided
325N/A * by Oracle in the LICENSE file that accompanied this code.
325N/A *
325N/A * This code is distributed in the hope that it will be useful, but WITHOUT
325N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
325N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
325N/A * version 2 for more details (a copy is included in the LICENSE file that
325N/A * accompanied this code).
325N/A *
325N/A * You should have received a copy of the GNU General Public License version
325N/A * 2 along with this work; if not, write to the Free Software Foundation,
325N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
325N/A *
325N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
325N/A * or visit www.oracle.com if you need additional information or have any
325N/A * questions.
325N/A */
325N/A
325N/Apackage com.sun.codemodel.internal;
325N/A
325N/Aimport java.lang.reflect.InvocationHandler;
325N/Aimport java.lang.reflect.Method;
325N/Aimport java.lang.reflect.Proxy;
325N/Aimport java.lang.reflect.Type;
325N/Aimport java.lang.reflect.ParameterizedType;
325N/Aimport java.lang.reflect.InvocationTargetException;
325N/Aimport java.lang.annotation.Annotation;
325N/Aimport java.util.Map;
325N/Aimport java.util.HashMap;
325N/A
325N/A/**
325N/A * Dynamically implements the typed annotation writer interfaces.
325N/A *
325N/A * @author Kohsuke Kawaguchi
325N/A */
325N/Aclass TypedAnnotationWriter<A extends Annotation,W extends JAnnotationWriter<A>>
325N/A implements InvocationHandler, JAnnotationWriter<A> {
325N/A /**
325N/A * This is what we are writing to.
325N/A */
325N/A private final JAnnotationUse use;
325N/A
325N/A /**
325N/A * The annotation that we are writing.
325N/A */
325N/A private final Class<A> annotation;
325N/A
325N/A /**
325N/A * The type of the writer.
325N/A */
325N/A private final Class<W> writerType;
325N/A
325N/A /**
325N/A * Keeps track of writers for array members.
325N/A * Lazily created.
325N/A */
325N/A private Map<String,JAnnotationArrayMember> arrays;
325N/A
325N/A public TypedAnnotationWriter(Class<A> annotation, Class<W> writer, JAnnotationUse use) {
325N/A this.annotation = annotation;
325N/A this.writerType = writer;
325N/A this.use = use;
325N/A }
325N/A
325N/A public JAnnotationUse getAnnotationUse() {
325N/A return use;
325N/A }
325N/A
325N/A public Class<A> getAnnotationType() {
325N/A return annotation;
325N/A }
325N/A
325N/A @SuppressWarnings("unchecked")
325N/A public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
325N/A
325N/A if(method.getDeclaringClass()==JAnnotationWriter.class) {
325N/A try {
325N/A return method.invoke(this,args);
325N/A } catch (InvocationTargetException e) {
325N/A throw e.getTargetException();
325N/A }
325N/A }
325N/A
325N/A String name = method.getName();
325N/A Object arg=null;
325N/A if(args!=null && args.length>0)
325N/A arg = args[0];
325N/A
325N/A // check how it's defined on the annotation
325N/A Method m = annotation.getDeclaredMethod(name);
325N/A Class<?> rt = m.getReturnType();
325N/A
325N/A // array value
325N/A if(rt.isArray()) {
325N/A return addArrayValue(proxy,name,rt.getComponentType(),method.getReturnType(),arg);
325N/A }
325N/A
325N/A // sub annotation
325N/A if(Annotation.class.isAssignableFrom(rt)) {
325N/A Class<? extends Annotation> r = (Class<? extends Annotation>)rt;
325N/A return new TypedAnnotationWriter(
325N/A r,method.getReturnType(),use.annotationParam(name,r)).createProxy();
325N/A }
325N/A
325N/A // scalar value
325N/A
325N/A if(arg instanceof JType) {
325N/A JType targ = (JType) arg;
325N/A checkType(Class.class,rt);
325N/A if(m.getDefaultValue()!=null) {
325N/A // check the default
325N/A if(targ.equals(targ.owner().ref((Class)m.getDefaultValue())))
325N/A return proxy; // defaulted
325N/A }
325N/A use.param(name,targ);
325N/A return proxy;
325N/A }
325N/A
325N/A // other Java built-in types
325N/A checkType(arg.getClass(),rt);
325N/A if(m.getDefaultValue()!=null && m.getDefaultValue().equals(arg))
325N/A // defaulted. no need to write out.
325N/A return proxy;
325N/A
325N/A if(arg instanceof String) {
325N/A use.param(name,(String)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Boolean) {
325N/A use.param(name,(Boolean)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Integer) {
325N/A use.param(name,(Integer)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Class) {
325N/A use.param(name,(Class)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Enum) {
325N/A use.param(name,(Enum)arg);
325N/A return proxy;
325N/A }
325N/A
325N/A throw new IllegalArgumentException("Unable to handle this method call "+method.toString());
325N/A }
325N/A
325N/A @SuppressWarnings("unchecked")
325N/A private Object addArrayValue(Object proxy,String name, Class itemType, Class expectedReturnType, Object arg) {
325N/A if(arrays==null)
325N/A arrays = new HashMap<String,JAnnotationArrayMember>();
325N/A JAnnotationArrayMember m = arrays.get(name);
325N/A if(m==null) {
325N/A m = use.paramArray(name);
325N/A arrays.put(name,m);
325N/A }
325N/A
325N/A // sub annotation
325N/A if(Annotation.class.isAssignableFrom(itemType)) {
325N/A Class<? extends Annotation> r = (Class<? extends Annotation>)itemType;
325N/A if(!JAnnotationWriter.class.isAssignableFrom(expectedReturnType))
325N/A throw new IllegalArgumentException("Unexpected return type "+expectedReturnType);
325N/A return new TypedAnnotationWriter(r,expectedReturnType,m.annotate(r)).createProxy();
325N/A }
325N/A
325N/A // primitive
325N/A if(arg instanceof JType) {
325N/A checkType(Class.class,itemType);
325N/A m.param((JType)arg);
325N/A return proxy;
325N/A }
325N/A checkType(arg.getClass(),itemType);
325N/A if(arg instanceof String) {
325N/A m.param((String)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Boolean) {
325N/A m.param((Boolean)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Integer) {
325N/A m.param((Integer)arg);
325N/A return proxy;
325N/A }
325N/A if(arg instanceof Class) {
325N/A m.param((Class)arg);
325N/A return proxy;
325N/A }
325N/A // TODO: enum constant. how should we handle it?
325N/A
325N/A throw new IllegalArgumentException("Unable to handle this method call ");
325N/A }
325N/A
325N/A
325N/A /**
325N/A * Check if the type of the argument matches our expectation.
325N/A * If not, report an error.
325N/A */
325N/A private void checkType(Class<?> actual, Class<?> expected) {
325N/A if(expected==actual || expected.isAssignableFrom(actual))
325N/A return; // no problem
325N/A
325N/A if( expected==JCodeModel.boxToPrimitive.get(actual) )
325N/A return; // no problem
325N/A
325N/A throw new IllegalArgumentException("Expected "+expected+" but found "+actual);
325N/A }
325N/A
325N/A /**
325N/A * Creates a proxy and returns it.
325N/A */
325N/A @SuppressWarnings("unchecked")
325N/A private W createProxy() {
325N/A return (W)Proxy.newProxyInstance(
325N/A writerType.getClassLoader(),new Class[]{writerType},this);
325N/A }
325N/A
325N/A /**
325N/A * Creates a new typed annotation writer.
325N/A */
325N/A @SuppressWarnings("unchecked")
325N/A static <W extends JAnnotationWriter<?>> W create(Class<W> w, JAnnotatable annotatable) {
325N/A Class<? extends Annotation> a = findAnnotationType(w);
325N/A return (W)new TypedAnnotationWriter(a,w,annotatable.annotate(a)).createProxy();
325N/A }
325N/A
325N/A private static Class<? extends Annotation> findAnnotationType(Class<?> clazz) {
325N/A for( Type t : clazz.getGenericInterfaces()) {
325N/A if(t instanceof ParameterizedType) {
325N/A ParameterizedType p = (ParameterizedType) t;
325N/A if(p.getRawType()==JAnnotationWriter.class)
325N/A return (Class<? extends Annotation>)p.getActualTypeArguments()[0];
325N/A }
325N/A if(t instanceof Class<?>) {
325N/A // recursive search
325N/A Class<? extends Annotation> r = findAnnotationType((Class<?>)t);
325N/A if(r!=null) return r;
325N/A }
325N/A }
325N/A return null;
325N/A }
325N/A}