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 java.util.ArrayDeque;
18 import java.util.Iterator;
19 import java.util.NoSuchElementException;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.opendaylight.yangtools.concepts.Mutable;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
25 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
26 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
27 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
28 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
29 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
31 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
34 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
35 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
38 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
39 * in {@link SchemaContextUtil} methods.
42 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
45 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider {
46 private final ArrayDeque<EffectiveStatement<QName, ?>> deque;
47 private final @NonNull EffectiveModelContext effectiveModel;
49 private @Nullable ModuleEffectiveStatement currentModule;
50 private int groupingDepth;
52 private SchemaInferenceStack(final SchemaInferenceStack source) {
53 this.deque = source.deque.clone();
54 this.effectiveModel = source.effectiveModel;
55 this.currentModule = source.currentModule;
56 this.groupingDepth = source.groupingDepth;
60 * Create a new empty stack backed by an effective model.
62 * @param effectiveModel EffectiveModelContext to which this stack is attached
63 * @throws NullPointerException {@code effectiveModel} is null
65 public SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
66 this.deque = new ArrayDeque<>();
67 this.effectiveModel = requireNonNull(effectiveModel);
71 public EffectiveModelContext getEffectiveModelContext() {
72 return effectiveModel;
76 * Create a deep copy of this object.
78 * @return An isolated copy of this object
80 public @NonNull SchemaInferenceStack copy() {
81 return new SchemaInferenceStack(this);
85 * Check if this stack is empty.
87 * @return True if this stack has not entered any node.
89 public boolean isEmpty() {
90 return deque.isEmpty();
94 * Return the statement at the top of the stack.
96 * @return Top statement
97 * @throws IllegalStateException if the stack is empty
99 public @NonNull EffectiveStatement<QName, ?> currentStatement() {
100 return checkNonNullState(deque.peekFirst());
104 * Return current module the stack has entered.
106 * @return Current module
107 * @throws IllegalStateException if the stack is empty
109 public @NonNull ModuleEffectiveStatement currentModule() {
110 return checkNonNullState(currentModule);
114 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there is no grouping
115 * (or similar construct) present in the stack.
117 * @return False if the stack is empty or contains a grouping, true otherwise.
119 public boolean inInstantiatedContext() {
120 return groupingDepth == 0 && !deque.isEmpty();
124 * Reset this stack to empty state.
126 public void clear() {
128 currentModule = null;
133 * Lookup a grouping by its node identifier and push it to the stack.
135 * @param nodeIdentifier Node identifier of the grouping to enter
136 * @return Resolved grouping
137 * @throws NullPointerException if {@code nodeIdentifier} is null
138 * @throws IllegalArgumentException if the corresponding grouping cannot be found
140 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
141 return pushGrouping(requireNonNull(nodeIdentifier));
145 * Lookup a schema tree child by its node identifier and push it to the stack.
147 * @param nodeIdentifier Node identifier of the schema tree child to enter
148 * @return Resolved schema tree child
149 * @throws NullPointerException if {@code nodeIdentifier} is null
150 * @throws IllegalArgumentException if the corresponding grouping cannot be found
152 public @NonNull EffectiveStatement<QName, ?> enterSchemaTree(final QName nodeIdentifier) {
153 return pushSchema(requireNonNull(nodeIdentifier));
157 * Pop the current statement from the stack.
159 * @return Previous statement
160 * @throws NoSuchElementException if this stack is empty
162 public @NonNull EffectiveStatement<QName, ?> exit() {
163 final EffectiveStatement<QName, ?> prev = deque.pop();
164 if (prev instanceof GroupingEffectiveStatement) {
167 if (deque.isEmpty()) {
168 currentModule = null;
174 * Convert current state into an absolute schema node identifier.
176 * @return Absolute schema node identifier representing current state
177 * @throws IllegalStateException if current state is not instantiated
179 public @NonNull Absolute toSchemaNodeIdentifier() {
180 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
181 final ImmutableList.Builder<QName> builder = ImmutableList.builderWithExpectedSize(deque.size());
182 deque.descendingIterator().forEachRemaining(stmt -> builder.add(stmt.argument()));
183 return Absolute.of(builder.build());
187 * Convert current state into a SchemaPath.
189 * @return Absolute SchemaPath representing current state
190 * @throws IllegalStateException if current state is not instantiated
191 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
194 public @NonNull SchemaPath toSchemaPath() {
195 SchemaPath ret = SchemaPath.ROOT;
196 final Iterator<EffectiveStatement<QName, ?>> it = deque.descendingIterator();
197 while (it.hasNext()) {
198 ret = ret.createChild(it.next().argument());
204 public String toString() {
205 return MoreObjects.toStringHelper(this).add("stack", deque).toString();
208 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
209 final EffectiveStatement<QName, ?> parent = deque.peekFirst();
210 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
213 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
214 final @NonNull QName nodeIdentifier) {
215 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
216 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
218 .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present"));
224 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
225 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
226 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
227 currentModule = module;
231 private @NonNull EffectiveStatement<QName, ?> pushSchema(final @NonNull QName nodeIdentifier) {
232 final EffectiveStatement<QName, ?> parent = deque.peekFirst();
233 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
236 private @NonNull EffectiveStatement<QName, ?> pushSchema(final EffectiveStatement<QName, ?> parent,
237 final @NonNull QName nodeIdentifier) {
238 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
239 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
242 private @NonNull EffectiveStatement<QName, ?> pushSchema(
243 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
244 final EffectiveStatement<QName, ?> ret = parent.findSchemaTreeNode(nodeIdentifier).orElseThrow(
245 () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present"));
250 private @NonNull EffectiveStatement<QName, ?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
251 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
252 final EffectiveStatement<QName, ?> ret = pushSchema(module, nodeIdentifier);
253 currentModule = module;
257 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
258 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
259 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
263 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
265 throw new IllegalStateException("Cannot execute on empty stack");