blob: a78987c6b75251a343bab0a7f24f05b73bb69624 [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
Ayaka Koshibe74b55272015-05-28 15:16:04 -0700208 @Override
209 public String toString() {
210 return (map == null) ? "null" : map.toString();
211 }
212
tomf5d85d42014-10-02 05:27:56 -0700213 /**
214 * Facility for gradually building model annotations.
215 */
216 public static final class Builder {
217
218 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700219 private final Map<String, String> builder = new HashMap<>();
220
221 // Private construction is forbidden.
222 private Builder() {
223 }
224
225 /**
HIGUCHI Yuta3cb7ff92016-01-20 12:11:31 -0800226 * Adds all specified annotation. Any previous value associated with
227 * the given annotations will be overwritten.
228 *
229 * @param base annotations
230 * @return self
231 */
232 public Builder putAll(Annotations base) {
233 if (base instanceof DefaultAnnotations) {
234 builder.putAll(((DefaultAnnotations) base).map);
235
236 } else if (base instanceof SparseAnnotations) {
237 final SparseAnnotations sparse = (SparseAnnotations) base;
238 for (String key : base.keys()) {
239 if (sparse.isRemoved(key)) {
240 remove(key);
241 } else {
242 set(key, base.value(key));
243 }
244 }
245
246 } else {
247 base.keys().forEach(key -> set(key, base.value(key)));
248
249 }
250 return this;
251 }
252
253 /**
Yuta HIGUCHI7438f5a2017-02-15 22:09:46 -0800254 * Adds all entries in specified map.
255 * Any previous entries with same key will be overwritten.
256 *
257 * @param entries annotation key and value entries
258 * @return self
259 */
260 public Builder putAll(Map<String, String> entries) {
261 builder.putAll(transformValues(entries, v -> (v == null) ? REMOVED : v));
262 return this;
263 }
264
265 /**
tomf5d85d42014-10-02 05:27:56 -0700266 * Adds the specified annotation. Any previous value associated with
267 * the given annotation key will be overwritten.
268 *
269 * @param key annotation key
270 * @param value annotation value
271 * @return self
272 */
273 public Builder set(String key, String value) {
274 builder.put(key, value);
275 return this;
276 }
277
278 /**
279 * Adds the specified annotation. Any previous value associated with
280 * the given annotation key will be tagged for removal.
281 *
282 * @param key annotation key
283 * @return self
284 */
285 public Builder remove(String key) {
286 builder.put(key, REMOVED);
287 return this;
288 }
289
290 /**
291 * Returns immutable annotations built from the accrued key/values pairs.
292 *
293 * @return annotations
294 */
295 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700296 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700297 }
298 }
299}