blob: 242b0e8ecd13a668ff9edbb6818b2958e70d3b6b [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;
27
tomf5d85d42014-10-02 05:27:56 -070028/**
29 * Represents a set of simple annotations that can be used to add arbitrary
30 * attributes to various parts of the data model.
31 */
32public final class DefaultAnnotations implements SparseAnnotations {
33
Sho SHIMIZUa3d67cd2015-06-04 11:46:48 -070034 public static final SparseAnnotations EMPTY = DefaultAnnotations.builder().build();
Thomas Vachuskaa8f4e7d2015-01-08 17:31:55 -080035
tomf5d85d42014-10-02 05:27:56 -070036 private final Map<String, String> map;
37
38 // For serialization
39 private DefaultAnnotations() {
40 this.map = null;
41 }
42
Marc De Leenheerbb382352015-04-23 18:20:34 -070043 @Override
44 public boolean equals(Object o) {
45 if (this == o) {
46 return true;
47 }
48 if (o == null || getClass() != o.getClass()) {
49 return false;
50 }
51
52 DefaultAnnotations that = (DefaultAnnotations) o;
53
54 return Objects.equals(this.map, that.map);
55
56 }
57
58 @Override
59 public int hashCode() {
60 return Objects.hashCode(this.map);
61 }
62
tomf5d85d42014-10-02 05:27:56 -070063 /**
Ayaka Koshibe08e457a2015-06-25 17:11:54 -070064 * Returns the annotations as a map.
65 *
66 * @return a copy of the contents of the annotations as a map.
67 */
68 public HashMap<String, String> asMap() {
69 return Maps.newHashMap(this.map);
70 }
71
72 /**
tom5a9383a2014-10-02 07:33:52 -070073 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070074 *
tom5a9383a2014-10-02 07:33:52 -070075 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070076 */
77 private DefaultAnnotations(Map<String, String> map) {
78 this.map = map;
79 }
80
81 /**
82 * Creates a new annotations builder.
83 *
84 * @return new annotations builder
85 */
86 public static Builder builder() {
87 return new Builder();
88 }
89
tom5a9383a2014-10-02 07:33:52 -070090 /**
91 * Merges the specified base set of annotations and additional sparse
92 * annotations into new combined annotations. If the supplied sparse
93 * annotations are empty, the original base annotations are returned.
94 * Any keys tagged for removal in the sparse annotations will be omitted
95 * in the resulting merged annotations.
96 *
97 * @param annotations base annotations
98 * @param sparseAnnotations additional sparse annotations
99 * @return combined annotations or the original base annotations if there
100 * are not additional annotations
101 */
102 public static DefaultAnnotations merge(DefaultAnnotations annotations,
103 SparseAnnotations sparseAnnotations) {
104 checkNotNull(annotations, "Annotations cannot be null");
105 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
106 return annotations;
107 }
108
109 // Merge the two maps. Yes, this is not very efficient, but the
110 // use-case implies small maps and infrequent merges, so we opt for
111 // simplicity.
112 Map<String, String> merged = copy(annotations.map);
113 for (String key : sparseAnnotations.keys()) {
114 if (sparseAnnotations.isRemoved(key)) {
115 merged.remove(key);
116 } else {
117 merged.put(key, sparseAnnotations.value(key));
118 }
119 }
120 return new DefaultAnnotations(merged);
121 }
tomf5d85d42014-10-02 05:27:56 -0700122
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700123 /**
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700124 * Creates the union of two given SparseAnnotations.
125 * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
126 * result will be {@link SparseAnnotations} instead of {@link Annotations}.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700127 *
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700128 * A key tagged for removal will remain in the output SparseAnnotations,
129 * if the counterpart of the input does not contain the same key.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700130 *
131 * @param annotations base annotations
132 * @param sparseAnnotations additional sparse annotations
133 * @return combined annotations or the original base annotations if there
134 * are not additional annotations
135 */
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700136 public static SparseAnnotations union(SparseAnnotations annotations,
137 SparseAnnotations sparseAnnotations) {
138
139 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
140 return annotations;
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700141 }
142
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700143 final HashMap<String, String> newMap;
144 if (annotations instanceof DefaultAnnotations) {
145 newMap = copy(((DefaultAnnotations) annotations).map);
146 } else {
147 newMap = new HashMap<>(annotations.keys().size() +
148 sparseAnnotations.keys().size());
149 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700150 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700151
152 putAllSparseAnnotations(newMap, sparseAnnotations);
153 return new DefaultAnnotations(newMap);
154 }
155
156 // adds the key-values contained in sparseAnnotations to
157 // newMap, if sparseAnnotations had a key tagged for removal,
158 // and corresponding key exist in newMap, entry will be removed.
159 // if corresponding key does not exist, removal tag will be added to
160 // the newMap.
161 private static void putAllSparseAnnotations(
162 final HashMap<String, String> newMap,
163 SparseAnnotations sparseAnnotations) {
164
165 for (String key : sparseAnnotations.keys()) {
166 if (sparseAnnotations.isRemoved(key)) {
167 if (newMap.containsKey(key)) {
168 newMap.remove(key);
169 } else {
170 newMap.put(key, Builder.REMOVED);
171 }
172 } else {
173 String value = sparseAnnotations.value(key);
174 newMap.put(key, value);
175 }
176 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700177 }
178
tomf5d85d42014-10-02 05:27:56 -0700179 @Override
180 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700181 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700182 }
183
184 @Override
185 public String value(String key) {
186 String value = map.get(key);
187 return Objects.equals(Builder.REMOVED, value) ? null : value;
188 }
189
190 @Override
191 public boolean isRemoved(String key) {
192 return Objects.equals(Builder.REMOVED, map.get(key));
193 }
194
tom5a9383a2014-10-02 07:33:52 -0700195 @SuppressWarnings("unchecked")
196 private static HashMap<String, String> copy(Map<String, String> original) {
197 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700198 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700199 }
200 throw new IllegalArgumentException("Expecting HashMap instance");
201 }
202
Ayaka Koshibe74b55272015-05-28 15:16:04 -0700203 @Override
204 public String toString() {
205 return (map == null) ? "null" : map.toString();
206 }
207
tomf5d85d42014-10-02 05:27:56 -0700208 /**
209 * Facility for gradually building model annotations.
210 */
211 public static final class Builder {
212
213 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700214 private final Map<String, String> builder = new HashMap<>();
215
216 // Private construction is forbidden.
217 private Builder() {
218 }
219
220 /**
HIGUCHI Yuta3cb7ff92016-01-20 12:11:31 -0800221 * Adds all specified annotation. Any previous value associated with
222 * the given annotations will be overwritten.
223 *
224 * @param base annotations
225 * @return self
226 */
227 public Builder putAll(Annotations base) {
228 if (base instanceof DefaultAnnotations) {
229 builder.putAll(((DefaultAnnotations) base).map);
230
231 } else if (base instanceof SparseAnnotations) {
232 final SparseAnnotations sparse = (SparseAnnotations) base;
233 for (String key : base.keys()) {
234 if (sparse.isRemoved(key)) {
235 remove(key);
236 } else {
237 set(key, base.value(key));
238 }
239 }
240
241 } else {
242 base.keys().forEach(key -> set(key, base.value(key)));
243
244 }
245 return this;
246 }
247
248 /**
tomf5d85d42014-10-02 05:27:56 -0700249 * Adds the specified annotation. Any previous value associated with
250 * the given annotation key will be overwritten.
251 *
252 * @param key annotation key
253 * @param value annotation value
254 * @return self
255 */
256 public Builder set(String key, String value) {
257 builder.put(key, value);
258 return this;
259 }
260
261 /**
262 * Adds the specified annotation. Any previous value associated with
263 * the given annotation key will be tagged for removal.
264 *
265 * @param key annotation key
266 * @return self
267 */
268 public Builder remove(String key) {
269 builder.put(key, REMOVED);
270 return this;
271 }
272
273 /**
274 * Returns immutable annotations built from the accrued key/values pairs.
275 *
276 * @return annotations
277 */
278 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700279 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700280 }
281 }
282}