blob: a0ed2619e50195e0124c677343025ccd90815a11 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
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
41 /**
tom5a9383a2014-10-02 07:33:52 -070042 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070043 *
tom5a9383a2014-10-02 07:33:52 -070044 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070045 */
46 private DefaultAnnotations(Map<String, String> map) {
47 this.map = map;
48 }
49
50 /**
51 * Creates a new annotations builder.
52 *
53 * @return new annotations builder
54 */
55 public static Builder builder() {
56 return new Builder();
57 }
58
tom5a9383a2014-10-02 07:33:52 -070059 /**
60 * Merges the specified base set of annotations and additional sparse
61 * annotations into new combined annotations. If the supplied sparse
62 * annotations are empty, the original base annotations are returned.
63 * Any keys tagged for removal in the sparse annotations will be omitted
64 * in the resulting merged annotations.
65 *
66 * @param annotations base annotations
67 * @param sparseAnnotations additional sparse annotations
68 * @return combined annotations or the original base annotations if there
69 * are not additional annotations
70 */
71 public static DefaultAnnotations merge(DefaultAnnotations annotations,
72 SparseAnnotations sparseAnnotations) {
73 checkNotNull(annotations, "Annotations cannot be null");
74 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
75 return annotations;
76 }
77
78 // Merge the two maps. Yes, this is not very efficient, but the
79 // use-case implies small maps and infrequent merges, so we opt for
80 // simplicity.
81 Map<String, String> merged = copy(annotations.map);
82 for (String key : sparseAnnotations.keys()) {
83 if (sparseAnnotations.isRemoved(key)) {
84 merged.remove(key);
85 } else {
86 merged.put(key, sparseAnnotations.value(key));
87 }
88 }
89 return new DefaultAnnotations(merged);
90 }
tomf5d85d42014-10-02 05:27:56 -070091
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070092 /**
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070093 * Creates the union of two given SparseAnnotations.
94 * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
95 * result will be {@link SparseAnnotations} instead of {@link Annotations}.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070096 *
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070097 * A key tagged for removal will remain in the output SparseAnnotations,
98 * if the counterpart of the input does not contain the same key.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070099 *
100 * @param annotations base annotations
101 * @param sparseAnnotations additional sparse annotations
102 * @return combined annotations or the original base annotations if there
103 * are not additional annotations
104 */
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700105 public static SparseAnnotations union(SparseAnnotations annotations,
106 SparseAnnotations sparseAnnotations) {
107
108 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
109 return annotations;
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700110 }
111
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700112 final HashMap<String, String> newMap;
113 if (annotations instanceof DefaultAnnotations) {
114 newMap = copy(((DefaultAnnotations) annotations).map);
115 } else {
116 newMap = new HashMap<>(annotations.keys().size() +
117 sparseAnnotations.keys().size());
118 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700119 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700120
121 putAllSparseAnnotations(newMap, sparseAnnotations);
122 return new DefaultAnnotations(newMap);
123 }
124
125 // adds the key-values contained in sparseAnnotations to
126 // newMap, if sparseAnnotations had a key tagged for removal,
127 // and corresponding key exist in newMap, entry will be removed.
128 // if corresponding key does not exist, removal tag will be added to
129 // the newMap.
130 private static void putAllSparseAnnotations(
131 final HashMap<String, String> newMap,
132 SparseAnnotations sparseAnnotations) {
133
134 for (String key : sparseAnnotations.keys()) {
135 if (sparseAnnotations.isRemoved(key)) {
136 if (newMap.containsKey(key)) {
137 newMap.remove(key);
138 } else {
139 newMap.put(key, Builder.REMOVED);
140 }
141 } else {
142 String value = sparseAnnotations.value(key);
143 newMap.put(key, value);
144 }
145 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700146 }
147
tomf5d85d42014-10-02 05:27:56 -0700148 @Override
149 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700150 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700151 }
152
153 @Override
154 public String value(String key) {
155 String value = map.get(key);
156 return Objects.equals(Builder.REMOVED, value) ? null : value;
157 }
158
159 @Override
160 public boolean isRemoved(String key) {
161 return Objects.equals(Builder.REMOVED, map.get(key));
162 }
163
tom5a9383a2014-10-02 07:33:52 -0700164 @SuppressWarnings("unchecked")
165 private static HashMap<String, String> copy(Map<String, String> original) {
166 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700167 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700168 }
169 throw new IllegalArgumentException("Expecting HashMap instance");
170 }
171
tomf5d85d42014-10-02 05:27:56 -0700172 /**
173 * Facility for gradually building model annotations.
174 */
175 public static final class Builder {
176
177 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700178 private final Map<String, String> builder = new HashMap<>();
179
180 // Private construction is forbidden.
181 private Builder() {
182 }
183
184 /**
185 * Adds the specified annotation. Any previous value associated with
186 * the given annotation key will be overwritten.
187 *
188 * @param key annotation key
189 * @param value annotation value
190 * @return self
191 */
192 public Builder set(String key, String value) {
193 builder.put(key, value);
194 return this;
195 }
196
197 /**
198 * Adds the specified annotation. Any previous value associated with
199 * the given annotation key will be tagged for removal.
200 *
201 * @param key annotation key
202 * @return self
203 */
204 public Builder remove(String key) {
205 builder.put(key, REMOVED);
206 return this;
207 }
208
209 /**
210 * Returns immutable annotations built from the accrued key/values pairs.
211 *
212 * @return annotations
213 */
214 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700215 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700216 }
217 }
218}