| /* |
| * Copyright 2014-present Open Networking Laboratory |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.onosproject.net; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import com.google.common.collect.Maps; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.collect.Maps.transformValues; |
| |
| /** |
| * Represents a set of simple annotations that can be used to add arbitrary |
| * attributes to various parts of the data model. |
| */ |
| public final class DefaultAnnotations implements SparseAnnotations { |
| |
| public static final SparseAnnotations EMPTY = DefaultAnnotations.builder().build(); |
| |
| private final Map<String, String> map; |
| |
| // For serialization |
| private DefaultAnnotations() { |
| this.map = null; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| DefaultAnnotations that = (DefaultAnnotations) o; |
| |
| return Objects.equals(this.map, that.map); |
| |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(this.map); |
| } |
| |
| /** |
| * Returns the annotations as a map. |
| * |
| * @return a copy of the contents of the annotations as a map. |
| */ |
| public HashMap<String, String> asMap() { |
| return Maps.newHashMap(this.map); |
| } |
| |
| /** |
| * Creates a new set of annotations using clone of the specified hash map. |
| * |
| * @param map hash map of key/value pairs |
| */ |
| private DefaultAnnotations(Map<String, String> map) { |
| this.map = map; |
| } |
| |
| /** |
| * Creates a new annotations builder. |
| * |
| * @return new annotations builder |
| */ |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| /** |
| * Merges the specified base set of annotations and additional sparse |
| * annotations into new combined annotations. If the supplied sparse |
| * annotations are empty, the original base annotations are returned. |
| * Any keys tagged for removal in the sparse annotations will be omitted |
| * in the resulting merged annotations. |
| * |
| * @param annotations base annotations |
| * @param sparseAnnotations additional sparse annotations |
| * @return combined annotations or the original base annotations if there |
| * are not additional annotations |
| */ |
| public static DefaultAnnotations merge(DefaultAnnotations annotations, |
| SparseAnnotations sparseAnnotations) { |
| checkNotNull(annotations, "Annotations cannot be null"); |
| if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) { |
| return annotations; |
| } |
| |
| // Merge the two maps. Yes, this is not very efficient, but the |
| // use-case implies small maps and infrequent merges, so we opt for |
| // simplicity. |
| Map<String, String> merged = copy(annotations.map); |
| for (String key : sparseAnnotations.keys()) { |
| if (sparseAnnotations.isRemoved(key)) { |
| merged.remove(key); |
| } else { |
| merged.put(key, sparseAnnotations.value(key)); |
| } |
| } |
| return new DefaultAnnotations(merged); |
| } |
| |
| /** |
| * Creates the union of two given SparseAnnotations. |
| * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method, |
| * result will be {@link SparseAnnotations} instead of {@link Annotations}. |
| * |
| * A key tagged for removal will remain in the output SparseAnnotations, |
| * if the counterpart of the input does not contain the same key. |
| * |
| * @param annotations base annotations |
| * @param sparseAnnotations additional sparse annotations |
| * @return combined annotations or the original base annotations if there |
| * are not additional annotations |
| */ |
| public static SparseAnnotations union(SparseAnnotations annotations, |
| SparseAnnotations sparseAnnotations) { |
| |
| if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) { |
| return annotations; |
| } |
| |
| if (annotations.keys().isEmpty()) { |
| return sparseAnnotations; |
| } |
| |
| final HashMap<String, String> newMap; |
| if (annotations instanceof DefaultAnnotations) { |
| newMap = copy(((DefaultAnnotations) annotations).map); |
| } else { |
| newMap = new HashMap<>(annotations.keys().size() + |
| sparseAnnotations.keys().size()); |
| putAllSparseAnnotations(newMap, annotations); |
| } |
| |
| putAllSparseAnnotations(newMap, sparseAnnotations); |
| return new DefaultAnnotations(newMap); |
| } |
| |
| // adds the key-values contained in sparseAnnotations to |
| // newMap, if sparseAnnotations had a key tagged for removal, |
| // and corresponding key exist in newMap, entry will be removed. |
| // if corresponding key does not exist, removal tag will be added to |
| // the newMap. |
| private static void putAllSparseAnnotations( |
| final HashMap<String, String> newMap, |
| SparseAnnotations sparseAnnotations) { |
| |
| for (String key : sparseAnnotations.keys()) { |
| if (sparseAnnotations.isRemoved(key)) { |
| if (newMap.containsKey(key)) { |
| newMap.remove(key); |
| } else { |
| newMap.put(key, Builder.REMOVED); |
| } |
| } else { |
| String value = sparseAnnotations.value(key); |
| newMap.put(key, value); |
| } |
| } |
| } |
| |
| @Override |
| public Set<String> keys() { |
| return Collections.unmodifiableSet(map.keySet()); |
| } |
| |
| @Override |
| public String value(String key) { |
| String value = map.get(key); |
| return Objects.equals(Builder.REMOVED, value) ? null : value; |
| } |
| |
| @Override |
| public boolean isRemoved(String key) { |
| return Objects.equals(Builder.REMOVED, map.get(key)); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static HashMap<String, String> copy(Map<String, String> original) { |
| if (original instanceof HashMap) { |
| return (HashMap<String, String>) ((HashMap<?, ?>) original).clone(); |
| } |
| throw new IllegalArgumentException("Expecting HashMap instance"); |
| } |
| |
| @Override |
| public String toString() { |
| return (map == null) ? "null" : map.toString(); |
| } |
| |
| /** |
| * Facility for gradually building model annotations. |
| */ |
| public static final class Builder { |
| |
| private static final String REMOVED = "~rEmOvEd~"; |
| private final Map<String, String> builder = new HashMap<>(); |
| |
| // Private construction is forbidden. |
| private Builder() { |
| } |
| |
| /** |
| * Adds all specified annotation. Any previous value associated with |
| * the given annotations will be overwritten. |
| * |
| * @param base annotations |
| * @return self |
| */ |
| public Builder putAll(Annotations base) { |
| if (base instanceof DefaultAnnotations) { |
| builder.putAll(((DefaultAnnotations) base).map); |
| |
| } else if (base instanceof SparseAnnotations) { |
| final SparseAnnotations sparse = (SparseAnnotations) base; |
| for (String key : base.keys()) { |
| if (sparse.isRemoved(key)) { |
| remove(key); |
| } else { |
| set(key, base.value(key)); |
| } |
| } |
| |
| } else { |
| base.keys().forEach(key -> set(key, base.value(key))); |
| |
| } |
| return this; |
| } |
| |
| /** |
| * Adds all entries in specified map. |
| * Any previous entries with same key will be overwritten. |
| * |
| * @param entries annotation key and value entries |
| * @return self |
| */ |
| public Builder putAll(Map<String, String> entries) { |
| builder.putAll(transformValues(entries, v -> (v == null) ? REMOVED : v)); |
| return this; |
| } |
| |
| /** |
| * Adds the specified annotation. Any previous value associated with |
| * the given annotation key will be overwritten. |
| * |
| * @param key annotation key |
| * @param value annotation value |
| * @return self |
| */ |
| public Builder set(String key, String value) { |
| builder.put(key, value); |
| return this; |
| } |
| |
| /** |
| * Adds the specified annotation. Any previous value associated with |
| * the given annotation key will be tagged for removal. |
| * |
| * @param key annotation key |
| * @return self |
| */ |
| public Builder remove(String key) { |
| builder.put(key, REMOVED); |
| return this; |
| } |
| |
| /** |
| * Returns immutable annotations built from the accrued key/values pairs. |
| * |
| * @return annotations |
| */ |
| public DefaultAnnotations build() { |
| return new DefaultAnnotations(copy(builder)); |
| } |
| } |
| } |