blob: 2a0eeb5fa4f05f9301735872a7163e4c1697be3f [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2014-present Open Networking Foundation
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 HIGUCHI9eed0b12017-06-07 11:59:18 -0700144 if (annotations.keys().isEmpty()) {
145 return sparseAnnotations;
146 }
147
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700148 final HashMap<String, String> newMap;
149 if (annotations instanceof DefaultAnnotations) {
150 newMap = copy(((DefaultAnnotations) annotations).map);
151 } else {
152 newMap = new HashMap<>(annotations.keys().size() +
153 sparseAnnotations.keys().size());
154 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700155 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700156
157 putAllSparseAnnotations(newMap, sparseAnnotations);
158 return new DefaultAnnotations(newMap);
159 }
160
161 // adds the key-values contained in sparseAnnotations to
162 // newMap, if sparseAnnotations had a key tagged for removal,
163 // and corresponding key exist in newMap, entry will be removed.
164 // if corresponding key does not exist, removal tag will be added to
165 // the newMap.
166 private static void putAllSparseAnnotations(
167 final HashMap<String, String> newMap,
168 SparseAnnotations sparseAnnotations) {
169
170 for (String key : sparseAnnotations.keys()) {
171 if (sparseAnnotations.isRemoved(key)) {
172 if (newMap.containsKey(key)) {
173 newMap.remove(key);
174 } else {
175 newMap.put(key, Builder.REMOVED);
176 }
177 } else {
178 String value = sparseAnnotations.value(key);
179 newMap.put(key, value);
180 }
181 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700182 }
183
tomf5d85d42014-10-02 05:27:56 -0700184 @Override
185 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700186 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700187 }
188
189 @Override
190 public String value(String key) {
191 String value = map.get(key);
192 return Objects.equals(Builder.REMOVED, value) ? null : value;
193 }
194
195 @Override
196 public boolean isRemoved(String key) {
197 return Objects.equals(Builder.REMOVED, map.get(key));
198 }
199
tom5a9383a2014-10-02 07:33:52 -0700200 @SuppressWarnings("unchecked")
201 private static HashMap<String, String> copy(Map<String, String> original) {
202 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700203 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700204 }
205 throw new IllegalArgumentException("Expecting HashMap instance");
206 }
207
Thomas Vachuskaf131e592018-05-07 11:52:14 -0700208 private static HashMap<String, String> compress(Map<String, String> original) {
209 HashMap<String, String> compressed = new HashMap<>();
210 original.forEach((k, v) -> {
211 if (!Objects.equals(v, Builder.REMOVED)) {
212 compressed.put(k, v);
213 }
214 });
215 return compressed;
216 }
217
Ayaka Koshibe74b55272015-05-28 15:16:04 -0700218 @Override
219 public String toString() {
220 return (map == null) ? "null" : map.toString();
221 }
222
tomf5d85d42014-10-02 05:27:56 -0700223 /**
224 * Facility for gradually building model annotations.
225 */
226 public static final class Builder {
227
228 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700229 private final Map<String, String> builder = new HashMap<>();
230
231 // Private construction is forbidden.
232 private Builder() {
233 }
234
235 /**
HIGUCHI Yuta3cb7ff92016-01-20 12:11:31 -0800236 * Adds all specified annotation. Any previous value associated with
237 * the given annotations will be overwritten.
238 *
239 * @param base annotations
240 * @return self
241 */
242 public Builder putAll(Annotations base) {
243 if (base instanceof DefaultAnnotations) {
244 builder.putAll(((DefaultAnnotations) base).map);
245
246 } else if (base instanceof SparseAnnotations) {
247 final SparseAnnotations sparse = (SparseAnnotations) base;
248 for (String key : base.keys()) {
249 if (sparse.isRemoved(key)) {
250 remove(key);
251 } else {
252 set(key, base.value(key));
253 }
254 }
255
256 } else {
257 base.keys().forEach(key -> set(key, base.value(key)));
258
259 }
260 return this;
261 }
262
263 /**
Yuta HIGUCHI7438f5a2017-02-15 22:09:46 -0800264 * Adds all entries in specified map.
265 * Any previous entries with same key will be overwritten.
266 *
267 * @param entries annotation key and value entries
268 * @return self
269 */
270 public Builder putAll(Map<String, String> entries) {
271 builder.putAll(transformValues(entries, v -> (v == null) ? REMOVED : v));
272 return this;
273 }
274
275 /**
tomf5d85d42014-10-02 05:27:56 -0700276 * Adds the specified annotation. Any previous value associated with
277 * the given annotation key will be overwritten.
278 *
279 * @param key annotation key
280 * @param value annotation value
281 * @return self
282 */
283 public Builder set(String key, String value) {
284 builder.put(key, value);
285 return this;
286 }
287
288 /**
289 * Adds the specified annotation. Any previous value associated with
290 * the given annotation key will be tagged for removal.
291 *
292 * @param key annotation key
293 * @return self
294 */
295 public Builder remove(String key) {
296 builder.put(key, REMOVED);
297 return this;
298 }
299
300 /**
301 * Returns immutable annotations built from the accrued key/values pairs.
Thomas Vachuskaf131e592018-05-07 11:52:14 -0700302 * Any removed annotation tombstones will be preserved.
tomf5d85d42014-10-02 05:27:56 -0700303 *
304 * @return annotations
305 */
306 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700307 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700308 }
Thomas Vachuskaf131e592018-05-07 11:52:14 -0700309
310 /**
311 * Returns immutable annotations built from the accrued key/values
312 * pairs after compressing them to eliminate removed annotation tombstones.
313 *
314 * @return annotations
315 */
316 public DefaultAnnotations buildCompressed() {
317 return new DefaultAnnotations(compress(builder));
318 }
tomf5d85d42014-10-02 05:27:56 -0700319 }
320}