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