Factor out {SharedSingleton,ImmutableOffset}MapTemplate 08/78108/4
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 13:50:48 +0000 (14:50 +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>
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 131d69277bd1baae1ec3e5561ff64f0cfa1035f3..3da4c38fb2a21cc1c1bcd9afe6b48233055e58e9 100644 (file)
@@ -35,6 +35,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 0ea88bc77238a37150453987b62d28236e87748f..0dd661481e45bbea559983e15a9c3c2cd5f7f8d6 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 53301acbae0f5c1c9315205a55b3ec8a2cce8a99..2bd9e094052da8bfc3bb90c94e4b1bdbdf784001 100644 (file)
@@ -537,9 +537,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() {