Add ImmutableMapTemplate 59/78059/18
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 22 Nov 2018 17:43:42 +0000 (18:43 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 23 Nov 2018 10:14:08 +0000 (11:14 +0100)
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 <robert.varga@pantheon.tech>
common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java [new file with mode: 0644]
common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java
common/util/src/main/java/org/opendaylight/yangtools/util/MutableOffsetMap.java
common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java
common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java
common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java [new file with mode: 0644]
common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java

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 (file)
index 0000000..50e3c9f
--- /dev/null
@@ -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 <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;
+    }
+}
index 4cadc268cb845466e8220c5b3c18953de2b97d1e..131d69277bd1baae1ec3e5561ff64f0cfa1035f3 100644 (file)
@@ -43,7 +43,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
     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);
         }
 
@@ -62,7 +62,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
     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);
         }
 
@@ -82,7 +82,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
 
     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;
 
@@ -93,7 +93,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
      * @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);
@@ -105,19 +105,16 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
     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);
@@ -132,7 +129,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
             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()) {
@@ -143,19 +140,16 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
     }
 
     /**
-     * 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);
@@ -170,7 +164,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
             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()) {
@@ -342,7 +336,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
         return sb.append('}').toString();
     }
 
-    final @NonNull Map<K, Integer> offsets() {
+    final @NonNull ImmutableMap<K, Integer> offsets() {
         return offsets;
     }
 
index 299a443d9086d4a142a50486825212caec924970..0cfc9a8f8f8fd56cfb98b0b1b563952d8b2cb6a3 100644 (file)
@@ -56,7 +56,7 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
             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<>());
         }
 
@@ -71,7 +71,7 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
         }
 
         @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);
         }
 
@@ -90,7 +90,7 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
             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<>());
         }
 
@@ -101,12 +101,12 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
 
         @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);
         }
 
@@ -118,7 +118,8 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
 
     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;
@@ -128,7 +129,7 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
     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);
@@ -141,7 +142,7 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
     }
 
     @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()) {
@@ -151,7 +152,14 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
         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();
         }
@@ -163,7 +171,14 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
         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();
         }
@@ -175,11 +190,21 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
         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<>();
     }
 
@@ -187,7 +212,7 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
 
     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();
 
index 52c9dc94d53b339dcd15dc43cf7f7decee8e004a..450f01747624937bd9ab6a8c6f9c851df4ec82a8 100644 (file)
@@ -29,10 +29,10 @@ 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<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);
                 }
             });
@@ -47,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<Set<?>, Map<?, Integer>> UNORDERED_CACHE =
+    private static final Cache<Set<?>, ImmutableMap<?, Integer>> UNORDERED_CACHE =
             CacheBuilder.newBuilder().weakValues().build();
 
     private OffsetMapCache() {
@@ -61,28 +61,28 @@ final class 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) {
@@ -103,7 +103,7 @@ final class OffsetMapCache {
         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;
 
index 8fa39312390cfdf2df6b3955a639f2316c750a1c..0ea88bc77238a37150453987b62d28236e87748f 100644 (file)
@@ -16,6 +16,7 @@ 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 org.eclipse.jdt.annotation.NonNull;
 
@@ -28,26 +29,34 @@ 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);
@@ -62,35 +71,71 @@ public abstract class SharedSingletonMap<K, V> implements Serializable, Unmodifi
                     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());
     }
 
@@ -182,4 +227,17 @@ public abstract class SharedSingletonMap<K, V> implements Serializable, Unmodifi
     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;
+    }
 }
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 (file)
index 0000000..7dda194
--- /dev/null
@@ -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<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
+        }
+    }
+}
index 9fdd532a724a4dec068c9654d1dbc4ee32ae034a..bc93d2cfde480884c1428444a720c582d17b9876 100644 (file)
@@ -53,7 +53,7 @@ public class OffsetMapTest {
 
     @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