1 /* 2 * Javassist, a Java-bytecode translator toolkit. 3 * Copyright (C) 2004 Bill Burke. All Rights Reserved. 4 * 5 * The contents of this file are subject to the Mozilla Public License Version 6 * 1.1 (the "License"); you may not use this file except in compliance with 7 * the License. Alternatively, the contents of this file may be used under 8 * the terms of the GNU Lesser General Public License Version 2.1 or later, 9 * or the Apache License Version 2.0. 10 * 11 * Software distributed under the License is distributed on an "AS IS" basis, 12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 13 * for the specific language governing rights and limitations under the 14 * License. 15 */ 16 17 package javassist.bytecode.annotation; 18 19 import java.io.IOException; 20 import java.util.LinkedHashMap; 21 import java.util.Map; 22 import java.util.Set; 23 24 import javassist.ClassPool; 25 import javassist.CtClass; 26 import javassist.CtMethod; 27 import javassist.NotFoundException; 28 import javassist.bytecode.ConstPool; 29 import javassist.bytecode.Descriptor; 30 31 /** 32 * The <code>annotation</code> structure. 33 * 34 * <p>An instance of this class is returned by 35 * <code>getAnnotations()</code> in <code>AnnotationsAttribute</code> 36 * or in <code>ParameterAnnotationsAttribute</code>. 37 * 38 * @see javassist.bytecode.AnnotationsAttribute#getAnnotations() 39 * @see javassist.bytecode.ParameterAnnotationsAttribute#getAnnotations() 40 * @see MemberValue 41 * @see MemberValueVisitor 42 * @see AnnotationsWriter 43 * 44 * @author <a href="mailto:bill@jboss.org">Bill Burke</a> 45 * @author Shigeru Chiba 46 * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a> 47 */ 48 public class Annotation { 49 static class Pair { 50 int name; 51 MemberValue value; 52 } 53 54 ConstPool pool; 55 int typeIndex; 56 Map<String,Pair> members; // this sould be LinkedHashMap 57 // but it is not supported by JDK 1.3. 58 59 /** 60 * Constructs an annotation including no members. A member can be 61 * later added to the created annotation by <code>addMemberValue()</code>. 62 * 63 * @param type the index into the constant pool table. 64 * the entry at that index must be the 65 * <code>CONSTANT_Utf8_Info</code> structure 66 * repreenting the name of the annotation interface type. 67 * @param cp the constant pool table. 68 * 69 * @see #addMemberValue(String, MemberValue) 70 */ Annotation(int type, ConstPool cp)71 public Annotation(int type, ConstPool cp) { 72 pool = cp; 73 typeIndex = type; 74 members = null; 75 } 76 77 /** 78 * Constructs an annotation including no members. A member can be 79 * later added to the created annotation by <code>addMemberValue()</code>. 80 * 81 * @param typeName the fully-qualified name of the annotation interface type. 82 * @param cp the constant pool table. 83 * 84 * @see #addMemberValue(String, MemberValue) 85 */ Annotation(String typeName, ConstPool cp)86 public Annotation(String typeName, ConstPool cp) { 87 this(cp.addUtf8Info(Descriptor.of(typeName)), cp); 88 } 89 90 /** 91 * Constructs an annotation that can be accessed through the interface 92 * represented by <code>clazz</code>. The values of the members are 93 * not specified. 94 * 95 * @param cp the constant pool table. 96 * @param clazz the interface. 97 * @throws NotFoundException when the clazz is not found 98 */ Annotation(ConstPool cp, CtClass clazz)99 public Annotation(ConstPool cp, CtClass clazz) 100 throws NotFoundException 101 { 102 // todo Enums are not supported right now. 103 this(cp.addUtf8Info(Descriptor.of(clazz.getName())), cp); 104 105 if (!clazz.isInterface()) 106 throw new RuntimeException( 107 "Only interfaces are allowed for Annotation creation."); 108 109 CtMethod[] methods = clazz.getDeclaredMethods(); 110 if (methods.length > 0) 111 members = new LinkedHashMap<String,Pair>(); 112 113 for (CtMethod m:methods) 114 addMemberValue(m.getName(), 115 createMemberValue(cp, m.getReturnType())); 116 } 117 118 /** 119 * Makes an instance of <code>MemberValue</code>. 120 * 121 * @param cp the constant pool table. 122 * @param type the type of the member. 123 * @return the member value 124 * @throws NotFoundException when the type is not found 125 */ createMemberValue(ConstPool cp, CtClass type)126 public static MemberValue createMemberValue(ConstPool cp, CtClass type) 127 throws NotFoundException 128 { 129 if (type == CtClass.booleanType) 130 return new BooleanMemberValue(cp); 131 else if (type == CtClass.byteType) 132 return new ByteMemberValue(cp); 133 else if (type == CtClass.charType) 134 return new CharMemberValue(cp); 135 else if (type == CtClass.shortType) 136 return new ShortMemberValue(cp); 137 else if (type == CtClass.intType) 138 return new IntegerMemberValue(cp); 139 else if (type == CtClass.longType) 140 return new LongMemberValue(cp); 141 else if (type == CtClass.floatType) 142 return new FloatMemberValue(cp); 143 else if (type == CtClass.doubleType) 144 return new DoubleMemberValue(cp); 145 else if (type.getName().equals("java.lang.Class")) 146 return new ClassMemberValue(cp); 147 else if (type.getName().equals("java.lang.String")) 148 return new StringMemberValue(cp); 149 else if (type.isArray()) { 150 CtClass arrayType = type.getComponentType(); 151 MemberValue member = createMemberValue(cp, arrayType); 152 return new ArrayMemberValue(member, cp); 153 } 154 else if (type.isInterface()) { 155 Annotation info = new Annotation(cp, type); 156 return new AnnotationMemberValue(info, cp); 157 } 158 else { 159 // treat as enum. I know this is not typed, 160 // but JBoss has an Annotation Compiler for JDK 1.4 161 // and I want it to work with that. - Bill Burke 162 EnumMemberValue emv = new EnumMemberValue(cp); 163 emv.setType(type.getName()); 164 return emv; 165 } 166 } 167 168 /** 169 * Adds a new member. 170 * 171 * @param nameIndex the index into the constant pool table. 172 * The entry at that index must be 173 * a <code>CONSTANT_Utf8_info</code> structure. 174 * structure representing the member name. 175 * @param value the member value. 176 */ addMemberValue(int nameIndex, MemberValue value)177 public void addMemberValue(int nameIndex, MemberValue value) { 178 Pair p = new Pair(); 179 p.name = nameIndex; 180 p.value = value; 181 addMemberValue(p); 182 } 183 184 /** 185 * Adds a new member. 186 * 187 * @param name the member name. 188 * @param value the member value. 189 */ addMemberValue(String name, MemberValue value)190 public void addMemberValue(String name, MemberValue value) { 191 Pair p = new Pair(); 192 p.name = pool.addUtf8Info(name); 193 p.value = value; 194 if (members == null) 195 members = new LinkedHashMap<String,Pair>(); 196 197 members.put(name, p); 198 } 199 addMemberValue(Pair pair)200 private void addMemberValue(Pair pair) { 201 String name = pool.getUtf8Info(pair.name); 202 if (members == null) 203 members = new LinkedHashMap<String,Pair>(); 204 205 members.put(name, pair); 206 } 207 208 /** 209 * Returns a string representation of the annotation. 210 */ 211 @Override toString()212 public String toString() { 213 StringBuffer buf = new StringBuffer("@"); 214 buf.append(getTypeName()); 215 if (members != null) { 216 buf.append("("); 217 for (String name:members.keySet()) { 218 buf.append(name).append("=") 219 .append(getMemberValue(name)) 220 .append(", "); 221 } 222 buf.setLength(buf.length()-2); 223 buf.append(")"); 224 } 225 226 return buf.toString(); 227 } 228 229 /** 230 * Obtains the name of the annotation type. 231 * 232 * @return the type name 233 */ getTypeName()234 public String getTypeName() { 235 return Descriptor.toClassName(pool.getUtf8Info(typeIndex)); 236 } 237 238 /** 239 * Obtains all the member names. 240 * 241 * @return null if no members are defined. 242 */ getMemberNames()243 public Set<String> getMemberNames() { 244 if (members == null) 245 return null; 246 return members.keySet(); 247 } 248 249 /** 250 * Obtains the member value with the given name. 251 * 252 * <p>If this annotation does not have a value for the 253 * specified member, 254 * this method returns null. It does not return a 255 * <code>MemberValue</code> with the default value. 256 * The default value can be obtained from the annotation type. 257 * 258 * @param name the member name 259 * @return null if the member cannot be found or if the value is 260 * the default value. 261 * 262 * @see javassist.bytecode.AnnotationDefaultAttribute 263 */ getMemberValue(String name)264 public MemberValue getMemberValue(String name) { 265 if (members == null||members.get(name) == null) 266 return null; 267 return members.get(name).value; 268 } 269 270 /** 271 * Constructs an annotation-type object representing this annotation. 272 * For example, if this annotation represents <code>@Author</code>, 273 * this method returns an <code>Author</code> object. 274 * 275 * @param cl class loader for loading an annotation type. 276 * @param cp class pool for obtaining class files. 277 * @return the annotation 278 * @throws ClassNotFoundException if the class cannot found. 279 * @throws NoSuchClassError if the class linkage fails. 280 */ toAnnotationType(ClassLoader cl, ClassPool cp)281 public Object toAnnotationType(ClassLoader cl, ClassPool cp) 282 throws ClassNotFoundException, NoSuchClassError 283 { 284 Class<?> clazz = MemberValue.loadClass(cl, getTypeName()); 285 try { 286 return AnnotationImpl.make(cl, clazz, cp, this); 287 } 288 catch (IllegalArgumentException e) { 289 /* AnnotationImpl.make() may throw this exception 290 * when it fails to make a proxy object for some 291 * reason. 292 */ 293 throw new ClassNotFoundException(clazz.getName(), e); 294 } 295 catch (IllegalAccessError e2) { 296 // also IllegalAccessError 297 throw new ClassNotFoundException(clazz.getName(), e2); 298 } 299 } 300 301 /** 302 * Writes this annotation. 303 * 304 * @param writer the output. 305 * @throws IOException for an error during the write 306 */ write(AnnotationsWriter writer)307 public void write(AnnotationsWriter writer) throws IOException { 308 String typeName = pool.getUtf8Info(typeIndex); 309 if (members == null) { 310 writer.annotation(typeName, 0); 311 return; 312 } 313 314 writer.annotation(typeName, members.size()); 315 for (Pair pair:members.values()) { 316 writer.memberValuePair(pair.name); 317 pair.value.write(writer); 318 } 319 } 320 321 @Override hashCode()322 public int hashCode() { 323 return getTypeName().hashCode() + 324 (members == null ? 0 : members.hashCode()); 325 } 326 327 /** 328 * Returns true if the given object represents the same annotation 329 * as this object. The equality test checks the member values. 330 */ 331 @Override equals(Object obj)332 public boolean equals(Object obj) { 333 if (obj == this) 334 return true; 335 if (obj == null || obj instanceof Annotation == false) 336 return false; 337 338 Annotation other = (Annotation) obj; 339 340 if (getTypeName().equals(other.getTypeName()) == false) 341 return false; 342 343 Map<String,Pair> otherMembers = other.members; 344 if (members == otherMembers) 345 return true; 346 else if (members == null) 347 return otherMembers == null; 348 else 349 if (otherMembers == null) 350 return false; 351 else 352 return members.equals(otherMembers); 353 } 354 } 355