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 com.google.common.base.Verify.verify;
13 import static com.google.common.base.Verify.verifyNotNull;
14 import static java.util.Objects.requireNonNull;
16 import com.google.common.annotations.Beta;
17 import com.google.common.annotations.VisibleForTesting;
18 import com.google.common.base.MoreObjects;
19 import com.google.common.base.VerifyException;
20 import com.google.common.collect.Collections2;
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.Iterables;
23 import java.util.ArrayDeque;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.NoSuchElementException;
28 import java.util.Optional;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.opendaylight.yangtools.concepts.Mutable;
32 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
33 import org.opendaylight.yangtools.yang.common.AbstractQName;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.common.QNameModule;
36 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
37 import org.opendaylight.yangtools.yang.common.YangDataName;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
39 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
40 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
41 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.PathExpression;
43 import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
44 import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
45 import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
46 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
47 import org.opendaylight.yangtools.yang.model.api.Status;
48 import org.opendaylight.yangtools.yang.model.api.TypeAware;
49 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
56 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
57 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
58 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
59 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
60 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
61 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
62 import org.opendaylight.yangtools.yang.model.api.stmt.StatusEffectiveStatement;
63 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefAwareEffectiveStatement;
64 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
65 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
68 import org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference;
69 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
70 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep;
71 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
72 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
73 import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
74 import org.slf4j.LoggerFactory;
77 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
78 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
81 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
84 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider, LeafrefResolver {
86 * Semantic binding of {@link EffectiveStatementInference} produced by {@link SchemaInferenceStack}. Sequence of
87 * {@link #statementPath()} is implementation-specific.
90 public static final class Inference extends AbstractEffectiveStatementInference<EffectiveStatement<?, ?>> {
91 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
92 private final ModuleEffectiveStatement currentModule;
93 private final int groupingDepth;
94 private final boolean clean;
96 Inference(final @NonNull EffectiveModelContext modelContext, final ArrayDeque<EffectiveStatement<?, ?>> deque,
97 final ModuleEffectiveStatement currentModule, final int groupingDepth, final boolean clean) {
99 this.deque = requireNonNull(deque);
100 this.currentModule = currentModule;
101 this.groupingDepth = groupingDepth;
106 * Create a new stack backed by an effective model and set up to point and specified data tree node.
108 * @param effectiveModel EffectiveModelContext to which this stack is attached
109 * @param qnames Data tree path qnames
110 * @return A new stack
111 * @throws NullPointerException if any argument is {@code null} or path contains a {@code null} element
112 * @throws IllegalArgumentException if a path element cannot be found
114 public static @NonNull Inference ofDataTreePath(final EffectiveModelContext effectiveModel,
115 final QName... qnames) {
116 return SchemaInferenceStack.ofDataTreePath(effectiveModel, qnames).toInference();
120 public List<EffectiveStatement<?, ?>> statementPath() {
121 return ImmutableList.copyOf(deque);
125 * Return {@code true} if this inference is empty. This is a more efficient alternative to
126 * {@code statementPath().isEmpty()}.
128 * @return {@code true} if {@link #statementPath()} returns an empty list
130 public boolean isEmpty() {
131 return deque.isEmpty();
135 * Convert this inference into a {@link SchemaInferenceStack}.
137 * @return A new stack
139 public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
140 return new SchemaInferenceStack(getEffectiveModelContext(), deque, currentModule, groupingDepth, clean);
144 private static final String VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP =
145 "org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.verifyDefaultSchemaTreeInference";
146 private static final boolean VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE =
147 Boolean.getBoolean(VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP);
150 if (VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE) {
151 LoggerFactory.getLogger(SchemaInferenceStack.class)
152 .info("SchemaTreeStack.ofInference(DefaultSchemaTreeInference) argument is being verified");
156 private final @NonNull EffectiveModelContext effectiveModel;
157 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
159 private @Nullable ModuleEffectiveStatement currentModule;
160 private int groupingDepth;
162 // True if there were only steps along grouping and schema tree, hence it is consistent with SchemaNodeIdentifier
163 // False if we have evidence of a data tree lookup succeeding
164 private boolean clean;
166 private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
167 deque = new ArrayDeque<>(expectedSize);
168 this.effectiveModel = requireNonNull(effectiveModel);
172 private SchemaInferenceStack(final SchemaInferenceStack source) {
173 deque = source.deque.clone();
174 effectiveModel = source.effectiveModel;
175 currentModule = source.currentModule;
176 groupingDepth = source.groupingDepth;
177 clean = source.clean;
180 private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
181 final ArrayDeque<EffectiveStatement<?, ?>> deque, final ModuleEffectiveStatement currentModule,
182 final int groupingDepth, final boolean clean) {
183 this.effectiveModel = requireNonNull(effectiveModel);
184 this.deque = deque.clone();
185 this.currentModule = currentModule;
186 this.groupingDepth = groupingDepth;
190 private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
191 this.effectiveModel = requireNonNull(effectiveModel);
192 deque = new ArrayDeque<>();
197 * Create a new empty stack backed by an effective model.
199 * @param effectiveModel EffectiveModelContext to which this stack is attached
200 * @return A new stack
201 * @throws NullPointerException if {@code effectiveModel} is {@code null}
203 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
204 return new SchemaInferenceStack(effectiveModel);
208 * Create a new stack backed by an effective model, pointing to specified schema node identified by
211 * @param effectiveModel EffectiveModelContext to which this stack is attached
212 * @return A new stack
213 * @throws NullPointerException if {@code effectiveModel} is {@code null}
214 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
216 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
217 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
218 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
223 * Create a new stack from an {@link EffectiveStatementInference}.
225 * @param inference Inference to use for initialization
226 * @return A new stack
227 * @throws NullPointerException if {@code inference} is {@code null}
228 * @throws IllegalArgumentException if {@code inference} implementation is not supported
230 public static @NonNull SchemaInferenceStack ofInference(final EffectiveStatementInference inference) {
231 if (inference.statementPath().isEmpty()) {
232 return new SchemaInferenceStack(inference.getEffectiveModelContext());
233 } else if (inference instanceof SchemaTreeInference sti) {
234 return ofInference(sti);
235 } else if (inference instanceof Inference inf) {
236 return inf.toSchemaInferenceStack();
238 throw new IllegalArgumentException("Unsupported Inference " + inference);
243 * Create a new stack from an {@link SchemaTreeInference}.
245 * @param inference SchemaTreeInference to use for initialization
246 * @return A new stack
247 * @throws NullPointerException if {@code inference} is {@code null}
248 * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
250 public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
251 return inference instanceof DefaultSchemaTreeInference dsti ? ofInference(dsti)
252 : of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
256 * Create a new stack from an {@link DefaultSchemaTreeInference}. The argument is nominally trusted to be an
257 * accurate representation of the schema tree.
260 * Run-time verification of {@code inference} can be enabled by setting the
261 * {@value #VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP} system property to {@code true}.
263 * @param inference DefaultSchemaTreeInference to use for initialization
264 * @return A new stack
265 * @throws NullPointerException if {@code inference} is {@code null}
266 * @throws IllegalArgumentException if {@code inference} refers to a missing module or when verification is enabled
267 * and it does not match its context's schema tree
269 public static @NonNull SchemaInferenceStack ofInference(final DefaultSchemaTreeInference inference) {
270 return VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE ? ofUntrusted(inference) : ofTrusted(inference);
273 private static @NonNull SchemaInferenceStack ofTrusted(final DefaultSchemaTreeInference inference) {
274 final var path = inference.statementPath();
275 final var ret = new SchemaInferenceStack(inference.getEffectiveModelContext(), path.size());
276 ret.currentModule = ret.getModule(path.get(0).argument());
277 ret.deque.addAll(path);
282 static @NonNull SchemaInferenceStack ofUntrusted(final DefaultSchemaTreeInference inference) {
283 final var ret = of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
284 if (!Iterables.elementsEqual(ret.deque, inference.statementPath())) {
285 throw new IllegalArgumentException("Provided " + inference + " is not consistent with resolved path "
286 + ret.toSchemaTreeInference());
292 * Create a new stack backed by an effective model and set up to point and specified data tree node.
294 * @param effectiveModel EffectiveModelContext to which this stack is attached
295 * @return A new stack
296 * @throws NullPointerException if any argument is {@code null} or path contains a {@code null} element
297 * @throws IllegalArgumentException if a path element cannot be found
299 public static @NonNull SchemaInferenceStack ofDataTreePath(final EffectiveModelContext effectiveModel,
300 final QName... path) {
301 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
302 for (QName qname : path) {
303 ret.enterDataTree(qname);
309 public EffectiveModelContext getEffectiveModelContext() {
310 return effectiveModel;
314 * Create a deep copy of this object.
316 * @return An isolated copy of this object
318 public @NonNull SchemaInferenceStack copy() {
319 return new SchemaInferenceStack(this);
323 * Check if this stack is empty.
325 * @return {@code true} if this stack has not entered any node
327 public boolean isEmpty() {
328 return deque.isEmpty();
332 * Return the statement at the top of the stack.
334 * @return Top statement
335 * @throws IllegalStateException if the stack is empty
337 public @NonNull EffectiveStatement<?, ?> currentStatement() {
338 return checkNonNullState(deque.peekLast());
342 * Return current module the stack has entered.
344 * @return Current module
345 * @throws IllegalStateException if the stack is empty
347 public @NonNull ModuleEffectiveStatement currentModule() {
348 return checkNonNullState(currentModule);
352 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only schema
353 * tree statements in the stack.
355 * @return {@code false} if the stack is empty or contains a statement which is not a
356 * {@link SchemaTreeEffectiveStatement}, {@code true} otherwise.
358 public boolean inInstantiatedContext() {
359 return groupingDepth == 0 && !deque.isEmpty()
360 && deque.stream().allMatch(SchemaTreeEffectiveStatement.class::isInstance);
364 * Check if the stack is in a {@code grouping} context.
366 * @return {@code false} if the stack contains a grouping.
368 public boolean inGrouping() {
369 return groupingDepth != 0;
373 * Return the effective {@code status} of the {@link #currentStatement()}, if present. This method operates on
374 * the effective view of the model and therefore does not reflect status the declaration hierarchy. Most notably
381 * leaf bar { type string; }
393 * will cause this method to report the status of {@code leaf bar} as:
395 * <li>{@link Status#OBSOLETE} at its original declaration site in {@code grouping foo}</li>
396 * <li>{@link Status#DEPRECATED} at its instantiation in {@code container foo}</li>
397 * <li>{@link Status#CURRENT} at its instantiation in {@code module foo}</li>
400 * @return {@link Status#CURRENT} if {@link #isEmpty()}, or the status of current statement as implied by its direct
401 * and its ancestors' substaments.
403 public @NonNull Status effectiveStatus() {
404 final var it = reconstructDeque().descendingIterator();
405 while (it.hasNext()) {
406 final var optStatus = it.next().findFirstEffectiveSubstatementArgument(StatusEffectiveStatement.class);
407 if (optStatus.isPresent()) {
408 return optStatus.orElseThrow();
411 return Status.CURRENT;
415 * Reset this stack to empty state.
417 public void clear() {
419 currentModule = null;
425 * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
426 * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case}
427 * intermediate schema tree children.
429 * @param nodeIdentifier Node identifier of the choice to enter
430 * @return Resolved choice
431 * @throws NullPointerException if {@code nodeIdentifier} is {@code null}
432 * @throws IllegalArgumentException if the corresponding choice cannot be found
434 public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
435 final QName nodeId = requireNonNull(nodeIdentifier);
436 final EffectiveStatement<?, ?> parent = deque.peekLast();
437 if (parent instanceof ChoiceEffectiveStatement choice) {
438 return enterChoice(choice, nodeId);
441 // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
442 final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeId);
443 if (result instanceof ChoiceEffectiveStatement choice) {
448 if (parent != null) {
449 throw notPresent(parent, "Choice", nodeId);
451 throw new IllegalArgumentException("Choice " + nodeId + " not present");
454 // choice -> choice transition, we have to deal with intermediate case nodes
455 private @NonNull ChoiceEffectiveStatement enterChoice(final @NonNull ChoiceEffectiveStatement parent,
456 final QName nodeIdentifier) {
457 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
458 if (stmt instanceof CaseEffectiveStatement caze) {
459 final Optional<ChoiceEffectiveStatement> optMatch = caze.findSchemaTreeNode(nodeIdentifier)
460 .filter(ChoiceEffectiveStatement.class::isInstance)
461 .map(ChoiceEffectiveStatement.class::cast);
462 if (optMatch.isPresent()) {
463 final var match = optMatch.orElseThrow();
464 deque.addLast(match);
470 throw notPresent(parent, "Choice", nodeIdentifier);
474 * Lookup a {@code grouping} by its node identifier and push it to the stack.
476 * @param nodeIdentifier Node identifier of the grouping to enter
477 * @return Resolved grouping
478 * @throws NullPointerException if {@code nodeIdentifier} is {@code null}
479 * @throws IllegalArgumentException if the corresponding grouping cannot be found
481 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
482 return pushGrouping(requireNonNull(nodeIdentifier));
486 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
488 * @param nodeIdentifier Node identifier of the schema tree child to enter
489 * @return Resolved schema tree child
490 * @throws NullPointerException if {@code nodeIdentifier} is {@code null}
491 * @throws IllegalArgumentException if the corresponding child cannot be found
493 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
494 return pushSchema(requireNonNull(nodeIdentifier));
498 * Lookup a {@code schema tree} node by its schema node identifier and push it to the stack.
500 * @param nodeIdentifier Schema node identifier of the schema tree node to enter
501 * @return Resolved schema tree node
502 * @throws NullPointerException if {@code nodeIdentifier} is {@code null}
503 * @throws IllegalArgumentException if the corresponding node cannot be found
505 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final SchemaNodeIdentifier nodeIdentifier) {
506 if (nodeIdentifier instanceof Absolute) {
510 final Iterator<QName> it = nodeIdentifier.getNodeIdentifiers().iterator();
511 SchemaTreeEffectiveStatement<?> ret;
513 ret = enterSchemaTree(it.next());
514 } while (it.hasNext());
520 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
522 * @param nodeIdentifier Node identifier of the date tree child to enter
523 * @return Resolved date tree child
524 * @throws NullPointerException if {@code nodeIdentifier} is {@code null}
525 * @throws IllegalArgumentException if the corresponding child cannot be found
527 public @NonNull DataTreeEffectiveStatement<?> enterDataTree(final QName nodeIdentifier) {
528 return pushData(requireNonNull(nodeIdentifier));
532 * Lookup a {@code typedef} by its node identifier and push it to the stack.
534 * @param nodeIdentifier Node identifier of the typedef to enter
535 * @return Resolved typedef
536 * @throws NullPointerException if {@code nodeIdentifier} is {@code null}
537 * @throws IllegalArgumentException if the corresponding typedef cannot be found
539 public @NonNull TypedefEffectiveStatement enterTypedef(final QName nodeIdentifier) {
540 return pushTypedef(requireNonNull(nodeIdentifier));
544 * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name.
546 * @param name Template name
547 * @return Resolved yang-data
548 * @throws NullPointerException if any argument is {@code null}
549 * @throws IllegalArgumentException if the corresponding yang-data cannot be found
550 * @throws IllegalStateException if this stack is not empty
552 public @NonNull YangDataEffectiveStatement enterYangData(final YangDataName name) {
553 final EffectiveStatement<?, ?> parent = deque.peekLast();
554 checkState(parent == null, "Cannot lookup yang-data in a non-empty stack");
556 final var checkedName = requireNonNull(name);
557 final var namespace = name.module();
558 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(namespace);
559 checkArgument(module != null, "Module for %s not found", namespace);
561 final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class)
562 .filter(stmt -> checkedName.equals(stmt.argument()))
565 () -> new IllegalArgumentException("yang-data " + checkedName.name() + " not present in " + namespace));
567 currentModule = module;
572 * Pop the current statement from the stack.
574 * @return Previous statement
575 * @throws NoSuchElementException if this stack is empty
577 public @NonNull EffectiveStatement<?, ?> exit() {
578 final EffectiveStatement<?, ?> prev = deque.removeLast();
579 if (prev instanceof GroupingEffectiveStatement) {
582 if (deque.isEmpty()) {
583 currentModule = null;
590 * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
591 * subsequent {@link #enterDataTree(QName)} will find it again.
593 * @return Previous statement
594 * @throws NoSuchElementException if this stack is empty
595 * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
596 * a {@link DataTreeAwareEffectiveStatement}
598 public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
599 final EffectiveStatement<?, ?> child = exit();
600 checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
601 EffectiveStatement<?, ?> parent = deque.peekLast();
602 while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) {
604 parent = deque.peekLast();
607 checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
608 return (DataTreeEffectiveStatement<?>) child;
612 public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
613 final SchemaInferenceStack tmp = copy();
615 LeafrefTypeDefinition current = type;
617 final EffectiveStatement<?, ?> resolved = tmp.resolvePathExpression(current.getPathStatement());
618 checkState(resolved instanceof TypeAware, "Unexpected result %s resultion of %s", resolved, type);
619 final TypeDefinition<?> result = ((TypedDataSchemaNode) resolved).getType();
620 if (result instanceof LeafrefTypeDefinition leafref) {
621 checkArgument(result != type, "Resolution of %s loops back onto itself via %s", type, current);
630 * Resolve a {@link PathExpression}.
633 * Note if this method throws, this stack may be in an undefined state.
635 * @param path Requested path
636 * @return Resolved schema tree child
637 * @throws NullPointerException if {@code path} is {@code null}
638 * @throws IllegalArgumentException if the target node cannot be found
639 * @throws VerifyException if path expression is invalid
641 public @NonNull EffectiveStatement<?, ?> resolvePathExpression(final PathExpression path) {
642 final Steps steps = path.getSteps();
643 if (steps instanceof LocationPathSteps location) {
644 return resolveLocationPath(location.getLocationPath());
645 } else if (steps instanceof DerefSteps deref) {
646 return resolveDeref(deref);
648 throw new VerifyException("Unhandled steps " + steps);
652 private @NonNull EffectiveStatement<?, ?> resolveDeref(final DerefSteps deref) {
653 final EffectiveStatement<?, ?> leafRefSchemaNode = currentStatement();
654 final YangLocationPath.Relative derefArg = deref.getDerefArgument();
655 final EffectiveStatement<?, ?> derefStmt = resolveLocationPath(derefArg);
656 checkArgument(derefStmt != null, "Cannot find deref(%s) target node %s in context of %s",
657 derefArg, leafRefSchemaNode);
658 checkArgument(derefStmt instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg,
661 // We have a deref() target, decide what to do about it
662 final TypeDefinition<?> targetType = ((TypedDataSchemaNode) derefStmt).getType();
663 if (targetType instanceof InstanceIdentifierTypeDefinition) {
664 // Static inference breaks down, we cannot determine where this points to
665 // FIXME: dedicated exception, users can recover from it, derive from IAE
666 throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType);
669 // deref() is defined only for instance-identifier and leafref types, handle the latter
670 checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType);
672 final PathExpression dereferencedLeafRefPath = ((LeafrefTypeDefinition) targetType).getPathStatement();
673 EffectiveStatement<?, ?> derefNode = resolvePathExpression(dereferencedLeafRefPath);
674 checkArgument(derefStmt != null, "Can not find target node of dereferenced node %s", derefStmt);
675 checkArgument(derefNode instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref,
676 dereferencedLeafRefPath);
677 return resolveLocationPath(deref.getRelativePath());
680 private @NonNull EffectiveStatement<?, ?> resolveLocationPath(final YangLocationPath path) {
681 // get the default namespace before we clear and loose our deque
682 final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peekLast().argument()).getModule();
683 if (path.isAbsolute()) {
687 EffectiveStatement<?, ?> current = null;
688 for (Step step : path.getSteps()) {
689 final YangXPathAxis axis = step.getAxis();
692 verify(step instanceof AxisStep, "Unexpected parent step %s", step);
694 current = exitToDataTree();
695 } catch (IllegalStateException | NoSuchElementException e) {
696 throw new IllegalArgumentException("Illegal parent access in " + path, e);
700 verify(step instanceof QNameStep, "Unexpected child step %s", step);
701 current = enterChild((QNameStep) step, defaultNamespace);
703 default -> throw new VerifyException("Unexpected step " + step);
707 return verifyNotNull(current);
710 private @NonNull EffectiveStatement<?, ?> enterChild(final QNameStep step, final QNameModule defaultNamespace) {
711 final AbstractQName toResolve = step.getQName();
713 if (toResolve instanceof QName) {
714 qname = (QName) toResolve;
715 } else if (toResolve instanceof Unqualified unqual) {
716 checkArgument(defaultNamespace != null, "Can not find target module of step %s", step);
717 qname = unqual.bindTo(defaultNamespace);
719 throw new VerifyException("Unexpected child step QName " + toResolve);
721 return enterDataTree(qname);
725 * Return an {@link Inference} equivalent of current state.
727 * @return An {@link Inference}
729 public @NonNull Inference toInference() {
730 return new Inference(effectiveModel, deque.clone(), currentModule, groupingDepth, clean);
734 * Return an {@link SchemaTreeInference} equivalent of current state.
736 * @return An {@link SchemaTreeInference}
737 * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
739 public @NonNull SchemaTreeInference toSchemaTreeInference() {
740 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
741 return DefaultSchemaTreeInference.unsafeOf(getEffectiveModelContext(), reconstructDeque().stream()
742 .map(stmt -> (SchemaTreeEffectiveStatement<?>) stmt)
743 .collect(ImmutableList.toImmutableList()));
746 private ArrayDeque<EffectiveStatement<?, ?>> reconstructDeque() {
747 return clean ? deque : reconstructSchemaInferenceStack().deque;
751 * Convert current state into an absolute schema node identifier.
753 * @return Absolute schema node identifier representing current state
754 * @throws IllegalStateException if current state is not instantiated
756 public @NonNull Absolute toSchemaNodeIdentifier() {
757 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
758 return Absolute.of(simplePathFromRoot());
762 public String toString() {
763 return MoreObjects.toStringHelper(this).add("path", deque).toString();
766 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
767 final EffectiveStatement<?, ?> parent = deque.peekLast();
768 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
771 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
772 final @NonNull QName nodeIdentifier) {
773 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
774 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
776 .orElseThrow(() -> notPresent(parent, "Grouping", nodeIdentifier));
782 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
783 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
784 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
785 currentModule = module;
789 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
790 final EffectiveStatement<?, ?> parent = deque.peekLast();
791 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
794 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<?, ?> parent,
795 final @NonNull QName nodeIdentifier) {
796 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
797 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
800 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
801 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
802 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier)
803 .orElseThrow(() -> notPresent(parent, "Schema tree child ", nodeIdentifier));
808 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
809 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
810 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
811 currentModule = module;
815 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
816 final EffectiveStatement<?, ?> parent = deque.peekLast();
817 return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
820 private @NonNull DataTreeEffectiveStatement<?> pushData(final EffectiveStatement<?, ?> parent,
821 final @NonNull QName nodeIdentifier) {
822 checkState(parent instanceof DataTreeAwareEffectiveStatement, "Cannot descend data tree at %s", parent);
823 return pushData((DataTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
826 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
827 final @NonNull QName nodeIdentifier) {
828 final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier)
829 .orElseThrow(() -> notPresent(parent, "Data tree child", nodeIdentifier));
835 private @NonNull DataTreeEffectiveStatement<?> pushFirstData(final @NonNull QName nodeIdentifier) {
836 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
837 final DataTreeEffectiveStatement<?> ret = pushData(module, nodeIdentifier);
838 currentModule = module;
842 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) {
843 final EffectiveStatement<?, ?> parent = deque.peekLast();
844 return parent != null ? pushTypedef(parent, nodeIdentifier) : pushFirstTypedef(nodeIdentifier);
847 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull EffectiveStatement<?, ?> parent,
848 final @NonNull QName nodeIdentifier) {
849 if (parent instanceof TypedefAwareEffectiveStatement<?, ?> aware) {
850 final TypedefEffectiveStatement ret = aware.findTypedef(nodeIdentifier)
851 .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier));
855 throw notPresent(parent, "Typedef", nodeIdentifier);
858 private @NonNull TypedefEffectiveStatement pushFirstTypedef(final @NonNull QName nodeIdentifier) {
859 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
860 final TypedefEffectiveStatement ret = pushTypedef(module, nodeIdentifier);
861 currentModule = module;
865 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
866 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
867 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
871 // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
872 // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
873 private Collection<QName> simplePathFromRoot() {
874 return clean ? qnames() : reconstructQNames();
877 private Collection<QName> qnames() {
878 return Collections2.transform(deque, stmt -> {
879 final Object argument = stmt.argument();
880 verify(argument instanceof QName, "Unexpected statement %s", stmt);
881 return (QName) argument;
885 // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
886 // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
888 private Collection<QName> reconstructQNames() {
889 return reconstructSchemaInferenceStack().qnames();
892 private SchemaInferenceStack reconstructSchemaInferenceStack() {
893 // Let's walk all statements and decipher them into a temporary stack
894 final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
895 for (EffectiveStatement<?, ?> stmt : deque) {
896 // Order of checks is significant
897 if (stmt instanceof DataTreeEffectiveStatement<?> dataTree) {
898 tmp.resolveDataTreeSteps(dataTree.argument());
899 } else if (stmt instanceof ChoiceEffectiveStatement choice) {
900 tmp.resolveChoiceSteps(choice.argument());
901 } else if (stmt instanceof SchemaTreeEffectiveStatement<?> schemaTree) {
902 tmp.enterSchemaTree(schemaTree.argument());
903 } else if (stmt instanceof GroupingEffectiveStatement grouping) {
904 tmp.enterGrouping(grouping.argument());
905 } else if (stmt instanceof TypedefEffectiveStatement typedef) {
906 tmp.enterTypedef(typedef.argument());
908 throw new VerifyException("Unexpected statement " + stmt);
912 // if the sizes match, we did not jump through hoops. let's remember that for future.
913 if (deque.size() == tmp.deque.size()) {
920 private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
921 final EffectiveStatement<?, ?> parent = deque.peekLast();
922 if (parent instanceof ChoiceEffectiveStatement choice) {
923 resolveChoiceSteps(choice, nodeIdentifier);
925 enterSchemaTree(nodeIdentifier);
929 private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
930 final @NonNull QName nodeIdentifier) {
931 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
932 if (stmt instanceof CaseEffectiveStatement caze) {
933 final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
934 if (found instanceof ChoiceEffectiveStatement) {
936 deque.addLast(found);
941 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
944 private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
945 final EffectiveStatement<?, ?> parent = deque.peekLast();
946 if (parent != null) {
947 verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
948 resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
952 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
953 resolveDataTreeSteps(module, nodeIdentifier);
954 currentModule = module;
957 private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
958 final @NonNull QName nodeIdentifier) {
959 // The algebra of identifiers in 'schema tree versus data tree':
960 // - data tree parents are always schema tree parents
961 // - data tree children are always schema tree children
963 // that implies that a data tree parent must satisfy schema tree queries with data tree children,
964 // so a successful lookup of 'data tree parent -> child' and 'schema tree parent -> child' has to be the same
965 // for a direct lookup.
966 final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
967 if (found instanceof DataTreeEffectiveStatement) {
968 // ... and it did, we are done
969 deque.addLast(found);
973 // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
974 // and employ a recursive match.
975 final var match = new ArrayDeque<EffectiveStatement<QName, ?>>();
976 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
977 if (stmt instanceof ChoiceEffectiveStatement choice && searchChoice(match, choice, nodeIdentifier)) {
983 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
986 private static boolean searchCase(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> result,
987 final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
988 result.addLast(parent);
989 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
990 if (stmt instanceof DataTreeEffectiveStatement<?> dataTree && nodeIdentifier.equals(stmt.argument())) {
991 result.addLast(dataTree);
994 if (stmt instanceof ChoiceEffectiveStatement choice && searchChoice(result, choice, nodeIdentifier)) {
1002 private static boolean searchChoice(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> result,
1003 final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
1004 result.addLast(parent);
1005 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1006 if (stmt instanceof CaseEffectiveStatement caze && searchCase(result, caze, nodeIdentifier)) {
1010 result.removeLast();
1014 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
1016 throw new IllegalStateException("Cannot execute on empty stack");
1021 private static @NonNull IllegalArgumentException notPresent(final @NonNull EffectiveStatement<?, ?> parent,
1022 final @NonNull String name, final QName nodeIdentifier) {
1023 return new IllegalArgumentException(name + " " + nodeIdentifier + " not present in " + describeParent(parent));
1026 private static @NonNull String describeParent(final @NonNull EffectiveStatement<?, ?> parent) {
1027 // Add just enough information to be useful without being overly-verbose. Note we want to expose namespace
1028 // information, so that we understand what revisions we are dealing with
1029 if (parent instanceof SchemaTreeEffectiveStatement) {
1030 return "schema parent " + parent.argument();
1031 } else if (parent instanceof GroupingEffectiveStatement) {
1032 return "grouping " + parent.argument();
1033 } else if (parent instanceof ModuleEffectiveStatement module) {
1034 return "module " + module.argument().bindTo(module.localQNameModule());
1036 // Shorthand for QNames, should provide enough context
1037 final Object arg = parent.argument();
1038 return "parent " + (arg instanceof QName ? arg : parent);