blob: b2e7e61b1f99107f35b23b85bfeeae023eb7b1e0 [file] [log] [blame]
tomf5d85d42014-10-02 05:27:56 -07001package org.onlab.onos.net;
2
Yuta HIGUCHI55710e72014-10-02 14:58:32 -07003import java.util.Collections;
tomf5d85d42014-10-02 05:27:56 -07004import java.util.HashMap;
5import java.util.Map;
6import java.util.Objects;
7import java.util.Set;
8
tom5a9383a2014-10-02 07:33:52 -07009import static com.google.common.base.Preconditions.checkNotNull;
10
tomf5d85d42014-10-02 05:27:56 -070011/**
12 * Represents a set of simple annotations that can be used to add arbitrary
13 * attributes to various parts of the data model.
14 */
15public final class DefaultAnnotations implements SparseAnnotations {
16
17 private final Map<String, String> map;
18
19 // For serialization
20 private DefaultAnnotations() {
21 this.map = null;
22 }
23
24 /**
tom5a9383a2014-10-02 07:33:52 -070025 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070026 *
tom5a9383a2014-10-02 07:33:52 -070027 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070028 */
29 private DefaultAnnotations(Map<String, String> map) {
30 this.map = map;
31 }
32
33 /**
34 * Creates a new annotations builder.
35 *
36 * @return new annotations builder
37 */
38 public static Builder builder() {
39 return new Builder();
40 }
41
tom5a9383a2014-10-02 07:33:52 -070042 /**
43 * Merges the specified base set of annotations and additional sparse
44 * annotations into new combined annotations. If the supplied sparse
45 * annotations are empty, the original base annotations are returned.
46 * Any keys tagged for removal in the sparse annotations will be omitted
47 * in the resulting merged annotations.
48 *
49 * @param annotations base annotations
50 * @param sparseAnnotations additional sparse annotations
51 * @return combined annotations or the original base annotations if there
52 * are not additional annotations
53 */
54 public static DefaultAnnotations merge(DefaultAnnotations annotations,
55 SparseAnnotations sparseAnnotations) {
56 checkNotNull(annotations, "Annotations cannot be null");
57 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
58 return annotations;
59 }
60
61 // Merge the two maps. Yes, this is not very efficient, but the
62 // use-case implies small maps and infrequent merges, so we opt for
63 // simplicity.
64 Map<String, String> merged = copy(annotations.map);
65 for (String key : sparseAnnotations.keys()) {
66 if (sparseAnnotations.isRemoved(key)) {
67 merged.remove(key);
68 } else {
69 merged.put(key, sparseAnnotations.value(key));
70 }
71 }
72 return new DefaultAnnotations(merged);
73 }
tomf5d85d42014-10-02 05:27:56 -070074
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070075 /**
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070076 * Creates the union of two given SparseAnnotations.
77 * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
78 * result will be {@link SparseAnnotations} instead of {@link Annotations}.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070079 *
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070080 * A key tagged for removal will remain in the output SparseAnnotations,
81 * if the counterpart of the input does not contain the same key.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070082 *
83 * @param annotations base annotations
84 * @param sparseAnnotations additional sparse annotations
85 * @return combined annotations or the original base annotations if there
86 * are not additional annotations
87 */
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070088 public static SparseAnnotations union(SparseAnnotations annotations,
89 SparseAnnotations sparseAnnotations) {
90
91 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
92 return annotations;
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070093 }
94
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070095 final HashMap<String, String> newMap;
96 if (annotations instanceof DefaultAnnotations) {
97 newMap = copy(((DefaultAnnotations) annotations).map);
98 } else {
99 newMap = new HashMap<>(annotations.keys().size() +
100 sparseAnnotations.keys().size());
101 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700102 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700103
104 putAllSparseAnnotations(newMap, sparseAnnotations);
105 return new DefaultAnnotations(newMap);
106 }
107
108 // adds the key-values contained in sparseAnnotations to
109 // newMap, if sparseAnnotations had a key tagged for removal,
110 // and corresponding key exist in newMap, entry will be removed.
111 // if corresponding key does not exist, removal tag will be added to
112 // the newMap.
113 private static void putAllSparseAnnotations(
114 final HashMap<String, String> newMap,
115 SparseAnnotations sparseAnnotations) {
116
117 for (String key : sparseAnnotations.keys()) {
118 if (sparseAnnotations.isRemoved(key)) {
119 if (newMap.containsKey(key)) {
120 newMap.remove(key);
121 } else {
122 newMap.put(key, Builder.REMOVED);
123 }
124 } else {
125 String value = sparseAnnotations.value(key);
126 newMap.put(key, value);
127 }
128 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700129 }
130
tomf5d85d42014-10-02 05:27:56 -0700131 @Override
132 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700133 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700134 }
135
136 @Override
137 public String value(String key) {
138 String value = map.get(key);
139 return Objects.equals(Builder.REMOVED, value) ? null : value;
140 }
141
142 @Override
143 public boolean isRemoved(String key) {
144 return Objects.equals(Builder.REMOVED, map.get(key));
145 }
146
tom5a9383a2014-10-02 07:33:52 -0700147 @SuppressWarnings("unchecked")
148 private static HashMap<String, String> copy(Map<String, String> original) {
149 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700150 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700151 }
152 throw new IllegalArgumentException("Expecting HashMap instance");
153 }
154
tomf5d85d42014-10-02 05:27:56 -0700155 /**
156 * Facility for gradually building model annotations.
157 */
158 public static final class Builder {
159
160 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700161 private final Map<String, String> builder = new HashMap<>();
162
163 // Private construction is forbidden.
164 private Builder() {
165 }
166
167 /**
168 * Adds the specified annotation. Any previous value associated with
169 * the given annotation key will be overwritten.
170 *
171 * @param key annotation key
172 * @param value annotation value
173 * @return self
174 */
175 public Builder set(String key, String value) {
176 builder.put(key, value);
177 return this;
178 }
179
180 /**
181 * Adds the specified annotation. Any previous value associated with
182 * the given annotation key will be tagged for removal.
183 *
184 * @param key annotation key
185 * @return self
186 */
187 public Builder remove(String key) {
188 builder.put(key, REMOVED);
189 return this;
190 }
191
192 /**
193 * Returns immutable annotations built from the accrued key/values pairs.
194 *
195 * @return annotations
196 */
197 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700198 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700199 }
200 }
201}