From bd16b6161802322cb33c39642ddeb8a59b3e5d48 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 25 Nov 2018 11:19:25 +0100 Subject: [PATCH] Factor out {SharedSingleton,ImmutableOffset}MapTemplate 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 --- .../yangtools/util/ImmutableMapTemplate.java | 157 +----------------- .../yangtools/util/ImmutableOffsetMap.java | 4 + .../util/ImmutableOffsetMapTemplate.java | 132 +++++++++++++++ .../yangtools/util/SharedSingletonMap.java | 4 + .../util/SharedSingletonMapTemplate.java | 123 ++++++++++++++ .../util/ImmutableMapTemplateTest.java | 2 +- .../yang/data/api/YangInstanceIdentifier.java | 13 +- 7 files changed, 284 insertions(+), 151 deletions(-) create mode 100644 common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMapTemplate.java create mode 100644 common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMapTemplate.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 index 50e3c9ffbf..4f9d4b0c53 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableMapTemplate.java @@ -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()}. * + *

+ * 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 the type of keys maintained by this template */ @Beta public abstract class ImmutableMapTemplate implements Immutable { - private abstract static class AbstractMultiple extends ImmutableMapTemplate { - private final @NonNull ImmutableMap offsets; - - AbstractMultiple(final ImmutableMap offsets) { - this.offsets = requireNonNull(offsets); - } - - @Override - public final Set keySet() { - return offsets.keySet(); - } - - @Override - public final @NonNull ImmutableOffsetMap instantiateTransformed(final Map fromMap, - final BiFunction valueTransformer) { - final int size = offsets.size(); - checkArgument(fromMap.size() == size); - - @SuppressWarnings("unchecked") - final V[] objects = (V[]) new Object[size]; - for (Entry 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 @NonNull UnmodifiableMapPhase 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 @NonNull ImmutableOffsetMap createMap(ImmutableMap offsets, V[] objects); - } - - private static final class Ordered extends AbstractMultiple { - Ordered(final Collection keys) { - super(OffsetMapCache.orderedOffsets(keys)); - } - - @Override - @NonNull ImmutableOffsetMap createMap(final ImmutableMap offsets, final V[] objects) { - return new ImmutableOffsetMap.Ordered<>(offsets, objects); - } - } - - private static final class Unordered extends AbstractMultiple { - Unordered(final Collection keys) { - super(OffsetMapCache.unorderedOffsets(keys)); - } - - @Override - @NonNull ImmutableOffsetMap createMap(final ImmutableMap offsets, final V[] objects) { - return new ImmutableOffsetMap.Unordered<>(offsets, objects); - } - } - - private abstract static class AbstractSingle extends ImmutableMapTemplate { - private final @NonNull SingletonSet keySet; - - AbstractSingle(final K key) { - this.keySet = SharedSingletonMap.cachedSet(key); - } - - @Override - public Set keySet() { - return keySet; - } - - @Override - public final @NonNull SharedSingletonMap instantiateTransformed(final Map fromMap, - final BiFunction valueTransformer) { - final Iterator> it = fromMap.entrySet().iterator(); - checkArgument(it.hasNext(), "Input is empty while expecting 1 item"); - - final Entry 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 @NonNull UnmodifiableMapPhase 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 @NonNull SharedSingletonMap createMap(SingletonSet keySet, V value); - } - - private static final class SingleOrdered extends AbstractSingle { - SingleOrdered(final K key) { - super(key); - } - - @Override - @NonNull SharedSingletonMap createMap(final SingletonSet keySet, final V value) { - return new SharedSingletonMap.Ordered<>(keySet, value); - } - } - - private static final class SingleUnordered extends AbstractSingle { - SingleUnordered(final K key) { - super(key); - } - - @Override - @NonNull SharedSingletonMap createMap(final SingletonSet keySet, final V value) { - return new SharedSingletonMap.Unordered<>(keySet, value); - } - } - ImmutableMapTemplate() { // Hidden on purpose } @@ -190,9 +51,9 @@ public abstract class ImmutableMapTemplate 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 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 implements Immutable { * @param 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 @NonNull UnmodifiableMapPhase instantiateWithValues(V... values); diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java index 131d69277b..3da4c38fb2 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMap.java @@ -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. * + *

+ * In case the set of keys is statically known, you can use {@link ImmutableOffsetMapTemplate} to efficiently create + * {@link ImmutableOffsetMap} instances. + * * @param the type of keys maintained by this map * @param 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 index 0000000000..091b40a4e8 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/ImmutableOffsetMapTemplate.java @@ -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 the type of keys maintained by this template + */ +public abstract class ImmutableOffsetMapTemplate extends ImmutableMapTemplate { + private static final class Ordered extends ImmutableOffsetMapTemplate { + Ordered(final Collection keys) { + super(OffsetMapCache.orderedOffsets(keys)); + } + + @Override + @NonNull ImmutableOffsetMap createMap(final ImmutableMap offsets, final V[] objects) { + return new ImmutableOffsetMap.Ordered<>(offsets, objects); + } + } + + private static final class Unordered extends ImmutableOffsetMapTemplate { + Unordered(final Collection keys) { + super(OffsetMapCache.unorderedOffsets(keys)); + } + + @Override + @NonNull ImmutableOffsetMap createMap(final ImmutableMap offsets, final V[] objects) { + return new ImmutableOffsetMap.Unordered<>(offsets, objects); + } + } + + private final @NonNull ImmutableMap offsets; + + ImmutableOffsetMapTemplate(final ImmutableMap 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 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 @NonNull ImmutableOffsetMapTemplate ordered(final Collection 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 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 @NonNull ImmutableOffsetMapTemplate unordered(final Collection keys) { + checkArgument(keys.size() > 1); + return new Unordered<>(keys); + } + + @Override + public final Set keySet() { + return offsets.keySet(); + } + + @Override + public final @NonNull ImmutableOffsetMap instantiateTransformed(final Map fromMap, + final BiFunction valueTransformer) { + final int size = offsets.size(); + checkArgument(fromMap.size() == size); + + @SuppressWarnings("unchecked") + final V[] objects = (V[]) new Object[size]; + for (Entry 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 @NonNull ImmutableOffsetMap 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 @NonNull ImmutableOffsetMap createMap(ImmutableMap offsets, V[] objects); +} \ No newline at end of file diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java b/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java index 0ea88bc772..0dd661481e 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMap.java @@ -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. * + *

+ * In case the set of keys is statically known, you can use {@link SharedSingletonMapTemplate} to efficiently create + * {@link SharedSingletonMap} instances. + * * @param the type of keys maintained by this map * @param 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 index 0000000000..97f3115295 --- /dev/null +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/SharedSingletonMapTemplate.java @@ -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 the type of keys maintained by this template + */ +public abstract class SharedSingletonMapTemplate extends ImmutableMapTemplate { + private static final class Ordered extends SharedSingletonMapTemplate { + Ordered(final K key) { + super(key); + } + + @Override + public @NonNull SharedSingletonMap instantiateWithValue(final V value) { + return new SharedSingletonMap.Ordered<>(keySet(), value); + } + } + + private static final class Unordered extends SharedSingletonMapTemplate { + Unordered(final K key) { + super(key); + } + + @Override + public @NonNull SharedSingletonMap instantiateWithValue(final V value) { + return new SharedSingletonMap.Unordered<>(keySet(), value); + } + } + + private final @NonNull SingletonSet 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 the type of keys maintained by resulting template + * @return A template object. + * @throws NullPointerException if {@code key} is null + */ + public static @NonNull SharedSingletonMapTemplate 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 the type of keys maintained by resulting template + * @return A template object. + * @throws NullPointerException if {@code key} is null + */ + public static @NonNull SharedSingletonMapTemplate unordered(final K key) { + return new Unordered<>(key); + } + + @Override + public final SingletonSet keySet() { + return keySet; + } + + @Override + public final @NonNull SharedSingletonMap instantiateTransformed(final Map fromMap, + final BiFunction valueTransformer) { + final Iterator> it = fromMap.entrySet().iterator(); + checkArgument(it.hasNext(), "Input is empty while expecting 1 item"); + + final Entry 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 @NonNull SharedSingletonMap 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 the type of mapped values + * @return An immutable map + * @throws NullPointerException if {@code value} is null + */ + public abstract @NonNull SharedSingletonMap instantiateWithValue(V value); + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).add("keySet", keySet).toString(); + } +} 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 index 7dda194e79..97caa8f105 100644 --- a/common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/ImmutableMapTemplateTest.java @@ -57,7 +57,7 @@ public class ImmutableMapTemplateTest { private static void assertOne(final ImmutableMapTemplate 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 map = template.instantiateWithValues(BAR); diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java index 53301acbae..2bd9e09405 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java @@ -537,9 +537,18 @@ public abstract class YangInstanceIdentifier implements Path keyValues) { + super(node); + this.keyValues = requireNonNull(keyValues); + } + + public NodeIdentifierWithPredicates(final QName node, final SharedSingletonMap 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 getKeyValues() { -- 2.36.6