--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.Beta;
+import java.util.function.Supplier;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * A capture of a tree-like construct, which can be formatted into a pretty-printed tree. The string can be acquired
+ * via {@link #get()}.
+ *
+ * <p>
+ * This concept is purposefully designed as an abstract class which defers its {@link #toString()} to {@link #get()}, as
+ * it allows convenient and light-weight use with logging:
+ *
+ * <pre>
+ * <code>
+ * PrettyTreeAware treeLike;
+ * LOG.debug("Tree is {}", treeLike.prettyTree());
+ * </code>
+ * </pre>
+ */
+@Beta
+public abstract class PrettyTree implements Supplier<String> {
+ @Override
+ public @NonNull String get() {
+ final StringBuilder sb = new StringBuilder();
+ appendTo(sb, 0);
+ return sb.toString();
+ }
+
+ @Override
+ public final @NonNull String toString() {
+ return get();
+ }
+
+ /**
+ * Format this object into specified {@link StringBuilder} starting at specified initial depth.
+ *
+ * @param sb Target {@link StringBuilder}
+ * @param depth Initial nesting depth
+ * @throws NullPointerException if {@code sb} is null
+ * @throws IllegalArgumentException if {@code depth} is negative
+ */
+ public abstract void appendTo(StringBuilder sb, int depth);
+
+ /**
+ * Append a number of spaces equivalent to specified tree nesting depth into the specified {@link StringBuilder}.
+ *
+ * @param sb Target {@link StringBuilder}
+ * @param depth Nesting depth
+ * @throws NullPointerException if {@code sb} is null
+ * @throws IllegalArgumentException if {@code depth} is negative
+ */
+ protected static final void appendIndent(final StringBuilder sb, final int depth) {
+ checkArgument(depth >= 0, "Invalid depth %s", depth);
+ PrettyTreeIndent.indent(sb, depth);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Utility interface to bridge structures which can be formatted via {@link PrettyTree}.
+ */
+@NonNullByDefault
+public interface PrettyTreeAware {
+ /**
+ * Return a {@link PrettyTree} view of this object.
+ *
+ * @return A {@link PrettyTree}.
+ */
+ PrettyTree prettyTree();
+}
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.concepts;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Indentation handling for {@link PrettyTree}. This class is split out to defer initialization of the string table --
+ * it might never be used after all.
+ *
+ * <p>
+ * We want to be formatting strings quickly and a lot of that can very easily be dominated by dealing with indents.
+ * To deal with that we pre-compute a few indentation strings and then append them directly using a specialized
+ * method. We allow tuning the default indentation at runtime, but choose a fixed string table size.
+ */
+final class PrettyTreeIndent {
+ private static final Logger LOG = LoggerFactory.getLogger(PrettyTreeIndent.class);
+ private static final int DEFAULT_INDENT = 4;
+ private static final int INDENT_STRINGS_SIZE = 16;
+ private static final String[] INDENT_STRINGS;
+
+ static {
+ int indent = Integer.getInteger("org.opendaylight.yangtools.concepts.pretty-tree-indent", DEFAULT_INDENT);
+ if (indent < 1) {
+ LOG.warn("Invalid pretty-tree-indent {}, using {} instead", indent, DEFAULT_INDENT);
+ indent = DEFAULT_INDENT;
+ } else if (indent != DEFAULT_INDENT) {
+ LOG.info("Using pretty-tree-indent {}", indent);
+ }
+
+ final String one = " ".repeat(indent);
+ final String[] strings = new String[INDENT_STRINGS_SIZE];
+ for (int i = 0; i < INDENT_STRINGS_SIZE; i++) {
+ strings[i] = one.repeat(i).intern();
+ }
+ INDENT_STRINGS = strings;
+ }
+
+ private PrettyTreeIndent() {
+ // Hidden on purpose
+ }
+
+ static void indent(final StringBuilder sb, final int depth) {
+ int remaining = depth;
+ while (remaining >= INDENT_STRINGS_SIZE) {
+ sb.append(INDENT_STRINGS[INDENT_STRINGS_SIZE - 1]);
+ remaining -= INDENT_STRINGS_SIZE;
+ }
+ sb.append(INDENT_STRINGS[remaining]);
+ }
+}
requires transitive org.opendaylight.yangtools.yang.data.api;
requires transitive org.opendaylight.yangtools.concepts;
requires org.opendaylight.yangtools.util;
+ requires org.opendaylight.yangtools.yang.common;
requires org.slf4j;
// Annotations
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.concepts.AbstractIdentifiable;
import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.concepts.PrettyTree;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
super(identifier);
}
+ @Override
+ public final PrettyTree prettyTree() {
+ return new NormalizedNodePrettyTree(this);
+ }
+
@Override
public final boolean equals(final Object obj) {
if (this == obj) {
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.spi.node;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.util.Base64;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.concepts.PrettyTree;
+import org.opendaylight.yangtools.concepts.PrettyTreeAware;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
+
+@Beta
+public final class NormalizedNodePrettyTree extends PrettyTree implements Immutable {
+ private final @NonNull NormalizedNode node;
+
+ public NormalizedNodePrettyTree(final @NonNull NormalizedNode node) {
+ this.node = requireNonNull(node);
+ }
+
+ @Override
+ public void appendTo(final StringBuilder sb, final int depth) {
+ appendNode(sb, depth, null, node);
+ }
+
+ private static void appendNode(final StringBuilder sb, final int depth, final QNameModule parentNamespace,
+ final NormalizedNode node) {
+ final String simpleName = node.contract().getSimpleName();
+ appendIndent(sb, depth);
+ sb.append(simpleName.toLowerCase(Locale.ROOT).charAt(0)).append(simpleName, 1, simpleName.length()).append(' ');
+
+ final QNameModule currentNamespace;
+ if (node instanceof AugmentationNode) {
+ // Add identifier, but augmentations are special enough
+ currentNamespace = ((AugmentationNode) node).getIdentifier().getPossibleChildNames().iterator().next()
+ .getModule();
+ if (appendNamespace(sb, parentNamespace, currentNamespace)) {
+ sb.append(' ');
+ }
+ } else {
+ final QName qname = node.getIdentifier().getNodeType();
+ currentNamespace = qname.getModule();
+ appendNamespace(sb, parentNamespace, currentNamespace);
+ sb.append(qname.getLocalName()).append(' ');
+ }
+
+ if (node instanceof NormalizedNodeContainer) {
+ final NormalizedNodeContainer<?> container = (NormalizedNodeContainer<?>) node;
+ sb.append("= {");
+
+ final Iterator<? extends NormalizedNode> it = container.body().iterator();
+ if (it.hasNext()) {
+ final int childIndent = depth + 1;
+ do {
+ sb.append('\n');
+ appendNode(sb, childIndent, currentNamespace, it.next());
+ } while (it.hasNext());
+
+ sb.append('\n');
+ appendIndent(sb, depth);
+ }
+ sb.append('}');
+ } else if (node instanceof ValueNode) {
+ sb.append("= ");
+ final Object value = node.body();
+ if (value instanceof byte[]) {
+ sb.append("(byte[])").append(Base64.getEncoder().encodeToString((byte[]) value));
+ } else if (value instanceof String) {
+ appendString(sb, (String) value);
+ } else {
+ sb.append(value);
+ }
+ } else if (node instanceof ForeignDataNode) {
+ final ForeignDataNode<?> data = (ForeignDataNode<?>) node;
+ sb.append("= (").append(data.bodyObjectModel().getName()).append(')');
+
+ final Object body = data.body();
+ if (body instanceof PrettyTreeAware) {
+ sb.append(" {\n");
+ ((PrettyTreeAware) body).prettyTree().appendTo(sb, depth + 1);
+ appendIndent(sb, depth);
+ sb.append('}');
+ }
+ } else {
+ throw new IllegalStateException("Unhandled node " + node);
+ }
+ }
+
+ private static boolean appendNamespace(final StringBuilder sb, final QNameModule parent,
+ final QNameModule current) {
+ if (!current.equals(parent)) {
+ sb.append('(').append(current.getNamespace());
+ final Optional<Revision> rev = current.getRevision();
+ if (rev.isPresent()) {
+ sb.append('@').append(rev.orElseThrow());
+ }
+ sb.append(')');
+ return true;
+ }
+ return false;
+ }
+
+ private static void appendString(final StringBuilder sb, final String str) {
+ // TODO: do some escaping: '\r' '\n' '"' '\\' to make things even more zazzy
+ sb.append('"').append(str).append('"');
+ }
+}
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-data-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-spi</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-model-api</artifactId>
requires org.opendaylight.yangtools.rfc8528.model.api;
requires org.opendaylight.yangtools.yang.common;
+ requires org.opendaylight.yangtools.yang.data.spi;
requires org.slf4j;
// Annotations
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.concepts.AbstractIdentifiable;
import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.concepts.PrettyTree;
import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
import org.opendaylight.yangtools.rfc8528.data.api.MountPointNode;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.spi.node.NormalizedNodePrettyTree;
@Beta
public final class ImmutableMountPointNode extends AbstractIdentifiable<PathArgument, MountPointIdentifier>
return delegate.childByArg(child);
}
+ @Override
+ public PrettyTree prettyTree() {
+ return new NormalizedNodePrettyTree(this);
+ }
+
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
return super.addToStringAttributes(helper).add("delegate", delegate);
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.concepts.PrettyTreeAware;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
/**
* boundary -- like RFC8528. Hence we cannot really have a reasonably-structured concept of unverified
* data. Nevertheless, this interface should be named 'NormalizedData'.
*/
-public interface NormalizedNode extends Identifiable<PathArgument> {
+public interface NormalizedNode extends Identifiable<PathArgument>, PrettyTreeAware {
@Override
// We override here, so that NormalizedNode.getIdentifier() has fewer implementations
PathArgument getIdentifier();
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntry;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntryBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapNodeBuilder;
+
+import java.util.List;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.schema.AnydataNode;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+
+public abstract class AbstractPrettyTreeTest {
+ protected static final QName ROOT_QNAME = QName.create(
+ "urn:opendaylight:controller:sal:dom:store:test", "2014-03-13", "root");
+ protected static final QName ANOTHER_QNAME = QName.create(
+ "urn:opendaylight:controller:sal:dom:store:another", "another");
+
+ protected static final QName LIST_A_QNAME = QName.create(ROOT_QNAME, "list-a");
+ protected static final QName LEAF_A_QNAME = QName.create(ROOT_QNAME, "leaf-a");
+ protected static final QName LIST_B_QNAME = QName.create(ROOT_QNAME, "list-b");
+ protected static final QName LEAF_B_QNAME = QName.create(ROOT_QNAME, "leaf-b");
+
+ protected static final QName CHOICE_QNAME = QName.create(ROOT_QNAME, "choice");
+ protected static final QName AUGMENT_QNAME = QName.create(ROOT_QNAME, "augment");
+
+ protected static final QName LIST_ANOTHER_NAMESPACE_QNAME = QName.create(ANOTHER_QNAME,
+ "list-from-another-namespace");
+ protected static final QName LEAF_ANOTHER_NAMESPACE_QNAME = QName.create(ANOTHER_QNAME,
+ "leaf-from-another-namespace");
+
+ protected static final QName LEAF_QNAME = QName.create(ROOT_QNAME, "leaf");
+ protected static final QName LEAF_SET_QNAME = QName.create(ROOT_QNAME, "leaf-set");
+
+ protected static final QName USER_LEAF_SET_QNAME = QName.create(ROOT_QNAME, "user-leaf-set");
+ protected static final QName USER_MAP_QNAME = QName.create(ROOT_QNAME, "user-map");
+ protected static final QName USER_MAP_ENTRY_QNAME = QName.create(ROOT_QNAME, "user-map-entry");
+
+ protected static final QName UNKEYED_LIST_QNAME = QName.create(ROOT_QNAME,
+ "unkeyed-list");
+ protected static final QName UNKEYED_LIST_ENTRY_QNAME = QName.create(ROOT_QNAME,
+ "unkeyed-list-entry");
+ protected static final QName UNKEYED_LIST_LEAF_QNAME = QName.create(ROOT_QNAME,
+ "unkeyed-list-leaf");
+
+ protected static final QName ANY_DATA_QNAME = QName.create(ROOT_QNAME, "any-data");
+
+ /**
+ * Return a test node.
+ *
+ * <pre>
+ * root
+ * list-a
+ * leaf-a "foo"
+ * list-a
+ * leaf-a "bar"
+ * list-b
+ * leaf-b "one"
+ * list-b
+ * leaf-b "two"
+ * choice
+ * augment
+ * augmented-leaf "Augmented leaf value"
+ * another
+ * list-from-another-namespace
+ * leaf-from-another-namespace "Leaf from another namespace value"
+ * leaf "Leaf value"
+ * leaf-set "Leaf set value"
+ * user-leaf-set "User leaf set value"
+ * user-map
+ * user-map-entry "User map entry value"
+ * unkeyed-list
+ * unkeyed-list-entry
+ * unkeyed-list-leaf "Unkeyed list leaf value"
+ * any-data "Any data value"
+ *
+ * </pre>
+ *
+ * @return A test node
+ */
+ protected static NormalizedNode createContainerNode() {
+ return ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(new NodeIdentifier(ROOT_QNAME))
+ .withChild(createMapNode())
+ .withChild(createChoiceNode())
+ .withChild(createContainerFromAnotherNamespace())
+ .withChild(createLeafNode())
+ .withChild(createLeafSetNode())
+ .withChild(createUserLeafSetNode())
+ .withChild(createUserMapNode())
+ .withChild(createUnkeyedListNode())
+ .withChild(createAnyDataNode())
+ .build();
+ }
+
+ protected static MapNode createMapNode() {
+ return mapNodeBuilder(LIST_A_QNAME)
+ .withChild(mapEntry(LIST_A_QNAME, LEAF_A_QNAME, "foo"))
+ .withChild(createMapEntryNode()).build();
+ }
+
+ protected static MapEntryNode createMapEntryNode() {
+ return mapEntryBuilder(LIST_A_QNAME, LEAF_A_QNAME, "bar")
+ .withChild(mapNodeBuilder(LIST_B_QNAME)
+ .withChild(mapEntry(LIST_B_QNAME, LEAF_B_QNAME, "one"))
+ .withChild(mapEntry(LIST_B_QNAME, LEAF_B_QNAME, "two"))
+ .build()).build();
+ }
+
+ protected static ChoiceNode createChoiceNode() {
+ return Builders.choiceBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(CHOICE_QNAME))
+ .withChild(createAugmentationNode())
+ .build();
+ }
+
+ protected static AugmentationNode createAugmentationNode() {
+ return Builders.augmentationBuilder()
+ .withNodeIdentifier(AugmentationIdentifier
+ .create(Set.of(AUGMENT_QNAME)))
+ .withChild(createAugmentedLeafNode())
+ .build();
+ }
+
+ protected static LeafNode<String> createAugmentedLeafNode() {
+ return leafNode(AUGMENT_QNAME, "Augmented leaf value");
+ }
+
+ protected static ContainerNode createContainerFromAnotherNamespace() {
+ return ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(new NodeIdentifier(ANOTHER_QNAME))
+ .withChild(mapNodeBuilder(LIST_ANOTHER_NAMESPACE_QNAME)
+ .withChild(mapEntry(LIST_ANOTHER_NAMESPACE_QNAME,
+ LEAF_ANOTHER_NAMESPACE_QNAME,
+ "Leaf from another namespace value"))
+ .build())
+ .build();
+ }
+
+ protected static LeafNode<String> createLeafNode() {
+ return Builders.<String>leafBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(LEAF_QNAME))
+ .withValue("Leaf value")
+ .build();
+ }
+
+ protected static LeafSetNode<String> createLeafSetNode() {
+ final String value = "Leaf set value";
+ final LeafSetEntryNode<String> leafSetValue = Builders.<String>leafSetEntryBuilder()
+ .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, value))
+ .withValue(value)
+ .build();
+ return Builders.<String>leafSetBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(LEAF_SET_QNAME))
+ .withValue(List.of(leafSetValue))
+ .build();
+ }
+
+ protected static UserLeafSetNode<String> createUserLeafSetNode() {
+ final String value = "User leaf set value";
+ final LeafSetEntryNode<String> leafSetValue = Builders.<String>leafSetEntryBuilder()
+ .withNodeIdentifier(new NodeWithValue<>(USER_LEAF_SET_QNAME, value))
+ .withValue(value)
+ .build();
+ return Builders.<String>orderedLeafSetBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(USER_LEAF_SET_QNAME))
+ .withValue(List.of(leafSetValue))
+ .build();
+ }
+
+ protected static UserMapNode createUserMapNode() {
+ return Builders.orderedMapBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(USER_MAP_QNAME))
+ .withValue(List.of(createUserMapEntryNode()))
+ .build();
+ }
+
+ protected static MapEntryNode createUserMapEntryNode() {
+ return mapEntry(USER_MAP_QNAME, USER_MAP_ENTRY_QNAME, "User map entry value");
+ }
+
+ protected static UnkeyedListNode createUnkeyedListNode() {
+ return Builders.unkeyedListBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(UNKEYED_LIST_QNAME))
+ .withChild(createUnkeyedListEntryNode())
+ .build();
+ }
+
+ protected static UnkeyedListEntryNode createUnkeyedListEntryNode() {
+ return Builders.unkeyedListEntryBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(UNKEYED_LIST_ENTRY_QNAME))
+ .withChild(leafNode(UNKEYED_LIST_LEAF_QNAME, "Unkeyed list leaf value"))
+ .build();
+ }
+
+ protected static AnydataNode<String> createAnyDataNode() {
+ return Builders.anydataBuilder(String.class)
+ .withNodeIdentifier(NodeIdentifier.create(ANY_DATA_QNAME))
+ .withValue("Any data value")
+ .build();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class NormalizedNodePrettyTreeTest extends AbstractPrettyTreeTest {
+ @Test
+ public void testMapNodePrettyTree() {
+ assertEquals(String.join("\n",
+ "systemMapNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)list-a = {",
+ " mapEntryNode list-a = {",
+ " leafNode leaf-a = \"bar\"",
+ " systemMapNode list-b = {",
+ " mapEntryNode list-b = {",
+ " leafNode leaf-b = \"two\"",
+ " }",
+ " mapEntryNode list-b = {",
+ " leafNode leaf-b = \"one\"",
+ " }",
+ " }",
+ " }",
+ " mapEntryNode list-a = {",
+ " leafNode leaf-a = \"foo\"",
+ " }",
+ "}"), createMapNode().prettyTree().get());
+ }
+
+ @Test
+ public void testMapEntryPrettyTree() {
+ assertEquals(String.join("\n",
+ "mapEntryNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)list-a = {",
+ " leafNode leaf-a = \"bar\"",
+ " systemMapNode list-b = {",
+ " mapEntryNode list-b = {",
+ " leafNode leaf-b = \"two\"",
+ " }",
+ " mapEntryNode list-b = {",
+ " leafNode leaf-b = \"one\"",
+ " }",
+ " }",
+ "}"), createMapEntryNode().prettyTree().get());
+ }
+
+ @Test
+ public void testChoicePrettyTree() {
+ assertEquals(String.join("\n",
+ "choiceNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)choice = {",
+ " augmentationNode = {",
+ " leafNode augment = \"Augmented leaf value\"",
+ " }",
+ "}"), createChoiceNode().prettyTree().get());
+ }
+
+ @Test
+ public void testAugmentationPrettyTree() {
+ assertEquals(String.join("\n",
+ "augmentationNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13) = {",
+ " leafNode augment = \"Augmented leaf value\"",
+ "}"), createAugmentationNode().prettyTree().get());
+ }
+
+ @Test
+ public void testLeafPrettyTree() {
+ assertEquals("leafNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)leaf = \"Leaf value\"",
+ createLeafNode().prettyTree().get());
+ }
+
+ @Test
+ public void testLeafSetPrettyTree() {
+ assertEquals(String.join("\n",
+ "systemLeafSetNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)leaf-set = {",
+ " leafSetEntryNode leaf-set = \"Leaf set value\"",
+ "}"), createLeafSetNode().prettyTree().get());
+ }
+
+ @Test
+ public void testUserLeafSetPrettyTree() {
+ assertEquals(String.join("\n",
+ "userLeafSetNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)user-leaf-set = {",
+ " leafSetEntryNode user-leaf-set = \"User leaf set value\"",
+ "}"), createUserLeafSetNode().prettyTree().get());
+ }
+
+ @Test
+ public void testUserMapPrettyTree() {
+ assertEquals(String.join("\n",
+ "userMapNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)user-map = {",
+ " mapEntryNode user-map = {",
+ " leafNode user-map-entry = \"User map entry value\"",
+ " }",
+ "}"), createUserMapNode().prettyTree().get());
+ }
+
+ @Test
+ public void testUserMapEntryPrettyTree() {
+ assertEquals(String.join("\n",
+ "mapEntryNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)user-map = {",
+ " leafNode user-map-entry = \"User map entry value\"",
+ "}"), createUserMapEntryNode().prettyTree().get());
+ }
+
+ @Test
+ public void testUnkeyedListPrettyTree() {
+ assertEquals(String.join("\n",
+ "unkeyedListNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)unkeyed-list = {",
+ " unkeyedListEntryNode unkeyed-list-entry = {",
+ " leafNode unkeyed-list-leaf = \"Unkeyed list leaf value\"",
+ " }",
+ "}"), createUnkeyedListNode().prettyTree().get());
+ }
+
+ @Test
+ public void testUnkeyedListEntryPrettyTree() {
+ assertEquals(String.join("\n",
+ "unkeyedListEntryNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)unkeyed-list-entry = {",
+ " leafNode unkeyed-list-leaf = \"Unkeyed list leaf value\"",
+ "}"), createUnkeyedListEntryNode().prettyTree().get());
+ }
+
+ @Test
+ public void testAnyDataPrettyTree() {
+ assertEquals(String.join("\n",
+ "anydataNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)any-data = (java.lang.String)"),
+ createAnyDataNode().prettyTree().get());
+ }
+
+ @Test
+ public void testContainerPrettyTree() {
+ assertEquals(String.join("\n",
+ "containerNode (urn:opendaylight:controller:sal:dom:store:test@2014-03-13)root = {",
+ " userMapNode user-map = {",
+ " mapEntryNode user-map = {",
+ " leafNode user-map-entry = \"User map entry value\"",
+ " }",
+ " }",
+ " userLeafSetNode user-leaf-set = {",
+ " leafSetEntryNode user-leaf-set = \"User leaf set value\"",
+ " }",
+ " systemMapNode list-a = {",
+ " mapEntryNode list-a = {",
+ " leafNode leaf-a = \"bar\"",
+ " systemMapNode list-b = {",
+ " mapEntryNode list-b = {",
+ " leafNode leaf-b = \"two\"",
+ " }",
+ " mapEntryNode list-b = {",
+ " leafNode leaf-b = \"one\"",
+ " }",
+ " }",
+ " }",
+ " mapEntryNode list-a = {",
+ " leafNode leaf-a = \"foo\"",
+ " }",
+ " }",
+ " containerNode (urn:opendaylight:controller:sal:dom:store:another)another = {",
+ " systemMapNode list-from-another-namespace = {",
+ " mapEntryNode list-from-another-namespace = {",
+ " leafNode leaf-from-another-namespace = \"Leaf from another namespace value\"",
+ " }",
+ " }",
+ " }",
+ " choiceNode choice = {",
+ " augmentationNode = {",
+ " leafNode augment = \"Augmented leaf value\"",
+ " }",
+ " }",
+ " anydataNode any-data = (java.lang.String)",
+ " unkeyedListNode unkeyed-list = {",
+ " unkeyedListEntryNode unkeyed-list-entry = {",
+ " leafNode unkeyed-list-leaf = \"Unkeyed list leaf value\"",
+ " }",
+ " }",
+ " leafNode leaf = \"Leaf value\"",
+ " systemLeafSetNode leaf-set = {",
+ " leafSetEntryNode leaf-set = \"Leaf set value\"",
+ " }",
+ "}"), createContainerNode().prettyTree().get());
+ }
+}
--- /dev/null
+module another {
+ yang-version 1.1;
+ namespace "urn:opendaylight:controller:sal:dom:store:another";
+ prefix another;
+
+ import test { prefix test; }
+
+ augment "/test:root" {
+ container another {
+ list list-from-another-namespace {
+ key "leaf-from-another-namespace";
+ leaf leaf-from-another-namespace {
+ type string;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+module test {
+ yang-version 1.1;
+ namespace "urn:opendaylight:controller:sal:dom:store:test";
+ prefix test;
+
+ revision 2014-03-13;
+
+ container root {
+ list list-a {
+ key "leaf-a";
+ leaf leaf-a {
+ type string;
+ }
+ list list-b {
+ key "leaf-b";
+ leaf leaf-b {
+ type string;
+ }
+ }
+ }
+
+ choice choice {
+ }
+
+ leaf leaf {
+ type string;
+ }
+
+ leaf-list leaf-set {
+ type string;
+ }
+
+ leaf-list user-leaf-set {
+ ordered-by user;
+ type string;
+ }
+
+ list user-map {
+ ordered-by user;
+ key "user-map-entry";
+ leaf user-map-entry {
+ type string;
+ }
+ }
+
+ list unkeyed-list {
+ leaf unkeyed-list-leaf {
+ type string;
+ }
+ }
+
+ anydata any-data {}
+ }
+
+ augment "/test:root/test:choice" {
+ leaf augment {
+ type string;
+ }
+ }
+}