Factor out {SharedSingleton,ImmutableOffset}MapTemplate 13/78113/1
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 25 Nov 2018 10:19:25 +0000 (11:19 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 25 Nov 2018 16:57:10 +0000 (17:57 +0100)
Exposing the single-entry map case has the advantage of providing
instantiateWithValue(Object) method, side-stepping the need for
a temporary array. Split it out of ImmutableMapTemplate, so that
users can provide customized handling as needed.

The same holds true if the keyset is known to contain more than
one key, in which case using ImmutableOffsetMapTemplate can be
faster.

JIRA: YANGTOOLS-917
Change-Id: If08f492ff59bc89f3826a4122cfbcf34657c2210
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit bd16b6161802322cb33c39642ddeb8a59b3e5d48)

common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java
common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java
common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMapTemplate.java [new file with mode: 0644]
common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java
common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMapTemplate.java [new file with mode: 0644]
common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java

index 50e3c9ffbf2f95b6fbabc53633d3576278d7af63..4f9d4b0c538ea38461c6114c3606bf544b992f3e 100644 (file)
@@ -8,17 +8,10 @@
 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;
@@ -30,146 +23,14 @@ import org.opendaylight.yangtools.concepts.Immutable;
  * using {@link #instantiateWithValues(Object[])} where the argument array has values ordered corresponding to the key
  * order defined by {@link #keySet()}.
  *
+ * <p>
+ * If the keySet is static known to contain only a single key, consider using {@link SharedSingletonMapTemplate}. If
+ * it is statically known to contain multiple keys, consider using {@link ImmutableOffsetMapTemplate}.
+ *
  * @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
     }
@@ -190,9 +51,9 @@ public abstract class ImmutableMapTemplate<K> implements Immutable {
             case 0:
                 throw new IllegalArgumentException("Proposed keyset must not be empty");
             case 1:
-                return new SingleOrdered<>(keys.iterator().next());
+                return SharedSingletonMapTemplate.ordered(keys.iterator().next());
             default:
-                return new Ordered<>(keys);
+                return ImmutableOffsetMapTemplate.ordered(keys);
         }
     }
 
@@ -212,9 +73,9 @@ public abstract class ImmutableMapTemplate<K> implements Immutable {
             case 0:
                 throw new IllegalArgumentException("Proposed keyset must not be empty");
             case 1:
-                return new SingleUnordered<>(keys.iterator().next());
+                return SharedSingletonMapTemplate.unordered(keys.iterator().next());
             default:
-                return new Unordered<>(keys);
+                return ImmutableOffsetMapTemplate.unordered(keys);
         }
     }
 
@@ -240,7 +101,7 @@ public abstract class ImmutableMapTemplate<K> implements Immutable {
      * @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
+     * @throws IllegalArgumentException if {@code values.length} does not match the number of keys in this template
      */
     @SuppressWarnings("unchecked")
     public abstract <V> @NonNull UnmodifiableMapPhase<K, V> instantiateWithValues(V... values);
index 9a15f96adfd3bda283053d4a6dbfff2bdc8941f7..473dda9915bea3d4c7ee18a731d68eb509416b41 100644 (file)
@@ -36,6 +36,10 @@ import org.eclipse.jdt.annotation.Nullable;
  * a backing array. This is useful for situations where the same key set is shared across a multitude of maps, as this
  * class uses a global cache to share the key-to-offset mapping.
  *
+ * <p>
+ * In case the set of keys is statically known, you can use {@link ImmutableOffsetMapTemplate} to efficiently create
+ * {@link ImmutableOffsetMap} instances.
+ *
  * @param <K> the type of keys maintained by this map
  * @param <V> the type of mapped values
  */
diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMapTemplate.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMapTemplate.java
new file mode 100644 (file)
index 0000000..091b40a
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.base.MoreObjects;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
+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;
+
+/**
+ * Template for instantiating {@link ImmutableOffsetMap} 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
+ */
+public abstract class ImmutableOffsetMapTemplate<K> extends ImmutableMapTemplate<K> {
+    private static final class Ordered<K> extends ImmutableOffsetMapTemplate<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 ImmutableOffsetMapTemplate<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 final @NonNull ImmutableMap<K, Integer> offsets;
+
+    ImmutableOffsetMapTemplate(final ImmutableMap<K, Integer> offsets) {
+        this.offsets = requireNonNull(offsets);
+    }
+
+    /**
+     * 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 does not have at least two keys
+     */
+    public static <K> @NonNull ImmutableOffsetMapTemplate<K> ordered(final Collection<K> keys) {
+        checkArgument(keys.size() > 1);
+        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 does not have at least two keys
+     */
+    public static <K> @NonNull ImmutableOffsetMapTemplate<K> unordered(final Collection<K> keys) {
+        checkArgument(keys.size() > 1);
+        return new Unordered<>(keys);
+    }
+
+    @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 ImmutableOffsetMap<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);
+}
\ No newline at end of file
index 84e41d3bba325daca724a94fa349a18f977dd66a..27cdc62e542d2dff787b2ca7ee42d9e34dd8cd75 100644 (file)
@@ -24,6 +24,10 @@ 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
  * which contain the same key. This implementation does not support null keys or values.
  *
+ * <p>
+ * In case the set of keys is statically known, you can use {@link SharedSingletonMapTemplate} to efficiently create
+ * {@link SharedSingletonMap} instances.
+ *
  * @param <K> the type of keys maintained by this map
  * @param <V> the type of mapped values
  */
diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMapTemplate.java b/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMapTemplate.java
new file mode 100644 (file)
index 0000000..97f3115
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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 com.google.common.base.MoreObjects;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.BiFunction;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * Template for instantiating {@link SharedSingletonMap} instances with a fixed key. The template can then be
+ * used as a factory for instances via using {@link #instantiateTransformed(Map, BiFunction)} or, more efficiently,
+ * using {@link #instantiateWithValue(Object)}.
+ *
+ * @param <K> the type of keys maintained by this template
+ */
+public abstract class SharedSingletonMapTemplate<K> extends ImmutableMapTemplate<K> {
+    private static final class Ordered<K> extends SharedSingletonMapTemplate<K> {
+        Ordered(final K key) {
+            super(key);
+        }
+
+        @Override
+        public <V> @NonNull SharedSingletonMap<K, V> instantiateWithValue(final V value) {
+            return new SharedSingletonMap.Ordered<>(keySet(), value);
+        }
+    }
+
+    private static final class Unordered<K> extends SharedSingletonMapTemplate<K> {
+        Unordered(final K key) {
+            super(key);
+        }
+
+        @Override
+        public <V> @NonNull SharedSingletonMap<K, V> instantiateWithValue(final V value) {
+            return new SharedSingletonMap.Unordered<>(keySet(), value);
+        }
+    }
+
+    private final @NonNull SingletonSet<K> keySet;
+
+    SharedSingletonMapTemplate(final K key) {
+        this.keySet = SharedSingletonMap.cachedSet(key);
+    }
+
+    /**
+     * Create a template which produces Maps with specified key. The resulting map will retain insertion order through
+     * {@link UnmodifiableMapPhase#toModifiableMap()} transformations.
+     *
+     * @param key Single key in resulting map
+     * @param <K> the type of keys maintained by resulting template
+     * @return A template object.
+     * @throws NullPointerException if {@code key} is null
+     */
+    public static <K> @NonNull SharedSingletonMapTemplate<K> ordered(final K key) {
+        return new Ordered<>(key);
+    }
+
+    /**
+     * Create a template which produces Maps with specified key. The resulting map will NOT retain ordering through
+     * {@link UnmodifiableMapPhase#toModifiableMap()} transformations.
+     *
+     * @param key Single key in resulting map
+     * @param <K> the type of keys maintained by resulting template
+     * @return A template object.
+     * @throws NullPointerException if {@code key} is null
+     */
+    public static <K> @NonNull SharedSingletonMapTemplate<K> unordered(final K key) {
+        return new Unordered<>(key);
+    }
+
+    @Override
+    public final SingletonSet<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 instantiateWithValue(value);
+    }
+
+    @Override
+    @SafeVarargs
+    public final <V> @NonNull SharedSingletonMap<K, V> instantiateWithValues(final V... values) {
+        checkArgument(values.length == 1);
+        return instantiateWithValue(values[0]);
+    }
+
+    /**
+     * Instantiate an immutable map with the value supplied.
+     *
+     * @param value Value to use
+     * @param <V> the type of mapped values
+     * @return An immutable map
+     * @throws NullPointerException if {@code value} is null
+     */
+    public abstract <V> @NonNull SharedSingletonMap<K, V> instantiateWithValue(V value);
+
+    @Override
+    public final String toString() {
+        return MoreObjects.toStringHelper(this).add("keySet", keySet).toString();
+    }
+}
index 7dda194e794b7cb79937329886e63863ffd8ad14..97caa8f105a4199a4f551890a919987f62c521eb 100644 (file)
@@ -57,7 +57,7 @@ public class ImmutableMapTemplateTest {
 
     private static void assertOne(final ImmutableMapTemplate<String> template, final Class<?> mapClass) {
         assertEquals(ONE_KEYSET, template.keySet());
-        assertEquals("Single" + mapClass.getSimpleName() + "{keySet=[foo]}", template.toString());
+        assertEquals(mapClass.getSimpleName() + "{keySet=[foo]}", template.toString());
 
         // Successful instantiation
         Map<String, String> map = template.instantiateWithValues(BAR);
index af66cd48d46b9dc9b828baa2f78136987b7d2b6d..b26d1b836711374958c2c6daa149f41356f3e730 100644 (file)
@@ -534,9 +534,18 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
             this.keyValues = ImmutableOffsetMap.unorderedCopyOf(keyValues);
         }
 
-        public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
+        public NodeIdentifierWithPredicates(final QName node, final ImmutableOffsetMap<QName, Object> keyValues) {
+            super(node);
+            this.keyValues = requireNonNull(keyValues);
+        }
+
+        public NodeIdentifierWithPredicates(final QName node, final SharedSingletonMap<QName, Object> keyValues) {
             super(node);
-            this.keyValues = SharedSingletonMap.unorderedOf(key, value);
+            this.keyValues = requireNonNull(keyValues);
+        }
+
+        public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
+            this(node, SharedSingletonMap.unorderedOf(key, value));
         }
 
         public Map<QName, Object> getKeyValues() {