blob: 0c0f375516b9803ab39700ea2860bb5cbde4e2e6 [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 /**
76 * Convert Annotations to DefaultAnnotations if needed and merges.
77 *
78 * @see #merge(DefaultAnnotations, SparseAnnotations)
79 *
80 * @param annotations base annotations
81 * @param sparseAnnotations additional sparse annotations
82 * @return combined annotations or the original base annotations if there
83 * are not additional annotations
84 */
85 public static DefaultAnnotations merge(Annotations annotations,
86 SparseAnnotations sparseAnnotations) {
87 if (annotations instanceof DefaultAnnotations) {
88 return merge((DefaultAnnotations) annotations, sparseAnnotations);
89 }
90
91 DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
92 for (String key : annotations.keys()) {
93 builder.set(key, annotations.value(key));
94 }
95 return merge(builder.build(), sparseAnnotations);
96 }
97
tomf5d85d42014-10-02 05:27:56 -070098 @Override
99 public Set<String> keys() {
Yuta HIGUCHI55710e72014-10-02 14:58:32 -0700100 // TODO: unmodifiable to be removed after switching to ImmutableMap;
101 return Collections.unmodifiableSet(map.keySet());
tomf5d85d42014-10-02 05:27:56 -0700102 }
103
104 @Override
105 public String value(String key) {
106 String value = map.get(key);
107 return Objects.equals(Builder.REMOVED, value) ? null : value;
108 }
109
110 @Override
111 public boolean isRemoved(String key) {
112 return Objects.equals(Builder.REMOVED, map.get(key));
113 }
114
tom5a9383a2014-10-02 07:33:52 -0700115 @SuppressWarnings("unchecked")
116 private static HashMap<String, String> copy(Map<String, String> original) {
117 if (original instanceof HashMap) {
118 return (HashMap) ((HashMap) original).clone();
119 }
120 throw new IllegalArgumentException("Expecting HashMap instance");
121 }
122
tomf5d85d42014-10-02 05:27:56 -0700123 /**
124 * Facility for gradually building model annotations.
125 */
126 public static final class Builder {
127
128 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700129 private final Map<String, String> builder = new HashMap<>();
130
131 // Private construction is forbidden.
132 private Builder() {
133 }
134
135 /**
136 * Adds the specified annotation. Any previous value associated with
137 * the given annotation key will be overwritten.
138 *
139 * @param key annotation key
140 * @param value annotation value
141 * @return self
142 */
143 public Builder set(String key, String value) {
144 builder.put(key, value);
145 return this;
146 }
147
148 /**
149 * Adds the specified annotation. Any previous value associated with
150 * the given annotation key will be tagged for removal.
151 *
152 * @param key annotation key
153 * @return self
154 */
155 public Builder remove(String key) {
156 builder.put(key, REMOVED);
157 return this;
158 }
159
160 /**
161 * Returns immutable annotations built from the accrued key/values pairs.
162 *
163 * @return annotations
164 */
165 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700166 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700167 }
168 }
169}