You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/02/09 10:48:28 UTC
[ignite-3] branch main updated: IGNITE-14121 Ability to generate
configuration trees from arbitrary sources (#44)
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 bca081e IGNITE-14121 Ability to generate configuration trees from arbitrary sources (#44)
bca081e is described below
commit bca081e2d05a23bef524e56f1c0ea5f47c298c1a
Author: ibessonov <be...@gmail.com>
AuthorDate: Tue Feb 9 13:48:22 2021 +0300
IGNITE-14121 Ability to generate configuration trees from arbitrary sources (#44)
---
.../processor/internal/Processor.java | 72 +++++-
.../sample/ConstructableTreeNodeTest.java | 147 ++++++++++++
...NodesTest.java => TraversableTreeNodeTest.java} | 10 +-
.../configuration/util/ConfigurationUtilTest.java | 97 +++++++-
.../configuration/tree/ConfigurationSource.java | 40 ++++
.../configuration/tree/ConstructableTreeNode.java | 41 ++++
.../ignite/configuration/tree/InnerNode.java | 37 ++-
.../ignite/configuration/tree/NamedListNode.java | 20 +-
.../configuration/util/ConfigurationUtil.java | 251 ++++++++++++++++++++-
9 files changed, 682 insertions(+), 33 deletions(-)
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
index f8fe5ef..ef64d72 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -74,6 +74,7 @@ import org.apache.ignite.configuration.processor.internal.pojo.ChangeClassGenera
import org.apache.ignite.configuration.processor.internal.pojo.InitClassGenerator;
import org.apache.ignite.configuration.processor.internal.pojo.ViewClassGenerator;
import org.apache.ignite.configuration.processor.internal.validation.ValidationGenerator;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.tree.NamedListChange;
@@ -875,11 +876,20 @@ public class Processor extends AbstractProcessor {
.addParameter(ClassName.get(ConfigurationVisitor.class), "visitor")
.beginControlFlow("switch (key)");
+ MethodSpec.Builder constructBuilder = MethodSpec.methodBuilder("construct")
+ .addAnnotation(Override.class)
+ .addJavadoc("{@inheritDoc}")
+ .addModifiers(PUBLIC)
+ .returns(TypeName.VOID)
+ .addParameter(ClassName.get(String.class), "key")
+ .addParameter(ClassName.get(ConfigurationSource.class), "src")
+ .beginControlFlow("switch (key)");
+
ClassName consumerClsName = ClassName.get(Consumer.class);
for (VariableElement field : fields) {
Value valAnnotation = field.getAnnotation(Value.class);
- boolean immutable = valAnnotation != null && valAnnotation.immutable();
+ boolean mutable = valAnnotation == null || !valAnnotation.immutable();
String fieldName = field.getSimpleName().toString();
TypeName schemaFieldType = TypeName.get(field.asType());
@@ -907,6 +917,8 @@ public class Processor extends AbstractProcessor {
((ClassName)schemaFieldType).simpleName().replace("ConfigurationSchema", "Node")
);
+ TypeName namedListParamType = nodeFieldType;
+
if (namedListField) {
viewFieldType = ParameterizedTypeName.get(ClassName.get(NamedListView.class), WildcardTypeName.subtypeOf(viewFieldType));
@@ -921,7 +933,7 @@ public class Processor extends AbstractProcessor {
FieldSpec.Builder nodeFieldBuilder = FieldSpec.builder(nodeFieldType, fieldName, PRIVATE);
if (namedListField)
- nodeFieldBuilder.initializer("new $T<>($T::new)", NamedListNode.class, ((ParameterizedTypeName)nodeFieldType).typeArguments.get(0));
+ nodeFieldBuilder.initializer("new $T<>($T::new)", NamedListNode.class, namedListParamType);
nodeClsBuilder.addField(nodeFieldBuilder.build());
}
@@ -940,13 +952,13 @@ public class Processor extends AbstractProcessor {
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(leafField ? viewFieldType : nodeFieldType)
- .addStatement("return $L", fieldName); //TODO Explicit null check?
+ .addStatement("return $L", fieldName);
nodeClsBuilder.addMethod(nodeGetMtdBuilder.build());
}
}
- if (!immutable) {
+ if (mutable) {
String changeMtdName = "change" + capitalize(fieldName);
{
@@ -1086,15 +1098,65 @@ public class Processor extends AbstractProcessor {
.addStatement(INDENT + "break");
}
}
+
+ {
+ if (leafField) {
+ constructBuilder.addStatement(
+ "case $S: $L = src == null ? null : src.unwrap($T.class)",
+ fieldName,
+ fieldName,
+ schemaFieldType.box()
+ )
+ .addStatement(INDENT + "break");
+ }
+ else if (namedListField) {
+ constructBuilder
+ .addStatement(
+ "case $S: if (src == null) $L = new $T<>($T::new)",
+ fieldName,
+ fieldName,
+ NamedListNode.class,
+ namedListParamType
+ )
+ .addStatement(
+ INDENT + "else src.descend($L = $L.copy())",
+ fieldName,
+ fieldName
+ )
+ .addStatement(INDENT + "break");
+ }
+ else {
+ constructBuilder
+ .addStatement(
+ "case $S: if (src == null) $L = null",
+ fieldName,
+ fieldName
+ )
+ .addStatement(
+ INDENT + "else src.descend($L = ($L == null ? new $T() : ($T)$L.copy()))",
+ fieldName,
+ fieldName,
+ nodeFieldType,
+ nodeFieldType,
+ fieldName
+ )
+ .addStatement(INDENT + "break");
+ }
+ }
}
traverseChildBuilder
.addStatement("default: throw new $T(key)", NoSuchElementException.class)
.endControlFlow();
+ constructBuilder
+ .addStatement("default: throw new $T(key)", NoSuchElementException.class)
+ .endControlFlow();
+
nodeClsBuilder
.addMethod(traverseChildrenBuilder.build())
- .addMethod(traverseChildBuilder.build());
+ .addMethod(traverseChildBuilder.build())
+ .addMethod(constructBuilder.build());
TypeSpec viewCls = viewClsBuilder.build();
TypeSpec changeCls = changeClsBuilder.build();
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
new file mode 100644
index 0000000..9791637
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.sample;
+
+import java.util.Collections;
+import java.util.NoSuchElementException;
+import org.apache.ignite.configuration.sample.impl.ChildNode;
+import org.apache.ignite.configuration.sample.impl.NamedElementNode;
+import org.apache.ignite.configuration.sample.impl.ParentNode;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
+import org.apache.ignite.configuration.tree.ConstructableTreeNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/** */
+public class ConstructableTreeNodeTest {
+ /** */
+ @Test
+ public void noKey() {
+ var childNode = new ChildNode();
+
+ assertThrows(NoSuchElementException.class, () -> childNode.construct("foo", null));
+ }
+
+ /** */
+ @Test
+ public void nullSource() {
+ var parentNode = new ParentNode().initChild(child ->
+ child.initStrCfg("value")
+ )
+ .initElements(elements ->
+ elements.put("name", element -> {})
+ );
+
+ // Named list node.
+ NamedListNode<NamedElementNode> elements = parentNode.elements();
+
+ parentNode.construct("elements", null);
+
+ assertNotNull(parentNode.elements());
+ assertNotSame(elements, parentNode.elements());
+ assertEquals(Collections.emptySet(), parentNode.elements().namedListKeys());
+
+ // Inner node.
+ NamedElementNode element = elements.get("name");
+
+ elements.construct("name", null);
+
+ assertNull(elements.get("name"));
+
+ // Leaf.
+ element.construct("strCfg", null);
+
+ assertNull(element.strCfg());
+ }
+
+ /** */
+ private static class ConstantConfigurationSource implements ConfigurationSource {
+ /** */
+ private final Object constant;
+
+ /**
+ * @param constant Constant.
+ */
+ private ConstantConfigurationSource(Object constant) {
+ this.constant = constant;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ return (T)constant;
+ }
+ }
+
+ /** */
+ @Test
+ public void unwrap() {
+ var childNode = new ChildNode();
+
+ childNode.construct("strCfg", new ConstantConfigurationSource("value"));
+
+ assertEquals("value", childNode.strCfg());
+
+ childNode.construct("intCfg", new ConstantConfigurationSource(255));
+
+ assertEquals(255, childNode.intCfg());
+
+ assertThrows(ClassCastException.class, () ->
+ childNode.construct("intCfg", new ConstantConfigurationSource(new Object()))
+ );
+ }
+
+ /** */
+ @Test
+ public void descend() {
+ // Inner node.
+ var parentNode = new ParentNode();
+
+ parentNode.construct("child", new ConfigurationSource() {
+ @Override public <T> T unwrap(Class<T> clazz) {
+ throw new UnsupportedOperationException("unwrap");
+ }
+
+ @Override public void descend(ConstructableTreeNode node) {
+ node.construct("strCfg", new ConstantConfigurationSource("value"));
+ }
+ });
+
+ assertEquals("value", parentNode.child().strCfg());
+
+ // Named list node.
+ NamedListNode<NamedElementNode> elementsNode = parentNode.elements();
+
+ elementsNode.construct("name", new ConfigurationSource() {
+ @Override public <T> T unwrap(Class<T> clazz) {
+ throw new UnsupportedOperationException("unwrap");
+ }
+
+ @Override public void descend(ConstructableTreeNode node) {
+ node.construct("strCfg", new ConstantConfigurationSource("value"));
+ }
+ });
+
+ assertEquals("value", elementsNode.get("name").strCfg());
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableNodesTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
similarity index 98%
rename from modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableNodesTest.java
rename to modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
index 2646711..393f17c 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableNodesTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
@@ -44,7 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
/** */
-public class TraversableNodesTest {
+public class TraversableTreeNodeTest {
/** */
@Config
public static class ParentConfigurationSchema {
@@ -165,9 +165,7 @@ public class TraversableNodesTest {
*/
@Test
public void initLeaf() {
- var childNode = new ChildNode();
-
- childNode.initStrCfg("value");
+ var childNode = new ChildNode().initStrCfg("value");
assertEquals("value", childNode.strCfg());
}
@@ -177,9 +175,7 @@ public class TraversableNodesTest {
*/
@Test
public void initInnerChild() {
- var parentNode = new ParentNode();
-
- parentNode.initChild(child -> {});
+ var parentNode = new ParentNode().initChild(child -> {});
ChildNode childNode = parentNode.child();
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/util/ConfigurationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/util/ConfigurationUtilTest.java
index 45cc921..6b12e28 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/util/ConfigurationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/util/ConfigurationUtilTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.configuration.util;
import java.util.List;
+import java.util.Map;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
@@ -25,7 +26,10 @@ import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.util.impl.ParentNode;
import org.junit.jupiter.api.Test;
+import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -95,9 +99,7 @@ public class ConfigurationUtilTest {
/** */
@Test
public void findSuccessfully() {
- var parent = new ParentNode();
-
- parent.changeElements(elements ->
+ var parent = new ParentNode().changeElements(elements ->
elements.put("name", element ->
element.changeChild(child ->
child.changeStr("value")
@@ -171,4 +173,93 @@ public class ConfigurationUtilTest {
() -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parent)
);
}
+
+ /** */
+ @Test
+ public void fillFromSuffixMapSuccessfully() {
+ var parentNode = new ParentNode();
+
+ ConfigurationUtil.fillFromSuffixMap(parentNode, Map.of(
+ "elements", Map.of(
+ "name1", Map.of(
+ "child", Map.of("str", "value1")
+ ),
+ "name2", Map.of(
+ "child", Map.of("str", "value2")
+ )
+ )
+ ));
+
+ assertEquals("value1", parentNode.elements().get("name1").child().str());
+ assertEquals("value2", parentNode.elements().get("name2").child().str());
+ }
+
+ /** */
+ @Test
+ public void fillFromSuffixMapSuccessfullyWithRemove() {
+ var parentNode = new ParentNode().changeElements(elements ->
+ elements.put("name", element ->
+ element.changeChild(child -> {})
+ )
+ );
+
+ ConfigurationUtil.fillFromSuffixMap(parentNode, Map.of(
+ "elements", singletonMap("name", null)
+ ));
+
+ assertNull(parentNode.elements().get("node"));
+ }
+
+ /** */
+ @Test
+ public void patch() {
+ var originalRoot = new ParentNode().initElements(elements ->
+ elements.put("name1", element ->
+ element.initChild(child -> child.initStr("value1"))
+ )
+ );
+
+ // Updating config.
+ ParentNode updatedRoot = ConfigurationUtil.patch(originalRoot, new ParentNode().changeElements(elements ->
+ elements.put("name1", element ->
+ element.changeChild(child -> child.changeStr("value2"))
+ )
+ ));
+
+ assertNotSame(originalRoot, updatedRoot);
+ assertNotSame(originalRoot.elements(), updatedRoot.elements());
+ assertNotSame(originalRoot.elements().get("name1"), updatedRoot.elements().get("name1"));
+ assertNotSame(originalRoot.elements().get("name1").child(), updatedRoot.elements().get("name1").child());
+
+ assertEquals("value1", originalRoot.elements().get("name1").child().str());
+ assertEquals("value2", updatedRoot.elements().get("name1").child().str());
+
+ // Expanding config.
+ ParentNode expandedRoot = ConfigurationUtil.patch(originalRoot, new ParentNode().changeElements(elements ->
+ elements.put("name2", element ->
+ element.changeChild(child -> child.changeStr("value2"))
+ )
+ ));
+
+ assertNotSame(originalRoot, expandedRoot);
+ assertNotSame(originalRoot.elements(), expandedRoot.elements());
+
+ assertSame(originalRoot.elements().get("name1"), expandedRoot.elements().get("name1"));
+ assertNull(originalRoot.elements().get("name2"));
+ assertNotNull(expandedRoot.elements().get("name2"));
+
+ assertEquals("value2", expandedRoot.elements().get("name2").child().str());
+
+ // Shrinking config.
+ ParentNode shrinkedRoot = ConfigurationUtil.patch(expandedRoot, new ParentNode().changeElements(elements ->
+ elements.remove("name1")
+ ));
+
+ assertNotSame(expandedRoot, shrinkedRoot);
+ assertNotSame(expandedRoot.elements(), shrinkedRoot.elements());
+
+ assertNotNull(expandedRoot.elements().get("name1"));
+ assertNull(shrinkedRoot.elements().get("name1"));
+ assertNotNull(shrinkedRoot.elements().get("name2"));
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationSource.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationSource.java
new file mode 100644
index 0000000..b1d6e30
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationSource.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.tree;
+
+/** */
+public interface ConfigurationSource {
+ /**
+ * Treat current configuration source as a leaf value and try to convert it to the specific class.
+ * Failing behaviour is not specified and depends on the implementation.
+ *
+ * @param <T> Type of the object for type safety during compilation.
+ * @param clazz Class instance of type to convert to.
+ * @return Converted leaf object.
+ */
+ <T> T unwrap(Class<T> clazz);
+
+ /**
+ * Treats current configuration source as an inner node. Tries to construct the content of {@code node} using
+ * available data from the source.
+ *
+ * @param node Constructable node which content will be modified by the configuration source.
+ */
+ default void descend(ConstructableTreeNode node) {
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
new file mode 100644
index 0000000..5cad648
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.tree;
+
+import java.util.NoSuchElementException;
+
+/** */
+public interface ConstructableTreeNode {
+ /**
+ * Initializes {@code key} element of the object with the content from the source.
+ * Please refer to implementation to find out exact details.
+ *
+ * @param key Field / named list element name to be constructed.
+ * @param src Source that provides data for construction.
+ * @throws NoSuchElementException If {@code key} cannot be constructed.
+ */
+ void construct(String key, ConfigurationSource src) throws NoSuchElementException;
+
+ /**
+ * Public equivalent of {@link Object#clone()} method. Creates a copy with effectively the same content.
+ * Helps to preserve trees immutability after construction is completed.
+ *
+ * @return Copy of the object.
+ */
+ ConstructableTreeNode copy();
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
index 8a14b1a..84e440c 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
@@ -20,7 +20,7 @@ package org.apache.ignite.configuration.tree;
import java.util.NoSuchElementException;
/** */
-public abstract class InnerNode implements TraversableTreeNode, Cloneable {
+public abstract class InnerNode implements TraversableTreeNode, ConstructableTreeNode, Cloneable {
/** {@inheritDoc} */
@Override public final void accept(String key, ConfigurationVisitor visitor) {
visitor.visitInnerNode(key, this);
@@ -79,10 +79,41 @@ public abstract class InnerNode implements TraversableTreeNode, Cloneable {
*/
public abstract void traverseChild(String key, ConfigurationVisitor visitor) throws NoSuchElementException;
+ /**
+ * Method with auto-generated implementation. Must look like this:
+ * <pre>{@code
+ * @Override public abstract void construct(String key, ConfigurationSource src) throws NoSuchElementException {
+ * switch (key) {
+ * case "namedList":
+ * if (src == null)
+ * namedList = new NamedListNode<>(Foo::new);
+ * else
+ * src.descend(namedList = namedList.copy());
+ * break;
+ *
+ * case "innerNode":
+ * if (src == null)
+ * innerNode = null;
+ * else
+ * src.descend(innerNode = (innerNode == null ? new Bar() : (Bar)innerNode.copy()));
+ * break;
+ *
+ * case "leaf":
+ * leaf = src == null ? null : src.unwrap(Integer.class);
+ * break;
+ *
+ * default: throw new NoSuchElementException(key);
+ * }
+ * }
+ * }</pre>
+ * {@inheritDoc}
+ */
+ @Override public abstract void construct(String key, ConfigurationSource src) throws NoSuchElementException;
+
/** {@inheritDoc} */
- @Override protected Object clone() {
+ @Override public InnerNode copy() {
try {
- return super.clone();
+ return (InnerNode)clone();
}
catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
index 38ce004..648e2cb 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
@@ -26,7 +26,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
/** */
-public final class NamedListNode<N extends InnerNode> implements NamedListView<N>, NamedListChange<N>, TraversableTreeNode, Cloneable {
+public final class NamedListNode<N extends InnerNode> implements NamedListView<N>, NamedListChange<N>, TraversableTreeNode, ConstructableTreeNode {
/** */
private final Supplier<N> valSupplier;
@@ -95,13 +95,23 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
return this;
}
- /** */
- public void delete(String key) {
- map.remove(key);
+ /** {@inheritDoc} */
+ @Override public void construct(String key, ConfigurationSource src) {
+ if (src == null)
+ map.remove(key);
+ else {
+ N val = map.get(key);
+
+ val = val == null ? valSupplier.get() : (N)val.copy();
+
+ map.put(key, val);
+
+ src.descend(val);
+ }
}
/** {@inheritDoc} */
- @Override public Object clone() {
+ @Override public NamedListNode<N> copy() {
return new NamedListNode<>(this);
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/util/ConfigurationUtil.java
index c752fc8..1fb8a73 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/util/ConfigurationUtil.java
@@ -20,28 +20,48 @@ package org.apache.ignite.configuration.util;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.stream.Collectors;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.tree.NamedListNode;
import org.apache.ignite.configuration.tree.TraversableTreeNode;
/** */
-public interface ConfigurationUtil {
- /** */
- static String escape(String key) {
+public class ConfigurationUtil {
+ /**
+ * Replaces all {@code .} and {@code \} characters with {@code \.} and {@code \\} respectively.
+ *
+ * @param key Unescaped string.
+ * @return Escaped string.
+ */
+ public static String escape(String key) {
return key.replaceAll("([.\\\\])", "\\\\$1");
}
- /** */
- static String unescape(String key) {
+ /**
+ * Replaces all {@code \.} and {@code \\} with {@code .} and {@code \} respectively.
+ *
+ * @param key Escaped string.
+ * @return Unescaped string.
+ */
+ public static String unescape(String key) {
return key.replaceAll("\\\\([.\\\\])", "$1");
}
- /** */
- static List<String> split(String keys) {
+ /**
+ * Splits string using unescaped {@code .} character as a separator.
+ *
+ * @param keys Qualified key where escaped subkeys are joined with dots.
+ * @return List of unescaped subkeys.
+ * @see #unescape(String)
+ * @see #join(List)
+ */
+ public static List<String> split(String keys) {
String[] split = keys.split("(?<!\\\\)[.]", -1);
for (int i = 0; i < split.length; i++)
@@ -50,8 +70,15 @@ public interface ConfigurationUtil {
return Arrays.asList(split);
}
- /** */
- static String join(List<String> keys) {
+ /**
+ * Joins list of keys with {@code .} character as a separator. All keys are preemptively escaped.
+ *
+ * @param keys List of unescaped keys.
+ * @return Escaped keys joined with dots.
+ * @see #escape(String)
+ * @see #split(String)
+ */
+ public static String join(List<String> keys) {
return keys.stream().map(ConfigurationUtil::escape).collect(Collectors.joining("."));
}
@@ -63,7 +90,7 @@ public interface ConfigurationUtil {
* @return Either {@link TraversableTreeNode} or {@link Serializable} depending on the keys and schema.
* @throws KeyNotFoundException If node is not found.
*/
- static Object find(List<String> keys, TraversableTreeNode node) throws KeyNotFoundException {
+ public static Object find(List<String> keys, TraversableTreeNode node) throws KeyNotFoundException {
assert keys instanceof RandomAccess : keys.getClass();
var visitor = new ConfigurationVisitor() {
@@ -110,4 +137,208 @@ public interface ConfigurationUtil {
return visitor.res;
}
+
+ /**
+ * Convert Map tree to configuration tree. No error handling here.
+ *
+ * @param node Node to fill. Not necessarily empty.
+ * @param prefixMap Map of {@link Serializable} values or other prefix maps (recursive structure).
+ * Every key is unescaped.
+ * @throws UnsupportedOperationException if prefix map structure doesn't correspond to actual tree structure.
+ * This will be fixed when method is actually used in configuration storage intergration.
+ */
+ public static void fillFromSuffixMap(ConstructableTreeNode node, Map<String, ?> prefixMap) {
+ assert node instanceof InnerNode;
+
+ /** */
+ class LeafConfigurationSource implements ConfigurationSource {
+ /** */
+ private final Serializable val;
+
+ /**
+ * @param val Value.
+ */
+ private LeafConfigurationSource(Serializable val) {
+ this.val = val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ assert val == null || clazz.isInstance(val);
+
+ return clazz.cast(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode node) {
+ throw new UnsupportedOperationException("descend");
+ }
+ }
+
+ /** */
+ class InnerConfigurationSource implements ConfigurationSource {
+ /** */
+ private final Map<String, ?> map;
+
+ /**
+ * @param map Prefix map.
+ */
+ private InnerConfigurationSource(Map<String, ?> map) {
+ this.map = map;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ throw new UnsupportedOperationException("unwrap");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode node) {
+ for (Map.Entry<String, ?> entry : map.entrySet()) {
+ String key = entry.getKey();
+ Object val = entry.getValue();
+
+ assert val == null || val instanceof Map || val instanceof Serializable;
+
+ if (val == null)
+ node.construct(key, null);
+ else if (val instanceof Map)
+ node.construct(key, new InnerConfigurationSource((Map<String, ?>)val));
+ else
+ node.construct(key, new LeafConfigurationSource((Serializable)val));
+ }
+ }
+ }
+
+ var src = new InnerConfigurationSource(prefixMap);
+
+ src.descend(node);
+ }
+
+ /**
+ * Apply changes on top of existing node. Creates completely new object while reusing parts of the original tree
+ * that weren't modified.
+ *
+ * @param root Immutable configuration node.
+ * @param changes Change or Init object to be applied.
+ */
+ public static <C extends ConstructableTreeNode> C patch(C root, TraversableTreeNode changes) {
+ assert root.getClass() == changes.getClass(); // Yes.
+
+ var scrHolder = new ConfigurationVisitor() {
+ ConfigurationSource src;
+
+ @Override public void visitInnerNode(String key, InnerNode node) {
+ src = new PatchInnerConfigurationSource(node);
+ }
+
+ @Override public <N extends InnerNode> void visitNamedListNode(String key, NamedListNode<N> node) {
+ src = new PatchNamedListConfigurationSource(node);
+ }
+ };
+
+ changes.accept(null, scrHolder);
+
+ assert scrHolder.src != null;
+
+ C copy = (C)root.copy();
+
+ scrHolder.src.descend(copy);
+
+ return copy;
+ }
+
+ /** */
+ private static class PatchLeafConfigurationSource implements ConfigurationSource {
+ /** */
+ private final Serializable val;
+
+ /**
+ * @param val Value.
+ */
+ PatchLeafConfigurationSource(Serializable val) {
+ this.val = val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ assert clazz.isInstance(val);
+
+ return clazz.cast(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode node) {
+ throw new UnsupportedOperationException("descend");
+ }
+ }
+
+ /** */
+ private static class PatchInnerConfigurationSource implements ConfigurationSource {
+ /** */
+ private final InnerNode srcNode;
+
+ /**
+ * @param srcNode Inner node.
+ */
+ PatchInnerConfigurationSource(InnerNode srcNode) {
+ this.srcNode = srcNode;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ throw new UnsupportedOperationException("unwrap");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode dstNode) {
+ assert srcNode.getClass() == dstNode.getClass();
+
+ srcNode.traverseChildren(new ConfigurationVisitor() {
+ @Override public void visitLeafNode(String key, Serializable val) {
+ if (val != null)
+ dstNode.construct(key, new PatchLeafConfigurationSource(val));
+ }
+
+ @Override public void visitInnerNode(String key, InnerNode node) {
+ if (node != null)
+ dstNode.construct(key, new PatchInnerConfigurationSource(node));
+ }
+
+ @Override public <N extends InnerNode> void visitNamedListNode(String key, NamedListNode<N> node) {
+ if (node != null)
+ dstNode.construct(key, new PatchNamedListConfigurationSource(node));
+ }
+ });
+ }
+ }
+
+ /** */
+ private static class PatchNamedListConfigurationSource implements ConfigurationSource {
+ /** */
+ private final NamedListNode<?> srcNode;
+
+ /**
+ * @param srcNode Named list node.
+ */
+ PatchNamedListConfigurationSource(NamedListNode<?> srcNode) {
+ this.srcNode = srcNode;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ throw new UnsupportedOperationException("unwrap");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode dstNode) {
+ assert srcNode.getClass() == dstNode.getClass();
+
+ for (String key : srcNode.namedListKeys()) {
+ InnerNode node = srcNode.get(key);
+
+ dstNode.construct(key, node == null ? null : new PatchInnerConfigurationSource(node));
+ }
+ }
+ }
}