blob: f86df5ecd2768a87311d86d2b747df8a7026bcb2 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-present Open Networking Laboratory
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07003 *
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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.net;
tomf5d85d42014-10-02 05:27:56 -070017
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070018import java.util.Collections;
tomf5d85d42014-10-02 05:27:56 -070019import java.util.HashMap;
20import java.util.Map;
21import java.util.Objects;
22import java.util.Set;
23
Ayaka Koshibe08e457a2015-06-25 17:11:54 -070024import com.google.common.collect.Maps;
25
tom5a9383a2014-10-02 07:33:52 -070026import static com.google.common.base.Preconditions.checkNotNull;
Yuta HIGUCHI7438f5a2017-02-15 22:09:46 -080027import static com.google.common.collect.Maps.transformValues;
tom5a9383a2014-10-02 07:33:52 -070028
tomf5d85d42014-10-02 05:27:56 -070029/**
30 * Represents a set of simple annotations that can be used to add arbitrary
31 * attributes to various parts of the data model.
32 */
33public final class DefaultAnnotations implements SparseAnnotations {
34
Sho SHIMIZUa3d67cd2015-06-04 11:46:48 -070035 public static final SparseAnnotations EMPTY = DefaultAnnotations.builder().build();
Thomas Vachuskaa8f4e7d2015-01-08 17:31:55 -080036
tomf5d85d42014-10-02 05:27:56 -070037 private final Map<String, String> map;
38
39 // For serialization
40 private DefaultAnnotations() {
41 this.map = null;
42 }
43
Marc De Leenheerbb382352015-04-23 18:20:34 -070044 @Override
45 public boolean equals(Object o) {
46 if (this == o) {
47 return true;
48 }
49 if (o == null || getClass() != o.getClass()) {
50 return false;
51 }
52
53 DefaultAnnotations that = (DefaultAnnotations) o;
54
55 return Objects.equals(this.map, that.map);
56
57 }
58
59 @Override
60 public int hashCode() {
61 return Objects.hashCode(this.map);
62 }
63
tomf5d85d42014-10-02 05:27:56 -070064 /**
Ayaka Koshibe08e457a2015-06-25 17:11:54 -070065 * Returns the annotations as a map.
66 *
67 * @return a copy of the contents of the annotations as a map.
68 */
69 public HashMap<String, String> asMap() {
70 return Maps.newHashMap(this.map);
71 }
72
73 /**
tom5a9383a2014-10-02 07:33:52 -070074 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070075 *
tom5a9383a2014-10-02 07:33:52 -070076 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070077 */
78 private DefaultAnnotations(Map<String, String> map) {
79 this.map = map;
80 }
81
82 /**
83 * Creates a new annotations builder.
84 *
85 * @return new annotations builder
86 */
87 public static Builder builder() {
88 return new Builder();
89 }
90
tom5a9383a2014-10-02 07:33:52 -070091 /**
92 * Merges the specified base set of annotations and additional sparse
93 * annotations into new combined annotations. If the supplied sparse
94 * annotations are empty, the original base annotations are returned.
95 * Any keys tagged for removal in the sparse annotations will be omitted
96 * in the resulting merged annotations.
97 *
98 * @param annotations base annotations
99 * @param sparseAnnotations additional sparse annotations
100 * @return combined annotations or the original base annotations if there
101 * are not additional annotations
102 */
103 public static DefaultAnnotations merge(DefaultAnnotations annotations,
104 SparseAnnotations sparseAnnotations) {
105 checkNotNull(annotations, "Annotations cannot be null");
106 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
107 return annotations;
108 }
109
110 // Merge the two maps. Yes, this is not very efficient, but the
111 // use-case implies small maps and infrequent merges, so we opt for
112 // simplicity.
113 Map<String, String> merged = copy(annotations.map);
114 for (String key : sparseAnnotations.keys()) {
115 if (sparseAnnotations.isRemoved(key)) {
116 merged.remove(key);
117 } else {
118 merged.put(key, sparseAnnotations.value(key));
119 }
120 }
121 return new DefaultAnnotations(merged);
122 }
tomf5d85d42014-10-02 05:27:56 -0700123
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700124 /**
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700125 * Creates the union of two given SparseAnnotations.
126 * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
127 * result will be {@link SparseAnnotations} instead of {@link Annotations}.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700128 *
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700129 * A key tagged for removal will remain in the output SparseAnnotations,
130 * if the counterpart of the input does not contain the same key.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700131 *
132 * @param annotations base annotations
133 * @param sparseAnnotations additional sparse annotations
134 * @return combined annotations or the original base annotations if there
135 * are not additional annotations
136 */
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700137 public static SparseAnnotations union(SparseAnnotations annotations,
138 SparseAnnotations sparseAnnotations) {
139
140 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
141 return annotations;
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700142 }
143
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700144 final HashMap<String, String> newMap;
145 if (annotations instanceof DefaultAnnotations) {
146 newMap = copy(((DefaultAnnotations) annotations).map);
147 } else {
148 newMap = new HashMap<>(annotations.keys().size() +
149 sparseAnnotations.keys().size());
150 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700151 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700152
153 putAllSparseAnnotations(newMap, sparseAnnotations);
154 return new DefaultAnnotations(newMap);
155 }
156
157 // adds the key-values contained in sparseAnnotations to
158 // newMap, if sparseAnnotations had a key tagged for removal,
159 // and corresponding key exist in newMap, entry will be removed.
160 // if corresponding key does not exist, removal tag will be added to
161 // the newMap.
162 private static void putAllSparseAnnotations(
163 final HashMap<String, String> newMap,
164 SparseAnnotations sparseAnnotations) {
165
166 for (String key : sparseAnnotations.keys()) {
167 if (sparseAnnotations.isRemoved(key)) {
168 if (newMap.containsKey(key)) {
169 newMap.remove(key);
170 } else {
171 newMap.put(key, Builder.REMOVED);
172 }
173 } else {
174 String value = sparseAnnotations.value(key);
175 newMap.put(key, value);
176 }
177 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700178 }
179
tomf5d85d42014-10-02 05:27:56 -0700180 @Override
181 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700182 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700183 }
184
185 @Override
186 public String value(String key) {
187 String value = map.get(key);
188 return Objects.equals(Builder.REMOVED, value) ? null : value;
189 }
190
191 @Override
192 public boolean isRemoved(String key) {
193 return Objects.equals(Builder.REMOVED, map.get(key));
194 }
195
tom5a9383a2014-10-02 07:33:52 -0700196 @SuppressWarnings("unchecked")
197 private static HashMap<String, String> copy(Map<String, String> original) {
198 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700199 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700200 }
201 throw new IllegalArgumentException("Expecting HashMap instance");
202 }
203
Ayaka Koshibe74b55272015-05-28 15:16:04 -0700204 @Override
205 public String toString() {
206 return (map == null) ? "null" : map.toString();
207 }
208
tomf5d85d42014-10-02 05:27:56 -0700209 /**
210 * Facility for gradually building model annotations.
211 */
212 public static final class Builder {
213
214 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700215 private final Map<String, String> builder = new HashMap<>();
216
217 // Private construction is forbidden.
218 private Builder() {
219 }
220
221 /**
HIGUCHI Yuta3cb7ff92016-01-20 12:11:31 -0800222 * Adds all specified annotation. Any previous value associated with
223 * the given annotations will be overwritten.
224 *
225 * @param base annotations
226 * @return self
227 */
228 public Builder putAll(Annotations base) {
229 if (base instanceof DefaultAnnotations) {
230 builder.putAll(((DefaultAnnotations) base).map);
231
232 } else if (base instanceof SparseAnnotations) {
233 final SparseAnnotations sparse = (SparseAnnotations) base;
234 for (String key : base.keys()) {
235 if (sparse.isRemoved(key)) {
236 remove(key);
237 } else {
238 set(key, base.value(key));
239 }
240 }
241
242 } else {
243 base.keys().forEach(key -> set(key, base.value(key)));
244
245 }
246 return this;
247 }
248
249 /**
Yuta HIGUCHI7438f5a2017-02-15 22:09:46 -0800250 * Adds all entries in specified map.
251 * Any previous entries with same key will be overwritten.
252 *
253 * @param entries annotation key and value entries
254 * @return self
255 */
256 public Builder putAll(Map<String, String> entries) {
257 builder.putAll(transformValues(entries, v -> (v == null) ? REMOVED : v));
258 return this;
259 }
260
261 /**
tomf5d85d42014-10-02 05:27:56 -0700262 * Adds the specified annotation. Any previous value associated with
263 * the given annotation key will be overwritten.
264 *
265 * @param key annotation key
266 * @param value annotation value
267 * @return self
268 */
269 public Builder set(String key, String value) {
270 builder.put(key, value);
271 return this;
272 }
273
274 /**
275 * Adds the specified annotation. Any previous value associated with
276 * the given annotation key will be tagged for removal.
277 *
278 * @param key annotation key
279 * @return self
280 */
281 public Builder remove(String key) {
282 builder.put(key, REMOVED);
283 return this;
284 }
285
286 /**
287 * Returns immutable annotations built from the accrued key/values pairs.
288 *
289 * @return annotations
290 */
291 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700292 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700293 }
294 }
295}