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.ImmutableList;
21 import com.google.common.collect.Iterators;
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.NoSuchElementException;
27 import java.util.Optional;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.opendaylight.yangtools.concepts.Mutable;
31 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
32 import org.opendaylight.yangtools.yang.common.AbstractQName;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.QNameModule;
35 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
39 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.PathExpression;
41 import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
42 import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
43 import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
44 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
45 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
46 import org.opendaylight.yangtools.yang.model.api.TypeAware;
47 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
56 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
57 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
58 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
59 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
60 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
61 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefNamespace;
62 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
65 import org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference;
66 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
67 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep;
68 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
69 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
70 import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
71 import org.slf4j.LoggerFactory;
74 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
75 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
78 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
79 * in {@link SchemaContextUtil} methods.
82 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
85 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider, LeafrefResolver {
87 * Semantic binding of {@link EffectiveStatementInference} produced by {@link SchemaInferenceStack}. Sequence of
88 * {@link #statementPath()} is implementation-specific.
91 public static final class Inference extends AbstractEffectiveStatementInference<EffectiveStatement<?, ?>> {
92 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
93 private final ModuleEffectiveStatement currentModule;
94 private final int groupingDepth;
95 private final boolean clean;
97 Inference(final @NonNull EffectiveModelContext modelContext, final ArrayDeque<EffectiveStatement<?, ?>> deque,
98 final ModuleEffectiveStatement currentModule, final int groupingDepth, final boolean clean) {
100 this.deque = requireNonNull(deque);
101 this.currentModule = currentModule;
102 this.groupingDepth = groupingDepth;
107 * Create a new stack backed by an effective model and set up to point and specified data tree node.
109 * @param effectiveModel EffectiveModelContext to which this stack is attached
110 * @param qnames Data tree path qnames
111 * @return A new stack
112 * @throws NullPointerException if any argument is null or path contains a null element
113 * @throws IllegalArgumentException if a path element cannot be found
115 public static @NonNull Inference ofDataTreePath(final EffectiveModelContext effectiveModel,
116 final QName... qnames) {
117 return SchemaInferenceStack.ofDataTreePath(effectiveModel, qnames).toInference();
121 public List<EffectiveStatement<?, ?>> statementPath() {
122 return ImmutableList.copyOf(deque.descendingIterator());
126 * Convert this inference into a {@link SchemaInferenceStack}.
128 * @return A new stack
130 public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
131 return new SchemaInferenceStack(getEffectiveModelContext(), deque, currentModule, groupingDepth, clean);
135 private static final String VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP =
136 "org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.verifyDefaultSchemaTreeInference";
137 private static final boolean VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE =
138 Boolean.getBoolean(VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP);
141 if (VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE) {
142 LoggerFactory.getLogger(SchemaInferenceStack.class)
143 .info("SchemaTreeStack.ofInference(DefaultSchemaTreeInference) argument is being verified");
147 private final @NonNull EffectiveModelContext effectiveModel;
148 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
150 private @Nullable ModuleEffectiveStatement currentModule;
151 private int groupingDepth;
153 // True if there were only steps along grouping and schema tree, hence it is consistent with SchemaNodeIdentifier
154 // False if we have evidence of a data tree lookup succeeding
155 private boolean clean;
157 private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
158 deque = new ArrayDeque<>(expectedSize);
159 this.effectiveModel = requireNonNull(effectiveModel);
163 private SchemaInferenceStack(final SchemaInferenceStack source) {
164 deque = source.deque.clone();
165 effectiveModel = source.effectiveModel;
166 currentModule = source.currentModule;
167 groupingDepth = source.groupingDepth;
168 clean = source.clean;
171 private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
172 final ArrayDeque<EffectiveStatement<?, ?>> deque, final ModuleEffectiveStatement currentModule,
173 final int groupingDepth, final boolean clean) {
174 this.effectiveModel = requireNonNull(effectiveModel);
175 this.deque = deque.clone();
176 this.currentModule = currentModule;
177 this.groupingDepth = groupingDepth;
181 private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
182 this.effectiveModel = requireNonNull(effectiveModel);
183 deque = new ArrayDeque<>();
188 * Create a new empty stack backed by an effective model.
190 * @param effectiveModel EffectiveModelContext to which this stack is attached
191 * @return A new stack
192 * @throws NullPointerException if {@code effectiveModel} is null
194 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
195 return new SchemaInferenceStack(effectiveModel);
199 * Create a new stack backed by an effective model, pointing to specified schema node identified by
202 * @param effectiveModel EffectiveModelContext to which this stack is attached
203 * @return A new stack
204 * @throws NullPointerException if {@code effectiveModel} is null
205 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
207 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
208 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
209 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
214 * Create a new stack from an {@link EffectiveStatementInference}.
216 * @param inference Inference to use for initialization
217 * @return A new stack
218 * @throws NullPointerException if {@code inference} is null
219 * @throws IllegalArgumentException if {@code inference} implementation is not supported
221 public static @NonNull SchemaInferenceStack ofInference(final EffectiveStatementInference inference) {
222 if (inference.statementPath().isEmpty()) {
223 return new SchemaInferenceStack(inference.getEffectiveModelContext());
224 } else if (inference instanceof SchemaTreeInference) {
225 return ofInference((SchemaTreeInference) inference);
226 } else if (inference instanceof Inference) {
227 return ((Inference) inference).toSchemaInferenceStack();
229 throw new IllegalArgumentException("Unsupported Inference " + inference);
234 * Create a new stack from an {@link SchemaTreeInference}.
236 * @param inference SchemaTreeInference to use for initialization
237 * @return A new stack
238 * @throws NullPointerException if {@code inference} is null
239 * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
241 public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
242 return inference instanceof DefaultSchemaTreeInference ? ofInference((DefaultSchemaTreeInference) inference)
243 : of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
247 * Create a new stack from an {@link DefaultSchemaTreeInference}. The argument is nominally trusted to be an
248 * accurate representation of the schema tree.
251 * Run-time verification of {@code inference} can be enabled by setting the
252 * {@value #VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP} system property to {@code true}.
254 * @param inference DefaultSchemaTreeInference to use for initialization
255 * @return A new stack
256 * @throws NullPointerException if {@code inference} is null
257 * @throws IllegalArgumentException if {@code inference} refers to a missing module or when verification is enabled
258 * and it does not match its context's scheam tree
260 public static @NonNull SchemaInferenceStack ofInference(final DefaultSchemaTreeInference inference) {
261 return VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE ? ofUntrusted(inference) : ofTrusted(inference);
264 private static @NonNull SchemaInferenceStack ofTrusted(final DefaultSchemaTreeInference inference) {
265 final var path = inference.statementPath();
266 final var ret = new SchemaInferenceStack(inference.getEffectiveModelContext(), path.size());
267 ret.currentModule = ret.getModule(path.get(0).argument());
268 path.forEach(ret.deque::push);
273 static @NonNull SchemaInferenceStack ofUntrusted(final DefaultSchemaTreeInference inference) {
274 final var ret = of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
275 if (!Iterators.elementsEqual(ret.deque.descendingIterator(), inference.statementPath().iterator())) {
276 throw new IllegalArgumentException("Provided " + inference + " is not consistent with resolved path "
277 + ret.toSchemaTreeInference());
283 * Create a new stack backed by an effective model and set up to point and specified data tree node.
285 * @param effectiveModel EffectiveModelContext to which this stack is attached
286 * @return A new stack
287 * @throws NullPointerException if any argument is null or path contains a null element
288 * @throws IllegalArgumentException if a path element cannot be found
290 public static @NonNull SchemaInferenceStack ofDataTreePath(final EffectiveModelContext effectiveModel,
291 final QName... path) {
292 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
293 for (QName qname : path) {
294 ret.enterDataTree(qname);
300 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
301 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()} interpreted as a schema node identifier.
303 * @param effectiveModel EffectiveModelContext to which this stack is attached
304 * @return A new stack
305 * @throws NullPointerException {@code effectiveModel} is null
306 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
310 public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
311 final SchemaPath path) {
312 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
313 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
314 path.getPathFromRoot().forEach(ret::enterSchemaTree);
319 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
320 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}, interpreted as a series of steps along primarily
321 * schema tree, with grouping namespace being the alternative lookup.
323 * @param effectiveModel EffectiveModelContext to which this stack is attached
324 * @return A new stack
325 * @throws NullPointerException {@code effectiveModel} is null
326 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
330 public static @NonNull SchemaInferenceStack ofSchemaPath(final EffectiveModelContext effectiveModel,
331 final SchemaPath path) {
332 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
333 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
335 for (QName step : path.getPathFromRoot()) {
337 ret.enterSchemaTree(step);
338 } catch (IllegalArgumentException schemaEx) {
340 ret.enterGrouping(step);
341 } catch (IllegalArgumentException ex) {
342 ex.addSuppressed(schemaEx);
352 public EffectiveModelContext getEffectiveModelContext() {
353 return effectiveModel;
357 * Create a deep copy of this object.
359 * @return An isolated copy of this object
361 public @NonNull SchemaInferenceStack copy() {
362 return new SchemaInferenceStack(this);
366 * Check if this stack is empty.
368 * @return True if this stack has not entered any node.
370 public boolean isEmpty() {
371 return deque.isEmpty();
375 * Return the statement at the top of the stack.
377 * @return Top statement
378 * @throws IllegalStateException if the stack is empty
380 public @NonNull EffectiveStatement<?, ?> currentStatement() {
381 return checkNonNullState(deque.peekFirst());
385 * Return current module the stack has entered.
387 * @return Current module
388 * @throws IllegalStateException if the stack is empty
390 public @NonNull ModuleEffectiveStatement currentModule() {
391 return checkNonNullState(currentModule);
395 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only schema
396 * tree statements in the stack.
398 * @return False if the stack is empty or contains a statement which is not a {@link SchemaTreeEffectiveStatement},
401 public boolean inInstantiatedContext() {
402 return groupingDepth == 0 && !deque.isEmpty()
403 && deque.stream().allMatch(SchemaTreeEffectiveStatement.class::isInstance);
407 * Check if the stack is in a {@code grouping} context.
409 * @return False if the stack contains a grouping.
411 public boolean inGrouping() {
412 return groupingDepth != 0;
416 * Reset this stack to empty state.
418 public void clear() {
420 currentModule = null;
426 * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
427 * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case}
428 * intermediate schema tree children.
430 * @param nodeIdentifier Node identifier of the choice to enter
431 * @return Resolved choice
432 * @throws NullPointerException if {@code nodeIdentifier} is null
433 * @throws IllegalArgumentException if the corresponding choice cannot be found
435 public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
436 final QName nodeId = requireNonNull(nodeIdentifier);
437 final EffectiveStatement<?, ?> parent = deque.peek();
438 if (parent instanceof ChoiceEffectiveStatement) {
439 return enterChoice((ChoiceEffectiveStatement) parent, nodeId);
442 // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
443 final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeId);
444 if (result instanceof ChoiceEffectiveStatement) {
445 return (ChoiceEffectiveStatement) result;
449 if (parent != null) {
450 throw notPresent(parent, "Choice", nodeId);
452 throw new IllegalArgumentException("Choice " + nodeId + " not present");
455 // choice -> choice transition, we have to deal with intermediate case nodes
456 private @NonNull ChoiceEffectiveStatement enterChoice(final @NonNull ChoiceEffectiveStatement parent,
457 final QName nodeIdentifier) {
458 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
459 if (stmt instanceof CaseEffectiveStatement) {
460 final Optional<ChoiceEffectiveStatement> optMatch = ((CaseEffectiveStatement) stmt)
461 .findSchemaTreeNode(nodeIdentifier)
462 .filter(ChoiceEffectiveStatement.class::isInstance)
463 .map(ChoiceEffectiveStatement.class::cast);
464 if (optMatch.isPresent()) {
465 final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
468 return (ChoiceEffectiveStatement) match;
472 throw notPresent(parent, "Choice", nodeIdentifier);
476 * Lookup a {@code grouping} by its node identifier and push it to the stack.
478 * @param nodeIdentifier Node identifier of the grouping to enter
479 * @return Resolved grouping
480 * @throws NullPointerException if {@code nodeIdentifier} is null
481 * @throws IllegalArgumentException if the corresponding grouping cannot be found
483 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
484 return pushGrouping(requireNonNull(nodeIdentifier));
488 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
490 * @param nodeIdentifier Node identifier of the schema tree child to enter
491 * @return Resolved schema tree child
492 * @throws NullPointerException if {@code nodeIdentifier} is null
493 * @throws IllegalArgumentException if the corresponding child cannot be found
495 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
496 return pushSchema(requireNonNull(nodeIdentifier));
500 * Lookup a {@code schema tree} node by its schema node identifier and push it to the stack.
502 * @param nodeIdentifier Schema node identifier of the schema tree node to enter
503 * @return Resolved schema tree node
504 * @throws NullPointerException if {@code nodeIdentifier} is null
505 * @throws IllegalArgumentException if the corresponding node cannot be found
507 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final SchemaNodeIdentifier nodeIdentifier) {
508 if (nodeIdentifier instanceof Absolute) {
512 final Iterator<QName> it = nodeIdentifier.getNodeIdentifiers().iterator();
513 SchemaTreeEffectiveStatement<?> ret;
515 ret = enterSchemaTree(it.next());
516 } while (it.hasNext());
522 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
524 * @param nodeIdentifier Node identifier of the date tree child to enter
525 * @return Resolved date tree child
526 * @throws NullPointerException if {@code nodeIdentifier} is null
527 * @throws IllegalArgumentException if the corresponding child cannot be found
529 public @NonNull DataTreeEffectiveStatement<?> enterDataTree(final QName nodeIdentifier) {
530 return pushData(requireNonNull(nodeIdentifier));
534 * Lookup a {@code typedef} by its node identifier and push it to the stack.
536 * @param nodeIdentifier Node identifier of the typedef to enter
537 * @return Resolved typedef
538 * @throws NullPointerException if {@code nodeIdentifier} is null
539 * @throws IllegalArgumentException if the corresponding typedef cannot be found
541 public @NonNull TypedefEffectiveStatement enterTypedef(final QName nodeIdentifier) {
542 return pushTypedef(requireNonNull(nodeIdentifier));
546 * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name.
548 * @param namespace Module namespace in which to lookup the template
549 * @param name Template name
550 * @return Resolved yang-data
551 * @throws NullPointerException if any argument is null
552 * @throws IllegalArgumentException if the corresponding yang-data cannot be found
553 * @throws IllegalStateException if this stack is not empty
555 public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) {
556 final EffectiveStatement<?, ?> parent = deque.peekFirst();
557 checkState(parent == null, "Cannot lookup yang-data in a non-empty stack");
559 final String templateName = requireNonNull(name);
560 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(requireNonNull(namespace));
561 checkArgument(module != null, "Module for %s not found", namespace);
563 final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class)
564 .filter(stmt -> templateName.equals(stmt.argument()))
567 () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace));
569 currentModule = module;
574 * Pop the current statement from the stack.
576 * @return Previous statement
577 * @throws NoSuchElementException if this stack is empty
579 public @NonNull EffectiveStatement<?, ?> exit() {
580 final EffectiveStatement<?, ?> prev = deque.pop();
581 if (prev instanceof GroupingEffectiveStatement) {
584 if (deque.isEmpty()) {
585 currentModule = null;
592 * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
593 * subsequent {@link #enterDataTree(QName)} will find it again.
595 * @return Previous statement
596 * @throws NoSuchElementException if this stack is empty
597 * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
598 * a {@link DataTreeAwareEffectiveStatement}
600 public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
601 final EffectiveStatement<?, ?> child = exit();
602 checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
603 EffectiveStatement<?, ?> parent = deque.peekFirst();
604 while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) {
606 parent = deque.peekFirst();
609 checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
610 return (DataTreeEffectiveStatement<?>) child;
614 public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
615 final SchemaInferenceStack tmp = copy();
617 LeafrefTypeDefinition current = type;
619 final EffectiveStatement<?, ?> resolved = tmp.resolvePathExpression(current.getPathStatement());
620 checkState(resolved instanceof TypeAware, "Unexpected result %s resultion of %s", resolved, type);
621 final TypeDefinition<?> result = ((TypedDataSchemaNode) resolved).getType();
622 if (result instanceof LeafrefTypeDefinition) {
623 checkArgument(result != type, "Resolution of %s loops back onto itself via %s", type, current);
624 current = (LeafrefTypeDefinition) result;
632 * Resolve a {@link PathExpression}.
635 * Note if this method throws, this stack may be in an undefined state.
637 * @param path Requested path
638 * @return Resolved schema tree child
639 * @throws NullPointerException if {@code path} is null
640 * @throws IllegalArgumentException if the target node cannot be found
641 * @throws VerifyException if path expression is invalid
643 public @NonNull EffectiveStatement<?, ?> resolvePathExpression(final PathExpression path) {
644 final Steps steps = path.getSteps();
645 if (steps instanceof LocationPathSteps) {
646 return resolveLocationPath(((LocationPathSteps) steps).getLocationPath());
647 } else if (steps instanceof DerefSteps) {
648 return resolveDeref((DerefSteps) steps);
650 throw new VerifyException("Unhandled steps " + steps);
654 private @NonNull EffectiveStatement<?, ?> resolveDeref(final DerefSteps deref) {
655 final EffectiveStatement<?, ?> leafRefSchemaNode = currentStatement();
656 final YangLocationPath.Relative derefArg = deref.getDerefArgument();
657 final EffectiveStatement<?, ?> derefStmt = resolveLocationPath(derefArg);
658 checkArgument(derefStmt != null, "Cannot find deref(%s) target node %s in context of %s",
659 derefArg, leafRefSchemaNode);
660 checkArgument(derefStmt instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg,
663 // We have a deref() target, decide what to do about it
664 final TypeDefinition<?> targetType = ((TypedDataSchemaNode) derefStmt).getType();
665 if (targetType instanceof InstanceIdentifierTypeDefinition) {
666 // Static inference breaks down, we cannot determine where this points to
667 // FIXME: dedicated exception, users can recover from it, derive from IAE
668 throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType);
671 // deref() is defined only for instance-identifier and leafref types, handle the latter
672 checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType);
674 final PathExpression dereferencedLeafRefPath = ((LeafrefTypeDefinition) targetType).getPathStatement();
675 EffectiveStatement<?, ?> derefNode = resolvePathExpression(dereferencedLeafRefPath);
676 checkArgument(derefStmt != null, "Can not find target node of dereferenced node %s", derefStmt);
677 checkArgument(derefNode instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref,
678 dereferencedLeafRefPath);
679 return resolveLocationPath(deref.getRelativePath());
682 private @NonNull EffectiveStatement<?, ?> resolveLocationPath(final YangLocationPath path) {
683 // get the default namespace before we clear and loose our deque
684 final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peek().argument()).getModule();
685 if (path.isAbsolute()) {
689 EffectiveStatement<?, ?> current = null;
690 for (Step step : path.getSteps()) {
691 final YangXPathAxis axis = step.getAxis();
694 verify(step instanceof AxisStep, "Unexpected parent step %s", step);
696 current = exitToDataTree();
697 } catch (IllegalStateException | NoSuchElementException e) {
698 throw new IllegalArgumentException("Illegal parent access in " + path, e);
702 verify(step instanceof QNameStep, "Unexpected child step %s", step);
703 current = enterChild((QNameStep) step, defaultNamespace);
706 throw new VerifyException("Unexpected step " + step);
710 return verifyNotNull(current);
713 private @NonNull EffectiveStatement<?, ?> enterChild(final QNameStep step, final QNameModule defaultNamespace) {
714 final AbstractQName toResolve = step.getQName();
716 if (toResolve instanceof QName) {
717 qname = (QName) toResolve;
718 } else if (toResolve instanceof Unqualified) {
719 checkArgument(defaultNamespace != null, "Can not find target module of step %s", step);
720 qname = ((Unqualified) toResolve).bindTo(defaultNamespace);
722 throw new VerifyException("Unexpected child step QName " + toResolve);
724 return enterDataTree(qname);
728 * Return an {@link Inference} equivalent of current state.
730 * @return An {@link Inference}
732 public @NonNull Inference toInference() {
733 return new Inference(effectiveModel, deque.clone(), currentModule, groupingDepth, clean);
737 * Return an {@link SchemaTreeInference} equivalent of current state.
739 * @return An {@link SchemaTreeInference}
740 * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
742 public @NonNull SchemaTreeInference toSchemaTreeInference() {
743 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
744 final var cleanDeque = clean ? deque : reconstructSchemaInferenceStack().deque;
745 return DefaultSchemaTreeInference.unsafeOf(getEffectiveModelContext(),
746 ImmutableList.<SchemaTreeEffectiveStatement<?>>builderWithExpectedSize(cleanDeque.size())
747 .addAll(Iterators.transform(cleanDeque.descendingIterator(),
748 stmt -> (SchemaTreeEffectiveStatement<?>) stmt))
753 * Convert current state into an absolute schema node identifier.
755 * @return Absolute schema node identifier representing current state
756 * @throws IllegalStateException if current state is not instantiated
758 public @NonNull Absolute toSchemaNodeIdentifier() {
759 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
760 return Absolute.of(ImmutableList.<QName>builderWithExpectedSize(deque.size())
761 .addAll(simplePathFromRoot())
766 * Convert current state into a SchemaPath.
768 * @return Absolute SchemaPath representing current state
769 * @throws IllegalStateException if current state is not instantiated
770 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
773 public @NonNull SchemaPath toSchemaPath() {
774 SchemaPath ret = SchemaPath.ROOT;
775 final Iterator<QName> it = simplePathFromRoot();
776 while (it.hasNext()) {
777 ret = ret.createChild(it.next());
783 * Return an iterator along {@link SchemaPath#getPathFromRoot()}. This method is a faster equivalent of
784 * {@code toSchemaPath().getPathFromRoot().iterator()}.
786 * @return An unmodifiable iterator
789 public @NonNull Iterator<QName> schemaPathIterator() {
790 return Iterators.unmodifiableIterator(simplePathFromRoot());
794 public String toString() {
795 return MoreObjects.toStringHelper(this).add("stack", deque).toString();
798 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
799 final EffectiveStatement<?, ?> parent = deque.peekFirst();
800 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
803 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
804 final @NonNull QName nodeIdentifier) {
805 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
806 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
808 .orElseThrow(() -> notPresent(parent, "Grouping", nodeIdentifier));
814 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
815 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
816 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
817 currentModule = module;
821 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
822 final EffectiveStatement<?, ?> parent = deque.peekFirst();
823 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
826 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<?, ?> parent,
827 final @NonNull QName nodeIdentifier) {
828 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
829 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
832 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
833 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
834 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier)
835 .orElseThrow(() -> notPresent(parent, "Schema tree child ", nodeIdentifier));
840 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
841 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
842 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
843 currentModule = module;
847 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
848 final EffectiveStatement<?, ?> parent = deque.peekFirst();
849 return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
852 private @NonNull DataTreeEffectiveStatement<?> pushData(final EffectiveStatement<?, ?> parent,
853 final @NonNull QName nodeIdentifier) {
854 checkState(parent instanceof DataTreeAwareEffectiveStatement, "Cannot descend data tree at %s", parent);
855 return pushData((DataTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
858 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
859 final @NonNull QName nodeIdentifier) {
860 final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier)
861 .orElseThrow(() -> notPresent(parent, "Data tree child", nodeIdentifier));
867 private @NonNull DataTreeEffectiveStatement<?> pushFirstData(final @NonNull QName nodeIdentifier) {
868 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
869 final DataTreeEffectiveStatement<?> ret = pushData(module, nodeIdentifier);
870 currentModule = module;
874 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) {
875 final EffectiveStatement<?, ?> parent = deque.peekFirst();
876 return parent != null ? pushTypedef(parent, nodeIdentifier) : pushFirstTypedef(nodeIdentifier);
879 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull EffectiveStatement<?, ?> parent,
880 final @NonNull QName nodeIdentifier) {
881 final TypedefEffectiveStatement ret = parent.get(TypedefNamespace.class, nodeIdentifier)
882 .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier));
887 private @NonNull TypedefEffectiveStatement pushFirstTypedef(final @NonNull QName nodeIdentifier) {
888 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
889 final TypedefEffectiveStatement ret = pushTypedef(module, nodeIdentifier);
890 currentModule = module;
894 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
895 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
896 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
900 // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
901 // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
902 private Iterator<QName> simplePathFromRoot() {
903 return clean ? iterateQNames() : reconstructQNames();
906 private Iterator<QName> iterateQNames() {
907 return Iterators.transform(deque.descendingIterator(), stmt -> {
908 final Object argument = stmt.argument();
909 verify(argument instanceof QName, "Unexpected statement %s", stmt);
910 return (QName) argument;
914 // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
915 // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
917 private Iterator<QName> reconstructQNames() {
918 return reconstructSchemaInferenceStack().iterateQNames();
921 private SchemaInferenceStack reconstructSchemaInferenceStack() {
922 // Let's walk all statements and decipher them into a temporary stack
923 final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
924 final Iterator<EffectiveStatement<?, ?>> it = deque.descendingIterator();
925 while (it.hasNext()) {
926 final EffectiveStatement<?, ?> stmt = it.next();
927 // Order of checks is significant
928 if (stmt instanceof DataTreeEffectiveStatement) {
929 tmp.resolveDataTreeSteps(((DataTreeEffectiveStatement<?>) stmt).argument());
930 } else if (stmt instanceof ChoiceEffectiveStatement) {
931 tmp.resolveChoiceSteps(((ChoiceEffectiveStatement) stmt).argument());
932 } else if (stmt instanceof SchemaTreeEffectiveStatement) {
933 tmp.enterSchemaTree(((SchemaTreeEffectiveStatement<?> )stmt).argument());
934 } else if (stmt instanceof GroupingEffectiveStatement) {
935 tmp.enterGrouping(((GroupingEffectiveStatement) stmt).argument());
936 } else if (stmt instanceof TypedefEffectiveStatement) {
937 tmp.enterTypedef(((TypedefEffectiveStatement) stmt).argument());
939 throw new VerifyException("Unexpected statement " + stmt);
943 // if the sizes match, we did not jump through hoops. let's remember that for future.
944 if (deque.size() == tmp.deque.size()) {
951 private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
952 final EffectiveStatement<?, ?> parent = deque.peekFirst();
953 if (parent instanceof ChoiceEffectiveStatement) {
954 resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
956 enterSchemaTree(nodeIdentifier);
960 private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
961 final @NonNull QName nodeIdentifier) {
962 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
963 if (stmt instanceof CaseEffectiveStatement) {
964 final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
965 final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
966 if (found instanceof ChoiceEffectiveStatement) {
973 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
976 private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
977 final EffectiveStatement<?, ?> parent = deque.peekFirst();
978 if (parent != null) {
979 verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
980 resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
984 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
985 resolveDataTreeSteps(module, nodeIdentifier);
986 currentModule = module;
989 private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
990 final @NonNull QName nodeIdentifier) {
991 // The algebra of identifiers in 'schema tree versus data tree':
992 // - data tree parents are always schema tree parents
993 // - data tree children are always schema tree children
995 // that implies that a data tree parent must satisfy schema tree queries with data tree children,
996 // so a successful lookup of 'data tree parent -> child' and 'schema tree parent -> child' has to be the same
997 // for a direct lookup.
998 final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
999 if (found instanceof DataTreeEffectiveStatement) {
1000 // ... and it did, we are done
1005 // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
1006 // and employ a recursive match.
1007 final Deque<EffectiveStatement<QName, ?>> match = new ArrayDeque<>();
1008 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1009 if (stmt instanceof ChoiceEffectiveStatement
1010 && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
1011 match.descendingIterator().forEachRemaining(deque::push);
1016 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
1019 private static boolean searchCase(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
1020 final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
1021 result.push(parent);
1022 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1023 if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) {
1024 result.push((DataTreeEffectiveStatement<?>) stmt);
1027 if (stmt instanceof ChoiceEffectiveStatement
1028 && searchChoice(result, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
1036 private static boolean searchChoice(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
1037 final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
1038 result.push(parent);
1039 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1040 if (stmt instanceof CaseEffectiveStatement
1041 && searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) {
1049 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
1051 throw new IllegalStateException("Cannot execute on empty stack");
1056 private static @NonNull IllegalArgumentException notPresent(final @NonNull EffectiveStatement<?, ?> parent,
1057 final @NonNull String name, final QName nodeIdentifier) {
1058 return new IllegalArgumentException(name + " " + nodeIdentifier + " not present in " + describeParent(parent));
1061 private static @NonNull String describeParent(final @NonNull EffectiveStatement<?, ?> parent) {
1062 // Add just enough information to be useful without being overly-verbose. Note we want to expose namespace
1063 // information, so that we understand what revisions we are dealing with
1064 if (parent instanceof SchemaTreeEffectiveStatement) {
1065 return "schema parent " + parent.argument();
1066 } else if (parent instanceof GroupingEffectiveStatement) {
1067 return "grouping " + parent.argument();
1068 } else if (parent instanceof ModuleEffectiveStatement) {
1069 final var module = (ModuleEffectiveStatement) parent;
1070 return "module " + module.argument().bindTo(module.localQNameModule());
1072 // Shorthand for QNames, should provide enough context
1073 final Object arg = parent.argument();
1074 return "parent " + (arg instanceof QName ? arg : parent);