blob: 001518e3bc4d405dc5006cdd541a12ca4e4be0a5 [file] [log] [blame]
tomf5d85d42014-10-02 05:27:56 -07001package org.onlab.onos.net;
2
3import java.util.HashMap;
4import java.util.Map;
5import java.util.Objects;
6import java.util.Set;
7
tom5a9383a2014-10-02 07:33:52 -07008import static com.google.common.base.Preconditions.checkNotNull;
9
tomf5d85d42014-10-02 05:27:56 -070010/**
11 * Represents a set of simple annotations that can be used to add arbitrary
12 * attributes to various parts of the data model.
13 */
14public final class DefaultAnnotations implements SparseAnnotations {
15
16 private final Map<String, String> map;
17
18 // For serialization
19 private DefaultAnnotations() {
20 this.map = null;
21 }
22
23 /**
tom5a9383a2014-10-02 07:33:52 -070024 * Creates a new set of annotations using clone of the specified hash map.
tomf5d85d42014-10-02 05:27:56 -070025 *
tom5a9383a2014-10-02 07:33:52 -070026 * @param map hash map of key/value pairs
tomf5d85d42014-10-02 05:27:56 -070027 */
28 private DefaultAnnotations(Map<String, String> map) {
29 this.map = map;
30 }
31
32 /**
33 * Creates a new annotations builder.
34 *
35 * @return new annotations builder
36 */
37 public static Builder builder() {
38 return new Builder();
39 }
40
tom5a9383a2014-10-02 07:33:52 -070041 /**
42 * Merges the specified base set of annotations and additional sparse
43 * annotations into new combined annotations. If the supplied sparse
44 * annotations are empty, the original base annotations are returned.
45 * Any keys tagged for removal in the sparse annotations will be omitted
46 * in the resulting merged annotations.
47 *
48 * @param annotations base annotations
49 * @param sparseAnnotations additional sparse annotations
50 * @return combined annotations or the original base annotations if there
51 * are not additional annotations
52 */
53 public static DefaultAnnotations merge(DefaultAnnotations annotations,
54 SparseAnnotations sparseAnnotations) {
55 checkNotNull(annotations, "Annotations cannot be null");
56 if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
57 return annotations;
58 }
59
60 // Merge the two maps. Yes, this is not very efficient, but the
61 // use-case implies small maps and infrequent merges, so we opt for
62 // simplicity.
63 Map<String, String> merged = copy(annotations.map);
64 for (String key : sparseAnnotations.keys()) {
65 if (sparseAnnotations.isRemoved(key)) {
66 merged.remove(key);
67 } else {
68 merged.put(key, sparseAnnotations.value(key));
69 }
70 }
71 return new DefaultAnnotations(merged);
72 }
tomf5d85d42014-10-02 05:27:56 -070073
74 @Override
75 public Set<String> keys() {
76 return map.keySet();
77 }
78
79 @Override
80 public String value(String key) {
81 String value = map.get(key);
82 return Objects.equals(Builder.REMOVED, value) ? null : value;
83 }
84
85 @Override
86 public boolean isRemoved(String key) {
87 return Objects.equals(Builder.REMOVED, map.get(key));
88 }
89
tom5a9383a2014-10-02 07:33:52 -070090 @SuppressWarnings("unchecked")
91 private static HashMap<String, String> copy(Map<String, String> original) {
92 if (original instanceof HashMap) {
93 return (HashMap) ((HashMap) original).clone();
94 }
95 throw new IllegalArgumentException("Expecting HashMap instance");
96 }
97
tomf5d85d42014-10-02 05:27:56 -070098 /**
99 * Facility for gradually building model annotations.
100 */
101 public static final class Builder {
102
103 private static final String REMOVED = "~rEmOvEd~";
tomf5d85d42014-10-02 05:27:56 -0700104 private final Map<String, String> builder = new HashMap<>();
105
106 // Private construction is forbidden.
107 private Builder() {
108 }
109
110 /**
111 * Adds the specified annotation. Any previous value associated with
112 * the given annotation key will be overwritten.
113 *
114 * @param key annotation key
115 * @param value annotation value
116 * @return self
117 */
118 public Builder set(String key, String value) {
119 builder.put(key, value);
120 return this;
121 }
122
123 /**
124 * Adds the specified annotation. Any previous value associated with
125 * the given annotation key will be tagged for removal.
126 *
127 * @param key annotation key
128 * @return self
129 */
130 public Builder remove(String key) {
131 builder.put(key, REMOVED);
132 return this;
133 }
134
135 /**
136 * Returns immutable annotations built from the accrued key/values pairs.
137 *
138 * @return annotations
139 */
140 public DefaultAnnotations build() {
tom5a9383a2014-10-02 07:33:52 -0700141 return new DefaultAnnotations(copy(builder));
tomf5d85d42014-10-02 05:27:56 -0700142 }
143 }
144}