--- /dev/null
+/*
+ * 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 <K> the type of keys maintained by this template
+ */
+@Beta
+public abstract class ImmutableMapTemplate<K> implements Immutable {
+ private abstract static class AbstractMultiple<K> extends ImmutableMapTemplate<K> {
+ private final @NonNull ImmutableMap<K, Integer> offsets;
+
+ AbstractMultiple(final ImmutableMap<K, Integer> offsets) {
+ this.offsets = requireNonNull(offsets);
+ }
+
+ @Override
+ public final Set<K> keySet() {
+ return offsets.keySet();
+ }
+
+ @Override
+ public final <T, V> @NonNull ImmutableOffsetMap<K, V> instantiateTransformed(final Map<K, T> fromMap,
+ final BiFunction<K, T, V> valueTransformer) {
+ final int size = offsets.size();
+ checkArgument(fromMap.size() == size);
+
+ @SuppressWarnings("unchecked")
+ final V[] objects = (V[]) new Object[size];
+ for (Entry<K, T> 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 <V> @NonNull UnmodifiableMapPhase<K, V> 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 <V> @NonNull ImmutableOffsetMap<K, V> createMap(ImmutableMap<K, Integer> offsets, V[] objects);
+ }
+
+ private static final class Ordered<K> extends AbstractMultiple<K> {
+ Ordered(final Collection<K> keys) {
+ super(OffsetMapCache.orderedOffsets(keys));
+ }
+
+ @Override
+ <V> @NonNull ImmutableOffsetMap<K, V> createMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
+ return new ImmutableOffsetMap.Ordered<>(offsets, objects);
+ }
+ }
+
+ private static final class Unordered<K> extends AbstractMultiple<K> {
+ Unordered(final Collection<K> keys) {
+ super(OffsetMapCache.unorderedOffsets(keys));
+ }
+
+ @Override
+ <V> @NonNull ImmutableOffsetMap<K, V> createMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
+ return new ImmutableOffsetMap.Unordered<>(offsets, objects);
+ }
+ }
+
+ private abstract static class AbstractSingle<K> extends ImmutableMapTemplate<K> {
+ private final @NonNull SingletonSet<K> keySet;
+
+ AbstractSingle(final K key) {
+ this.keySet = SharedSingletonMap.cachedSet(key);
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return keySet;
+ }
+
+ @Override
+ public final <T, V> @NonNull SharedSingletonMap<K, V> instantiateTransformed(final Map<K, T> fromMap,
+ final BiFunction<K, T, V> valueTransformer) {
+ final Iterator<Entry<K, T>> it = fromMap.entrySet().iterator();
+ checkArgument(it.hasNext(), "Input is empty while expecting 1 item");
+
+ final Entry<K, T> 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 <V> @NonNull UnmodifiableMapPhase<K, V> 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 <V> @NonNull SharedSingletonMap<K, V> createMap(SingletonSet<K> keySet, V value);
+ }
+
+ private static final class SingleOrdered<K> extends AbstractSingle<K> {
+ SingleOrdered(final K key) {
+ super(key);
+ }
+
+ @Override
+ <V> @NonNull SharedSingletonMap<K, V> createMap(final SingletonSet<K> keySet, final V value) {
+ return new SharedSingletonMap.Ordered<>(keySet, value);
+ }
+ }
+
+ private static final class SingleUnordered<K> extends AbstractSingle<K> {
+ SingleUnordered(final K key) {
+ super(key);
+ }
+
+ @Override
+ <V> @NonNull SharedSingletonMap<K, V> createMap(final SingletonSet<K> 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 <K> 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 <K> @NonNull ImmutableMapTemplate<K> ordered(final Collection<K> 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 <K> 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 <K> @NonNull ImmutableMapTemplate<K> unordered(final Collection<K> 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 <T> the type of input values
+ * @param <V> 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 <T, V> @NonNull UnmodifiableMapPhase<K, V> instantiateTransformed(Map<K, T> fromMap,
+ BiFunction<K, T, V> 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 <V> 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 <V> @NonNull UnmodifiableMapPhase<K, V> 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<K> keySet();
+
+ final <T, V> @NonNull V transformValue(final K key, final T input, final BiFunction<K, T, V> transformer) {
+ final V value = transformer.apply(key, input);
+ checkArgument(value != null, "Transformer returned null for input %s at key %s", input, key);
+ return value;
+ }
+}
static final class Ordered<K, V> extends ImmutableOffsetMap<K, V> {
private static final long serialVersionUID = 1L;
- Ordered(final @NonNull Map<K, Integer> offsets, final @NonNull V[] objects) {
+ Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
super(offsets, objects);
}
static final class Unordered<K, V> extends ImmutableOffsetMap<K, V> {
private static final long serialVersionUID = 1L;
- Unordered(final Map<K, Integer> offsets, final V[] objects) {
+ Unordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
super(offsets, objects);
}
private static final long serialVersionUID = 1L;
- private final transient @NonNull Map<K, Integer> offsets;
+ private final transient @NonNull ImmutableMap<K, Integer> offsets;
private final transient @NonNull V[] objects;
private transient int hashCode;
* @param objects Array of value object, may not be null. The array is stored as is, the caller
* is responsible for ensuring its contents remain unmodified.
*/
- ImmutableOffsetMap(final @NonNull Map<K, Integer> offsets, final @NonNull V[] objects) {
+ ImmutableOffsetMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
this.offsets = requireNonNull(offsets);
this.objects = requireNonNull(objects);
checkArgument(offsets.size() == objects.length);
abstract void setFields(List<K> 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.
*/
public static <K, V> @NonNull Map<K, V> orderedCopyOf(final @NonNull Map<K, V> map) {
final Map<K, V> common = commonCopy(map);
return SharedSingletonMap.orderedOf(e.getKey(), e.getValue());
}
- final Map<K, Integer> offsets = OffsetMapCache.orderedOffsets(map.keySet());
+ final ImmutableMap<K, Integer> offsets = OffsetMapCache.orderedOffsets(map.keySet());
@SuppressWarnings("unchecked")
final V[] array = (V[]) new Object[offsets.size()];
for (Entry<K, V> e : map.entrySet()) {
}
/**
- * 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}. Iterator
- * order is not guaranteed to be retained.
+ * 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}. Iterator order is not guaranteed to be retained.
*
- * @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.
*/
public static <K, V> @NonNull Map<K, V> unorderedCopyOf(final @NonNull Map<K, V> map) {
final Map<K, V> common = commonCopy(map);
return SharedSingletonMap.unorderedOf(e.getKey(), e.getValue());
}
- final Map<K, Integer> offsets = OffsetMapCache.unorderedOffsets(map.keySet());
+ final ImmutableMap<K, Integer> offsets = OffsetMapCache.unorderedOffsets(map.keySet());
@SuppressWarnings("unchecked")
final V[] array = (V[]) new Object[offsets.size()];
for (Entry<K, V> e : map.entrySet()) {
return sb.append('}').toString();
}
- final @NonNull Map<K, Integer> offsets() {
+ final @NonNull ImmutableMap<K, Integer> offsets() {
return offsets;
}
super(OffsetMapCache.orderedOffsets(source.keySet()), source, new LinkedHashMap<>());
}
- Ordered(final Map<K, Integer> offsets, final V[] objects) {
+ Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
super(offsets, objects, new LinkedHashMap<>());
}
}
@Override
- UnmodifiableMapPhase<K, V> unmodifiedMap(final Map<K, Integer> offsetMap, final V[] values) {
+ UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
return new ImmutableOffsetMap.Ordered<>(offsetMap, values);
}
super(OffsetMapCache.unorderedOffsets(source.keySet()), source, new HashMap<>());
}
- Unordered(final Map<K, Integer> offsets, final V[] objects) {
+ Unordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
super(offsets, objects, new HashMap<>());
}
@Override
UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] values) {
- final Map<K, Integer> offsets = OffsetMapCache.unorderedOffsets(keys);
+ final ImmutableMap<K, Integer> offsets = OffsetMapCache.unorderedOffsets(keys);
return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, values));
}
@Override
- UnmodifiableMapPhase<K, V> unmodifiedMap(final Map<K, Integer> offsetMap, final V[] values) {
+ UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
return new ImmutableOffsetMap.Unordered<>(offsetMap, values);
}
private static final Object[] EMPTY_ARRAY = new Object[0];
private static final Object REMOVED = new Object();
- private final Map<K, Integer> offsets;
+
+ private final ImmutableMap<K, Integer> offsets;
private HashMap<K, V> newKeys;
private Object[] objects;
private int removed = 0;
private transient volatile int modCount;
private boolean needClone = true;
- MutableOffsetMap(final Map<K, Integer> offsets, final V[] objects, final HashMap<K, V> newKeys) {
+ MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final V[] objects, final HashMap<K, V> newKeys) {
verify(newKeys.isEmpty());
this.offsets = requireNonNull(offsets);
this.objects = requireNonNull(objects);
}
@SuppressWarnings("unchecked")
- MutableOffsetMap(final Map<K, Integer> offsets, final Map<K, V> source, final HashMap<K, V> newKeys) {
+ MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final Map<K, V> source, final HashMap<K, V> newKeys) {
this(offsets, (V[]) new Object[offsets.size()], newKeys);
for (Entry<K, V> e : source.entrySet()) {
this.needClone = false;
}
- public static <K, V> MutableOffsetMap<K, V> orderedCopyOf(final Map<K, V> 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 <K, V> @NonNull MutableOffsetMap<K, V> orderedCopyOf(final Map<K, V> map) {
if (map instanceof Ordered) {
return ((Ordered<K, V>) map).clone();
}
return new Ordered<>(map);
}
- public static <K, V> MutableOffsetMap<K, V> unorderedCopyOf(final Map<K, V> 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 <K, V> @NonNull MutableOffsetMap<K, V> unorderedCopyOf(final Map<K, V> map) {
if (map instanceof Unordered) {
return ((Unordered<K, V>) map).clone();
}
return new Unordered<>(map);
}
- public static <K, V> MutableOffsetMap<K, V> ordered() {
+ /**
+ * Create an empty {@link MutableOffsetMap} which has an iteration order matching the insertion order.
+ *
+ * @return MutableOffsetMap which preserves insertion order
+ */
+ public static <K, V> @NonNull MutableOffsetMap<K, V> ordered() {
return new MutableOffsetMap.Ordered<>();
}
- public static <K, V> MutableOffsetMap<K, V> unordered() {
+ /**
+ * Create an empty {@link MutableOffsetMap} which has unspecified iteration order.
+ *
+ * @return An MutableOffsetMap
+ */
+ public static <K, V> @NonNull MutableOffsetMap<K, V> unordered() {
return new MutableOffsetMap.Unordered<>();
}
abstract UnmodifiableMapPhase<K, V> modifiedMap(List<K> keys, V[] values);
- abstract UnmodifiableMapPhase<K, V> unmodifiedMap(Map<K, Integer> offsetMap, V[] values);
+ abstract UnmodifiableMapPhase<K, V> unmodifiedMap(ImmutableMap<K, Integer> offsetMap, V[] values);
abstract SharedSingletonMap<K, V> singletonMap();
* 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<List<?>, Map<?, Integer>> ORDERED_CACHE =
- CacheBuilder.newBuilder().weakValues().build(new CacheLoader<List<?>, Map<?, Integer>>() {
+ private static final LoadingCache<List<?>, ImmutableMap<?, Integer>> ORDERED_CACHE =
+ CacheBuilder.newBuilder().weakValues().build(new CacheLoader<List<?>, ImmutableMap<?, Integer>>() {
@Override
- public Map<?, Integer> load(final List<?> key) {
+ public ImmutableMap<?, Integer> load(final List<?> key) {
return createMap(key);
}
});
* 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<Set<?>, Map<?, Integer>> UNORDERED_CACHE =
+ private static final Cache<Set<?>, ImmutableMap<?, Integer>> UNORDERED_CACHE =
CacheBuilder.newBuilder().weakValues().build();
private OffsetMapCache() {
}
@SuppressWarnings("unchecked")
- static <T> Map<T, Integer> orderedOffsets(final Collection<T> args) {
+ static <T> ImmutableMap<T, Integer> orderedOffsets(final Collection<T> args) {
if (args.size() == 1) {
return unorderedOffsets(args);
}
- return (Map<T, Integer>) ORDERED_CACHE.getUnchecked(ImmutableList.copyOf(args));
+ return (ImmutableMap<T, Integer>) ORDERED_CACHE.getUnchecked(ImmutableList.copyOf(args));
}
- static <T> Map<T, Integer> unorderedOffsets(final Collection<T> args) {
+ static <T> ImmutableMap<T, Integer> unorderedOffsets(final Collection<T> args) {
return unorderedOffsets(args instanceof Set ? (Set<T>)args : ImmutableSet.copyOf(args));
}
@SuppressWarnings("unchecked")
- private static <T> Map<T, Integer> unorderedOffsets(final Set<T> args) {
- final Map<T, Integer> existing = (Map<T, Integer>) UNORDERED_CACHE.getIfPresent(args);
+ private static <T> ImmutableMap<T, Integer> unorderedOffsets(final Set<T> args) {
+ final ImmutableMap<T, Integer> existing = (ImmutableMap<T, Integer>) UNORDERED_CACHE.getIfPresent(args);
if (existing != null) {
return existing;
}
- final Map<T, Integer> newMap = createMap(args);
- final Map<?, Integer> raced = UNORDERED_CACHE.asMap().putIfAbsent(newMap.keySet(), newMap);
- return raced == null ? newMap : (Map<T, Integer>)raced;
+ final ImmutableMap<T, Integer> newMap = createMap(args);
+ final ImmutableMap<?, Integer> raced = UNORDERED_CACHE.asMap().putIfAbsent(newMap.keySet(), newMap);
+ return raced == null ? newMap : (ImmutableMap<T, Integer>)raced;
}
static <K, V> V[] adjustedArray(final Map<K, Integer> offsets, final List<K> keys, final V[] array) {
return array;
}
- private static <T> Map<T, Integer> createMap(final Collection<T> keys) {
+ private static <T> ImmutableMap<T, Integer> createMap(final Collection<T> keys) {
final Builder<T, Integer> b = ImmutableMap.builder();
int counter = 0;
import com.google.common.cache.LoadingCache;
import java.io.Serializable;
import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Iterator;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
*/
@Beta
public abstract class SharedSingletonMap<K, V> implements Serializable, UnmodifiableMapPhase<K, V> {
- private static final class Ordered<K, V> extends SharedSingletonMap<K, V> {
+ static final class Ordered<K, V> extends SharedSingletonMap<K, V> {
private static final long serialVersionUID = 1L;
Ordered(final K key, final V value) {
super(key, value);
}
+ Ordered(final SingletonSet<K> keySet, final V value) {
+ super(keySet, value);
+ }
+
@Override
public @NonNull ModifiableMapPhase<K, V> toModifiableMap() {
return MutableOffsetMap.orderedCopyOf(this);
}
}
- private static final class Unordered<K, V> extends SharedSingletonMap<K, V> {
+ static final class Unordered<K, V> extends SharedSingletonMap<K, V> {
private static final long serialVersionUID = 1L;
Unordered(final K key, final V value) {
super(key, value);
}
+ Unordered(final SingletonSet<K> keySet, final V value) {
+ super(keySet, value);
+ }
+
@Override
public @NonNull ModifiableMapPhase<K, V> toModifiableMap() {
return MutableOffsetMap.unorderedCopyOf(this);
return SingletonSet.of(key);
}
});
- private final SingletonSet<K> keySet;
- private final V value;
+
+ private final @NonNull SingletonSet<K> keySet;
+ private final @NonNull V value;
private int hashCode;
- @SuppressWarnings("unchecked")
- SharedSingletonMap(final K key, final V value) {
- this.keySet = (SingletonSet<K>) CACHE.getUnchecked(key);
+ SharedSingletonMap(final SingletonSet<K> keySet, final V value) {
+ this.keySet = requireNonNull(keySet);
this.value = requireNonNull(value);
}
- public static <K, V> SharedSingletonMap<K, V> 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 <K, V> @NonNull SharedSingletonMap<K, V> orderedOf(final K key, final V value) {
return new Ordered<>(key, value);
}
- public static <K, V> SharedSingletonMap<K, V> 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 <K, V> @NonNull SharedSingletonMap<K, V> unorderedOf(final K key, final V value) {
return new Unordered<>(key, value);
}
- public static <K, V> SharedSingletonMap<K, V> orderedCopyOf(final Map<K, V> map) {
- checkArgument(map.size() == 1);
-
- final Entry<K, V> 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 <K, V> @NonNull SharedSingletonMap<K, V> orderedCopyOf(final Map<K, V> map) {
+ final Entry<K, V> e = singleEntry(map);
return new Ordered<>(e.getKey(), e.getValue());
}
- public static <K, V> SharedSingletonMap<K, V> unorderedCopyOf(final Map<K, V> map) {
- checkArgument(map.size() == 1);
-
- final Entry<K, V> 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 <K, V> @NonNull SharedSingletonMap<K, V> unorderedCopyOf(final Map<K, V> map) {
+ final Entry<K, V> e = singleEntry(map);
return new Unordered<>(e.getKey(), e.getValue());
}
public final String toString() {
return "{" + keySet.getElement() + '=' + value + '}';
}
+
+ @SuppressWarnings("unchecked")
+ static <K> @NonNull SingletonSet<K> cachedSet(final K key) {
+ return (SingletonSet<K>) CACHE.getUnchecked(key);
+ }
+
+ private static <K, V> Entry<K, V> singleEntry(final Map<K, V> map) {
+ final Iterator<Entry<K, V>> it = map.entrySet().iterator();
+ checkArgument(it.hasNext(), "Input map is empty");
+ final Entry<K, V> ret = it.next();
+ checkArgument(!it.hasNext(), "Input map has more than one entry");
+ return ret;
+ }
}
--- /dev/null
+/*
+ * 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<String> ONE_KEYSET = ImmutableSet.of(FOO);
+ private static final Set<String> 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<String> template, final Class<?> mapClass) {
+ assertEquals(ONE_KEYSET, template.keySet());
+ assertEquals("Single" + mapClass.getSimpleName() + "{keySet=[foo]}", template.toString());
+
+ // Successful instantiation
+ Map<String, String> 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<String> template, final Class<?> mapClass) {
+ assertEquals(TWO_KEYSET, template.keySet());
+ assertEquals(mapClass.getSimpleName() + "{offsets={foo=0, bar=1}}", template.toString());
+
+ // Successful instantiation
+ Map<String, String> 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
+ }
+ }
+}
@Test(expected = IllegalArgumentException.class)
public void testWrongImmutableConstruction() {
- new ImmutableOffsetMap.Ordered<>(Collections.<String, Integer>emptyMap(), new String[1]);
+ new ImmutableOffsetMap.Ordered<>(ImmutableMap.of(), new String[1]);
}
@Test