1 /*
   2  * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.codemodel.internal;
  27 
  28 import java.lang.reflect.InvocationHandler;
  29 import java.lang.reflect.Method;
  30 import java.lang.reflect.Proxy;
  31 import java.lang.reflect.Type;
  32 import java.lang.reflect.ParameterizedType;
  33 import java.lang.reflect.InvocationTargetException;
  34 import java.lang.annotation.Annotation;
  35 import java.util.Map;
  36 import java.util.HashMap;
  37 
  38 /**
  39  * Dynamically implements the typed annotation writer interfaces.
  40  *
  41  * @author Kohsuke Kawaguchi
  42  */
  43 class TypedAnnotationWriter<A extends Annotation,W extends JAnnotationWriter<A>>
  44     implements InvocationHandler, JAnnotationWriter<A> {
  45     /**
  46      * This is what we are writing to.
  47      */
  48     private final JAnnotationUse use;
  49 
  50     /**
  51      * The annotation that we are writing.
  52      */
  53     private final Class<A> annotation;
  54 
  55     /**
  56      * The type of the writer.
  57      */
  58     private final Class<W> writerType;
  59 
  60     /**
  61      * Keeps track of writers for array members.
  62      * Lazily created.
  63      */
  64     private Map<String,JAnnotationArrayMember> arrays;
  65 
  66     public TypedAnnotationWriter(Class<A> annotation, Class<W> writer, JAnnotationUse use) {
  67         this.annotation = annotation;
  68         this.writerType = writer;
  69         this.use = use;
  70     }
  71 
  72     public JAnnotationUse getAnnotationUse() {
  73         return use;
  74     }
  75 
  76     public Class<A> getAnnotationType() {
  77         return annotation;
  78     }
  79 
  80     @SuppressWarnings("unchecked")
  81         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  82 
  83         if(method.getDeclaringClass()==JAnnotationWriter.class) {
  84             try {
  85                 return method.invoke(this,args);
  86             } catch (InvocationTargetException e) {
  87                 throw e.getTargetException();
  88             }
  89         }
  90 
  91         String name = method.getName();
  92         Object arg=null;
  93         if(args!=null && args.length>0)
  94             arg = args[0];
  95 
  96         // check how it's defined on the annotation
  97         Method m = annotation.getDeclaredMethod(name);
  98         Class<?> rt = m.getReturnType();
  99 
 100         // array value
 101         if(rt.isArray()) {
 102             return addArrayValue(proxy,name,rt.getComponentType(),method.getReturnType(),arg);
 103         }
 104 
 105         // sub annotation
 106         if(Annotation.class.isAssignableFrom(rt)) {
 107             Class<? extends Annotation> r = (Class<? extends Annotation>)rt;
 108             return new TypedAnnotationWriter(
 109                 r,method.getReturnType(),use.annotationParam(name,r)).createProxy();
 110         }
 111 
 112         // scalar value
 113 
 114         if(arg instanceof JType) {
 115             JType targ = (JType) arg;
 116             checkType(Class.class,rt);
 117             if(m.getDefaultValue()!=null) {
 118                 // check the default
 119                 if(targ.equals(targ.owner().ref((Class)m.getDefaultValue())))
 120                     return proxy;   // defaulted
 121             }
 122             use.param(name,targ);
 123             return proxy;
 124         }
 125 
 126         // other Java built-in types
 127         checkType(arg.getClass(),rt);
 128         if(m.getDefaultValue()!=null && m.getDefaultValue().equals(arg))
 129             // defaulted. no need to write out.
 130             return proxy;
 131 
 132         if(arg instanceof String) {
 133             use.param(name,(String)arg);
 134             return proxy;
 135         }
 136         if(arg instanceof Boolean) {
 137             use.param(name,(Boolean)arg);
 138             return proxy;
 139         }
 140         if(arg instanceof Integer) {
 141             use.param(name,(Integer)arg);
 142             return proxy;
 143         }
 144         if(arg instanceof Class) {
 145             use.param(name,(Class)arg);
 146             return proxy;
 147         }
 148         if(arg instanceof Enum) {
 149             use.param(name,(Enum)arg);
 150             return proxy;
 151         }
 152 
 153         throw new IllegalArgumentException("Unable to handle this method call "+method.toString());
 154     }
 155 
 156     @SuppressWarnings("unchecked")
 157         private Object addArrayValue(Object proxy,String name, Class itemType, Class expectedReturnType, Object arg) {
 158         if(arrays==null)
 159             arrays = new HashMap<String,JAnnotationArrayMember>();
 160         JAnnotationArrayMember m = arrays.get(name);
 161         if(m==null) {
 162             m = use.paramArray(name);
 163             arrays.put(name,m);
 164         }
 165 
 166         // sub annotation
 167         if(Annotation.class.isAssignableFrom(itemType)) {
 168             Class<? extends Annotation> r = (Class<? extends Annotation>)itemType;
 169             if(!JAnnotationWriter.class.isAssignableFrom(expectedReturnType))
 170                 throw new IllegalArgumentException("Unexpected return type "+expectedReturnType);
 171             return new TypedAnnotationWriter(r,expectedReturnType,m.annotate(r)).createProxy();
 172         }
 173 
 174         // primitive
 175         if(arg instanceof JType) {
 176             checkType(Class.class,itemType);
 177             m.param((JType)arg);
 178             return proxy;
 179         }
 180         checkType(arg.getClass(),itemType);
 181         if(arg instanceof String) {
 182             m.param((String)arg);
 183             return proxy;
 184         }
 185         if(arg instanceof Boolean) {
 186             m.param((Boolean)arg);
 187             return proxy;
 188         }
 189         if(arg instanceof Integer) {
 190             m.param((Integer)arg);
 191             return proxy;
 192         }
 193         if(arg instanceof Class) {
 194             m.param((Class)arg);
 195             return proxy;
 196         }
 197         // TODO: enum constant. how should we handle it?
 198 
 199         throw new IllegalArgumentException("Unable to handle this method call ");
 200     }
 201 
 202 
 203     /**
 204      * Check if the type of the argument matches our expectation.
 205      * If not, report an error.
 206      */
 207     private void checkType(Class<?> actual, Class<?> expected) {
 208         if(expected==actual || expected.isAssignableFrom(actual))
 209             return; // no problem
 210 
 211         if( expected==JCodeModel.boxToPrimitive.get(actual) )
 212             return; // no problem
 213 
 214         throw new IllegalArgumentException("Expected "+expected+" but found "+actual);
 215     }
 216 
 217     /**
 218      * Creates a proxy and returns it.
 219      */
 220     @SuppressWarnings("unchecked")
 221         private W createProxy() {
 222         return (W)Proxy.newProxyInstance(
 223             SecureLoader.getClassClassLoader(writerType),new Class[]{writerType},this);
 224     }
 225 
 226     /**
 227      * Creates a new typed annotation writer.
 228      */
 229     @SuppressWarnings("unchecked")
 230         static <W extends JAnnotationWriter<?>> W create(Class<W> w, JAnnotatable annotatable) {
 231         Class<? extends Annotation> a = findAnnotationType(w);
 232         return (W)new TypedAnnotationWriter(a,w,annotatable.annotate(a)).createProxy();
 233     }
 234 
 235     private static Class<? extends Annotation> findAnnotationType(Class<?> clazz) {
 236         for( Type t : clazz.getGenericInterfaces()) {
 237             if(t instanceof ParameterizedType) {
 238                 ParameterizedType p = (ParameterizedType) t;
 239                 if(p.getRawType()==JAnnotationWriter.class)
 240                     return (Class<? extends Annotation>)p.getActualTypeArguments()[0];
 241             }
 242             if(t instanceof Class<?>) {
 243                 // recursive search
 244                 Class<? extends Annotation> r = findAnnotationType((Class<?>)t);
 245                 if(r!=null)     return r;
 246             }
 247         }
 248         return null;
 249     }
 250 }