blob: 514152e267e4438349eb886238135dbe4779bb79 [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
32 private final Map<String, String> map;
33
34 // For serialization
35 private DefaultAnnotations() {
36 this.map = null;
37 }
38
39 /**
tom5a9383a2014-10-02 07:33:52 -070040 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070041 *
tom5a9383a2014-10-02 07:33:52 -070042 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070043 */
44 private DefaultAnnotations(Map<String, String> map) {
45 this.map = map;
46 }
47
48 /**
49 * Creates a new annotations builder.
50 *
51 * @return new annotations builder
52 */
53 public static Builder builder() {
54 return new Builder();
55 }
56
tom5a9383a2014-10-02 07:33:52 -070057 /**
58 * Merges the specified base set of annotations and additional sparse
59 * annotations into new combined annotations. If the supplied sparse
60 * annotations are empty, the original base annotations are returned.
61 * Any keys tagged for removal in the sparse annotations will be omitted
62 * in the resulting merged annotations.
63 *
64 * @param annotations base annotations
65 * @param sparseAnnotations additional sparse annotations
66 * @return combined annotations or the original base annotations if there
67 * are not additional annotations
68 */
69 public static DefaultAnnotations merge(DefaultAnnotations annotations,
70 SparseAnnotations sparseAnnotations) {
71 checkNotNull(annotations, "Annotations cannot be null");
72 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
73 return annotations;
74 }
75
76 // Merge the two maps. Yes, this is not very efficient, but the
77 // use-case implies small maps and infrequent merges, so we opt for
78 // simplicity.
79 Map<String, String> merged = copy(annotations.map);
80 for (String key : sparseAnnotations.keys()) {
81 if (sparseAnnotations.isRemoved(key)) {
82 merged.remove(key);
83 } else {
84 merged.put(key, sparseAnnotations.value(key));
85 }
86 }
87 return new DefaultAnnotations(merged);
88 }
tomf5d85d42014-10-02 05:27:56 -070089
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070090 /**
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070091 * Creates the union of two given SparseAnnotations.
92 * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
93 * result will be {@link SparseAnnotations} instead of {@link Annotations}.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070094 *
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -070095 * A key tagged for removal will remain in the output SparseAnnotations,
96 * if the counterpart of the input does not contain the same key.
Yuta HIGUCHI55710e72014-10-02 14:58:32 -070097 *
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 */
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700103 public static SparseAnnotations union(SparseAnnotations annotations,
104 SparseAnnotations sparseAnnotations) {
105
106 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
107 return annotations;
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700108 }
109
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700110 final HashMap<String, String> newMap;
111 if (annotations instanceof DefaultAnnotations) {
112 newMap = copy(((DefaultAnnotations) annotations).map);
113 } else {
114 newMap = new HashMap<>(annotations.keys().size() +
115 sparseAnnotations.keys().size());
116 putAllSparseAnnotations(newMap, annotations);
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700117 }
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700118
119 putAllSparseAnnotations(newMap, sparseAnnotations);
120 return new DefaultAnnotations(newMap);
121 }
122
123 // adds the key-values contained in sparseAnnotations to
124 // newMap, if sparseAnnotations had a key tagged for removal,
125 // and corresponding key exist in newMap, entry will be removed.
126 // if corresponding key does not exist, removal tag will be added to
127 // the newMap.
128 private static void putAllSparseAnnotations(
129 final HashMap<String, String> newMap,
130 SparseAnnotations sparseAnnotations) {
131
132 for (String key : sparseAnnotations.keys()) {
133 if (sparseAnnotations.isRemoved(key)) {
134 if (newMap.containsKey(key)) {
135 newMap.remove(key);
136 } else {
137 newMap.put(key, Builder.REMOVED);
138 }
139 } else {
140 String value = sparseAnnotations.value(key);
141 newMap.put(key, value);
142 }
143 }
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700144 }
145
tomf5d85d42014-10-02 05:27:56 -0700146 @Override
147 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700148 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700149 }
150
151 @Override
152 public String value(String key) {
153 String value = map.get(key);
154 return Objects.equals(Builder.REMOVED, value) ? null : value;
155 }
156
157 @Override
158 public boolean isRemoved(String key) {
159 return Objects.equals(Builder.REMOVED, map.get(key));
160 }
161
tom5a9383a2014-10-02 07:33:52 -0700162 @SuppressWarnings("unchecked")
163 private static HashMap<String, String> copy(Map<String, String> original) {
164 if (original instanceof HashMap) {
Yuta HIGUCHI885be1d2014-10-04 21:47:26 -0700165 return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
tom5a9383a2014-10-02 07:33:52 -0700166 }
167 throw new IllegalArgumentException("Expecting HashMap instance");
168 }
169
tomf5d85d42014-10-02 05:27:56 -0700170 /**
171 * Facility for gradually building model annotations.
172 */
173 public static final class Builder {
174
175 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700176 private final Map<String, String> builder = new HashMap<>();
177
178 // Private construction is forbidden.
179 private Builder() {
180 }
181
182 /**
183 * Adds the specified annotation. Any previous value associated with
184 * the given annotation key will be overwritten.
185 *
186 * @param key annotation key
187 * @param value annotation value
188 * @return self
189 */
190 public Builder set(String key, String value) {
191 builder.put(key, value);
192 return this;
193 }
194
195 /**
196 * Adds the specified annotation. Any previous value associated with
197 * the given annotation key will be tagged for removal.
198 *
199 * @param key annotation key
200 * @return self
201 */
202 public Builder remove(String key) {
203 builder.put(key, REMOVED);
204 return this;
205 }
206
207 /**
208 * Returns immutable annotations built from the accrued key/values pairs.
209 *
210 * @return annotations
211 */
212 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700213 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700214 }
215 }
216}