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);
+ }
}