From: Robert Varga Date: Thu, 28 Mar 2019 11:46:34 +0000 (+0100) Subject: Add ReusableNormalizedNodePruner X-Git-Tag: release/neon-sr1~19 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F93%2F81293%2F3;hp=0e832d6416ce9ca28902de813a2eee3d4c161798;p=controller.git Add ReusableNormalizedNodePruner This version of NormalizedNodePruner is reusable across invocations, and improving CPU and memory efficiency in bulk operations due to not needing to allocate stack nor lookup DataSchemaContextTree. JIRA: CONTROLLER-1887 Change-Id: I1798f5f6255cd23702bf3792c1f4f5149f92d208 Signed-off-by: Robert Varga (cherry picked from commit 7bef3c9e3f411d3e4abdb5ff43a618537cc9c936) --- diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/AbstractNormalizedNodePruner.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/AbstractNormalizedNodePruner.java new file mode 100644 index 0000000000..9ce039cd1a --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/AbstractNormalizedNodePruner.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. 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.controller.cluster.datastore.node.utils.transformer; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.NoSuchElementException; +import java.util.Optional; +import javax.xml.transform.dom.DOMSource; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +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.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The NormalizedNodePruner removes all nodes from the input NormalizedNode that do not have a corresponding + * schema element in the passed in SchemaContext. + */ +abstract class AbstractNormalizedNodePruner implements NormalizedNodeStreamWriter { + enum State { + UNITIALIZED, + OPEN, + CLOSED; + } + + private static final Logger LOG = LoggerFactory.getLogger(AbstractNormalizedNodePruner.class); + + private final Deque stack = new ArrayDeque<>(); + private final DataSchemaContextTree tree; + + private DataSchemaContextNode nodePathSchemaNode; + private State state = State.UNITIALIZED; + + // FIXME: package-private to support unguarded NormalizedNodePruner access + NormalizedNode normalizedNode; + + AbstractNormalizedNodePruner(final DataSchemaContextTree tree) { + this.tree = requireNonNull(tree); + } + + AbstractNormalizedNodePruner(final SchemaContext schemaContext) { + this(DataSchemaContextTree.from(schemaContext)); + } + + final DataSchemaContextTree getTree() { + return tree; + } + + final void initialize(final YangInstanceIdentifier nodePath) { + nodePathSchemaNode = tree.findChild(nodePath).orElse(null); + normalizedNode = null; + stack.clear(); + state = State.OPEN; + } + + @SuppressWarnings("unchecked") + @Override + public void leafNode(final NodeIdentifier nodeIdentifier, final Object value) { + checkNotSealed(); + + NormalizedNodeBuilderWrapper parent = stack.peek(); + LeafNode leafNode = Builders.leafBuilder().withNodeIdentifier(nodeIdentifier).withValue(value).build(); + if (parent != null) { + if (hasValidSchema(nodeIdentifier.getNodeType(), parent)) { + parent.builder().addChild(leafNode); + } + } else { + // If there's no parent node then this is a stand alone LeafNode. + if (nodePathSchemaNode != null) { + this.normalizedNode = leafNode; + } + + state = State.CLOSED; + } + } + + @Override + public void startLeafSet(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.leafSetBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startOrderedLeafSet(final NodeIdentifier nodeIdentifier, final int str) { + addBuilder(Builders.orderedLeafSetBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @SuppressWarnings("unchecked") + @Override + public void leafSetEntryNode(final QName name, final Object value) { + checkNotSealed(); + + NormalizedNodeBuilderWrapper parent = stack.peek(); + if (parent != null) { + if (hasValidSchema(name, parent)) { + parent.builder().addChild(Builders.leafSetEntryBuilder().withValue(value) + .withNodeIdentifier(new NodeWithValue<>(parent.nodeType(), value)) + .build()); + } + } else { + // If there's no parent LeafSetNode then this is a stand alone + // LeafSetEntryNode. + if (nodePathSchemaNode != null) { + this.normalizedNode = Builders.leafSetEntryBuilder().withValue(value).withNodeIdentifier( + new NodeWithValue<>(name, value)).build(); + } + + state = State.CLOSED; + } + } + + @Override + public void startContainerNode(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.containerBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startYangModeledAnyXmlNode(final NodeIdentifier nodeIdentifier, final int count) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void startUnkeyedList(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.unkeyedListBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startUnkeyedListItem(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.unkeyedListEntryBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startMapNode(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.mapBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startMapEntryNode(final NodeIdentifierWithPredicates nodeIdentifierWithPredicates, final int count) { + addBuilder(Builders.mapEntryBuilder().withNodeIdentifier(nodeIdentifierWithPredicates), + nodeIdentifierWithPredicates); + } + + @Override + public void startOrderedMapNode(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.orderedMapBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startChoiceNode(final NodeIdentifier nodeIdentifier, final int count) { + addBuilder(Builders.choiceBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); + } + + @Override + public void startAugmentationNode(final AugmentationIdentifier augmentationIdentifier) { + addBuilder(Builders.augmentationBuilder().withNodeIdentifier(augmentationIdentifier), augmentationIdentifier); + } + + @SuppressWarnings("unchecked") + @Override + public void anyxmlNode(final NodeIdentifier nodeIdentifier, final Object value) { + checkNotSealed(); + + NormalizedNodeBuilderWrapper parent = stack.peek(); + AnyXmlNode anyXmlNode = Builders.anyXmlBuilder().withNodeIdentifier(nodeIdentifier).withValue((DOMSource) value) + .build(); + if (parent != null) { + if (hasValidSchema(nodeIdentifier.getNodeType(), parent)) { + parent.builder().addChild(anyXmlNode); + } + } else { + // If there's no parent node then this is a stand alone AnyXmlNode. + if (nodePathSchemaNode != null) { + this.normalizedNode = anyXmlNode; + } + + state = State.CLOSED; + } + } + + @SuppressWarnings("unchecked") + @Override + public void endNode() { + checkNotSealed(); + + final NormalizedNodeBuilderWrapper child; + try { + child = stack.pop(); + } catch (NoSuchElementException e) { + throw new IllegalStateException("endNode called on an empty stack", e); + } + + if (child.getSchema() == null) { + LOG.debug("Schema not found for {}", child.identifier()); + if (stack.isEmpty()) { + normalizedNode = null; + state = State.CLOSED; + } + return; + } + + final NormalizedNode newNode = child.builder().build(); + final NormalizedNodeBuilderWrapper parent = stack.peek(); + if (parent == null) { + normalizedNode = newNode; + state = State.CLOSED; + } else { + parent.builder().addChild(newNode); + } + } + + @Override + public void close() { + state = State.CLOSED; + stack.clear(); + } + + @Override + public void flush() { + // No-op + } + + /** + * Return the resulting normalized node. + * + * @return Resulting node for the path, if it was not pruned + * @throws IllegalStateException if this pruner has not been closed + */ + public final Optional> getResult() { + checkState(state == State.CLOSED, "Cannot get result in state %s", state); + return Optional.ofNullable(normalizedNode); + } + + private void checkNotSealed() { + checkState(state == State.OPEN, "Illegal operation in state %s", state); + } + + private static boolean hasValidSchema(final QName name, final NormalizedNodeBuilderWrapper parent) { + final DataSchemaContextNode parentSchema = parent.getSchema(); + final boolean valid = parentSchema != null && parentSchema.getChild(name) != null; + if (!valid) { + LOG.debug("Schema not found for {}", name); + } + + return valid; + } + + private NormalizedNodeBuilderWrapper addBuilder(final NormalizedNodeContainerBuilder builder, + final PathArgument identifier) { + checkNotSealed(); + + final DataSchemaContextNode schemaNode; + final NormalizedNodeBuilderWrapper parent = stack.peek(); + if (parent != null) { + final DataSchemaContextNode parentSchema = parent.getSchema(); + schemaNode = parentSchema == null ? null : parentSchema.getChild(identifier); + } else { + schemaNode = nodePathSchemaNode; + } + + NormalizedNodeBuilderWrapper wrapper = new NormalizedNodeBuilderWrapper(builder, identifier, schemaNode); + stack.push(wrapper); + return wrapper; + } +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java index 2082654543..4e3bb5e82f 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java @@ -7,244 +7,27 @@ */ package org.opendaylight.controller.cluster.datastore.node.utils.transformer; -import static com.google.common.base.Preconditions.checkState; - import java.net.URI; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.NoSuchElementException; -import javax.xml.transform.dom.DOMSource; -import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -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.NodeIdentifierWithPredicates; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; -import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; -import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; -import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The NormalizedNodePruner removes all nodes from the input NormalizedNode that do not have a corresponding * schema element in the passed in SchemaContext. + * + * @deprecated Use {@link AbstractNormalizedNodePruner} instead. */ -public class NormalizedNodePruner implements NormalizedNodeStreamWriter { - private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodePruner.class); - +@Deprecated +public class NormalizedNodePruner extends AbstractNormalizedNodePruner { public static final URI BASE_NAMESPACE = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); - private final Deque stack = new ArrayDeque<>(); - private final DataSchemaContextNode nodePathSchemaNode; - - private NormalizedNode normalizedNode; - private boolean sealed = false; - public NormalizedNodePruner(final YangInstanceIdentifier nodePath, final SchemaContext schemaContext) { - nodePathSchemaNode = DataSchemaContextTree.from(schemaContext).findChild(nodePath).orElse(null); - } - - @SuppressWarnings("unchecked") - @Override - public void leafNode(final NodeIdentifier nodeIdentifier, final Object value) { - checkNotSealed(); - - NormalizedNodeBuilderWrapper parent = stack.peek(); - LeafNode leafNode = Builders.leafBuilder().withNodeIdentifier(nodeIdentifier).withValue(value).build(); - if (parent != null) { - if (hasValidSchema(nodeIdentifier.getNodeType(), parent)) { - parent.builder().addChild(leafNode); - } - } else { - // If there's no parent node then this is a stand alone LeafNode. - if (nodePathSchemaNode != null) { - this.normalizedNode = leafNode; - } - - sealed = true; - } - } - - @Override - public void startLeafSet(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.leafSetBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startOrderedLeafSet(final NodeIdentifier nodeIdentifier, final int str) { - addBuilder(Builders.orderedLeafSetBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @SuppressWarnings("unchecked") - @Override - public void leafSetEntryNode(final QName name, final Object value) { - checkNotSealed(); - - NormalizedNodeBuilderWrapper parent = stack.peek(); - if (parent != null) { - if (hasValidSchema(name, parent)) { - parent.builder().addChild(Builders.leafSetEntryBuilder().withValue(value) - .withNodeIdentifier(new NodeWithValue<>(parent.nodeType(), value)) - .build()); - } - } else { - // If there's no parent LeafSetNode then this is a stand alone - // LeafSetEntryNode. - if (nodePathSchemaNode != null) { - this.normalizedNode = Builders.leafSetEntryBuilder().withValue(value).withNodeIdentifier( - new NodeWithValue<>(name, value)).build(); - } - - sealed = true; - } - } - - @Override - public void startContainerNode(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.containerBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startYangModeledAnyXmlNode(final NodeIdentifier nodeIdentifier, final int count) { - throw new UnsupportedOperationException("Not implemented yet"); - } - - @Override - public void startUnkeyedList(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.unkeyedListBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startUnkeyedListItem(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.unkeyedListEntryBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startMapNode(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.mapBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startMapEntryNode(final NodeIdentifierWithPredicates nodeIdentifierWithPredicates, final int count) { - addBuilder(Builders.mapEntryBuilder().withNodeIdentifier(nodeIdentifierWithPredicates), - nodeIdentifierWithPredicates); - } - - @Override - public void startOrderedMapNode(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.orderedMapBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startChoiceNode(final NodeIdentifier nodeIdentifier, final int count) { - addBuilder(Builders.choiceBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier); - } - - @Override - public void startAugmentationNode(final AugmentationIdentifier augmentationIdentifier) { - addBuilder(Builders.augmentationBuilder().withNodeIdentifier(augmentationIdentifier), augmentationIdentifier); - } - - @SuppressWarnings("unchecked") - @Override - public void anyxmlNode(final NodeIdentifier nodeIdentifier, final Object value) { - checkNotSealed(); - - NormalizedNodeBuilderWrapper parent = stack.peek(); - AnyXmlNode anyXmlNode = Builders.anyXmlBuilder().withNodeIdentifier(nodeIdentifier).withValue((DOMSource) value) - .build(); - if (parent != null) { - if (hasValidSchema(nodeIdentifier.getNodeType(), parent)) { - parent.builder().addChild(anyXmlNode); - } - } else { - // If there's no parent node then this is a stand alone AnyXmlNode. - if (nodePathSchemaNode != null) { - this.normalizedNode = anyXmlNode; - } - - sealed = true; - } - } - - @SuppressWarnings("unchecked") - @Override - public void endNode() { - checkNotSealed(); - - final NormalizedNodeBuilderWrapper child; - try { - child = stack.pop(); - } catch (NoSuchElementException e) { - throw new IllegalStateException("endNode called on an empty stack", e); - } - - if (child.getSchema() == null) { - LOG.debug("Schema not found for {}", child.identifier()); - return; - } - - NormalizedNode newNode = child.builder().build(); - if (stack.size() > 0) { - NormalizedNodeBuilderWrapper parent = stack.peek(); - parent.builder().addChild(newNode); - } else { - this.normalizedNode = newNode; - sealed = true; - } - } - - @Override - public void close() { - sealed = true; - } - - @Override - public void flush() { - // No-op + super(schemaContext); + initialize(nodePath); } public NormalizedNode normalizedNode() { return normalizedNode; } - - private void checkNotSealed() { - checkState(!sealed, "Pruner can be used only once"); - } - - private static boolean hasValidSchema(final QName name, final NormalizedNodeBuilderWrapper parent) { - final DataSchemaContextNode parentSchema = parent.getSchema(); - final boolean valid = parentSchema != null && parentSchema.getChild(name) != null; - if (!valid) { - LOG.debug("Schema not found for {}", name); - } - - return valid; - } - - private NormalizedNodeBuilderWrapper addBuilder(final NormalizedNodeContainerBuilder builder, - final PathArgument identifier) { - checkNotSealed(); - - final DataSchemaContextNode schemaNode; - final NormalizedNodeBuilderWrapper parent = stack.peek(); - if (parent != null) { - final DataSchemaContextNode parentSchema = parent.getSchema(); - schemaNode = parentSchema == null ? null : parentSchema.getChild(identifier); - } else { - schemaNode = nodePathSchemaNode; - } - - NormalizedNodeBuilderWrapper wrapper = new NormalizedNodeBuilderWrapper(builder, identifier, schemaNode); - stack.push(wrapper); - return wrapper; - } } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/ReusableNormalizedNodePruner.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/ReusableNormalizedNodePruner.java new file mode 100644 index 0000000000..1e95766f84 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/ReusableNormalizedNodePruner.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 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.controller.cluster.datastore.node.utils.transformer; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * The NormalizedNodePruner removes all nodes from the input NormalizedNode that do not have a corresponding + * schema element in the passed in SchemaContext. + * + *

+ * Unlike {@link NormalizedNodePruner}, this class can be reused multiple times and must be initialized before each use + * through {@link #initializeForPath(YangInstanceIdentifier)}. + */ +@Beta +public final class ReusableNormalizedNodePruner extends AbstractNormalizedNodePruner { + private ReusableNormalizedNodePruner(final SchemaContext schemaContext) { + super(schemaContext); + } + + private ReusableNormalizedNodePruner(final DataSchemaContextTree tree) { + super(tree); + } + + /** + * Create a new pruner bound to a SchemaContext. + * + * @param schemaContext SchemaContext to use + * @return A new uninitialized pruner + * @throws NullPointerException if {@code schemaContext} is null + */ + public static @NonNull ReusableNormalizedNodePruner forSchemaContext(final SchemaContext schemaContext) { + return new ReusableNormalizedNodePruner(schemaContext); + } + + /** + * Create a new pruner bound to a DataSchemaContextTree. This is a more efficient alternative of + * {@link #forSchemaContext(SchemaContext)}. + * + * @param tree DataSchemaContextTree to use + * @return A new uninitialized pruner + * @throws NullPointerException if {@code schemaContext} is null + */ + public static @NonNull ReusableNormalizedNodePruner forDataSchemaContext(final DataSchemaContextTree tree) { + return new ReusableNormalizedNodePruner(tree); + } + + /** + * Return a new instance, which is backed but the same DataSchemaContextTree, but does not share any state and is + * uninitialized. This is equivalent to {@link #forDataSchemaContext(DataSchemaContextTree)} and is provided for + * convenience. + * + * @return A new uninitialized pruner bound to the same SchemaContext as this one. + */ + public @NonNull ReusableNormalizedNodePruner duplicate() { + return new ReusableNormalizedNodePruner(getTree()); + } + + /** + * Initialize this pruner for processing a node at specified path. + * + * @param path Path that will be processed next + * @throws NullPointerException if {@code path} is null + */ + public void initializeForPath(final YangInstanceIdentifier path) { + initialize(path); + } +}