Add ImmutableMapTemplate 99/78099/2
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 16:53:20 +0000 (17:53 +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>
(cherry picked from commit 048042bd88cc9b1f481c018363c331bca83e4e1f)

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 e899a1e60e695f022253c5d0c00de760839aa9fc..9a15f96adfd3bda283053d4a6dbfff2bdc8941f7 100644 (file)
@@ -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<K, V> implements UnmodifiableMapPhase<K
     static final class Ordered<K, V> extends ImmutableOffsetMap<K, V> {
         private static final long serialVersionUID = 1L;
 
-        Ordered(final Map<K, Integer> offsets, final V[] objects) {
+        Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
             super(offsets, objects);
         }
 
@@ -63,7 +64,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);
         }
 
@@ -84,8 +85,8 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
 
     private static final long serialVersionUID = 1L;
 
-    private final transient Map<K, Integer> offsets;
-    private final transient V[] objects;
+    private final transient @NonNull ImmutableMap<K, Integer> offsets;
+    private final transient @NonNull V[] objects;
     private transient int hashCode;
 
     /**
@@ -95,7 +96,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(@Nonnull final Map<K, Integer> offsets, @Nonnull final V[] objects) {
+    ImmutableOffsetMap(final ImmutableMap<K, Integer> 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<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.
      */
     @Nonnull public static <K, V> Map<K, V> orderedCopyOf(@Nonnull final Map<K, V> map) {
         final Map<K, V> common = commonCopy(map);
@@ -135,7 +133,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()) {
@@ -146,19 +144,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.
      */
     @Nonnull public static <K, V> Map<K, V> unorderedCopyOf(@Nonnull final Map<K, V> map) {
         final Map<K, V> common = commonCopy(map);
@@ -173,7 +168,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()) {
@@ -347,7 +342,7 @@ public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K
         return sb.append('}').toString();
     }
 
-    final Map<K, Integer> offsets() {
+    final @NonNull ImmutableMap<K, Integer> offsets() {
         return offsets;
     }
 
index 09511523bb901bf9abdedc36ca805d5410560eca..d06859438686a027d6886790e3f8f3124ce81954 100644 (file)
@@ -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<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<>());
         }
 
@@ -69,7 +69,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);
         }
 
@@ -88,7 +88,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<>());
         }
 
@@ -99,12 +99,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);
         }
 
@@ -116,7 +116,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;
@@ -126,7 +127,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);
@@ -139,7 +140,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()) {
@@ -149,7 +150,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();
         }
@@ -161,7 +169,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();
         }
@@ -173,11 +188,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<>();
     }
 
@@ -185,7 +210,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();
 
@@ -318,13 +343,11 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
         }
     }
 
-    @Nonnull
     @Override
     public final Set<Entry<K, V>> entrySet() {
         return new EntrySet();
     }
 
-    @Nonnull
     @Override
     public Map<K, V> toUnmodifiableMap() {
         if (removed == 0 && newKeys.isEmpty()) {
@@ -478,7 +501,6 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
         return true;
     }
 
-    @Nonnull
     @Override
     public final Set<K> keySet() {
         return new KeySet();
@@ -500,7 +522,6 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
     }
 
     private final class EntrySet extends AbstractSet<Entry<K, V>> {
-        @Nonnull
         @Override
         public Iterator<Entry<K, V>> iterator() {
             return new AbstractSetIterator<Entry<K, V>>() {
@@ -569,7 +590,6 @@ public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implement
     }
 
     private final class KeySet extends AbstractSet<K> {
-        @Nonnull
         @Override
         public Iterator<K> iterator() {
             return new AbstractSetIterator<K>() {
index df6c35e3f649dd7baa5569c2ce2939fba1ea084c..450f01747624937bd9ab6a8c6f9c851df4ec82a8 100644 (file)
@@ -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<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(@Nonnull final List<?> key) {
+                public ImmutableMap<?, Integer> 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<Set<?>, Map<?, Integer>> UNORDERED_CACHE =
+    private static final Cache<Set<?>, ImmutableMap<?, Integer>> UNORDERED_CACHE =
             CacheBuilder.newBuilder().weakValues().build();
 
     private OffsetMapCache() {
@@ -62,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) {
@@ -104,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 8e36661c3ac61b146d64985076cb417d73cad798..84e41d3bba325daca724a94fa349a18f977dd66a 100644 (file)
@@ -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<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);
         }
 
-        @Nonnull
+        Ordered(final SingletonSet<K> keySet, final V value) {
+            super(keySet, value);
+        }
+
         @Override
         public 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);
         }
 
-        @Nonnull
+        Unordered(final SingletonSet<K> keySet, final V value) {
+            super(keySet, value);
+        }
+
         @Override
         public ModifiableMapPhase<K, V> toModifiableMap() {
             return MutableOffsetMap.unorderedCopyOf(this);
@@ -60,55 +67,88 @@ public abstract class SharedSingletonMap<K, V> implements Serializable, Unmodifi
     private static final LoadingCache<Object, SingletonSet<Object>> CACHE = CacheBuilder.newBuilder().weakValues()
             .build(new CacheLoader<Object, SingletonSet<Object>>() {
                 @Override
-                public SingletonSet<Object> load(@Nonnull final Object key) {
+                public SingletonSet<Object> load(final Object key) {
                     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());
     }
 
-    @Nonnull
     @Override
     public final SingletonSet<Entry<K, V>> entrySet() {
         return SingletonSet.of(new SimpleImmutableEntry<>(keySet.getElement(), value));
     }
 
-    @Nonnull
     @Override
     public final SingletonSet<K> keySet() {
         return keySet;
     }
 
-    @Nonnull
     @Override
     public final SingletonSet<V> values() {
         return SingletonSet.of(value);
@@ -153,7 +193,7 @@ public abstract class SharedSingletonMap<K, V> implements Serializable, Unmodifi
 
     @Override
     @SuppressWarnings("checkstyle:parameterName")
-    public final void putAll(@Nonnull final Map<? extends K, ? extends V> m) {
+    public final void putAll(final Map<? extends K, ? extends V> m) {
         throw new UnsupportedOperationException();
     }
 
@@ -187,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