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.dx.rop.annotation;
18
19import com.android.dx.rop.cst.CstString;
20import com.android.dx.rop.cst.CstType;
21import com.android.dx.util.MutabilityControl;
22import com.android.dx.util.ToHuman;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.Iterator;
26import java.util.TreeMap;
27
28/**
29 * An annotation on an element of a class. Annotations have an
30 * associated type and additionally consist of a set of (name, value)
31 * pairs, where the names are unique.
32 */
33public final class Annotation extends MutabilityControl
34        implements Comparable<Annotation>, ToHuman {
35    /** {@code non-null;} type of the annotation */
36    private final CstType type;
37
38    /** {@code non-null;} the visibility of the annotation */
39    private final AnnotationVisibility visibility;
40
41    /** {@code non-null;} map from names to {@link NameValuePair} instances */
42    private final TreeMap<CstString, NameValuePair> elements;
43
44    /**
45     * Construct an instance. It initially contains no elements.
46     *
47     * @param type {@code non-null;} type of the annotation
48     * @param visibility {@code non-null;} the visibility of the annotation
49     */
50    public Annotation(CstType type, AnnotationVisibility visibility) {
51        if (type == null) {
52            throw new NullPointerException("type == null");
53        }
54
55        if (visibility == null) {
56            throw new NullPointerException("visibility == null");
57        }
58
59        this.type = type;
60        this.visibility = visibility;
61        this.elements = new TreeMap<CstString, NameValuePair>();
62    }
63
64    /** {@inheritDoc} */
65    @Override
66    public boolean equals(Object other) {
67        if (! (other instanceof Annotation)) {
68            return false;
69        }
70
71        Annotation otherAnnotation = (Annotation) other;
72
73        if (! (type.equals(otherAnnotation.type)
74                        && (visibility == otherAnnotation.visibility))) {
75            return false;
76        }
77
78        return elements.equals(otherAnnotation.elements);
79    }
80
81    /** {@inheritDoc} */
82    public int hashCode() {
83        int hash = type.hashCode();
84        hash = (hash * 31) + elements.hashCode();
85        hash = (hash * 31) + visibility.hashCode();
86        return hash;
87    }
88
89    /** {@inheritDoc} */
90    public int compareTo(Annotation other) {
91        int result = type.compareTo(other.type);
92
93        if (result != 0) {
94            return result;
95        }
96
97        result = visibility.compareTo(other.visibility);
98
99        if (result != 0) {
100            return result;
101        }
102
103        Iterator<NameValuePair> thisIter = elements.values().iterator();
104        Iterator<NameValuePair> otherIter = other.elements.values().iterator();
105
106        while (thisIter.hasNext() && otherIter.hasNext()) {
107            NameValuePair thisOne = thisIter.next();
108            NameValuePair otherOne = otherIter.next();
109
110            result = thisOne.compareTo(otherOne);
111            if (result != 0) {
112                return result;
113            }
114        }
115
116        if (thisIter.hasNext()) {
117            return 1;
118        } else if (otherIter.hasNext()) {
119            return -1;
120        }
121
122        return 0;
123    }
124
125    /** {@inheritDoc} */
126    @Override
127    public String toString() {
128        return toHuman();
129    }
130
131    /** {@inheritDoc} */
132    public String toHuman() {
133        StringBuilder sb = new StringBuilder();
134
135        sb.append(visibility.toHuman());
136        sb.append("-annotation ");
137        sb.append(type.toHuman());
138        sb.append(" {");
139
140        boolean first = true;
141        for (NameValuePair pair : elements.values()) {
142            if (first) {
143                first = false;
144            } else {
145                sb.append(", ");
146            }
147            sb.append(pair.getName().toHuman());
148            sb.append(": ");
149            sb.append(pair.getValue().toHuman());
150        }
151
152        sb.append("}");
153        return sb.toString();
154    }
155
156    /**
157     * Gets the type of this instance.
158     *
159     * @return {@code non-null;} the type
160     */
161    public CstType getType() {
162        return type;
163    }
164
165    /**
166     * Gets the visibility of this instance.
167     *
168     * @return {@code non-null;} the visibility
169     */
170    public AnnotationVisibility getVisibility() {
171        return visibility;
172    }
173
174    /**
175     * Put an element into the set of (name, value) pairs for this instance.
176     * If there is a preexisting element with the same name, it will be
177     * replaced by this method.
178     *
179     * @param pair {@code non-null;} the (name, value) pair to place into this instance
180     */
181    public void put(NameValuePair pair) {
182        throwIfImmutable();
183
184        if (pair == null) {
185            throw new NullPointerException("pair == null");
186        }
187
188        elements.put(pair.getName(), pair);
189    }
190
191    /**
192     * Add an element to the set of (name, value) pairs for this instance.
193     * It is an error to call this method if there is a preexisting element
194     * with the same name.
195     *
196     * @param pair {@code non-null;} the (name, value) pair to add to this instance
197     */
198    public void add(NameValuePair pair) {
199        throwIfImmutable();
200
201        if (pair == null) {
202            throw new NullPointerException("pair == null");
203        }
204
205        CstString name = pair.getName();
206
207        if (elements.get(name) != null) {
208            throw new IllegalArgumentException("name already added: " + name);
209        }
210
211        elements.put(name, pair);
212    }
213
214    /**
215     * Gets the set of name-value pairs contained in this instance. The
216     * result is always unmodifiable.
217     *
218     * @return {@code non-null;} the set of name-value pairs
219     */
220    public Collection<NameValuePair> getNameValuePairs() {
221        return Collections.unmodifiableCollection(elements.values());
222    }
223}
224