blob: f6ddfd00ed76e1ea157f1daedddee3f5dc0bfe7f [file] [log] [blame]
/*
* 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.internal.configuration.tree;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeSet;
import org.apache.ignite.configuration.NamedListChange;
import org.apache.ignite.configuration.NamedListView;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.validation.Immutable;
import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
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;
/** */
public class TraversableTreeNodeTest {
private static ConfigurationAsmGenerator cgen;
@BeforeAll
public static void beforeAll() {
cgen = new ConfigurationAsmGenerator();
cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
}
@AfterAll
public static void afterAll() {
cgen = null;
}
public static <P extends InnerNode & ParentChange> P newParentInstance() {
return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
}
public static <C extends InnerNode & ChildChange> C newChildInstance() {
return (C)cgen.instantiateNode(ChildConfigurationSchema.class);
}
/** */
@Config
public static class ParentConfigurationSchema {
/** */
@ConfigValue
public ChildConfigurationSchema child;
/** */
@NamedConfigValue
public NamedElementConfigurationSchema elements;
}
/** */
@Config
public static class ChildConfigurationSchema {
/** */
@Value(hasDefault = true)
@Immutable
public int intCfg = 99;
/** */
@Value
public String strCfg;
}
/** */
@Config
public static class NamedElementConfigurationSchema {
/** */
@Value
public String strCfg;
}
/** */
private static class VisitException extends RuntimeException {
/** Serial version uid. */
private static final long serialVersionUID = 0L;
}
/**
* Test that generated node classes implement generated VIEW and CHANGE interfaces.
*/
@Test
public void nodeClassesImplementRequiredInterfaces() {
var parentNode = newParentInstance();
assertThat(parentNode, instanceOf(ParentView.class));
assertThat(parentNode, instanceOf(ParentChange.class));
var namedElementNode = cgen.instantiateNode(NamedElementConfigurationSchema.class);
assertThat(namedElementNode, instanceOf(NamedElementView.class));
assertThat(namedElementNode, instanceOf(NamedElementChange.class));
var childNode = newChildInstance();
assertThat(childNode, instanceOf(ChildView.class));
assertThat(childNode, instanceOf(ChildChange.class));
}
/**
* Test for signature and implementation of "change" method on leaves.
*/
@Test
public void changeLeaf() {
var childNode = newChildInstance();
assertNull(childNode.strCfg());
childNode.changeStrCfg("value");
assertEquals("value", childNode.strCfg());
assertThrows(NullPointerException.class, () -> childNode.changeStrCfg(null));
}
/**
* Test for signature and implementation of "change" method on inner nodes.
*/
@Test
public void changeInnerChild() {
var parentNode = newParentInstance();
assertNull(parentNode.child());
parentNode.changeChild(child -> {});
ChildView childNode = parentNode.child();
assertNotNull(childNode);
parentNode.changeChild(child -> child.changeStrCfg("value"));
assertNotSame(childNode, parentNode.child());
assertThrows(NullPointerException.class, () -> parentNode.changeChild(null));
assertThrows(NullPointerException.class, () -> parentNode.changeElements(null));
}
/**
* Test for signature and implementation of "change" method on named list nodes.
*/
@Test
public void changeNamedChild() {
var parentNode = newParentInstance();
NamedListView<? extends NamedElementView> elementsNode = parentNode.elements();
// Named list node must always be instantiated.
assertNotNull(elementsNode);
parentNode.changeElements(elements -> elements.createOrUpdate("key", element -> {}));
assertNotSame(elementsNode, parentNode.elements());
}
/**
* Test for signature and implementation of "put" and "remove" methods on elements of named list nodes.
*/
@Test
public void putRemoveNamedConfiguration() {
var elementsNode = (NamedListChange<NamedElementChange>)newParentInstance().elements();
assertEquals(List.of(), elementsNode.namedListKeys());
elementsNode.createOrUpdate("keyPut", element -> {});
assertThrows(IllegalArgumentException.class, () -> elementsNode.create("keyPut", element -> {}));
assertThat(elementsNode.namedListKeys(), hasItem("keyPut"));
NamedElementView elementNode = elementsNode.get("keyPut");
assertNotNull(elementNode);
assertSame(elementNode, elementsNode.get(0));
assertNull(elementNode.strCfg());
assertThrows(IndexOutOfBoundsException.class, () -> elementsNode.get(-1));
assertThrows(IndexOutOfBoundsException.class, () -> elementsNode.get(1));
elementsNode.createOrUpdate("keyPut", element -> element.changeStrCfg("val"));
// Assert that consecutive put methods create new object every time.
assertNotSame(elementNode, elementsNode.get("keyPut"));
elementNode = elementsNode.get("keyPut");
assertEquals("val", elementNode.strCfg());
elementsNode.delete("keyPut");
assertThat(elementsNode.namedListKeys(), CoreMatchers.hasItem("keyPut"));
assertNull(elementsNode.get("keyPut"));
elementsNode.delete("keyPut");
// Assert that "remove" method creates null element inside of the node.
assertThat(elementsNode.namedListKeys(), hasItem("keyPut"));
assertNull(elementsNode.get("keyPut"));
// Assert that once you remove something from list, you can't put it back again with different set of fields.
assertThrows(IllegalArgumentException.class, () -> elementsNode.createOrUpdate("keyPut", element -> {}));
}
/**
* Test that inner nodes properly implement visitor interface.
*/
@Test
public void innerNodeAcceptVisitor() {
var parentNode = newParentInstance();
assertThrows(VisitException.class, () ->
parentNode.accept("root", new ConfigurationVisitor<Void>() {
@Override public Void visitInnerNode(String key, InnerNode node) {
throw new VisitException();
}
})
);
}
/**
* Test that named list nodes properly implement visitor interface.
*/
@Test
public void namedListNodeAcceptVisitor() {
var elementsNode = (TraversableTreeNode)newParentInstance().elements();
assertThrows(VisitException.class, () ->
elementsNode.accept("root", new ConfigurationVisitor<Void>() {
@Override public <N extends InnerNode> Void visitNamedListNode(String key, NamedListNode<N> node) {
throw new VisitException();
}
})
);
}
/**
* Test for "traverseChildren" method implementation on generated inner nodes classes.
*/
@Test
public void traverseChildren() {
var parentNode = newParentInstance();
Collection<String> keys = new TreeSet<>();
parentNode.traverseChildren(new ConfigurationVisitor<Object>() {
@Override public Object visitInnerNode(String key, InnerNode node) {
assertNull(node);
assertEquals("child", key);
return keys.add(key);
}
@Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> node) {
assertEquals("elements", key);
return keys.add(key);
}
}, true);
// Assert that updates happened in the same order as fields declaration in schema.
assertEquals(new TreeSet<>(List.of("child", "elements")), keys);
keys.clear();
var childNode = newChildInstance();
childNode.traverseChildren(new ConfigurationVisitor<Object>() {
@Override public Object visitLeafNode(String key, Serializable val) {
return keys.add(key);
}
}, true);
// Assert that updates happened in the same order as fields declaration in schema.
assertEquals(new TreeSet<>(List.of("intCfg", "strCfg")), keys);
}
/**
* Test for "traverseChild" method implementation on generated inner nodes classes.
*/
@Test
public void traverseSingleChild() {
var parentNode = newParentInstance();
// Assert that proper method has been invoked.
assertThrows(VisitException.class, () ->
parentNode.traverseChild("child", new ConfigurationVisitor<Void>() {
@Override public Void visitInnerNode(String key, InnerNode node) {
assertEquals("child", key);
throw new VisitException();
}
}, true)
);
// Assert that proper method has been invoked.
assertThrows(VisitException.class, () ->
parentNode.traverseChild("elements", new ConfigurationVisitor<Void>() {
@Override
public <N extends InnerNode> Void visitNamedListNode(String key, NamedListNode<N> node) {
assertEquals("elements", key);
throw new VisitException();
}
}, true)
);
var childNode = newChildInstance();
// Assert that proper method has been invoked.
assertThrows(VisitException.class, () ->
childNode.traverseChild("intCfg", new ConfigurationVisitor<Void>() {
@Override public Void visitLeafNode(String key, Serializable val) {
assertEquals("intCfg", key);
throw new VisitException();
}
}, true)
);
// Assert that traversing inexistent field leads to exception.
assertThrows(NoSuchElementException.class, () ->
childNode.traverseChild("foo", new ConfigurationVisitor<>() {}, true)
);
}
}