2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.model.util;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.annotations.Beta;
15 import com.google.common.base.MoreObjects;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.Iterators;
18 import java.util.ArrayDeque;
19 import java.util.Iterator;
20 import java.util.NoSuchElementException;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.yangtools.concepts.Mutable;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
26 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
27 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
28 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
29 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
31 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
32 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
36 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
37 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
40 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
41 * in {@link SchemaContextUtil} methods.
44 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
47 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider {
48 private final ArrayDeque<EffectiveStatement<QName, ?>> deque;
49 private final @NonNull EffectiveModelContext effectiveModel;
51 private @Nullable ModuleEffectiveStatement currentModule;
52 private int groupingDepth;
54 private SchemaInferenceStack(final SchemaInferenceStack source) {
55 this.deque = source.deque.clone();
56 this.effectiveModel = source.effectiveModel;
57 this.currentModule = source.currentModule;
58 this.groupingDepth = source.groupingDepth;
62 * Create a new empty stack backed by an effective model.
64 * @param effectiveModel EffectiveModelContext to which this stack is attached
65 * @throws NullPointerException {@code effectiveModel} is null
67 public SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
68 this.deque = new ArrayDeque<>();
69 this.effectiveModel = requireNonNull(effectiveModel);
73 * Create a new stack backed by an effective model, pointing to specified schema node identified by
76 * @param effectiveModel EffectiveModelContext to which this stack is attached
77 * @throws NullPointerException {@code effectiveModel} is null
78 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
80 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
81 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
82 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
87 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
88 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}.
90 * @param effectiveModel EffectiveModelContext to which this stack is attached
91 * @throws NullPointerException {@code effectiveModel} is null
92 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
95 // FIXME: 7.0.0: consider deprecating this method
96 public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
97 final SchemaPath path) {
98 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
99 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
100 path.getPathFromRoot().forEach(ret::enterSchemaTree);
105 public EffectiveModelContext getEffectiveModelContext() {
106 return effectiveModel;
110 * Create a deep copy of this object.
112 * @return An isolated copy of this object
114 public @NonNull SchemaInferenceStack copy() {
115 return new SchemaInferenceStack(this);
119 * Check if this stack is empty.
121 * @return True if this stack has not entered any node.
123 public boolean isEmpty() {
124 return deque.isEmpty();
128 * Return the statement at the top of the stack.
130 * @return Top statement
131 * @throws IllegalStateException if the stack is empty
133 public @NonNull EffectiveStatement<QName, ?> currentStatement() {
134 return checkNonNullState(deque.peekFirst());
138 * Return current module the stack has entered.
140 * @return Current module
141 * @throws IllegalStateException if the stack is empty
143 public @NonNull ModuleEffectiveStatement currentModule() {
144 return checkNonNullState(currentModule);
148 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there is no grouping
149 * (or similar construct) present in the stack.
151 * @return False if the stack is empty or contains a grouping, true otherwise.
153 public boolean inInstantiatedContext() {
154 return groupingDepth == 0 && !deque.isEmpty();
158 * Reset this stack to empty state.
160 public void clear() {
162 currentModule = null;
167 * Lookup a {@code grouping} by its node identifier and push it to the stack.
169 * @param nodeIdentifier Node identifier of the grouping to enter
170 * @return Resolved grouping
171 * @throws NullPointerException if {@code nodeIdentifier} is null
172 * @throws IllegalArgumentException if the corresponding grouping cannot be found
174 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
175 return pushGrouping(requireNonNull(nodeIdentifier));
179 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
181 * @param nodeIdentifier Node identifier of the schema tree child to enter
182 * @return Resolved schema tree child
183 * @throws NullPointerException if {@code nodeIdentifier} is null
184 * @throws IllegalArgumentException if the corresponding child cannot be found
186 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
187 return pushSchema(requireNonNull(nodeIdentifier));
191 * Pop the current statement from the stack.
193 * @return Previous statement
194 * @throws NoSuchElementException if this stack is empty
196 public @NonNull EffectiveStatement<QName, ?> exit() {
197 final EffectiveStatement<QName, ?> prev = deque.pop();
198 if (prev instanceof GroupingEffectiveStatement) {
201 if (deque.isEmpty()) {
202 currentModule = null;
208 * Convert current state into an absolute schema node identifier.
210 * @return Absolute schema node identifier representing current state
211 * @throws IllegalStateException if current state is not instantiated
213 public @NonNull Absolute toSchemaNodeIdentifier() {
214 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
215 final ImmutableList.Builder<QName> builder = ImmutableList.builderWithExpectedSize(deque.size());
216 deque.descendingIterator().forEachRemaining(stmt -> builder.add(stmt.argument()));
217 return Absolute.of(builder.build());
221 * Convert current state into a SchemaPath.
223 * @return Absolute SchemaPath representing current state
224 * @throws IllegalStateException if current state is not instantiated
225 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
228 public @NonNull SchemaPath toSchemaPath() {
229 SchemaPath ret = SchemaPath.ROOT;
230 final Iterator<EffectiveStatement<QName, ?>> it = deque.descendingIterator();
231 while (it.hasNext()) {
232 ret = ret.createChild(it.next().argument());
238 * Return an iterator along {@link SchemaPath#getPathFromRoot()}. This method is a faster equivalent of
239 * {@code toSchemaPath().getPathFromRoot().iterator()}.
241 * @return An unmodifiable iterator
244 public @NonNull Iterator<QName> schemaPathIterator() {
245 return Iterators.unmodifiableIterator(Iterators.transform(deque.descendingIterator(),
246 EffectiveStatement::argument));
250 public String toString() {
251 return MoreObjects.toStringHelper(this).add("stack", deque).toString();
254 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
255 final EffectiveStatement<QName, ?> parent = deque.peekFirst();
256 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
259 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
260 final @NonNull QName nodeIdentifier) {
261 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
262 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
264 .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present"));
270 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
271 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
272 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
273 currentModule = module;
277 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
278 final EffectiveStatement<QName, ?> parent = deque.peekFirst();
279 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
282 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<QName, ?> parent,
283 final @NonNull QName nodeIdentifier) {
284 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
285 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
288 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
289 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
290 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier).orElseThrow(
291 () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present"));
296 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
297 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
298 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
299 currentModule = module;
303 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
304 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
305 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
309 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
311 throw new IllegalStateException("Cannot execute on empty stack");