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 }