blob: 13e74b851f4463ccde85cb5889a23ffc9b51a28b [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Ray Milkey34c95902015-04-15 09:47:53 -07002 * Copyright 2014-2015 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
tom5a9383a2014-10-02 07:33:52 -070024import static com.google.common.base.Preconditions.checkNotNull;
25
tomf5d85d42014-10-02 05:27:56 -070026/**
27 * Represents a set of simple annotations that can be used to add arbitrary
28 * attributes to various parts of the data model.
29 */
30public final class DefaultAnnotations implements SparseAnnotations {
31
Thomas Vachuskaa8f4e7d2015-01-08 17:31:55 -080032 public static final Annotations EMPTY = DefaultAnnotations.builder().build();
33
tomf5d85d42014-10-02 05:27:56 -070034 private final Map<String, String> map;
35
36 // For serialization
37 private DefaultAnnotations() {
38 this.map = null;
39 }
40
Marc De Leenheerbb382352015-04-23 18:20:34 -070041 @Override
42 public boolean equals(Object o) {
43 if (this == o) {
44 return true;
45 }
46 if (o == null || getClass() != o.getClass()) {
47 return false;
48 }
49
50 DefaultAnnotations that = (DefaultAnnotations) o;
51
52 return Objects.equals(this.map, that.map);
53
54 }
55
56 @Override
57 public int hashCode() {
58 return Objects.hashCode(this.map);
59 }
60
tomf5d85d42014-10-02 05:27:56 -070061 /**
tom5a9383a2014-10-02 07:33:52 -070062 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070063 *
tom5a9383a2014-10-02 07:33:52 -070064 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070065 */
66 private DefaultAnnotations(Map<String, String> map) {
67 this.map = map;
68 }
69
70 /**
71 * Creates a new annotations builder.
72 *
73 * @return new annotations builder
74 */
75 public static Builder builder() {
76 return new Builder();
77 }
78
tom5a9383a2014-10-02 07:33:52 -070079 /**
80 * Merges the specified base set of annotations and additional sparse
81 * annotations into new combined annotations. If the supplied sparse
82 * annotations are empty, the original base annotations are returned.
83 * Any keys tagged for removal in the sparse annotations will be omitted
84 * in the resulting merged annotations.
85 *
86 * @param annotations base annotations
87 * @param sparseAnnotations additional sparse annotations
88 * @return combined annotations or the original base annotations if there
89 * are not additional annotations
90 */
91 public static DefaultAnnotations merge(DefaultAnnotations annotations,
92 SparseAnnotations sparseAnnotations) {
93 checkNotNull(annotations, "Annotations cannot be null");
94 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
95 return annotations;
96 }
97
98 // Merge the two maps. Yes, this is not very efficient, but the
99 // use-case implies small maps and infrequent merges, so we opt for
100 // simplicity.
101 Map<String, String> merged = copy(annotations.map);
102 for (String key : sparseAnnotations.keys()) {
103 if (sparseAnnotations.isRemoved(key)) {
104 merged.remove(key);
105 } else {
106 merged.put(key, sparseAnnotations.value(key));
107 }
108 }
109 return new DefaultAnnotations(merged);
110 }
tomf5d85d42014-10-02 05:27:56 -0700111
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700112 /**
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700113 * Creates the union of two given SparseAnnotations.
114 * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
115 * result will be {@link SparseAnnotations} instead of {@link Annotations}.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700116 *
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700117 * A key tagged for removal will remain in the output SparseAnnotations,
118 * if the counterpart of the input does not contain the same key.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700119 *
120 * @param annotations base annotations
121 * @param sparseAnnotations additional sparse annotations
122 * @return combined annotations or the original base annotations if there
123 * are not additional annotations
124 */
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700125 public static SparseAnnotations union(SparseAnnotations annotations,
126 SparseAnnotations sparseAnnotations) {
127
128 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
129 return annotations;
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700130 }
131
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700132 final HashMap<String, String> newMap;
133 if (annotations instanceof DefaultAnnotations) {
134 newMap = copy(((DefaultAnnotations) annotations).map);
135 } else {
136 newMap = new HashMap<>(annotations.keys().size() +
137 sparseAnnotations.keys().size());
138 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700139 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700140
141 putAllSparseAnnotations(newMap, sparseAnnotations);
142 return new DefaultAnnotations(newMap);
143 }
144
145 // adds the key-values contained in sparseAnnotations to
146 // newMap, if sparseAnnotations had a key tagged for removal,
147 // and corresponding key exist in newMap, entry will be removed.
148 // if corresponding key does not exist, removal tag will be added to
149 // the newMap.
150 private static void putAllSparseAnnotations(
151 final HashMap<String, String> newMap,
152 SparseAnnotations sparseAnnotations) {
153
154 for (String key : sparseAnnotations.keys()) {
155 if (sparseAnnotations.isRemoved(key)) {
156 if (newMap.containsKey(key)) {
157 newMap.remove(key);
158 } else {
159 newMap.put(key, Builder.REMOVED);
160 }
161 } else {
162 String value = sparseAnnotations.value(key);
163 newMap.put(key, value);
164 }
165 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700166 }
167
tomf5d85d42014-10-02 05:27:56 -0700168 @Override
169 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700170 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700171 }
172
173 @Override
174 public String value(String key) {
175 String value = map.get(key);
176 return Objects.equals(Builder.REMOVED, value) ? null : value;
177 }
178
179 @Override
180 public boolean isRemoved(String key) {
181 return Objects.equals(Builder.REMOVED, map.get(key));
182 }
183
tom5a9383a2014-10-02 07:33:52 -0700184 @SuppressWarnings("unchecked")
185 private static HashMap<String, String> copy(Map<String, String> original) {
186 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700187 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700188 }
189 throw new IllegalArgumentException("Expecting HashMap instance");
190 }
191
tomf5d85d42014-10-02 05:27:56 -0700192 /**
193 * Facility for gradually building model annotations.
194 */
195 public static final class Builder {
196
197 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700198 private final Map<String, String> builder = new HashMap<>();
199
200 // Private construction is forbidden.
201 private Builder() {
202 }
203
204 /**
205 * Adds the specified annotation. Any previous value associated with
206 * the given annotation key will be overwritten.
207 *
208 * @param key annotation key
209 * @param value annotation value
210 * @return self
211 */
212 public Builder set(String key, String value) {
213 builder.put(key, value);
214 return this;
215 }
216
217 /**
218 * Adds the specified annotation. Any previous value associated with
219 * the given annotation key will be tagged for removal.
220 *
221 * @param key annotation key
222 * @return self
223 */
224 public Builder remove(String key) {
225 builder.put(key, REMOVED);
226 return this;
227 }
228
229 /**
230 * Returns immutable annotations built from the accrued key/values pairs.
231 *
232 * @return annotations
233 */
234 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700235 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700236 }
237 }
238}