1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dexgen.rop.annotation;
18
19import com.android.dexgen.rop.cst.Constant;
20import com.android.dexgen.rop.cst.CstAnnotation;
21import com.android.dexgen.rop.cst.CstFieldRef;
22import com.android.dexgen.rop.cst.CstLiteralBits;
23import com.android.dexgen.rop.cst.CstMethodRef;
24import com.android.dexgen.rop.cst.CstNat;
25import com.android.dexgen.rop.cst.CstType;
26import com.android.dexgen.rop.cst.CstUtf8;
27import com.android.dexgen.rop.cst.TypedConstant;
28import com.android.dexgen.util.Hex;
29import com.android.dexgen.util.MutabilityControl;
30import com.android.dexgen.util.ToHuman;
31
32import java.util.Collection;
33import java.util.Collections;
34import java.util.Iterator;
35import java.util.TreeMap;
36
37/**
38 * An annotation on an element of a class. Annotations have an
39 * associated type and additionally consist of a set of (name, value)
40 * pairs, where the names are unique.
41 */
42public final class Annotation extends MutabilityControl
43        implements Comparable<Annotation>, ToHuman {
44    /** {@code non-null;} type of the annotation */
45    private final CstType type;
46
47    /** {@code non-null;} the visibility of the annotation */
48    private final AnnotationVisibility visibility;
49
50    /** {@code non-null;} map from names to {@link NameValuePair} instances */
51    private final TreeMap<CstUtf8, NameValuePair> elements;
52
53    /**
54     * Construct an instance. It initially contains no elements.
55     *
56     * @param type {@code non-null;} type of the annotation
57     * @param visibility {@code non-null;} the visibility of the annotation
58     */
59    public Annotation(CstType type, AnnotationVisibility visibility) {
60        if (type == null) {
61            throw new NullPointerException("type == null");
62        }
63
64        if (visibility == null) {
65            throw new NullPointerException("visibility == null");
66        }
67
68        this.type = type;
69        this.visibility = visibility;
70        this.elements = new TreeMap<CstUtf8, NameValuePair>();
71    }
72
73    /** {@inheritDoc} */
74    @Override
75    public boolean equals(Object other) {
76        if (! (other instanceof Annotation)) {
77            return false;
78        }
79
80        Annotation otherAnnotation = (Annotation) other;
81
82        if (! (type.equals(otherAnnotation.type)
83                        && (visibility == otherAnnotation.visibility))) {
84            return false;
85        }
86
87        return elements.equals(otherAnnotation.elements);
88    }
89
90    /** {@inheritDoc} */
91    public int hashCode() {
92        int hash = type.hashCode();
93        hash = (hash * 31) + elements.hashCode();
94        hash = (hash * 31) + visibility.hashCode();
95        return hash;
96    }
97
98    /** {@inheritDoc} */
99    public int compareTo(Annotation other) {
100        int result = type.compareTo(other.type);
101
102        if (result != 0) {
103            return result;
104        }
105
106        result = visibility.compareTo(other.visibility);
107
108        if (result != 0) {
109            return result;
110        }
111
112        Iterator<NameValuePair> thisIter = elements.values().iterator();
113        Iterator<NameValuePair> otherIter = other.elements.values().iterator();
114
115        while (thisIter.hasNext() && otherIter.hasNext()) {
116            NameValuePair thisOne = thisIter.next();
117            NameValuePair otherOne = otherIter.next();
118
119            result = thisOne.compareTo(otherOne);
120            if (result != 0) {
121                return result;
122            }
123        }
124
125        if (thisIter.hasNext()) {
126            return 1;
127        } else if (otherIter.hasNext()) {
128            return -1;
129        }
130
131        return 0;
132    }
133
134    /** {@inheritDoc} */
135    @Override
136    public String toString() {
137        return toHuman();
138    }
139
140    /** {@inheritDoc} */
141    public String toHuman() {
142        StringBuilder sb = new StringBuilder();
143
144        sb.append(visibility.toHuman());
145        sb.append("-annotation ");
146        sb.append(type.toHuman());
147        sb.append(" {");
148
149        boolean first = true;
150        for (NameValuePair pair : elements.values()) {
151            if (first) {
152                first = false;
153            } else {
154                sb.append(", ");
155            }
156            sb.append(pair.getName().toHuman());
157            sb.append(": ");
158            sb.append(pair.getValue().toHuman());
159        }
160
161        sb.append("}");
162        return sb.toString();
163    }
164
165    /**
166     * Gets the type of this instance.
167     *
168     * @return {@code non-null;} the type
169     */
170    public CstType getType() {
171        return type;
172    }
173
174    /**
175     * Gets the visibility of this instance.
176     *
177     * @return {@code non-null;} the visibility
178     */
179    public AnnotationVisibility getVisibility() {
180        return visibility;
181    }
182
183    /**
184     * Put an element into the set of (name, value) pairs for this instance.
185     * If there is a preexisting element with the same name, it will be
186     * replaced by this method.
187     *
188     * @param pair {@code non-null;} the (name, value) pair to place into this instance
189     */
190    public void put(NameValuePair pair) {
191        throwIfImmutable();
192
193        if (pair == null) {
194            throw new NullPointerException("pair == null");
195        }
196
197        elements.put(pair.getName(), pair);
198    }
199
200    /**
201     * Add an element to the set of (name, value) pairs for this instance.
202     * It is an error to call this method if there is a preexisting element
203     * with the same name.
204     *
205     * @param pair {@code non-null;} the (name, value) pair to add to this instance
206     */
207    public void add(NameValuePair pair) {
208        throwIfImmutable();
209
210        if (pair == null) {
211            throw new NullPointerException("pair == null");
212        }
213
214        CstUtf8 name = pair.getName();
215
216        if (elements.get(name) != null) {
217            throw new IllegalArgumentException("name already added: " + name);
218        }
219
220        elements.put(name, pair);
221    }
222
223    /**
224     * Gets the set of name-value pairs contained in this instance. The
225     * result is always unmodifiable.
226     *
227     * @return {@code non-null;} the set of name-value pairs
228     */
229    public Collection<NameValuePair> getNameValuePairs() {
230        return Collections.unmodifiableCollection(elements.values());
231    }
232}
233