From: Robert Varga Date: Thu, 22 Nov 2018 17:43:42 +0000 (+0100) Subject: Add ImmutableMapTemplate X-Git-Tag: v2.0.13~6 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=6ce1221b48e7ef7ac6ef0d439ee8ec127565bf12;p=yangtools.git Add ImmutableMapTemplate We have couple some hot paths where we know the keySet of an immutable map we want to instantiate. For these, using ImmutableOffsetMap.*copyOf() is wasteful, as it performs unnecessary cache looks and map lookups. ImmutableMapTemplate can be used to amortize these preparatory steps, so that an ImmutableOffsetMap (or SharedSingletonMap) can efficiently be produced by either transforming values of some other map, or by supplying values in the correct order. Since we are in the area, improve documentation and @NonNull annotations of both ImmutableOffsetMap and SharedSingletonMap. JIRA: YANGTOOLS-917 Change-Id: I8e7808b9bfe751885b842715f7e0346c0e7485b2 Signed-off-by: Robert Varga (cherry picked from commit 048042bd88cc9b1f481c018363c331bca83e4e1f) --- diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java new file mode 100644 index 0000000000..50e3c9ffbf --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.util; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiFunction; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.concepts.Immutable; + +/** + * Template for instantiating {@link UnmodifiableMapPhase} instances with a fixed set of keys. The template can then be + * used as a factory for instances via using {@link #instantiateTransformed(Map, BiFunction)} or, more efficiently, + * using {@link #instantiateWithValues(Object[])} where the argument array has values ordered corresponding to the key + * order defined by {@link #keySet()}. + * + * @param the type of keys maintained by this template + */ +@Beta +public abstract class ImmutableMapTemplate implements Immutable { + private abstract static class AbstractMultiple extends ImmutableMapTemplate { + private final @NonNull ImmutableMap offsets; + + AbstractMultiple(final ImmutableMap offsets) { + this.offsets = requireNonNull(offsets); + } + + @Override + public final Set keySet() { + return offsets.keySet(); + } + + @Override + public final @NonNull ImmutableOffsetMap instantiateTransformed(final Map fromMap, + final BiFunction valueTransformer) { + final int size = offsets.size(); + checkArgument(fromMap.size() == size); + + @SuppressWarnings("unchecked") + final V[] objects = (V[]) new Object[size]; + for (Entry entry : fromMap.entrySet()) { + final K key = requireNonNull(entry.getKey()); + final Integer offset = offsets.get(key); + checkArgument(offset != null, "Key %s present in input, but not in offsets %s", key, offsets); + + objects[offset.intValue()] = transformValue(key, entry.getValue(), valueTransformer); + } + + return createMap(offsets, objects); + } + + @Override + @SafeVarargs + public final @NonNull UnmodifiableMapPhase instantiateWithValues(final V... values) { + checkArgument(values.length == offsets.size()); + final V[] copy = values.clone(); + Arrays.stream(copy).forEach(Objects::requireNonNull); + return createMap(offsets, values); + } + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).add("offsets", offsets).toString(); + } + + abstract @NonNull ImmutableOffsetMap createMap(ImmutableMap offsets, V[] objects); + } + + private static final class Ordered extends AbstractMultiple { + Ordered(final Collection keys) { + super(OffsetMapCache.orderedOffsets(keys)); + } + + @Override + @NonNull ImmutableOffsetMap createMap(final ImmutableMap offsets, final V[] objects) { + return new ImmutableOffsetMap.Ordered<>(offsets, objects); + } + } + + private static final class Unordered extends AbstractMultiple { + Unordered(final Collection keys) { + super(OffsetMapCache.unorderedOffsets(keys)); + } + + @Override + @NonNull ImmutableOffsetMap createMap(final ImmutableMap offsets, final V[] objects) { + return new ImmutableOffsetMap.Unordered<>(offsets, objects); + } + } + + private abstract static class AbstractSingle extends ImmutableMapTemplate { + private final @NonNull SingletonSet keySet; + + AbstractSingle(final K key) { + this.keySet = SharedSingletonMap.cachedSet(key); + } + + @Override + public Set keySet() { + return keySet; + } + + @Override + public final @NonNull SharedSingletonMap instantiateTransformed(final Map fromMap, + final BiFunction valueTransformer) { + final Iterator> it = fromMap.entrySet().iterator(); + checkArgument(it.hasNext(), "Input is empty while expecting 1 item"); + + final Entry entry = it.next(); + final K expected = keySet.getElement(); + final K actual = entry.getKey(); + checkArgument(expected.equals(actual), "Unexpected key %s, expecting %s", actual, expected); + + final V value = transformValue(actual, entry.getValue(), valueTransformer); + checkArgument(!it.hasNext(), "Input has more than one item"); + + return createMap(keySet, value); + } + + @Override + @SafeVarargs + public final @NonNull UnmodifiableMapPhase instantiateWithValues(final V... values) { + checkArgument(values.length == 1); + return createMap(keySet, values[0]); + } + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).add("keySet", keySet).toString(); + } + + abstract @NonNull SharedSingletonMap createMap(SingletonSet keySet, V value); + } + + private static final class SingleOrdered extends AbstractSingle { + SingleOrdered(final K key) { + super(key); + } + + @Override + @NonNull SharedSingletonMap createMap(final SingletonSet keySet, final V value) { + return new SharedSingletonMap.Ordered<>(keySet, value); + } + } + + private static final class SingleUnordered extends AbstractSingle { + SingleUnordered(final K key) { + super(key); + } + + @Override + @NonNull SharedSingletonMap createMap(final SingletonSet keySet, final V value) { + return new SharedSingletonMap.Unordered<>(keySet, value); + } + } + + ImmutableMapTemplate() { + // Hidden on purpose + } + + /** + * Create a template which produces Maps with specified keys, with iteration order matching the iteration order + * of {@code keys}. {@link #keySet()} will return these keys in exactly the same order. The resulting map will + * retain insertion order through {@link UnmodifiableMapPhase#toModifiableMap()} transformations. + * + * @param keys Keys in requested iteration order. + * @param the type of keys maintained by resulting template + * @return A template object. + * @throws NullPointerException if {@code keys} or any of its elements is null + * @throws IllegalArgumentException if {@code keys} is empty + */ + public static @NonNull ImmutableMapTemplate ordered(final Collection keys) { + switch (keys.size()) { + case 0: + throw new IllegalArgumentException("Proposed keyset must not be empty"); + case 1: + return new SingleOrdered<>(keys.iterator().next()); + default: + return new Ordered<>(keys); + } + } + + /** + * Create a template which produces Maps with specified keys, with unconstrained iteration order. Produced maps + * will have the iteration order matching the order returned by {@link #keySet()}. The resulting map will + * NOT retain ordering through {@link UnmodifiableMapPhase#toModifiableMap()} transformations. + * + * @param keys Keys in any iteration order. + * @param the type of keys maintained by resulting template + * @return A template object. + * @throws NullPointerException if {@code keys} or any of its elements is null + * @throws IllegalArgumentException if {@code keys} is empty + */ + public static @NonNull ImmutableMapTemplate unordered(final Collection keys) { + switch (keys.size()) { + case 0: + throw new IllegalArgumentException("Proposed keyset must not be empty"); + case 1: + return new SingleUnordered<>(keys.iterator().next()); + default: + return new Unordered<>(keys); + } + } + + /** + * Instantiate an immutable map by applying specified {@code transformer} to values of {@code fromMap}. + * + * @param fromMap Input map + * @param keyValueTransformer Transformation to apply to values + * @param the type of input values + * @param the type of mapped values + * @return An immutable map + * @throws NullPointerException if any of the arguments is null or if the transformer produces a {@code null} value + * @throws IllegalArgumentException if {@code fromMap#keySet()} does not match this template's keys + */ + public abstract @NonNull UnmodifiableMapPhase instantiateTransformed(Map fromMap, + BiFunction keyValueTransformer); + + /** + * Instantiate an immutable map by filling values from provided array. The array MUST be ordered to match key order + * as returned by {@link #keySet()}. + * + * @param values Values to use + * @param the type of mapped values + * @return An immutable map + * @throws NullPointerException if {@code values} or any of its elements is null + * @throws IllegalArgumentException if {@code values.lenght} does not match the number of keys in this template + */ + @SuppressWarnings("unchecked") + public abstract @NonNull UnmodifiableMapPhase instantiateWithValues(V... values); + + /** + * Returns the set of keys expected by this template, in the iteration order Maps resulting from instantiation + * will have. + * + * @return This template's key set + * @see Map#keySet() + */ + public abstract Set keySet(); + + final @NonNull V transformValue(final K key, final T input, final BiFunction transformer) { + final V value = transformer.apply(key, input); + checkArgument(value != null, "Transformer returned null for input %s at key %s", input, key); + return value; + } +} diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java index e899a1e60e..9a15f96adf 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; /** @@ -43,7 +44,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase extends ImmutableOffsetMap { private static final long serialVersionUID = 1L; - Ordered(final Map offsets, final V[] objects) { + Ordered(final ImmutableMap offsets, final V[] objects) { super(offsets, objects); } @@ -63,7 +64,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase extends ImmutableOffsetMap { private static final long serialVersionUID = 1L; - Unordered(final Map offsets, final V[] objects) { + Unordered(final ImmutableMap offsets, final V[] objects) { super(offsets, objects); } @@ -84,8 +85,8 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase offsets; - private final transient V[] objects; + private final transient @NonNull ImmutableMap offsets; + private final transient @NonNull V[] objects; private transient int hashCode; /** @@ -95,7 +96,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase offsets, @Nonnull final V[] objects) { + ImmutableOffsetMap(final ImmutableMap offsets, final V[] objects) { this.offsets = requireNonNull(offsets); this.objects = requireNonNull(objects); checkArgument(offsets.size() == objects.length); @@ -108,19 +109,16 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase keys, V[] values) throws IOException; /** - * Create an {@link ImmutableOffsetMap} as a copy of an existing map. This - * is actually not completely true, as this method returns an - * {@link ImmutableMap} for empty and singleton inputs, as those are more - * memory-efficient. This method also recognizes {@link ImmutableOffsetMap} - * on input, and returns it back without doing anything else. It also - * recognizes {@link MutableOffsetMap} (as returned by - * {@link #toModifiableMap()}) and makes an efficient copy of its contents. - * All other maps are converted to an {@link ImmutableOffsetMap} with the - * same iteration order as input. + * Create an {@link ImmutableOffsetMap} as a copy of an existing map. This is actually not completely true, as this + * method returns an {@link ImmutableMap} for empty and singleton inputs, as those are more memory-efficient. This + * method also recognizes {@link ImmutableOffsetMap} and {@link SharedSingletonMap} on input, and returns it back + * without doing anything else. It also recognizes {@link MutableOffsetMap} (as returned by + * {@link #toModifiableMap()}) and makes an efficient copy of its contents. All other maps are converted to an + * {@link ImmutableOffsetMap} with the same iteration order as input. * - * @param map - * Input map, may not be null. + * @param map Input map, may not be null. * @return An isolated, immutable copy of the input map + * @throws NullPointerException if {@code map} or any of its elements is null. */ @Nonnull public static Map orderedCopyOf(@Nonnull final Map map) { final Map common = commonCopy(map); @@ -135,7 +133,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase offsets = OffsetMapCache.orderedOffsets(map.keySet()); + final ImmutableMap offsets = OffsetMapCache.orderedOffsets(map.keySet()); @SuppressWarnings("unchecked") final V[] array = (V[]) new Object[offsets.size()]; for (Entry e : map.entrySet()) { @@ -146,19 +144,16 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase Map unorderedCopyOf(@Nonnull final Map map) { final Map common = commonCopy(map); @@ -173,7 +168,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase offsets = OffsetMapCache.unorderedOffsets(map.keySet()); + final ImmutableMap offsets = OffsetMapCache.unorderedOffsets(map.keySet()); @SuppressWarnings("unchecked") final V[] array = (V[]) new Object[offsets.size()]; for (Entry e : map.entrySet()) { @@ -347,7 +342,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase offsets() { + final @NonNull ImmutableMap offsets() { return offsets; } diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/MutableOffsetMap.java b/common/util/src/main/java/org/opendaylight/yangtools/util/MutableOffsetMap.java index 09511523bb..d068594386 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/MutableOffsetMap.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/MutableOffsetMap.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import javax.annotation.Nonnull; +import org.eclipse.jdt.annotation.NonNull; /** * A mutable version of {@link ImmutableOffsetMap}. It inherits the set of mappings from the immutable version and @@ -54,7 +54,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement super(OffsetMapCache.orderedOffsets(source.keySet()), source, new LinkedHashMap<>()); } - Ordered(final Map offsets, final V[] objects) { + Ordered(final ImmutableMap offsets, final V[] objects) { super(offsets, objects, new LinkedHashMap<>()); } @@ -69,7 +69,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement } @Override - UnmodifiableMapPhase unmodifiedMap(final Map offsetMap, final V[] values) { + UnmodifiableMapPhase unmodifiedMap(final ImmutableMap offsetMap, final V[] values) { return new ImmutableOffsetMap.Ordered<>(offsetMap, values); } @@ -88,7 +88,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement super(OffsetMapCache.unorderedOffsets(source.keySet()), source, new HashMap<>()); } - Unordered(final Map offsets, final V[] objects) { + Unordered(final ImmutableMap offsets, final V[] objects) { super(offsets, objects, new HashMap<>()); } @@ -99,12 +99,12 @@ public abstract class MutableOffsetMap extends AbstractMap implement @Override UnmodifiableMapPhase modifiedMap(final List keys, final V[] values) { - final Map offsets = OffsetMapCache.unorderedOffsets(keys); + final ImmutableMap offsets = OffsetMapCache.unorderedOffsets(keys); return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, values)); } @Override - UnmodifiableMapPhase unmodifiedMap(final Map offsetMap, final V[] values) { + UnmodifiableMapPhase unmodifiedMap(final ImmutableMap offsetMap, final V[] values) { return new ImmutableOffsetMap.Unordered<>(offsetMap, values); } @@ -116,7 +116,8 @@ public abstract class MutableOffsetMap extends AbstractMap implement private static final Object[] EMPTY_ARRAY = new Object[0]; private static final Object REMOVED = new Object(); - private final Map offsets; + + private final ImmutableMap offsets; private HashMap newKeys; private Object[] objects; private int removed = 0; @@ -126,7 +127,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement private transient volatile int modCount; private boolean needClone = true; - MutableOffsetMap(final Map offsets, final V[] objects, final HashMap newKeys) { + MutableOffsetMap(final ImmutableMap offsets, final V[] objects, final HashMap newKeys) { verify(newKeys.isEmpty()); this.offsets = requireNonNull(offsets); this.objects = requireNonNull(objects); @@ -139,7 +140,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement } @SuppressWarnings("unchecked") - MutableOffsetMap(final Map offsets, final Map source, final HashMap newKeys) { + MutableOffsetMap(final ImmutableMap offsets, final Map source, final HashMap newKeys) { this(offsets, (V[]) new Object[offsets.size()], newKeys); for (Entry e : source.entrySet()) { @@ -149,7 +150,14 @@ public abstract class MutableOffsetMap extends AbstractMap implement this.needClone = false; } - public static MutableOffsetMap orderedCopyOf(final Map map) { + /** + * Create a {@link MutableOffsetMap} of the specified map, retaining its iteration order. + * + * @param map input map + * @return MutableOffsetMap with the same iteration order + * @throws NullPointerException if {@code map} is null + */ + public static @NonNull MutableOffsetMap orderedCopyOf(final Map map) { if (map instanceof Ordered) { return ((Ordered) map).clone(); } @@ -161,7 +169,14 @@ public abstract class MutableOffsetMap extends AbstractMap implement return new Ordered<>(map); } - public static MutableOffsetMap unorderedCopyOf(final Map map) { + /** + * Create a {@link MutableOffsetMap} of the specified map, potentially with a different iteration order. + * + * @param map input map + * @return MutableOffsetMap with undefined iteration order + * @throws NullPointerException if {@code map} is null + */ + public static @NonNull MutableOffsetMap unorderedCopyOf(final Map map) { if (map instanceof Unordered) { return ((Unordered) map).clone(); } @@ -173,11 +188,21 @@ public abstract class MutableOffsetMap extends AbstractMap implement return new Unordered<>(map); } - public static MutableOffsetMap ordered() { + /** + * Create an empty {@link MutableOffsetMap} which has an iteration order matching the insertion order. + * + * @return MutableOffsetMap which preserves insertion order + */ + public static @NonNull MutableOffsetMap ordered() { return new MutableOffsetMap.Ordered<>(); } - public static MutableOffsetMap unordered() { + /** + * Create an empty {@link MutableOffsetMap} which has unspecified iteration order. + * + * @return An MutableOffsetMap + */ + public static @NonNull MutableOffsetMap unordered() { return new MutableOffsetMap.Unordered<>(); } @@ -185,7 +210,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement abstract UnmodifiableMapPhase modifiedMap(List keys, V[] values); - abstract UnmodifiableMapPhase unmodifiedMap(Map offsetMap, V[] values); + abstract UnmodifiableMapPhase unmodifiedMap(ImmutableMap offsetMap, V[] values); abstract SharedSingletonMap singletonMap(); @@ -318,13 +343,11 @@ public abstract class MutableOffsetMap extends AbstractMap implement } } - @Nonnull @Override public final Set> entrySet() { return new EntrySet(); } - @Nonnull @Override public Map toUnmodifiableMap() { if (removed == 0 && newKeys.isEmpty()) { @@ -478,7 +501,6 @@ public abstract class MutableOffsetMap extends AbstractMap implement return true; } - @Nonnull @Override public final Set keySet() { return new KeySet(); @@ -500,7 +522,6 @@ public abstract class MutableOffsetMap extends AbstractMap implement } private final class EntrySet extends AbstractSet> { - @Nonnull @Override public Iterator> iterator() { return new AbstractSetIterator>() { @@ -569,7 +590,6 @@ public abstract class MutableOffsetMap extends AbstractMap implement } private final class KeySet extends AbstractSet { - @Nonnull @Override public Iterator iterator() { return new AbstractSetIterator() { diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java index df6c35e3f6..450f017476 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java @@ -23,17 +23,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.Nonnull; final class OffsetMapCache { /* * Cache for offsets where order matters. The key is a List, which defines the iteration order. Since we want * to retain this order, it is okay to use a simple LoadingCache. */ - private static final LoadingCache, Map> ORDERED_CACHE = - CacheBuilder.newBuilder().weakValues().build(new CacheLoader, Map>() { + private static final LoadingCache, ImmutableMap> ORDERED_CACHE = + CacheBuilder.newBuilder().weakValues().build(new CacheLoader, ImmutableMap>() { @Override - public Map load(@Nonnull final List key) { + public ImmutableMap load(final List key) { return createMap(key); } }); @@ -48,7 +47,7 @@ final class OffsetMapCache { * we construct the map and put it conditionally with Map.keySet() as the key. This will detect concurrent loading * and also lead to the cache and the map sharing the same Set. */ - private static final Cache, Map> UNORDERED_CACHE = + private static final Cache, ImmutableMap> UNORDERED_CACHE = CacheBuilder.newBuilder().weakValues().build(); private OffsetMapCache() { @@ -62,28 +61,28 @@ final class OffsetMapCache { } @SuppressWarnings("unchecked") - static Map orderedOffsets(final Collection args) { + static ImmutableMap orderedOffsets(final Collection args) { if (args.size() == 1) { return unorderedOffsets(args); } - return (Map) ORDERED_CACHE.getUnchecked(ImmutableList.copyOf(args)); + return (ImmutableMap) ORDERED_CACHE.getUnchecked(ImmutableList.copyOf(args)); } - static Map unorderedOffsets(final Collection args) { + static ImmutableMap unorderedOffsets(final Collection args) { return unorderedOffsets(args instanceof Set ? (Set)args : ImmutableSet.copyOf(args)); } @SuppressWarnings("unchecked") - private static Map unorderedOffsets(final Set args) { - final Map existing = (Map) UNORDERED_CACHE.getIfPresent(args); + private static ImmutableMap unorderedOffsets(final Set args) { + final ImmutableMap existing = (ImmutableMap) UNORDERED_CACHE.getIfPresent(args); if (existing != null) { return existing; } - final Map newMap = createMap(args); - final Map raced = UNORDERED_CACHE.asMap().putIfAbsent(newMap.keySet(), newMap); - return raced == null ? newMap : (Map)raced; + final ImmutableMap newMap = createMap(args); + final ImmutableMap raced = UNORDERED_CACHE.asMap().putIfAbsent(newMap.keySet(), newMap); + return raced == null ? newMap : (ImmutableMap)raced; } static V[] adjustedArray(final Map offsets, final List keys, final V[] array) { @@ -104,7 +103,7 @@ final class OffsetMapCache { return array; } - private static Map createMap(final Collection keys) { + private static ImmutableMap createMap(final Collection keys) { final Builder b = ImmutableMap.builder(); int counter = 0; diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java b/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java index 8e36661c3a..84e41d3bba 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java @@ -16,8 +16,9 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.io.Serializable; import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Iterator; import java.util.Map; -import javax.annotation.Nonnull; +import org.eclipse.jdt.annotation.NonNull; /** * Implementation of the {@link Map} interface which stores a single mapping. The key set is shared among all instances @@ -28,28 +29,34 @@ import javax.annotation.Nonnull; */ @Beta public abstract class SharedSingletonMap implements Serializable, UnmodifiableMapPhase { - private static final class Ordered extends SharedSingletonMap { + static final class Ordered extends SharedSingletonMap { private static final long serialVersionUID = 1L; Ordered(final K key, final V value) { super(key, value); } - @Nonnull + Ordered(final SingletonSet keySet, final V value) { + super(keySet, value); + } + @Override public ModifiableMapPhase toModifiableMap() { return MutableOffsetMap.orderedCopyOf(this); } } - private static final class Unordered extends SharedSingletonMap { + static final class Unordered extends SharedSingletonMap { private static final long serialVersionUID = 1L; Unordered(final K key, final V value) { super(key, value); } - @Nonnull + Unordered(final SingletonSet keySet, final V value) { + super(keySet, value); + } + @Override public ModifiableMapPhase toModifiableMap() { return MutableOffsetMap.unorderedCopyOf(this); @@ -60,55 +67,88 @@ public abstract class SharedSingletonMap implements Serializable, Unmodifi private static final LoadingCache> CACHE = CacheBuilder.newBuilder().weakValues() .build(new CacheLoader>() { @Override - public SingletonSet load(@Nonnull final Object key) { + public SingletonSet load(final Object key) { return SingletonSet.of(key); } }); - private final SingletonSet keySet; - private final V value; + + private final @NonNull SingletonSet keySet; + private final @NonNull V value; private int hashCode; - @SuppressWarnings("unchecked") - SharedSingletonMap(final K key, final V value) { - this.keySet = (SingletonSet) CACHE.getUnchecked(key); + SharedSingletonMap(final SingletonSet keySet, final V value) { + this.keySet = requireNonNull(keySet); this.value = requireNonNull(value); } - public static SharedSingletonMap orderedOf(final K key, final V value) { + SharedSingletonMap(final K key, final V value) { + this(cachedSet(key), value); + } + + /** + * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which retains insertion order + * when transformed via {@link #toModifiableMap()}. + * + * @param key key + * @param value value + * @return A SharedSingletonMap + * @throws NullPointerException if any of the arguments is null + */ + public static @NonNull SharedSingletonMap orderedOf(final K key, final V value) { return new Ordered<>(key, value); } - public static SharedSingletonMap unorderedOf(final K key, final V value) { + /** + * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which does not retain insertion + * order when transformed via {@link #toModifiableMap()}. + * + * @param key key + * @param value value + * @return A SharedSingletonMap + * @throws NullPointerException if any of the arguments is null + */ + public static @NonNull SharedSingletonMap unorderedOf(final K key, final V value) { return new Unordered<>(key, value); } - public static SharedSingletonMap orderedCopyOf(final Map map) { - checkArgument(map.size() == 1); - - final Entry e = map.entrySet().iterator().next(); + /** + * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which retains insertion order + * when transformed via {@link #toModifiableMap()}. + * + * @param map input map + * @return A SharedSingletonMap + * @throws NullPointerException if {@code map} is null + * @throws IllegalArgumentException if {@code map} does not have exactly one entry + */ + public static @NonNull SharedSingletonMap orderedCopyOf(final Map map) { + final Entry e = singleEntry(map); return new Ordered<>(e.getKey(), e.getValue()); } - public static SharedSingletonMap unorderedCopyOf(final Map map) { - checkArgument(map.size() == 1); - - final Entry e = map.entrySet().iterator().next(); + /** + * Create a {@link SharedSingletonMap} from specified single-element map, which does not retain insertion order when + * transformed via {@link #toModifiableMap()}. + * + * @param map input map + * @return A SharedSingletonMap + * @throws NullPointerException if {@code map} is null + * @throws IllegalArgumentException if {@code map} does not have exactly one entry + */ + public static @NonNull SharedSingletonMap unorderedCopyOf(final Map map) { + final Entry e = singleEntry(map); return new Unordered<>(e.getKey(), e.getValue()); } - @Nonnull @Override public final SingletonSet> entrySet() { return SingletonSet.of(new SimpleImmutableEntry<>(keySet.getElement(), value)); } - @Nonnull @Override public final SingletonSet keySet() { return keySet; } - @Nonnull @Override public final SingletonSet values() { return SingletonSet.of(value); @@ -153,7 +193,7 @@ public abstract class SharedSingletonMap implements Serializable, Unmodifi @Override @SuppressWarnings("checkstyle:parameterName") - public final void putAll(@Nonnull final Map m) { + public final void putAll(final Map m) { throw new UnsupportedOperationException(); } @@ -187,4 +227,17 @@ public abstract class SharedSingletonMap implements Serializable, Unmodifi public final String toString() { return "{" + keySet.getElement() + '=' + value + '}'; } + + @SuppressWarnings("unchecked") + static @NonNull SingletonSet cachedSet(final K key) { + return (SingletonSet) CACHE.getUnchecked(key); + } + + private static Entry singleEntry(final Map map) { + final Iterator> it = map.entrySet().iterator(); + checkArgument(it.hasNext(), "Input map is empty"); + final Entry ret = it.next(); + checkArgument(!it.hasNext(), "Input map has more than one entry"); + return ret; + } } diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java new file mode 100644 index 0000000000..7dda194e79 --- /dev/null +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Map; +import java.util.Set; +import org.junit.Test; + +public class ImmutableMapTemplateTest { + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + private static final Set ONE_KEYSET = ImmutableSet.of(FOO); + private static final Set TWO_KEYSET = ImmutableSet.of(FOO, BAR); + + @Test + public void testEmpty() { + ImmutableMapTemplate template; + try { + template = ImmutableMapTemplate.ordered(ImmutableList.of()); + fail("Returned template " + template); + } catch (IllegalArgumentException e) { + // expected + } + + try { + template = ImmutableMapTemplate.unordered(ImmutableList.of()); + fail("Returned template " + template); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Test + public void testOneKeyTemplate() { + assertOne(ImmutableMapTemplate.ordered(ONE_KEYSET), SharedSingletonMap.Ordered.class); + assertOne(ImmutableMapTemplate.unordered(ONE_KEYSET), SharedSingletonMap.Unordered.class); + } + + @Test + public void testTwoKeyTemplate() { + assertTwo(ImmutableMapTemplate.ordered(TWO_KEYSET), ImmutableOffsetMap.Ordered.class); + assertTwo(ImmutableMapTemplate.unordered(TWO_KEYSET), ImmutableOffsetMap.Unordered.class); + } + + private static void assertOne(final ImmutableMapTemplate template, final Class mapClass) { + assertEquals(ONE_KEYSET, template.keySet()); + assertEquals("Single" + mapClass.getSimpleName() + "{keySet=[foo]}", template.toString()); + + // Successful instantiation + Map map = template.instantiateWithValues(BAR); + assertTrue(mapClass.isInstance(map)); + assertEquals(ImmutableMap.of(FOO, BAR), map); + assertEquals("{foo=bar}", map.toString()); + + map = template.instantiateTransformed(ImmutableMap.of(FOO, BAR), (key, value) -> key); + assertTrue(mapClass.isInstance(map)); + assertEquals(ImmutableMap.of(FOO, FOO), map); + assertEquals("{foo=foo}", map.toString()); + + // Null transformation + try { + map = template.instantiateTransformed(ImmutableMap.of(FOO, BAR), (key, value) -> null); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + + // Empty input + try { + map = template.instantiateWithValues(); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + try { + map = template.instantiateTransformed(ImmutableMap.of(), (key, value) -> key); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + + // Two-item input + try { + map = template.instantiateWithValues(FOO, BAR); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + try { + map = template.instantiateTransformed(ImmutableMap.of(FOO, FOO, BAR, BAR), (key, value) -> key); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + + // Mismatched input + try { + map = template.instantiateTransformed(ImmutableMap.of(BAR, FOO), (key, value) -> key); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + } + + private static void assertTwo(final ImmutableMapTemplate template, final Class mapClass) { + assertEquals(TWO_KEYSET, template.keySet()); + assertEquals(mapClass.getSimpleName() + "{offsets={foo=0, bar=1}}", template.toString()); + + // Successful instantiation + Map map = template.instantiateWithValues(BAR, FOO); + assertTrue(mapClass.isInstance(map)); + assertEquals(ImmutableMap.of(FOO, BAR, BAR, FOO), map); + assertEquals("{foo=bar, bar=foo}", map.toString()); + + map = template.instantiateTransformed(ImmutableMap.of(FOO, BAR, BAR, FOO), (key, value) -> key); + assertTrue(mapClass.isInstance(map)); + assertEquals(ImmutableMap.of(FOO, FOO, BAR, BAR), map); + assertEquals("{foo=foo, bar=bar}", map.toString()); + + // Empty input + try { + map = template.instantiateWithValues(); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + try { + map = template.instantiateTransformed(ImmutableMap.of(), (key, value) -> key); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + + // One-item input + try { + map = template.instantiateWithValues(FOO); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + try { + map = template.instantiateTransformed(ImmutableMap.of(FOO, BAR), (key, value) -> key); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + + // Mismatched input + try { + map = template.instantiateTransformed(ImmutableMap.of(FOO, BAR, BAZ, FOO), (key, value) -> key); + fail("Returned map " + map); + } catch (IllegalArgumentException e) { + // expected + } + } +} diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java index 9fdd532a72..bc93d2cfde 100644 --- a/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java @@ -53,7 +53,7 @@ public class OffsetMapTest { @Test(expected = IllegalArgumentException.class) public void testWrongImmutableConstruction() { - new ImmutableOffsetMap.Ordered<>(Collections.emptyMap(), new String[1]); + new ImmutableOffsetMap.Ordered<>(ImmutableMap.of(), new String[1]); } @Test