This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 1442ecb152 IGNITE-18643 Mutability assertions in configuration trees. 
(#1582)
1442ecb152 is described below

commit 1442ecb15287032e89bbcac715ca49db18314cbe
Author: Ivan Bessonov <[email protected]>
AuthorDate: Fri Feb 10 12:21:57 2023 +0300

    IGNITE-18643 Mutability assertions in configuration trees. (#1582)
---
 .../configuration/ConfigurationChanger.java        | 32 +++++++++++++++
 .../ignite/internal/configuration/SuperRoot.java   |  4 ++
 .../configuration/asm/InnerNodeAsmGenerator.java   | 32 +++++++++++++++
 .../configuration/tree/ConstructableTreeNode.java  |  7 ++++
 .../internal/configuration/tree/InnerNode.java     | 34 +++++++++++++++-
 .../internal/configuration/tree/NamedListNode.java | 46 ++++++++++++++++++++++
 .../tree/ConstructableTreeNodeTest.java            | 22 +++++++++++
 .../configuration/tree/InternalIdTest.java         | 12 ++++--
 .../configuration/tree/NamedListNodeTest.java      | 37 +++++++++++++++--
 9 files changed, 216 insertions(+), 10 deletions(-)

diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
index e018ee70d8..fca871f5dc 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
@@ -65,6 +65,7 @@ import 
org.apache.ignite.internal.configuration.direct.KeyPathNode;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.Data;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.NamedListNode;
@@ -147,9 +148,40 @@ public abstract class ConfigurationChanger implements 
DynamicConfigurationChange
         private StorageRoots(SuperRoot roots, long version) {
             this.roots = roots;
             this.version = version;
+
+            makeImmutable(roots);
         }
     }
 
+    /**
+     * Makes the node immutable by calling {@link 
ConstructableTreeNode#makeImmutable()} on each sub-node recursively.
+     */
+    private static void makeImmutable(InnerNode node) {
+        if (!node.makeImmutable()) {
+            return;
+        }
+
+        node.traverseChildren(new ConfigurationVisitor<>() {
+            @Override
+            public @Nullable Object visitInnerNode(String key, InnerNode node) 
{
+                makeImmutable(node);
+
+                return null;
+            }
+
+            @Override
+            public @Nullable Object visitNamedListNode(String key, 
NamedListNode<?> node) {
+                if (node.makeImmutable()) {
+                    for (String namedListKey : node.namedListKeys()) {
+                        makeImmutable(node.getInnerNode(namedListKey));
+                    }
+                }
+
+                return null;
+            }
+        }, true);
+    }
+
     /**
      * Constructor.
      *
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
index 7113b70c39..27abaa6005 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
@@ -79,6 +79,8 @@ public final class SuperRoot extends InnerNode {
     public void addRoot(RootKey<?, ?> rootKey, InnerNode root) {
         assert !roots.containsKey(rootKey.key()) : rootKey.key() + " : " + 
roots;
 
+        assertMutability();
+
         roots.put(rootKey.key(), new RootInnerNode(rootKey, root));
     }
 
@@ -128,6 +130,8 @@ public final class SuperRoot extends InnerNode {
             ConfigurationSource src,
             boolean includeInternal
     ) throws NoSuchElementException {
+        assertMutability();
+
         RootInnerNode root = roots.get(key);
 
         if (root == null) {
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
index fd6ac4a357..e60d524065 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
@@ -160,6 +160,9 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
     /** {@link InnerNode#internalSchemaTypes}. */
     private static final Method INTERNAL_SCHEMA_TYPES_MTD;
 
+    /** {@link InnerNode#assertMutability()}. */
+    private static final Method ASSERT_MUTABILITY_MTD;
+
     /** {@code Node#convert} method name. */
     private static final String CONVERT_MTD_NAME = "convert";
 
@@ -205,6 +208,7 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
 
             INTERNAL_SCHEMA_TYPES_MTD = 
InnerNode.class.getDeclaredMethod("internalSchemaTypes");
 
+            ASSERT_MUTABILITY_MTD = 
InnerNode.class.getDeclaredMethod("assertMutability");
         } catch (NoSuchMethodException nsme) {
             throw new ExceptionInInitializerError(nsme);
         }
@@ -696,6 +700,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
 
         BytecodeBlock bytecodeBlock = new BytecodeBlock();
 
+        addAssertMutabilityMethodCall(classDef, changeMtd, bytecodeBlock);
+
         if (!schemaFieldType.isPrimitive()) {
             // Objects.requireNonNull(newValue, "change");
             bytecodeBlock.append(invokeStatic(REQUIRE_NON_NULL, changeVar, 
constantString("change")));
@@ -759,6 +765,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
 
         BytecodeBlock shortBytecodeBlock = new BytecodeBlock();
 
+        addAssertMutabilityMethodCall(classDef, shortChangeMtd, 
shortBytecodeBlock);
+
         BytecodeExpression newValue;
 
         if (isConfigValue(schemaField)) {
@@ -791,6 +799,24 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
         return shortChangeMtd;
     }
 
+    /**
+     * Adds a call of {@link InnerNode#assertMutability()} to a body.
+     * Should be done for every method that mutates the node instance.
+     */
+    private void addAssertMutabilityMethodCall(ClassDefinition classDef, 
MethodDefinition changeMtd, BytecodeBlock body) {
+        if (classDef == innerNodeClassDef) {
+            // this.assertMutability();
+            body.append(changeMtd.getThis().invoke(ASSERT_MUTABILITY_MTD));
+        } else {
+            // this.this$0.assertMutability();
+            body.append(
+                    changeMtd.getThis()
+                            .getField(classDef.getType(), "this$0", 
innerNodeClassDef.getType())
+                            .invoke(ASSERT_MUTABILITY_MTD)
+            );
+        }
+    }
+
     /**
      * Adds a check that expected polymorphic type ID matches the real one. 
Throws exception otherwise. Simply adds {@code bytecodeBlock}
      * into methods body if {@code getPolymorphicTypeIdFieldFun} is {@code 
null} (this means that class is not polymorphic).
@@ -1086,6 +1112,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
         Variable keyVar = constructMtd.getScope().getVariable("key");
         Variable srcVar = constructMtd.getScope().getVariable("src");
 
+        addAssertMutabilityMethodCall(innerNodeClassDef, constructMtd, 
constructMtd.getBody());
+
         // Create switch for public (common in case polymorphic config) fields 
only.
         StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
@@ -1339,6 +1367,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
                 arg("key", String.class)
         ).addException(NoSuchElementException.class);
 
+        addAssertMutabilityMethodCall(innerNodeClassDef, constructDfltMtd, 
constructDfltMtd.getBody());
+
         Variable keyVar = constructDfltMtd.getScope().getVariable("key");
 
         // Create switch for public (common in case polymorphic config) + 
internal fields.
@@ -1489,6 +1519,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
                 arg("value", String.class)
         );
 
+        addAssertMutabilityMethodCall(innerNodeClassDef, 
setInjectedNameFieldValueMtd, setInjectedNameFieldValueMtd.getBody());
+
         Variable valueVar = 
setInjectedNameFieldValueMtd.getScope().getVariable("value");
 
         setInjectedNameFieldValueMtd.getBody()
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
index 5be2602fe7..a82d45fa46 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
@@ -41,4 +41,11 @@ public interface ConstructableTreeNode {
      * @return Copy of the object.
      */
     ConstructableTreeNode copy();
+
+    /**
+     * Make the node immutable. Following calls of mutating methods on the 
node should not happen and may result in errors.
+     *
+     * @return {@code true} if node became immutable, {@code false} if it has 
already been immutable before.
+     */
+    boolean makeImmutable();
 }
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
index 1b76a7e586..bc3a6dac10 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
@@ -40,6 +40,9 @@ public abstract class InnerNode implements 
TraversableTreeNode, ConstructableTre
     @Nullable
     private UUID internalId;
 
+    /** Immutability flag. */
+    private boolean immutable = false;
+
     /**
      * Returns internal id of the node.
      */
@@ -53,6 +56,8 @@ public abstract class InnerNode implements 
TraversableTreeNode, ConstructableTre
      * @param internalId New internal id value.
      */
     public final void internalId(UUID internalId) {
+        assertMutability();
+
         this.internalId = internalId;
     }
 
@@ -202,12 +207,37 @@ public abstract class InnerNode implements 
TraversableTreeNode, ConstructableTre
     @Override
     public InnerNode copy() {
         try {
-            return (InnerNode) clone();
+            InnerNode clone = (InnerNode) clone();
+
+            clone.immutable = false;
+
+            return clone;
         } catch (CloneNotSupportedException e) {
             throw new IllegalStateException(e);
         }
     }
 
+    /**
+     * Checks that current instance is mutable.
+     *
+     * @throws AssertionError If the object is immutable.
+     * @see ConstructableTreeNode#makeImmutable()
+     */
+    public final void assertMutability() {
+        if (immutable) {
+            throw new AssertionError("Mutating immutable configuration");
+        }
+    }
+
+    @Override
+    public boolean makeImmutable() {
+        boolean updated = !immutable;
+
+        immutable = true;
+
+        return updated;
+    }
+
     /**
      * Returns specific {@code Node} of the value. Overridden for polymorphic 
configuration to get a specific polymorphic configuration
      * instance.
@@ -229,7 +259,7 @@ public abstract class InnerNode implements 
TraversableTreeNode, ConstructableTre
      * Sets the value of a field with {@link InjectedName}.
      */
     public void setInjectedNameFieldValue(String value) {
-        // No-op.
+        assertMutability();
     }
 
     /**
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index 2c9f52eb0d..4957bd497d 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -69,6 +69,9 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
     @Nullable
     private final String typeIdFieldName;
 
+    /** Immutability flag. */
+    private boolean immutable = false;
+
     /**
      * Default constructor.
      *
@@ -151,6 +154,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
+        assertMutability();
+
         checkNewKey(key);
 
         ElementDescriptor element = newElementDescriptor(key);
@@ -170,6 +175,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
+        assertMutability();
+
         if (index < 0 || index > map.size()) {
             throw new IndexOutOfBoundsException(index);
         }
@@ -194,6 +201,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
+        assertMutability();
+
         ElementDescriptor precedingElement = map.get(precedingKey);
 
         if (precedingElement == null) {
@@ -221,6 +230,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
+        assertMutability();
+
         ElementDescriptor element = map.get(key);
 
         if (element != null && element.value == null) {
@@ -248,6 +259,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
+        assertMutability();
+
         ElementDescriptor element = map.get(key);
 
         if (element == null) {
@@ -271,6 +284,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         Objects.requireNonNull(oldKey, "oldKey");
         Objects.requireNonNull(newKey, "newKey");
 
+        assertMutability();
+
         if (oldKey.equals(newKey)) {
             return this;
         }
@@ -321,6 +336,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
     public NamedListChange<N, N> delete(String key) {
         Objects.requireNonNull(key, "key");
 
+        assertMutability();
+
         ElementDescriptor element = map.get(key);
 
         if (element != null) {
@@ -348,6 +365,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
      * @param internalId New id to associate with the key.
      */
     public void setInternalId(String key, UUID internalId) {
+        assertMutability();
+
         ElementDescriptor element = map.get(key);
 
         if (element != null) {
@@ -402,6 +421,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
      * @param key Element's key.
      */
     public void forceDelete(String key) {
+        assertMutability();
+
         ElementDescriptor removed = map.remove(key);
 
         if (removed != null) {
@@ -415,6 +436,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
      * @param orderedKeys List of keys in new order. Must have the same set of 
keys in it.
      */
     public void reorderKeys(List<String> orderedKeys) {
+        assertMutability();
+
         map.reorderKeys(orderedKeys);
     }
 
@@ -423,6 +446,8 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
     public void construct(String key, ConfigurationSource src, boolean 
includeInternal) {
         Objects.requireNonNull(key, "key");
 
+        assertMutability();
+
         if (src == null) {
             delete(key);
         } else {
@@ -484,6 +509,27 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
         return new NamedListNode<>(this);
     }
 
+    /**
+     * Checks that current instance is mutable.
+     *
+     * @throws AssertionError If the object is immutable.
+     * @see ConstructableTreeNode#makeImmutable()
+     */
+    private void assertMutability() {
+        if (immutable) {
+            throw new AssertionError("Mutating immutable configuration");
+        }
+    }
+
+    @Override
+    public boolean makeImmutable() {
+        boolean updated = !immutable;
+
+        immutable = true;
+
+        return updated;
+    }
+
     /**
      * Creates new element instance with initialized defaults.
      *
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
index 2f5954945c..8559c090ae 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
@@ -26,7 +26,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.UUID;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -213,4 +215,24 @@ public class ConstructableTreeNodeTest {
 
         assertEquals(99, child.intCfg());
     }
+
+    @Test
+    public void makeImmutable() {
+        // Inner node with no leaves.
+        var parentNode = newParentInstance();
+
+        parentNode.makeImmutable();
+
+        assertThrows(AssertionError.class, () -> parentNode.construct("child", 
ConfigurationUtil.EMPTY_CFG_SRC, true));
+        assertThrows(AssertionError.class, () -> 
parentNode.constructDefault("child"));
+
+        assertThrows(AssertionError.class, () -> parentNode.changeChild());
+        assertThrows(AssertionError.class, () -> parentNode.changeChild(child 
-> {}));
+
+        assertThrows(AssertionError.class, () -> 
parentNode.internalId(UUID.randomUUID()));
+        assertThrows(AssertionError.class, () -> 
parentNode.setInjectedNameFieldValue("foo"));
+
+        // Copy is always mutable.
+        parentNode.copy().assertMutability();
+    }
 }
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
index c032f0cdfb..dfcc0a5d3a 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.configuration.tree;
 
 
 import static 
org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -38,6 +39,7 @@ import 
org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.direct.DirectPropertiesTest;
 import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -60,7 +62,7 @@ public class InternalIdTest {
         public UUID id;
     }
 
-    /** Schema for the polumorphic configuration. */
+    /** Schema for the polymorphic configuration. */
     @PolymorphicConfig
     public static class InternalIdPolymorphicConfigurationSchema {
         @PolymorphicId
@@ -70,7 +72,7 @@ public class InternalIdTest {
         public UUID id;
     }
 
-    /** Single polymorhic extension. */
+    /** Single polymorphic extension. */
     @PolymorphicConfigInstance("foo")
     public static class InternalIdFooConfigurationSchema extends 
InternalIdPolymorphicConfigurationSchema {
     }
@@ -104,8 +106,10 @@ public class InternalIdTest {
 
         UUID internalId = UUID.randomUUID();
 
+        assertThat(cfg.change(change -> ((InnerNode) 
change).internalId(internalId)), willCompleteSuccessfully());
+
         // Put it there manually, this simplifies the test.
-        ((InnerNode) cfg.value()).internalId(internalId);
+        IgniteTestUtils.setFieldValue(cfg.value(), InnerNode.class, 
"internalId", internalId);
 
         // Getting it from the explicit configuration cast should work.
         assertThat(((InternalIdInternalConfiguration) cfg).id().value(), 
is(equalTo(internalId)));
@@ -115,7 +119,7 @@ public class InternalIdTest {
     }
 
     /**
-     * Tests that internal id, decared in polymorphic configuration, works 
properly.
+     * Tests that internal id, declared in polymorphic configuration, works 
properly.
      */
     @Test
     public void testPolymorphicExtension() throws Exception {
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index 07932cf2c1..83418ce89b 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -42,6 +42,7 @@ import 
org.apache.ignite.internal.configuration.TestConfigurationChanger;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.Data;
 import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
@@ -307,7 +308,7 @@ public class NamedListNodeTest {
     /** Tests exceptions described in methods signatures. */
     @Test
     public void errors() throws Exception {
-        var b = new NamedListNode<>("name", () -> 
cgen.instantiateNode(SecondConfigurationSchema.class), null);
+        var b = new NamedListNode<>("name", 
NamedListNodeTest::instantiateChildNode, null);
 
         b.create("X", x -> {
         }).create("Y", y -> {
@@ -364,7 +365,7 @@ public class NamedListNodeTest {
      */
     @Test
     public void testUpdate() {
-        var list = new NamedListNode<SecondChange>("name", () -> 
cgen.instantiateNode(SecondConfigurationSchema.class), null);
+        var list = new NamedListNode<SecondChange>("name", 
NamedListNodeTest::instantiateChildNode, null);
 
         list.create("foo", ch -> ch.changeStr("bar"));
 
@@ -382,7 +383,7 @@ public class NamedListNodeTest {
 
     @Test
     public void testUpdateErrors() {
-        var list = new NamedListNode<SecondChange>("name", () -> 
cgen.instantiateNode(SecondConfigurationSchema.class), null);
+        var list = new NamedListNode<SecondChange>("name", 
NamedListNodeTest::instantiateChildNode, null);
 
         assertThrows(NullPointerException.class, () -> list.update(null, ch -> 
{}));
         assertThrows(NullPointerException.class, () -> list.update("foo", 
null));
@@ -393,7 +394,7 @@ public class NamedListNodeTest {
 
     @Test
     void testCreateAfterErrors() {
-        var list = new NamedListNode<>("name", () -> 
cgen.instantiateNode(SecondConfigurationSchema.class), null);
+        var list = new NamedListNode<>("name", 
NamedListNodeTest::instantiateChildNode, null);
 
         list
                 .create("X", x -> {})
@@ -414,4 +415,32 @@ public class NamedListNodeTest {
         // inserting after a removed key should throw
         assertThrows(IllegalArgumentException.class, () -> 
list.delete("X").createAfter("X", "foo", foo -> {}));
     }
+
+    @Test
+    public void makeImmutable() {
+        var list = new NamedListNode<>("name", 
NamedListNodeTest::instantiateChildNode, null);
+
+        list.makeImmutable();
+
+        assertThrows(AssertionError.class, () -> list.construct("elem", 
ConfigurationUtil.EMPTY_CFG_SRC, true));
+
+        assertThrows(AssertionError.class, () -> list.setInternalId("elem", 
UUID.randomUUID()));
+
+        assertThrows(AssertionError.class, () -> list.reorderKeys(List.of()));
+
+        assertThrows(AssertionError.class, () -> list.create("elem", elem -> 
{}));
+        assertThrows(AssertionError.class, () -> list.create(0, "elem", elem 
-> {}));
+        assertThrows(AssertionError.class, () -> list.createAfter("foo", 
"elem", elem -> {}));
+        assertThrows(AssertionError.class, () -> list.createOrUpdate("elem", 
elem -> {}));
+
+        assertThrows(AssertionError.class, () -> list.delete("elem"));
+        assertThrows(AssertionError.class, () -> list.forceDelete("elem"));
+
+        // Copy is always mutable.
+        list.copy().create("elem", elem -> {});
+    }
+
+    private static InnerNode instantiateChildNode() {
+        return cgen.instantiateNode(SecondConfigurationSchema.class);
+    }
 }

Reply via email to