From: Robert Varga Date: Mon, 21 Dec 2015 18:42:25 +0000 (+0100) Subject: BUG-4803: introduce unordered offset maps X-Git-Tag: release/beryllium~56 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=yangtools.git;a=commitdiff_plain;h=24d06767f3a0ead8152a745fb05eda1d4a37ba77 BUG-4803: introduce unordered offset maps This patch introduces the static factories and methods which allow users to select the appropriate implementation, either to retain or to ignore iteration order. Change-Id: I07dcf77927660461cbd266439463e8d64a1c89db Signed-off-by: Robert Varga --- 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 dc184b25ad..7e5dd349ff 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 @@ -46,20 +46,42 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase toModifiableMap() { - return MutableOffsetMap.copyOf(this); + return MutableOffsetMap.orderedCopyOf(this); } @Override - Map resolveKeys(final List keys) { - return OffsetMapCache.orderedOffsets(keys); + void setFields(final List keys, final V[] values) throws IOException { + setField(this, OFFSETS_FIELD, OffsetMapCache.orderedOffsets(keys)); + setField(this, ARRAY_FIELD, values); + } + } + + static final class Unordered extends ImmutableOffsetMap { + private static final long serialVersionUID = 1L; + + Unordered(final Map offsets, final V[] objects) { + super(offsets, objects); + } + + @Override + public MutableOffsetMap toModifiableMap() { + return MutableOffsetMap.unorderedCopyOf(this); + } + + @Override + void setFields(final List keys, final V[] values) throws IOException { + final Map offsets = OffsetMapCache.unorderedOffsets(keys); + + setField(this, OFFSETS_FIELD, offsets); + setField(this, ARRAY_FIELD, OffsetMapCache.adjustedArray(offsets, keys, values)); } } private static final long serialVersionUID = 1L; - private final Map offsets; - private final V[] objects; - private int hashCode; + private transient final Map offsets; + private transient final V[] objects; + private transient int hashCode; /** * Construct a new instance backed by specified key-to-offset map and array of objects. @@ -77,7 +99,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase toModifiableMap(); - abstract Map resolveKeys(List keys); + abstract void setFields(List keys, V[] values) throws IOException; /** * Create an {@link ImmutableOffsetMap} as a copy of an existing map. This is actually not completely true, @@ -89,8 +111,25 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase Map copyOf(@Nonnull final Map m) { + return orderedCopyOf(m); + } + + /** + * 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. + * + * @param m Input map, may not be null. + * @return An isolated, immutable copy of the input map + */ + @Nonnull public static Map orderedCopyOf(@Nonnull final Map m) { // Prevent a copy. Note that ImmutableMap is not listed here because of its potentially larger keySet overhead. if (m instanceof ImmutableOffsetMap || m instanceof SharedSingletonMap) { return m; @@ -109,7 +148,7 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase e = m.entrySet().iterator().next(); - return SharedSingletonMap.of(e.getKey(), e.getValue()); + return SharedSingletonMap.orderedOf(e.getKey(), e.getValue()); } final Map offsets = OffsetMapCache.orderedOffsets(m.keySet()); @@ -122,6 +161,49 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase(offsets, array); } + /** + * 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. + * + * @param m Input map, may not be null. + * @return An isolated, immutable copy of the input map + */ + @Nonnull public static Map unorderedCopyOf(@Nonnull final Map m) { + // Prevent a copy. Note that ImmutableMap is not listed here because of its potentially larger keySet overhead. + if (m instanceof ImmutableOffsetMap || m instanceof SharedSingletonMap) { + return m; + } + + // Familiar and efficient to copy + if (m instanceof MutableOffsetMap) { + return ((MutableOffsetMap) m).toUnmodifiableMap(); + } + + final int size = m.size(); + if (size == 0) { + // Shares a single object + return ImmutableMap.of(); + } + if (size == 1) { + // Efficient single-entry implementation + final Entry e = m.entrySet().iterator().next(); + return SharedSingletonMap.unorderedOf(e.getKey(), e.getValue()); + } + + final Map offsets = OffsetMapCache.unorderedOffsets(m.keySet()); + @SuppressWarnings("unchecked") + final V[] array = (V[]) new Object[offsets.size()]; + for (Entry e : m.entrySet()) { + array[offsets.get(e.getKey())] = e.getValue(); + } + + return new Unordered<>(offsets, array); + } + @Override public final int size() { return offsets.size(); @@ -320,9 +402,9 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase map, final Field field, final Object value) throws IOException { try { - field.set(this, value); + field.set(map, value); } catch (IllegalArgumentException | IllegalAccessException e) { throw new IOException("Failed to set field " + field, e); } @@ -340,7 +422,6 @@ public abstract class ImmutableOffsetMap implements UnmodifiableMapPhase extends AbstractMap implement UnmodifiableMapPhase unmodifiedMap(final Map offsets, final V[] objects) { return new ImmutableOffsetMap.Ordered<>(offsets, objects); } + + @Override + SharedSingletonMap singletonMap() { + return SharedSingletonMap.orderedCopyOf(this); + } + } + + static final class Unordered extends MutableOffsetMap { + Unordered() { + super(new HashMap()); + } + + Unordered(final Map source) { + super(OffsetMapCache.unorderedOffsets(source.keySet()), source, new HashMap()); + } + + Unordered(final Map offsets, final V[] objects) { + super(offsets, objects, new HashMap()); + } + + @Override + Object removedObject() { + return null; + } + + @Override + UnmodifiableMapPhase modifiedMap(final List keys, final V[] objects) { + final Map offsets = OffsetMapCache.unorderedOffsets(keys); + return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, objects)); + } + + @Override + UnmodifiableMapPhase unmodifiedMap(final Map offsets, final V[] objects) { + return new ImmutableOffsetMap.Unordered<>(offsets, objects); + } + + @Override + SharedSingletonMap singletonMap() { + return SharedSingletonMap.unorderedCopyOf(this); + } } private static final Object[] EMPTY_ARRAY = new Object[0]; @@ -103,25 +143,58 @@ public abstract class MutableOffsetMap extends AbstractMap implement this.needClone = false; } + /** + * @deprecated Use {@link #orderedCopyOf(Map)} or {@link #unorderedCopyOf(Map)} instead. + */ + @Deprecated public static MutableOffsetMap copyOf(final Map m) { - if (m instanceof MutableOffsetMap) { - return ((MutableOffsetMap) m).clone(); + return orderedCopyOf(m); + } + + public static MutableOffsetMap orderedCopyOf(final Map m) { + if (m instanceof Ordered) { + return ((Ordered) m).clone(); } if (m instanceof ImmutableOffsetMap) { final ImmutableOffsetMap om = (ImmutableOffsetMap) m; - return new MutableOffsetMap.Ordered<>(om.offsets(), om.objects()); + return new Ordered<>(om.offsets(), om.objects()); } - return new MutableOffsetMap.Ordered<>(m); + return new Ordered<>(m); } + public static MutableOffsetMap unorderedCopyOf(final Map m) { + if (m instanceof Unordered) { + return ((Unordered) m).clone(); + } + if (m instanceof ImmutableOffsetMap) { + final ImmutableOffsetMap om = (ImmutableOffsetMap) m; + return new Unordered<>(om.offsets(), om.objects()); + } + + return new Unordered<>(m); + } + + /** + * @deprecated Use {@link #ordered()} or {@link #unordered()} instead. + */ + @Deprecated public static MutableOffsetMap of() { + return ordered(); + } + + public static MutableOffsetMap ordered() { return new MutableOffsetMap.Ordered<>(); } + public static MutableOffsetMap unordered() { + return new MutableOffsetMap.Unordered<>(); + } + abstract Object removedObject(); abstract UnmodifiableMapPhase modifiedMap(List keys, V[] objects); abstract UnmodifiableMapPhase unmodifiedMap(Map offsets, V[] objects); + abstract SharedSingletonMap singletonMap(); @Override public final int size() { @@ -280,7 +353,7 @@ public abstract class MutableOffsetMap extends AbstractMap implement return ImmutableMap.of(); } if (s == 1) { - return SharedSingletonMap.copyOf(this); + return singletonMap(); } // Construct the set of keys diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java index b9776e3421..b7ba220379 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java @@ -8,6 +8,7 @@ package org.opendaylight.yangtools.util; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Verify; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -15,7 +16,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet; +import java.lang.reflect.Array; import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.Map; final class OffsetMapCache { @@ -63,4 +67,35 @@ final class OffsetMapCache { return offsets(ImmutableSet.copyOf(args)); } + + private static V[] adjustArray(final Map offsets, final List keys, final V[] array) { + @SuppressWarnings("unchecked") + final V[] ret = (V[]) Array.newInstance(array.getClass().getComponentType(), array.length); + + int i = 0; + for (final K k : keys) { + final Integer o = Verify.verifyNotNull(offsets.get(k), "Key %s not present in offsets %s", k, offsets); + ret[o] = array[i++]; + } + + return ret; + } + + static V[] adjustedArray(final Map offsets, final List keys, final V[] array) { + Verify.verify(offsets.size() == keys.size(), "Offsets %s do not match keys %s", offsets, keys); + + // This relies on the fact that offsets has an ascending iterator + final Iterator oi = offsets.keySet().iterator(); + final Iterator ki = keys.iterator(); + + while (oi.hasNext()) { + final K o = oi.next(); + final K k = ki.next(); + if (!k.equals(o)) { + return adjustArray(offsets, keys, array); + } + } + + return array; + } } 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 9e1776adfb..ef420b4b57 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 @@ -34,7 +34,20 @@ public abstract class SharedSingletonMap implements Serializable, Unmodifi @Override public ModifiableMapPhase toModifiableMap() { - return MutableOffsetMap.copyOf(this); + return MutableOffsetMap.orderedCopyOf(this); + } + } + + private static final class Unordered extends SharedSingletonMap { + private static final long serialVersionUID = 1L; + + Unordered(final K key, final V value) { + super(key, value); + } + + @Override + public ModifiableMapPhase toModifiableMap() { + return MutableOffsetMap.unorderedCopyOf(this); } } @@ -56,17 +69,44 @@ public abstract class SharedSingletonMap implements Serializable, Unmodifi this.value = Preconditions.checkNotNull(value); } + /** + * @deprecated Use {@link #orderedOf(Object, Object)} or {@link #unorderedOf(Object, Object)} instead. + */ + @Deprecated public static SharedSingletonMap of(final K key, final V value) { return new Ordered<>(key, value); } + public static SharedSingletonMap orderedOf(final K key, final V value) { + return new Ordered<>(key, value); + } + + public static SharedSingletonMap unorderedOf(final K key, final V value) { + return new Unordered<>(key, value); + } + + /** + * @deprecated Use {@link #orderedCopyOf(Map)} or {@link #unorderedCopyOf(Map)} instead. + */ + @Deprecated public static SharedSingletonMap copyOf(final Map m) { + return orderedCopyOf(m); + } + + public static SharedSingletonMap orderedCopyOf(final Map m) { Preconditions.checkArgument(m.size() == 1); final Entry e = m.entrySet().iterator().next(); return new Ordered<>(e.getKey(), e.getValue()); } + public static SharedSingletonMap unorderedCopyOf(final Map m) { + Preconditions.checkArgument(m.size() == 1); + + final Entry e = m.entrySet().iterator().next(); + return new Unordered<>(e.getKey(), e.getValue()); + } + @Override public final SingletonSet> entrySet() { return SingletonSet.>of(new SimpleImmutableEntry<>(keySet.getElement(), value)); diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java index 578b87bc14..fad44069e5 100644 --- a/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/OffsetMapTest.java @@ -38,7 +38,11 @@ public class OffsetMapTest { private final Map threeEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"); private ImmutableOffsetMap createMap() { - return (ImmutableOffsetMap) ImmutableOffsetMap.copyOf(twoEntryMap); + return (ImmutableOffsetMap) ImmutableOffsetMap.orderedCopyOf(twoEntryMap); + } + + private ImmutableOffsetMap unorderedMap() { + return (ImmutableOffsetMap) ImmutableOffsetMap.unorderedCopyOf(twoEntryMap); } @Before @@ -54,7 +58,7 @@ public class OffsetMapTest { @Test public void testCopyEmptyMap() { final Map source = Collections.emptyMap(); - final Map result = ImmutableOffsetMap.copyOf(source); + final Map result = ImmutableOffsetMap.orderedCopyOf(source); assertEquals(source, result); assertTrue(result instanceof ImmutableMap); @@ -63,7 +67,7 @@ public class OffsetMapTest { @Test public void testCopySingletonMap() { final Map source = Collections.singletonMap("a", "b"); - final Map result = ImmutableOffsetMap.copyOf(source); + final Map result = ImmutableOffsetMap.orderedCopyOf(source); assertEquals(source, result); assertTrue(result instanceof SharedSingletonMap); @@ -84,10 +88,10 @@ public class OffsetMapTest { assertTrue(Iterators.elementsEqual(twoEntryMap.entrySet().iterator(), map.entrySet().iterator())); // Should result in the same object - assertSame(map, ImmutableOffsetMap.copyOf(map)); + assertSame(map, ImmutableOffsetMap.orderedCopyOf(map)); final Map mutable = map.toModifiableMap(); - final Map copy = ImmutableOffsetMap.copyOf(mutable); + final Map copy = ImmutableOffsetMap.orderedCopyOf(mutable); assertEquals(mutable, copy); assertEquals(map, copy); @@ -334,9 +338,28 @@ public class OffsetMapTest { assertFalse(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator())); } + @Test + public void testReusedOffsetsUnordered() { + final ImmutableOffsetMap source = unorderedMap(); + final MutableOffsetMap mutable = source.toModifiableMap(); + + mutable.remove("k1"); + mutable.put("k1", "v1"); + + final ImmutableOffsetMap result = (ImmutableOffsetMap) mutable.toUnmodifiableMap(); + assertEquals(source, result); + + // Only offsets should be shared + assertSame(source.offsets(), result.offsets()); + assertNotSame(source.objects(), result.objects()); + + // Iterator order needs to be preserved + assertTrue(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator())); + } + @Test public void testEmptyMutable() throws CloneNotSupportedException { - final MutableOffsetMap map = MutableOffsetMap.of(); + final MutableOffsetMap map = MutableOffsetMap.ordered(); assertTrue(map.isEmpty()); final Map other = map.clone(); @@ -412,6 +435,24 @@ public class OffsetMapTest { assertFalse(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator())); } + @Test + public void testExpansionWithoutOrder() { + final MutableOffsetMap mutable = unorderedMap().toModifiableMap(); + + mutable.remove("k1"); + mutable.put("k3", "v3"); + mutable.put("k1", "v1"); + + assertEquals(ImmutableMap.of("k3", "v3"), mutable.newKeys()); + + final Map result = mutable.toUnmodifiableMap(); + + assertTrue(result instanceof ImmutableOffsetMap); + assertEquals(threeEntryMap, result); + assertEquals(result, threeEntryMap); + assertTrue(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator())); + } + @Test public void testReplacedValue() { final ImmutableOffsetMap source = createMap(); @@ -462,6 +503,38 @@ public class OffsetMapTest { assertTrue(Iterables.elementsEqual(source.entrySet(), immutable.entrySet())); } + @Test + public void testCloneableFlippingUnordered() throws CloneNotSupportedException { + final MutableOffsetMap source = unorderedMap().toModifiableMap(); + + // Must clone before mutation + assertTrue(source.needClone()); + + // Non-existent entry, should not clone + source.remove("non-existent"); + assertTrue(source.needClone()); + + // Changes the array, should clone + source.remove("k1"); + assertFalse(source.needClone()); + + // Create a clone of the map, which shares the array + final MutableOffsetMap result = source.clone(); + assertFalse(source.needClone()); + assertTrue(result.needClone()); + assertSame(source.array(), result.array()); + + // Changes the array, should clone + source.put("k1", "v2"); + assertFalse(source.needClone()); + assertTrue(result.needClone()); + + // Creates a immutable view, which shares the array + final ImmutableOffsetMap immutable = (ImmutableOffsetMap) source.toUnmodifiableMap(); + assertTrue(source.needClone()); + assertSame(source.array(), immutable.objects()); + } + @Test public void testMutableEntrySet() { final MutableOffsetMap map = createMap().toModifiableMap(); diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/SharedSingletonMapTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/SharedSingletonMapTest.java index 3e0bb753df..58064e87bc 100644 --- a/common/util/src/test/java/org/opendaylight/yangtools/util/SharedSingletonMapTest.java +++ b/common/util/src/test/java/org/opendaylight/yangtools/util/SharedSingletonMapTest.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -18,7 +19,7 @@ import org.junit.Test; public class SharedSingletonMapTest { private static UnmodifiableMapPhase create() { - return SharedSingletonMap.of("k1", "v1"); + return SharedSingletonMap.orderedOf("k1", "v1"); } @Test @@ -59,12 +60,39 @@ public class SharedSingletonMapTest { assertFalse(m.equals(Collections.singletonMap("k1", null))); assertFalse(m.equals(Collections.singletonMap(null, "v1"))); assertFalse(m.equals(Collections.singletonMap("k1", "v2"))); + assertFalse(m.equals(ImmutableMap.of("k1", "v1", "k2", "v2"))); final Set set = m.keySet(); assertTrue(set instanceof SingletonSet); assertTrue(set.contains("k1")); } + @Test + public void testOrderedCopyOf() { + final Map t = Collections.singletonMap("k1", "v1"); + final Map m = SharedSingletonMap.orderedCopyOf(t); + assertTrue(t.equals(m)); + assertTrue(m.equals(t)); + } + + @Test + public void testUnorderedCopyOf() { + final Map t = Collections.singletonMap("k1", "v1"); + final Map m = SharedSingletonMap.unorderedCopyOf(t); + assertTrue(t.equals(m)); + assertTrue(m.equals(t)); + } + + @Test(expected=IllegalArgumentException.class) + public void testEmptyOrderedCopyOf() { + SharedSingletonMap.orderedCopyOf(ImmutableMap.of()); + } + + @Test(expected=IllegalArgumentException.class) + public void testEmptyUnorderedCopyOf() { + SharedSingletonMap.unorderedCopyOf(ImmutableMap.of()); + } + @Test(expected=UnsupportedOperationException.class) public void testClear() { create().clear(); 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 717b4cadd7..9944d741e6 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 @@ -509,11 +509,12 @@ public abstract class YangInstanceIdentifier implements Path keyValues) { super(node); // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set. - this.keyValues = ImmutableOffsetMap.copyOf(keyValues); + this.keyValues = ImmutableOffsetMap.unorderedCopyOf(keyValues); } public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) { - this(node, SharedSingletonMap.of(key, value)); + super(node); + this.keyValues = SharedSingletonMap.unorderedOf(key, value); } public Map getKeyValues() { diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/AbstractImmutableDataContainerNode.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/AbstractImmutableDataContainerNode.java index a406b154f9..ea6e954da7 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/AbstractImmutableDataContainerNode.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/AbstractImmutableDataContainerNode.java @@ -16,14 +16,16 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgum import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; -public abstract class AbstractImmutableDataContainerNode extends AbstractImmutableNormalizedNode>> implements Immutable, DataContainerNode { +public abstract class AbstractImmutableDataContainerNode + extends AbstractImmutableNormalizedNode>> + implements Immutable, DataContainerNode { private final Map> children; public AbstractImmutableDataContainerNode( final Map> children, final K nodeIdentifier) { super(nodeIdentifier); - this.children = ImmutableOffsetMap.copyOf(children); + this.children = ImmutableOffsetMap.unorderedCopyOf(children); } @Override