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