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 com.google.common.collect.Iterators;
24 import java.util.ArrayDeque;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.NoSuchElementException;
29 import java.util.Optional;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.opendaylight.yangtools.concepts.Mutable;
33 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
34 import org.opendaylight.yangtools.yang.common.AbstractQName;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.common.QNameModule;
37 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
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.SchemaPath;
47 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
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.TypedefEffectiveStatement;
63 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefNamespace;
64 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
67 import org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference;
68 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
69 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep;
70 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
71 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
72 import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
73 import org.slf4j.LoggerFactory;
76 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
77 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
80 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
81 * in {@link SchemaContextUtil} methods.
84 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
87 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider, LeafrefResolver {
89 * Semantic binding of {@link EffectiveStatementInference} produced by {@link SchemaInferenceStack}. Sequence of
90 * {@link #statementPath()} is implementation-specific.
93 public static final class Inference extends AbstractEffectiveStatementInference<EffectiveStatement<?, ?>> {
94 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
95 private final ModuleEffectiveStatement currentModule;
96 private final int groupingDepth;
97 private final boolean clean;
99 Inference(final @NonNull EffectiveModelContext modelContext, final ArrayDeque<EffectiveStatement<?, ?>> deque,
100 final ModuleEffectiveStatement currentModule, final int groupingDepth, final boolean clean) {
102 this.deque = requireNonNull(deque);
103 this.currentModule = currentModule;
104 this.groupingDepth = groupingDepth;
109 * Create a new stack backed by an effective model and set up to point and specified data tree node.
111 * @param effectiveModel EffectiveModelContext to which this stack is attached
112 * @param qnames Data tree path qnames
113 * @return A new stack
114 * @throws NullPointerException if any argument is null or path contains a null element
115 * @throws IllegalArgumentException if a path element cannot be found
117 public static @NonNull Inference ofDataTreePath(final EffectiveModelContext effectiveModel,
118 final QName... qnames) {
119 return SchemaInferenceStack.ofDataTreePath(effectiveModel, qnames).toInference();
123 public List<EffectiveStatement<?, ?>> statementPath() {
124 return ImmutableList.copyOf(deque);
128 * Convert this inference into a {@link SchemaInferenceStack}.
130 * @return A new stack
132 public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
133 return new SchemaInferenceStack(getEffectiveModelContext(), deque, currentModule, groupingDepth, clean);
137 private static final String VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP =
138 "org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.verifyDefaultSchemaTreeInference";
139 private static final boolean VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE =
140 Boolean.getBoolean(VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP);
143 if (VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE) {
144 LoggerFactory.getLogger(SchemaInferenceStack.class)
145 .info("SchemaTreeStack.ofInference(DefaultSchemaTreeInference) argument is being verified");
149 private final @NonNull EffectiveModelContext effectiveModel;
150 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
152 private @Nullable ModuleEffectiveStatement currentModule;
153 private int groupingDepth;
155 // True if there were only steps along grouping and schema tree, hence it is consistent with SchemaNodeIdentifier
156 // False if we have evidence of a data tree lookup succeeding
157 private boolean clean;
159 private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
160 deque = new ArrayDeque<>(expectedSize);
161 this.effectiveModel = requireNonNull(effectiveModel);
165 private SchemaInferenceStack(final SchemaInferenceStack source) {
166 deque = source.deque.clone();
167 effectiveModel = source.effectiveModel;
168 currentModule = source.currentModule;
169 groupingDepth = source.groupingDepth;
170 clean = source.clean;
173 private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
174 final ArrayDeque<EffectiveStatement<?, ?>> deque, final ModuleEffectiveStatement currentModule,
175 final int groupingDepth, final boolean clean) {
176 this.effectiveModel = requireNonNull(effectiveModel);
177 this.deque = deque.clone();
178 this.currentModule = currentModule;
179 this.groupingDepth = groupingDepth;
183 private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
184 this.effectiveModel = requireNonNull(effectiveModel);
185 deque = new ArrayDeque<>();
190 * Create a new empty stack backed by an effective model.
192 * @param effectiveModel EffectiveModelContext to which this stack is attached
193 * @return A new stack
194 * @throws NullPointerException if {@code effectiveModel} is null
196 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
197 return new SchemaInferenceStack(effectiveModel);
201 * Create a new stack backed by an effective model, pointing to specified schema node identified by
204 * @param effectiveModel EffectiveModelContext to which this stack is attached
205 * @return A new stack
206 * @throws NullPointerException if {@code effectiveModel} is null
207 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
209 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
210 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
211 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
216 * Create a new stack from an {@link EffectiveStatementInference}.
218 * @param inference Inference to use for initialization
219 * @return A new stack
220 * @throws NullPointerException if {@code inference} is null
221 * @throws IllegalArgumentException if {@code inference} implementation is not supported
223 public static @NonNull SchemaInferenceStack ofInference(final EffectiveStatementInference inference) {
224 if (inference.statementPath().isEmpty()) {
225 return new SchemaInferenceStack(inference.getEffectiveModelContext());
226 } else if (inference instanceof SchemaTreeInference) {
227 return ofInference((SchemaTreeInference) inference);
228 } else if (inference instanceof Inference) {
229 return ((Inference) inference).toSchemaInferenceStack();
231 throw new IllegalArgumentException("Unsupported Inference " + inference);
236 * Create a new stack from an {@link SchemaTreeInference}.
238 * @param inference SchemaTreeInference to use for initialization
239 * @return A new stack
240 * @throws NullPointerException if {@code inference} is null
241 * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
243 public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
244 return inference instanceof DefaultSchemaTreeInference ? ofInference((DefaultSchemaTreeInference) inference)
245 : of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
249 * Create a new stack from an {@link DefaultSchemaTreeInference}. The argument is nominally trusted to be an
250 * accurate representation of the schema tree.
253 * Run-time verification of {@code inference} can be enabled by setting the
254 * {@value #VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP} system property to {@code true}.
256 * @param inference DefaultSchemaTreeInference to use for initialization
257 * @return A new stack
258 * @throws NullPointerException if {@code inference} is null
259 * @throws IllegalArgumentException if {@code inference} refers to a missing module or when verification is enabled
260 * and it does not match its context's scheam tree
262 public static @NonNull SchemaInferenceStack ofInference(final DefaultSchemaTreeInference inference) {
263 return VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE ? ofUntrusted(inference) : ofTrusted(inference);
266 private static @NonNull SchemaInferenceStack ofTrusted(final DefaultSchemaTreeInference inference) {
267 final var path = inference.statementPath();
268 final var ret = new SchemaInferenceStack(inference.getEffectiveModelContext(), path.size());
269 ret.currentModule = ret.getModule(path.get(0).argument());
270 ret.deque.addAll(path);
275 static @NonNull SchemaInferenceStack ofUntrusted(final DefaultSchemaTreeInference inference) {
276 final var ret = of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
277 if (!Iterables.elementsEqual(ret.deque, inference.statementPath())) {
278 throw new IllegalArgumentException("Provided " + inference + " is not consistent with resolved path "
279 + ret.toSchemaTreeInference());
285 * Create a new stack backed by an effective model and set up to point and specified data tree node.
287 * @param effectiveModel EffectiveModelContext to which this stack is attached
288 * @return A new stack
289 * @throws NullPointerException if any argument is null or path contains a null element
290 * @throws IllegalArgumentException if a path element cannot be found
292 public static @NonNull SchemaInferenceStack ofDataTreePath(final EffectiveModelContext effectiveModel,
293 final QName... path) {
294 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
295 for (QName qname : path) {
296 ret.enterDataTree(qname);
302 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
303 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()} interpreted as a schema node identifier.
305 * @param effectiveModel EffectiveModelContext to which this stack is attached
306 * @return A new stack
307 * @throws NullPointerException {@code effectiveModel} is null
308 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
312 public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
313 final SchemaPath path) {
314 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
315 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
316 path.getPathFromRoot().forEach(ret::enterSchemaTree);
321 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
322 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}, interpreted as a series of steps along primarily
323 * schema tree, with grouping namespace being the alternative lookup.
325 * @param effectiveModel EffectiveModelContext to which this stack is attached
326 * @return A new stack
327 * @throws NullPointerException {@code effectiveModel} is null
328 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
332 public static @NonNull SchemaInferenceStack ofSchemaPath(final EffectiveModelContext effectiveModel,
333 final SchemaPath path) {
334 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
335 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
337 for (QName step : path.getPathFromRoot()) {
339 ret.enterSchemaTree(step);
340 } catch (IllegalArgumentException schemaEx) {
342 ret.enterGrouping(step);
343 } catch (IllegalArgumentException ex) {
344 ex.addSuppressed(schemaEx);
354 public EffectiveModelContext getEffectiveModelContext() {
355 return effectiveModel;
359 * Create a deep copy of this object.
361 * @return An isolated copy of this object
363 public @NonNull SchemaInferenceStack copy() {
364 return new SchemaInferenceStack(this);
368 * Check if this stack is empty.
370 * @return True if this stack has not entered any node.
372 public boolean isEmpty() {
373 return deque.isEmpty();
377 * Return the statement at the top of the stack.
379 * @return Top statement
380 * @throws IllegalStateException if the stack is empty
382 public @NonNull EffectiveStatement<?, ?> currentStatement() {
383 return checkNonNullState(deque.peekLast());
387 * Return current module the stack has entered.
389 * @return Current module
390 * @throws IllegalStateException if the stack is empty
392 public @NonNull ModuleEffectiveStatement currentModule() {
393 return checkNonNullState(currentModule);
397 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only schema
398 * tree statements in the stack.
400 * @return False if the stack is empty or contains a statement which is not a {@link SchemaTreeEffectiveStatement},
403 public boolean inInstantiatedContext() {
404 return groupingDepth == 0 && !deque.isEmpty()
405 && deque.stream().allMatch(SchemaTreeEffectiveStatement.class::isInstance);
409 * Check if the stack is in a {@code grouping} context.
411 * @return False if the stack contains a grouping.
413 public boolean inGrouping() {
414 return groupingDepth != 0;
418 * Reset this stack to empty state.
420 public void clear() {
422 currentModule = null;
428 * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
429 * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case}
430 * intermediate schema tree children.
432 * @param nodeIdentifier Node identifier of the choice to enter
433 * @return Resolved choice
434 * @throws NullPointerException if {@code nodeIdentifier} is null
435 * @throws IllegalArgumentException if the corresponding choice cannot be found
437 public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
438 final QName nodeId = requireNonNull(nodeIdentifier);
439 final EffectiveStatement<?, ?> parent = deque.peekLast();
440 if (parent instanceof ChoiceEffectiveStatement) {
441 return enterChoice((ChoiceEffectiveStatement) parent, nodeId);
444 // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
445 final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeId);
446 if (result instanceof ChoiceEffectiveStatement) {
447 return (ChoiceEffectiveStatement) result;
451 if (parent != null) {
452 throw notPresent(parent, "Choice", nodeId);
454 throw new IllegalArgumentException("Choice " + nodeId + " not present");
457 // choice -> choice transition, we have to deal with intermediate case nodes
458 private @NonNull ChoiceEffectiveStatement enterChoice(final @NonNull ChoiceEffectiveStatement parent,
459 final QName nodeIdentifier) {
460 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
461 if (stmt instanceof CaseEffectiveStatement) {
462 final Optional<ChoiceEffectiveStatement> optMatch = ((CaseEffectiveStatement) stmt)
463 .findSchemaTreeNode(nodeIdentifier)
464 .filter(ChoiceEffectiveStatement.class::isInstance)
465 .map(ChoiceEffectiveStatement.class::cast);
466 if (optMatch.isPresent()) {
467 final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
468 deque.addLast(match);
470 return (ChoiceEffectiveStatement) match;
474 throw notPresent(parent, "Choice", nodeIdentifier);
478 * Lookup a {@code grouping} by its node identifier and push it to the stack.
480 * @param nodeIdentifier Node identifier of the grouping to enter
481 * @return Resolved grouping
482 * @throws NullPointerException if {@code nodeIdentifier} is null
483 * @throws IllegalArgumentException if the corresponding grouping cannot be found
485 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
486 return pushGrouping(requireNonNull(nodeIdentifier));
490 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
492 * @param nodeIdentifier Node identifier of the schema tree child to enter
493 * @return Resolved schema tree child
494 * @throws NullPointerException if {@code nodeIdentifier} is null
495 * @throws IllegalArgumentException if the corresponding child cannot be found
497 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
498 return pushSchema(requireNonNull(nodeIdentifier));
502 * Lookup a {@code schema tree} node by its schema node identifier and push it to the stack.
504 * @param nodeIdentifier Schema node identifier of the schema tree node to enter
505 * @return Resolved schema tree node
506 * @throws NullPointerException if {@code nodeIdentifier} is null
507 * @throws IllegalArgumentException if the corresponding node cannot be found
509 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final SchemaNodeIdentifier nodeIdentifier) {
510 if (nodeIdentifier instanceof Absolute) {
514 final Iterator<QName> it = nodeIdentifier.getNodeIdentifiers().iterator();
515 SchemaTreeEffectiveStatement<?> ret;
517 ret = enterSchemaTree(it.next());
518 } while (it.hasNext());
524 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
526 * @param nodeIdentifier Node identifier of the date tree child to enter
527 * @return Resolved date tree child
528 * @throws NullPointerException if {@code nodeIdentifier} is null
529 * @throws IllegalArgumentException if the corresponding child cannot be found
531 public @NonNull DataTreeEffectiveStatement<?> enterDataTree(final QName nodeIdentifier) {
532 return pushData(requireNonNull(nodeIdentifier));
536 * Lookup a {@code typedef} by its node identifier and push it to the stack.
538 * @param nodeIdentifier Node identifier of the typedef to enter
539 * @return Resolved typedef
540 * @throws NullPointerException if {@code nodeIdentifier} is null
541 * @throws IllegalArgumentException if the corresponding typedef cannot be found
543 public @NonNull TypedefEffectiveStatement enterTypedef(final QName nodeIdentifier) {
544 return pushTypedef(requireNonNull(nodeIdentifier));
548 * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name.
550 * @param namespace Module namespace in which to lookup the template
551 * @param name Template name
552 * @return Resolved yang-data
553 * @throws NullPointerException if any argument is null
554 * @throws IllegalArgumentException if the corresponding yang-data cannot be found
555 * @throws IllegalStateException if this stack is not empty
557 public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) {
558 final EffectiveStatement<?, ?> parent = deque.peekLast();
559 checkState(parent == null, "Cannot lookup yang-data in a non-empty stack");
561 final String templateName = requireNonNull(name);
562 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(requireNonNull(namespace));
563 checkArgument(module != null, "Module for %s not found", namespace);
565 final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class)
566 .filter(stmt -> templateName.equals(stmt.argument()))
569 () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace));
571 currentModule = module;
576 * Pop the current statement from the stack.
578 * @return Previous statement
579 * @throws NoSuchElementException if this stack is empty
581 public @NonNull EffectiveStatement<?, ?> exit() {
582 final EffectiveStatement<?, ?> prev = deque.removeLast();
583 if (prev instanceof GroupingEffectiveStatement) {
586 if (deque.isEmpty()) {
587 currentModule = null;
594 * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
595 * subsequent {@link #enterDataTree(QName)} will find it again.
597 * @return Previous statement
598 * @throws NoSuchElementException if this stack is empty
599 * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
600 * a {@link DataTreeAwareEffectiveStatement}
602 public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
603 final EffectiveStatement<?, ?> child = exit();
604 checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
605 EffectiveStatement<?, ?> parent = deque.peekLast();
606 while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) {
608 parent = deque.peekLast();
611 checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
612 return (DataTreeEffectiveStatement<?>) child;
616 public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
617 final SchemaInferenceStack tmp = copy();
619 LeafrefTypeDefinition current = type;
621 final EffectiveStatement<?, ?> resolved = tmp.resolvePathExpression(current.getPathStatement());
622 checkState(resolved instanceof TypeAware, "Unexpected result %s resultion of %s", resolved, type);
623 final TypeDefinition<?> result = ((TypedDataSchemaNode) resolved).getType();
624 if (result instanceof LeafrefTypeDefinition) {
625 checkArgument(result != type, "Resolution of %s loops back onto itself via %s", type, current);
626 current = (LeafrefTypeDefinition) result;
634 * Resolve a {@link PathExpression}.
637 * Note if this method throws, this stack may be in an undefined state.
639 * @param path Requested path
640 * @return Resolved schema tree child
641 * @throws NullPointerException if {@code path} is null
642 * @throws IllegalArgumentException if the target node cannot be found
643 * @throws VerifyException if path expression is invalid
645 public @NonNull EffectiveStatement<?, ?> resolvePathExpression(final PathExpression path) {
646 final Steps steps = path.getSteps();
647 if (steps instanceof LocationPathSteps) {
648 return resolveLocationPath(((LocationPathSteps) steps).getLocationPath());
649 } else if (steps instanceof DerefSteps) {
650 return resolveDeref((DerefSteps) steps);
652 throw new VerifyException("Unhandled steps " + steps);
656 private @NonNull EffectiveStatement<?, ?> resolveDeref(final DerefSteps deref) {
657 final EffectiveStatement<?, ?> leafRefSchemaNode = currentStatement();
658 final YangLocationPath.Relative derefArg = deref.getDerefArgument();
659 final EffectiveStatement<?, ?> derefStmt = resolveLocationPath(derefArg);
660 checkArgument(derefStmt != null, "Cannot find deref(%s) target node %s in context of %s",
661 derefArg, leafRefSchemaNode);
662 checkArgument(derefStmt instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg,
665 // We have a deref() target, decide what to do about it
666 final TypeDefinition<?> targetType = ((TypedDataSchemaNode) derefStmt).getType();
667 if (targetType instanceof InstanceIdentifierTypeDefinition) {
668 // Static inference breaks down, we cannot determine where this points to
669 // FIXME: dedicated exception, users can recover from it, derive from IAE
670 throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType);
673 // deref() is defined only for instance-identifier and leafref types, handle the latter
674 checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType);
676 final PathExpression dereferencedLeafRefPath = ((LeafrefTypeDefinition) targetType).getPathStatement();
677 EffectiveStatement<?, ?> derefNode = resolvePathExpression(dereferencedLeafRefPath);
678 checkArgument(derefStmt != null, "Can not find target node of dereferenced node %s", derefStmt);
679 checkArgument(derefNode instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref,
680 dereferencedLeafRefPath);
681 return resolveLocationPath(deref.getRelativePath());
684 private @NonNull EffectiveStatement<?, ?> resolveLocationPath(final YangLocationPath path) {
685 // get the default namespace before we clear and loose our deque
686 final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peekLast().argument()).getModule();
687 if (path.isAbsolute()) {
691 EffectiveStatement<?, ?> current = null;
692 for (Step step : path.getSteps()) {
693 final YangXPathAxis axis = step.getAxis();
696 verify(step instanceof AxisStep, "Unexpected parent step %s", step);
698 current = exitToDataTree();
699 } catch (IllegalStateException | NoSuchElementException e) {
700 throw new IllegalArgumentException("Illegal parent access in " + path, e);
704 verify(step instanceof QNameStep, "Unexpected child step %s", step);
705 current = enterChild((QNameStep) step, defaultNamespace);
708 throw new VerifyException("Unexpected step " + step);
712 return verifyNotNull(current);
715 private @NonNull EffectiveStatement<?, ?> enterChild(final QNameStep step, final QNameModule defaultNamespace) {
716 final AbstractQName toResolve = step.getQName();
718 if (toResolve instanceof QName) {
719 qname = (QName) toResolve;
720 } else if (toResolve instanceof Unqualified) {
721 checkArgument(defaultNamespace != null, "Can not find target module of step %s", step);
722 qname = ((Unqualified) toResolve).bindTo(defaultNamespace);
724 throw new VerifyException("Unexpected child step QName " + toResolve);
726 return enterDataTree(qname);
730 * Return an {@link Inference} equivalent of current state.
732 * @return An {@link Inference}
734 public @NonNull Inference toInference() {
735 return new Inference(effectiveModel, deque.clone(), currentModule, groupingDepth, clean);
739 * Return an {@link SchemaTreeInference} equivalent of current state.
741 * @return An {@link SchemaTreeInference}
742 * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
744 public @NonNull SchemaTreeInference toSchemaTreeInference() {
745 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
746 final var cleanDeque = clean ? deque : reconstructSchemaInferenceStack().deque;
747 return DefaultSchemaTreeInference.unsafeOf(getEffectiveModelContext(), cleanDeque.stream()
748 .map(stmt -> (SchemaTreeEffectiveStatement<?>) stmt)
749 .collect(ImmutableList.toImmutableList()));
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(simplePathFromRoot());
764 * Convert current state into a SchemaPath.
766 * @return Absolute SchemaPath representing current state
767 * @throws IllegalStateException if current state is not instantiated
768 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
771 public @NonNull SchemaPath toSchemaPath() {
772 return SchemaPath.create(simplePathFromRoot(), true);
776 * Return an iterator along {@link SchemaPath#getPathFromRoot()}. This method is a faster equivalent of
777 * {@code toSchemaPath().getPathFromRoot().iterator()}.
779 * @return An unmodifiable iterator
782 public @NonNull Iterator<QName> schemaPathIterator() {
783 return Iterators.unmodifiableIterator(simplePathFromRoot().iterator());
787 public String toString() {
788 return MoreObjects.toStringHelper(this).add("path", deque).toString();
791 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
792 final EffectiveStatement<?, ?> parent = deque.peekLast();
793 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
796 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
797 final @NonNull QName nodeIdentifier) {
798 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
799 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
801 .orElseThrow(() -> notPresent(parent, "Grouping", nodeIdentifier));
807 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
808 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
809 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
810 currentModule = module;
814 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
815 final EffectiveStatement<?, ?> parent = deque.peekLast();
816 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
819 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<?, ?> parent,
820 final @NonNull QName nodeIdentifier) {
821 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
822 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
825 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
826 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
827 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier)
828 .orElseThrow(() -> notPresent(parent, "Schema tree child ", nodeIdentifier));
833 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
834 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
835 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
836 currentModule = module;
840 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
841 final EffectiveStatement<?, ?> parent = deque.peekLast();
842 return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
845 private @NonNull DataTreeEffectiveStatement<?> pushData(final EffectiveStatement<?, ?> parent,
846 final @NonNull QName nodeIdentifier) {
847 checkState(parent instanceof DataTreeAwareEffectiveStatement, "Cannot descend data tree at %s", parent);
848 return pushData((DataTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
851 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
852 final @NonNull QName nodeIdentifier) {
853 final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier)
854 .orElseThrow(() -> notPresent(parent, "Data tree child", nodeIdentifier));
860 private @NonNull DataTreeEffectiveStatement<?> pushFirstData(final @NonNull QName nodeIdentifier) {
861 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
862 final DataTreeEffectiveStatement<?> ret = pushData(module, nodeIdentifier);
863 currentModule = module;
867 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) {
868 final EffectiveStatement<?, ?> parent = deque.peekLast();
869 return parent != null ? pushTypedef(parent, nodeIdentifier) : pushFirstTypedef(nodeIdentifier);
872 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull EffectiveStatement<?, ?> parent,
873 final @NonNull QName nodeIdentifier) {
874 final TypedefEffectiveStatement ret = parent.get(TypedefNamespace.class, nodeIdentifier)
875 .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier));
880 private @NonNull TypedefEffectiveStatement pushFirstTypedef(final @NonNull QName nodeIdentifier) {
881 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
882 final TypedefEffectiveStatement ret = pushTypedef(module, nodeIdentifier);
883 currentModule = module;
887 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
888 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
889 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
893 // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
894 // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
895 private Collection<QName> simplePathFromRoot() {
896 return clean ? qnames() : reconstructQNames();
899 private Collection<QName> qnames() {
900 return Collections2.transform(deque, stmt -> {
901 final Object argument = stmt.argument();
902 verify(argument instanceof QName, "Unexpected statement %s", stmt);
903 return (QName) argument;
907 // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
908 // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
910 private Collection<QName> reconstructQNames() {
911 return reconstructSchemaInferenceStack().qnames();
914 private SchemaInferenceStack reconstructSchemaInferenceStack() {
915 // Let's walk all statements and decipher them into a temporary stack
916 final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
917 final Iterator<EffectiveStatement<?, ?>> it = deque.iterator();
918 while (it.hasNext()) {
919 final EffectiveStatement<?, ?> stmt = it.next();
920 // Order of checks is significant
921 if (stmt instanceof DataTreeEffectiveStatement) {
922 tmp.resolveDataTreeSteps(((DataTreeEffectiveStatement<?>) stmt).argument());
923 } else if (stmt instanceof ChoiceEffectiveStatement) {
924 tmp.resolveChoiceSteps(((ChoiceEffectiveStatement) stmt).argument());
925 } else if (stmt instanceof SchemaTreeEffectiveStatement) {
926 tmp.enterSchemaTree(((SchemaTreeEffectiveStatement<?> )stmt).argument());
927 } else if (stmt instanceof GroupingEffectiveStatement) {
928 tmp.enterGrouping(((GroupingEffectiveStatement) stmt).argument());
929 } else if (stmt instanceof TypedefEffectiveStatement) {
930 tmp.enterTypedef(((TypedefEffectiveStatement) stmt).argument());
932 throw new VerifyException("Unexpected statement " + stmt);
936 // if the sizes match, we did not jump through hoops. let's remember that for future.
937 if (deque.size() == tmp.deque.size()) {
944 private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
945 final EffectiveStatement<?, ?> parent = deque.peekLast();
946 if (parent instanceof ChoiceEffectiveStatement) {
947 resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
949 enterSchemaTree(nodeIdentifier);
953 private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
954 final @NonNull QName nodeIdentifier) {
955 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
956 if (stmt instanceof CaseEffectiveStatement) {
957 final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
958 final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
959 if (found instanceof ChoiceEffectiveStatement) {
961 deque.addLast(found);
966 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
969 private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
970 final EffectiveStatement<?, ?> parent = deque.peekLast();
971 if (parent != null) {
972 verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
973 resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
977 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
978 resolveDataTreeSteps(module, nodeIdentifier);
979 currentModule = module;
982 private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
983 final @NonNull QName nodeIdentifier) {
984 // The algebra of identifiers in 'schema tree versus data tree':
985 // - data tree parents are always schema tree parents
986 // - data tree children are always schema tree children
988 // that implies that a data tree parent must satisfy schema tree queries with data tree children,
989 // so a successful lookup of 'data tree parent -> child' and 'schema tree parent -> child' has to be the same
990 // for a direct lookup.
991 final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
992 if (found instanceof DataTreeEffectiveStatement) {
993 // ... and it did, we are done
994 deque.addLast(found);
998 // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
999 // and employ a recursive match.
1000 final var match = new ArrayDeque<EffectiveStatement<QName, ?>>();
1001 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1002 if (stmt instanceof ChoiceEffectiveStatement
1003 && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
1004 deque.addAll(match);
1009 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
1012 private static boolean searchCase(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> result,
1013 final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
1014 result.addLast(parent);
1015 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1016 if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) {
1017 result.addLast((DataTreeEffectiveStatement<?>) stmt);
1020 if (stmt instanceof ChoiceEffectiveStatement
1021 && searchChoice(result, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
1025 result.removeLast();
1029 private static boolean searchChoice(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> result,
1030 final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
1031 result.addLast(parent);
1032 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
1033 if (stmt instanceof CaseEffectiveStatement
1034 && searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) {
1038 result.removeLast();
1042 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
1044 throw new IllegalStateException("Cannot execute on empty stack");
1049 private static @NonNull IllegalArgumentException notPresent(final @NonNull EffectiveStatement<?, ?> parent,
1050 final @NonNull String name, final QName nodeIdentifier) {
1051 return new IllegalArgumentException(name + " " + nodeIdentifier + " not present in " + describeParent(parent));
1054 private static @NonNull String describeParent(final @NonNull EffectiveStatement<?, ?> parent) {
1055 // Add just enough information to be useful without being overly-verbose. Note we want to expose namespace
1056 // information, so that we understand what revisions we are dealing with
1057 if (parent instanceof SchemaTreeEffectiveStatement) {
1058 return "schema parent " + parent.argument();
1059 } else if (parent instanceof GroupingEffectiveStatement) {
1060 return "grouping " + parent.argument();
1061 } else if (parent instanceof ModuleEffectiveStatement) {
1062 final var module = (ModuleEffectiveStatement) parent;
1063 return "module " + module.argument().bindTo(module.localQNameModule());
1065 // Shorthand for QNames, should provide enough context
1066 final Object arg = parent.argument();
1067 return "parent " + (arg instanceof QName ? arg : parent);