/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later, * or the Apache License Version 2.0. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * MODIFIED BY James Nelson of We The Internet, 2013. * Repackaged to avoid conflicts with different versions of Javassist, * and modified Javassist APIs to make them more accessible to outside code. */ package xapi.bytecode.annotation; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Set; import xapi.bytecode.ClassPool; import xapi.bytecode.ConstPool; import xapi.bytecode.CtClass; import xapi.bytecode.CtMethod; import xapi.bytecode.Descriptor; import xapi.bytecode.NoSuchClassError; import xapi.bytecode.NotFoundException; public class Annotation { static class Pair { int name; MemberValue value; } ConstPool pool; int typeIndex; LinkedHashMap<String, Pair> members; // this sould be LinkedHashMap // but it is not supported by JDK 1.3. /** * Constructs an annotation including no members. A member can be * later added to the created annotation by <code>addMemberValue()</code>. * * @param type the index into the constant pool table. * the entry at that index must be the * <code>CONSTANT_Utf8_Info</code> structure * repreenting the name of the annotation interface type. * @param cp the constant pool table. * * @see #addMemberValue(String, MemberValue) */ public Annotation(int type, ConstPool cp) { pool = cp; typeIndex = type; members = null; } /** * Constructs an annotation including no members. A member can be * later added to the created annotation by <code>addMemberValue()</code>. * * @param typeName the name of the annotation interface type. * @param cp the constant pool table. * * @see #addMemberValue(String, MemberValue) */ public Annotation(String typeName, ConstPool cp) { this(cp.addUtf8Info(Descriptor.of(typeName)), cp); } /** * Constructs an annotation that can be accessed through the interface * represented by <code>clazz</code>. The values of the members are * not specified. * * @param cp the constant pool table. * @param clazz the interface. * @throws NotFoundException when the clazz is not found */ public Annotation(ConstPool cp, CtClass clazz) throws NotFoundException { // TODO Enums are not supported right now. this(cp.addUtf8Info(Descriptor.of(clazz.getName())), cp); if (!clazz.isInterface()) { throw new RuntimeException( "Only interfaces are allowed for Annotation creation."); } CtMethod methods[] = clazz.getDeclaredMethods(); if (methods.length > 0) { members = new LinkedHashMap<String, Pair>(); } for (int i = 0; i < methods.length; i++) { CtClass returnType = methods[i].getReturnType(); addMemberValue(methods[i].getName(), createMemberValue(cp, returnType)); } } /** * Makes an instance of <code>MemberValue</code>. * * @param cp the constant pool table. * @param type the type of the member. * @return the member value * @throws NotFoundException when the type is not found */ public static MemberValue createMemberValue(ConstPool cp, CtClass type) throws NotFoundException { if (type == CtClass.booleanType) { return new BooleanMemberValue(cp); } else if (type == CtClass.byteType) { return new ByteMemberValue(cp); } else if (type == CtClass.charType) { return new CharMemberValue(cp); } else if (type == CtClass.shortType) { return new ShortMemberValue(cp); } else if (type == CtClass.intType) { return new IntegerMemberValue(cp); } else if (type == CtClass.longType) { return new LongMemberValue(cp); } else if (type == CtClass.floatType) { return new FloatMemberValue(cp); } else if (type == CtClass.doubleType) { return new DoubleMemberValue(cp); } else if (type.getName().equals("java.lang.Class")) { return new ClassMemberValue(cp); } else if (type.getName().equals("java.lang.String")) { return new StringMemberValue(cp); } else if (type.isArray()) { CtClass arrayType = type.getComponentType(); MemberValue member = createMemberValue(cp, arrayType); return new ArrayMemberValue(member, cp); } else if (type.isInterface()) { Annotation info = new Annotation(cp, type); return new AnnotationMemberValue(info, cp); } else { // treat as enum. I know this is not typed, // but JBoss has an Annotation Compiler for JDK 1.4 // and I want it to work with that. - Bill Burke EnumMemberValue emv = new EnumMemberValue(cp); emv.setType(type.getName()); return emv; } } /** * Adds a new member. * * @param nameIndex the index into the constant pool table. * The entry at that index must be * a <code>CONSTANT_Utf8_info</code> structure. * structure representing the member name. * @param value the member value. */ public void addMemberValue(int nameIndex, MemberValue value) { Pair p = new Pair(); p.name = nameIndex; p.value = value; addMemberValue(p); } /** * Adds a new member. * * @param name the member name. * @param value the member value. */ public void addMemberValue(String name, MemberValue value) { Pair p = new Pair(); p.name = pool.addUtf8Info(name); p.value = value; if (members == null) { members = new LinkedHashMap<String, Pair>(); } members.put(name, p); } private void addMemberValue(Pair pair) { String name = pool.getUtf8Info(pair.name); if (members == null) { members = new LinkedHashMap<String, Pair>(); } members.put(name, pair); } /** * Returns a string representation of the annotation. */ @Override public String toString() { StringBuffer buf = new StringBuffer("@"); buf.append(getTypeName()); if (members != null) { buf.append("("); Iterator<String> mit = members.keySet().iterator(); while (mit.hasNext()) { String name = (String)mit.next(); buf.append(name).append("=").append(getMemberValue(name)); if (mit.hasNext()) { buf.append(", "); } } buf.append(")"); } return buf.toString(); } /** * Obtains the name of the annotation type. * * @return the type name */ public String getTypeName() { return Descriptor.toClassName(pool.getUtf8Info(typeIndex)); } /** * Obtains all the member names. * * @return null if no members are defined. */ public Set<String> getMemberNames() { if (members == null) { return new HashSet<String>(); } else { return members.keySet(); } } /** * Obtains the member value with the given name. * * <p>If this annotation does not have a value for the * specified member, * this method returns null. It does not return a * <code>MemberValue</code> with the default value. * The default value can be obtained from the annotation type. * * @param name the member name * @return null if the member cannot be found or if the value is * the default value. * * @see javassist.bytecode.AnnotationDefaultAttribute */ public MemberValue getMemberValue(String name) { if (members == null) { return null; } else { Pair p = (Pair)members.get(name); if (p == null) { return null; } else { return p.value; } } } /** * Constructs an annotation-type object representing this annotation. * For example, if this annotation represents <code>@Author</code>, * this method returns an <code>Author</code> object. * * @param cl class loader for loading an annotation type. * @param cp class pool for obtaining class files. * @return the annotation * @throws ClassNotFoundException if the class cannot found. * @throws NoSuchClassError if the class linkage fails. */ public Object toAnnotationType(ClassLoader cl, ClassPool cp) throws ClassNotFoundException, NoSuchClassError { return AnnotationImpl.make(cl, MemberValue.loadClass(cl, getTypeName()), cp, this); } /** * Writes this annotation. * * @param writer the output. * @throws IOException for an error during the write */ public void write(AnnotationsWriter writer) throws IOException { String typeName = pool.getUtf8Info(typeIndex); if (members == null) { writer.annotation(typeName, 0); return; } writer.annotation(typeName, members.size()); Iterator<Pair> it = members.values().iterator(); while (it.hasNext()) { Pair pair = (Pair)it.next(); writer.memberValuePair(pair.name); pair.value.write(writer); } } /** * Returns true if the given object represents the same annotation * as this object. The equality test checks the member values. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj instanceof Annotation == false) { return false; } Annotation other = (Annotation) obj; if (getTypeName().equals(other.getTypeName()) == false) { return false; } LinkedHashMap<String, Pair> otherMembers = other.members; if (members == otherMembers) { return true; } else if (members == null) { return otherMembers == null; } else if (otherMembers == null) { return false; } else { return members.equals(otherMembers); } } }