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.base.MoreObjects;
18 import com.google.common.base.VerifyException;
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.Iterators;
21 import java.util.ArrayDeque;
22 import java.util.Deque;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.NoSuchElementException;
26 import java.util.Optional;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.opendaylight.yangtools.concepts.Mutable;
30 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
31 import org.opendaylight.yangtools.yang.common.AbstractQName;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.QNameModule;
34 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
38 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.PathExpression;
40 import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
41 import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
42 import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
43 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
44 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
45 import org.opendaylight.yangtools.yang.model.api.TypeAware;
46 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
56 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
57 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
58 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
59 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
60 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefNamespace;
61 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
64 import org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference;
65 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
66 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep;
67 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
68 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
69 import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
72 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
73 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
76 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
77 * in {@link SchemaContextUtil} methods.
80 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
83 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider, LeafrefResolver {
85 * Semantic binding of {@link EffectiveStatementInference} produced by {@link SchemaInferenceStack}. Sequence of
86 * {@link #statementPath()} is implementation-specific.
89 public static final class Inference extends AbstractEffectiveStatementInference<EffectiveStatement<?, ?>> {
90 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
91 private final ModuleEffectiveStatement currentModule;
92 private final int groupingDepth;
93 private final boolean clean;
95 Inference(final @NonNull EffectiveModelContext modelContext, final ArrayDeque<EffectiveStatement<?, ?>> deque,
96 final ModuleEffectiveStatement currentModule, final int groupingDepth, final boolean clean) {
98 this.deque = requireNonNull(deque);
99 this.currentModule = currentModule;
100 this.groupingDepth = groupingDepth;
105 * Create a new stack backed by an effective model and set up to point and specified data tree node.
107 * @param effectiveModel EffectiveModelContext to which this stack is attached
108 * @param qnames Data tree path qnames
109 * @return A new stack
110 * @throws NullPointerException if any argument is null or path contains a null element
111 * @throws IllegalArgumentException if a path element cannot be found
113 public static @NonNull Inference ofDataTreePath(final EffectiveModelContext effectiveModel,
114 final QName... qnames) {
115 return SchemaInferenceStack.ofDataTreePath(effectiveModel, qnames).toInference();
119 public List<EffectiveStatement<?, ?>> statementPath() {
120 return ImmutableList.copyOf(deque.descendingIterator());
124 * Convert this inference into a {@link SchemaInferenceStack}.
126 * @return A new stack
128 public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
129 return new SchemaInferenceStack(getEffectiveModelContext(), deque, currentModule, groupingDepth, clean);
133 private final @NonNull EffectiveModelContext effectiveModel;
134 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
136 private @Nullable ModuleEffectiveStatement currentModule;
137 private int groupingDepth;
139 // True if there were only steps along grouping and schema tree, hence it is consistent with SchemaNodeIdentifier
140 // False if we have evidence of a data tree lookup succeeding
141 private boolean clean;
143 private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
144 deque = new ArrayDeque<>(expectedSize);
145 this.effectiveModel = requireNonNull(effectiveModel);
149 private SchemaInferenceStack(final SchemaInferenceStack source) {
150 deque = source.deque.clone();
151 effectiveModel = source.effectiveModel;
152 currentModule = source.currentModule;
153 groupingDepth = source.groupingDepth;
154 clean = source.clean;
157 private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
158 final ArrayDeque<EffectiveStatement<?, ?>> deque, final ModuleEffectiveStatement currentModule,
159 final int groupingDepth, final boolean clean) {
160 this.effectiveModel = requireNonNull(effectiveModel);
161 this.deque = deque.clone();
162 this.currentModule = currentModule;
163 this.groupingDepth = groupingDepth;
167 private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
168 this.effectiveModel = requireNonNull(effectiveModel);
169 deque = new ArrayDeque<>();
174 * Create a new empty stack backed by an effective model.
176 * @param effectiveModel EffectiveModelContext to which this stack is attached
177 * @return A new stack
178 * @throws NullPointerException if {@code effectiveModel} is null
180 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
181 return new SchemaInferenceStack(effectiveModel);
185 * Create a new stack backed by an effective model, pointing to specified schema node identified by
188 * @param effectiveModel EffectiveModelContext to which this stack is attached
189 * @return A new stack
190 * @throws NullPointerException if {@code effectiveModel} is null
191 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
193 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
194 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
195 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
200 * Create a new stack from an {@link EffectiveStatementInference}.
202 * @param inference Inference to use for initialization
203 * @return A new stack
204 * @throws NullPointerException if {@code inference} is null
205 * @throws IllegalArgumentException if {@code inference} implementation is not supported
207 public static @NonNull SchemaInferenceStack ofInference(final EffectiveStatementInference inference) {
208 if (inference.statementPath().isEmpty()) {
209 return new SchemaInferenceStack(inference.getEffectiveModelContext());
210 } else if (inference instanceof SchemaTreeInference) {
211 return ofInference((SchemaTreeInference) inference);
212 } else if (inference instanceof Inference) {
213 return ((Inference) inference).toSchemaInferenceStack();
215 throw new IllegalArgumentException("Unsupported Inference " + inference);
220 * Create a new stack from an {@link SchemaTreeInference}.
222 * @param inference SchemaTreeInference to use for initialization
223 * @return A new stack
224 * @throws NullPointerException if {@code inference} is null
225 * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
227 public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
228 return of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
232 * Create a new stack backed by an effective model and set up to point and specified data tree node.
234 * @param effectiveModel EffectiveModelContext to which this stack is attached
235 * @return A new stack
236 * @throws NullPointerException if any argument is null or path contains a null element
237 * @throws IllegalArgumentException if a path element cannot be found
239 public static @NonNull SchemaInferenceStack ofDataTreePath(final EffectiveModelContext effectiveModel,
240 final QName... path) {
241 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
242 for (QName qname : path) {
243 ret.enterDataTree(qname);
249 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
250 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()} interpreted as a schema node identifier.
252 * @param effectiveModel EffectiveModelContext to which this stack is attached
253 * @return A new stack
254 * @throws NullPointerException {@code effectiveModel} is null
255 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
259 public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
260 final SchemaPath path) {
261 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
262 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
263 path.getPathFromRoot().forEach(ret::enterSchemaTree);
268 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
269 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}, interpreted as a series of steps along primarily
270 * schema tree, with grouping namespace being the alternative lookup.
272 * @param effectiveModel EffectiveModelContext to which this stack is attached
273 * @return A new stack
274 * @throws NullPointerException {@code effectiveModel} is null
275 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
279 public static @NonNull SchemaInferenceStack ofSchemaPath(final EffectiveModelContext effectiveModel,
280 final SchemaPath path) {
281 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
282 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
284 for (QName step : path.getPathFromRoot()) {
286 ret.enterSchemaTree(step);
287 } catch (IllegalArgumentException schemaEx) {
289 ret.enterGrouping(step);
290 } catch (IllegalArgumentException ex) {
291 ex.addSuppressed(schemaEx);
301 public EffectiveModelContext getEffectiveModelContext() {
302 return effectiveModel;
306 * Create a deep copy of this object.
308 * @return An isolated copy of this object
310 public @NonNull SchemaInferenceStack copy() {
311 return new SchemaInferenceStack(this);
315 * Check if this stack is empty.
317 * @return True if this stack has not entered any node.
319 public boolean isEmpty() {
320 return deque.isEmpty();
324 * Return the statement at the top of the stack.
326 * @return Top statement
327 * @throws IllegalStateException if the stack is empty
329 public @NonNull EffectiveStatement<?, ?> currentStatement() {
330 return checkNonNullState(deque.peekFirst());
334 * Return current module the stack has entered.
336 * @return Current module
337 * @throws IllegalStateException if the stack is empty
339 public @NonNull ModuleEffectiveStatement currentModule() {
340 return checkNonNullState(currentModule);
344 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only schema
345 * tree statements in the stack.
347 * @return False if the stack is empty or contains a statement which is not a {@link SchemaTreeEffectiveStatement},
350 public boolean inInstantiatedContext() {
351 return groupingDepth == 0 && !deque.isEmpty()
352 && deque.stream().allMatch(SchemaTreeEffectiveStatement.class::isInstance);
356 * Check if the stack is in a {@code grouping} context.
358 * @return False if the stack contains a grouping.
360 public boolean inGrouping() {
361 return groupingDepth != 0;
365 * Reset this stack to empty state.
367 public void clear() {
369 currentModule = null;
375 * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
376 * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case}
377 * intermediate schema tree children.
379 * @param nodeIdentifier Node identifier of the choice to enter
380 * @return Resolved choice
381 * @throws NullPointerException if {@code nodeIdentifier} is null
382 * @throws IllegalArgumentException if the corresponding choice cannot be found
384 public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
385 final QName nodeId = requireNonNull(nodeIdentifier);
386 final EffectiveStatement<?, ?> parent = deque.peek();
387 if (parent instanceof ChoiceEffectiveStatement) {
388 return enterChoice((ChoiceEffectiveStatement) parent, nodeId);
391 // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
392 final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeId);
393 if (result instanceof ChoiceEffectiveStatement) {
394 return (ChoiceEffectiveStatement) result;
398 if (parent != null) {
399 throw notPresent(parent, "Choice", nodeId);
401 throw new IllegalArgumentException("Choice " + nodeId + " not present");
404 // choice -> choice transition, we have to deal with intermediate case nodes
405 private @NonNull ChoiceEffectiveStatement enterChoice(final @NonNull ChoiceEffectiveStatement parent,
406 final QName nodeIdentifier) {
407 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
408 if (stmt instanceof CaseEffectiveStatement) {
409 final Optional<ChoiceEffectiveStatement> optMatch = ((CaseEffectiveStatement) stmt)
410 .findSchemaTreeNode(nodeIdentifier)
411 .filter(ChoiceEffectiveStatement.class::isInstance)
412 .map(ChoiceEffectiveStatement.class::cast);
413 if (optMatch.isPresent()) {
414 final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
417 return (ChoiceEffectiveStatement) match;
421 throw notPresent(parent, "Choice", nodeIdentifier);
425 * Lookup a {@code grouping} by its node identifier and push it to the stack.
427 * @param nodeIdentifier Node identifier of the grouping to enter
428 * @return Resolved grouping
429 * @throws NullPointerException if {@code nodeIdentifier} is null
430 * @throws IllegalArgumentException if the corresponding grouping cannot be found
432 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
433 return pushGrouping(requireNonNull(nodeIdentifier));
437 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
439 * @param nodeIdentifier Node identifier of the schema tree child to enter
440 * @return Resolved schema tree child
441 * @throws NullPointerException if {@code nodeIdentifier} is null
442 * @throws IllegalArgumentException if the corresponding child cannot be found
444 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
445 return pushSchema(requireNonNull(nodeIdentifier));
449 * Lookup a {@code schema tree} node by its schema node identifier and push it to the stack.
451 * @param nodeIdentifier Schema node identifier of the schema tree node to enter
452 * @return Resolved schema tree node
453 * @throws NullPointerException if {@code nodeIdentifier} is null
454 * @throws IllegalArgumentException if the corresponding node cannot be found
456 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final SchemaNodeIdentifier nodeIdentifier) {
457 if (nodeIdentifier instanceof Absolute) {
461 final Iterator<QName> it = nodeIdentifier.getNodeIdentifiers().iterator();
462 SchemaTreeEffectiveStatement<?> ret;
464 ret = enterSchemaTree(it.next());
465 } while (it.hasNext());
471 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
473 * @param nodeIdentifier Node identifier of the date tree child to enter
474 * @return Resolved date tree child
475 * @throws NullPointerException if {@code nodeIdentifier} is null
476 * @throws IllegalArgumentException if the corresponding child cannot be found
478 public @NonNull DataTreeEffectiveStatement<?> enterDataTree(final QName nodeIdentifier) {
479 return pushData(requireNonNull(nodeIdentifier));
483 * Lookup a {@code typedef} by its node identifier and push it to the stack.
485 * @param nodeIdentifier Node identifier of the typedef to enter
486 * @return Resolved typedef
487 * @throws NullPointerException if {@code nodeIdentifier} is null
488 * @throws IllegalArgumentException if the corresponding typedef cannot be found
490 public @NonNull TypedefEffectiveStatement enterTypedef(final QName nodeIdentifier) {
491 return pushTypedef(requireNonNull(nodeIdentifier));
495 * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name.
497 * @param namespace Module namespace in which to lookup the template
498 * @param name Template name
499 * @return Resolved yang-data
500 * @throws NullPointerException if any argument is null
501 * @throws IllegalArgumentException if the corresponding yang-data cannot be found
502 * @throws IllegalStateException if this stack is not empty
504 public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) {
505 final EffectiveStatement<?, ?> parent = deque.peekFirst();
506 checkState(parent == null, "Cannot lookup yang-data in a non-empty stack");
508 final String templateName = requireNonNull(name);
509 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(requireNonNull(namespace));
510 checkArgument(module != null, "Module for %s not found", namespace);
512 final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class)
513 .filter(stmt -> templateName.equals(stmt.argument()))
516 () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace));
518 currentModule = module;
523 * Pop the current statement from the stack.
525 * @return Previous statement
526 * @throws NoSuchElementException if this stack is empty
528 public @NonNull EffectiveStatement<?, ?> exit() {
529 final EffectiveStatement<?, ?> prev = deque.pop();
530 if (prev instanceof GroupingEffectiveStatement) {
533 if (deque.isEmpty()) {
534 currentModule = null;
541 * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
542 * subsequent {@link #enterDataTree(QName)} will find it again.
544 * @return Previous statement
545 * @throws NoSuchElementException if this stack is empty
546 * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
547 * a {@link DataTreeAwareEffectiveStatement}
549 public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
550 final EffectiveStatement<?, ?> child = exit();
551 checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
552 EffectiveStatement<?, ?> parent = deque.peekFirst();
553 while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) {
555 parent = deque.peekFirst();
558 checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
559 return (DataTreeEffectiveStatement<?>) child;
564 public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
565 final SchemaInferenceStack tmp = copy();
567 LeafrefTypeDefinition current = type;
569 final EffectiveStatement<?, ?> resolved = tmp.resolvePathExpression(current.getPathStatement());
570 checkState(resolved instanceof TypeAware, "Unexpected result %s resultion of %s", resolved, type);
571 final TypeDefinition<?> result = ((TypedDataSchemaNode) resolved).getType();
572 if (result instanceof LeafrefTypeDefinition) {
573 checkArgument(result != type, "Resolution of %s loops back onto itself via %s", type, current);
574 current = (LeafrefTypeDefinition) result;
582 * Resolve a {@link PathExpression}.
585 * Note if this method throws, this stack may be in an undefined state.
587 * @param path Requested path
588 * @return Resolved schema tree child
589 * @throws NullPointerException if {@code path} is null
590 * @throws IllegalArgumentException if the target node cannot be found
591 * @throws VerifyException if path expression is invalid
593 public @NonNull EffectiveStatement<?, ?> resolvePathExpression(final PathExpression path) {
594 final Steps steps = path.getSteps();
595 if (steps instanceof LocationPathSteps) {
596 return resolveLocationPath(((LocationPathSteps) steps).getLocationPath());
597 } else if (steps instanceof DerefSteps) {
598 return resolveDeref((DerefSteps) steps);
600 throw new VerifyException("Unhandled steps " + steps);
604 private @NonNull EffectiveStatement<?, ?> resolveDeref(final DerefSteps deref) {
605 final EffectiveStatement<?, ?> leafRefSchemaNode = currentStatement();
606 final YangLocationPath.Relative derefArg = deref.getDerefArgument();
607 final EffectiveStatement<?, ?> derefStmt = resolveLocationPath(derefArg);
608 checkArgument(derefStmt != null, "Cannot find deref(%s) target node %s in context of %s",
609 derefArg, leafRefSchemaNode);
610 checkArgument(derefStmt instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg,
613 // We have a deref() target, decide what to do about it
614 final TypeDefinition<?> targetType = ((TypedDataSchemaNode) derefStmt).getType();
615 if (targetType instanceof InstanceIdentifierTypeDefinition) {
616 // Static inference breaks down, we cannot determine where this points to
617 // FIXME: dedicated exception, users can recover from it, derive from IAE
618 throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType);
621 // deref() is defined only for instance-identifier and leafref types, handle the latter
622 checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType);
624 final PathExpression dereferencedLeafRefPath = ((LeafrefTypeDefinition) targetType).getPathStatement();
625 EffectiveStatement<?, ?> derefNode = resolvePathExpression(dereferencedLeafRefPath);
626 checkArgument(derefStmt != null, "Can not find target node of dereferenced node %s", derefStmt);
627 checkArgument(derefNode instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref,
628 dereferencedLeafRefPath);
629 return resolveLocationPath(deref.getRelativePath());
632 private @NonNull EffectiveStatement<?, ?> resolveLocationPath(final YangLocationPath path) {
633 // get the default namespace before we clear and loose our deque
634 final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peek().argument()).getModule();
635 if (path.isAbsolute()) {
639 EffectiveStatement<?, ?> current = null;
640 for (Step step : path.getSteps()) {
641 final YangXPathAxis axis = step.getAxis();
644 verify(step instanceof AxisStep, "Unexpected parent step %s", step);
646 current = exitToDataTree();
647 } catch (IllegalStateException | NoSuchElementException e) {
648 throw new IllegalArgumentException("Illegal parent access in " + path, e);
652 verify(step instanceof QNameStep, "Unexpected child step %s", step);
653 current = enterChild((QNameStep) step, defaultNamespace);
656 throw new VerifyException("Unexpected step " + step);
660 return verifyNotNull(current);
663 private @NonNull EffectiveStatement<?, ?> enterChild(final QNameStep step, final QNameModule defaultNamespace) {
664 final AbstractQName toResolve = step.getQName();
666 if (toResolve instanceof QName) {
667 qname = (QName) toResolve;
668 } else if (toResolve instanceof Unqualified) {
669 checkArgument(defaultNamespace != null, "Can not find target module of step %s", step);
670 qname = ((Unqualified) toResolve).bindTo(defaultNamespace);
672 throw new VerifyException("Unexpected child step QName " + toResolve);
674 return enterDataTree(qname);
678 * Return an {@link Inference} equivalent of current state.
680 * @return An {@link Inference}
682 public @NonNull Inference toInference() {
683 return new Inference(effectiveModel, deque.clone(), currentModule, groupingDepth, clean);
687 * Return an {@link SchemaTreeInference} equivalent of current state.
689 * @return An {@link SchemaTreeInference}
690 * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
692 public @NonNull SchemaTreeInference toSchemaTreeInference() {
693 return DefaultSchemaTreeInference.of(getEffectiveModelContext(), toSchemaNodeIdentifier());
697 * Convert current state into an absolute schema node identifier.
699 * @return Absolute schema node identifier representing current state
700 * @throws IllegalStateException if current state is not instantiated
702 public @NonNull Absolute toSchemaNodeIdentifier() {
703 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
704 return Absolute.of(ImmutableList.<QName>builderWithExpectedSize(deque.size())
705 .addAll(simplePathFromRoot())
710 * Convert current state into a SchemaPath.
712 * @return Absolute SchemaPath representing current state
713 * @throws IllegalStateException if current state is not instantiated
714 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
717 public @NonNull SchemaPath toSchemaPath() {
718 SchemaPath ret = SchemaPath.ROOT;
719 final Iterator<QName> it = simplePathFromRoot();
720 while (it.hasNext()) {
721 ret = ret.createChild(it.next());
727 * Return an iterator along {@link SchemaPath#getPathFromRoot()}. This method is a faster equivalent of
728 * {@code toSchemaPath().getPathFromRoot().iterator()}.
730 * @return An unmodifiable iterator
733 public @NonNull Iterator<QName> schemaPathIterator() {
734 return Iterators.unmodifiableIterator(simplePathFromRoot());
738 public String toString() {
739 return MoreObjects.toStringHelper(this).add("stack", deque).toString();
742 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
743 final EffectiveStatement<?, ?> parent = deque.peekFirst();
744 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
747 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
748 final @NonNull QName nodeIdentifier) {
749 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
750 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
752 .orElseThrow(() -> notPresent(parent, "Grouping", nodeIdentifier));
758 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
759 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
760 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
761 currentModule = module;
765 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
766 final EffectiveStatement<?, ?> parent = deque.peekFirst();
767 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
770 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<?, ?> parent,
771 final @NonNull QName nodeIdentifier) {
772 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
773 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
776 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
777 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
778 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier)
779 .orElseThrow(() -> notPresent(parent, "Schema tree child ", nodeIdentifier));
784 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
785 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
786 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
787 currentModule = module;
791 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
792 final EffectiveStatement<?, ?> parent = deque.peekFirst();
793 return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
796 private @NonNull DataTreeEffectiveStatement<?> pushData(final EffectiveStatement<?, ?> parent,
797 final @NonNull QName nodeIdentifier) {
798 checkState(parent instanceof DataTreeAwareEffectiveStatement, "Cannot descend data tree at %s", parent);
799 return pushData((DataTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
802 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
803 final @NonNull QName nodeIdentifier) {
804 final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier)
805 .orElseThrow(() -> notPresent(parent, "Data tree child", nodeIdentifier));
811 private @NonNull DataTreeEffectiveStatement<?> pushFirstData(final @NonNull QName nodeIdentifier) {
812 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
813 final DataTreeEffectiveStatement<?> ret = pushData(module, nodeIdentifier);
814 currentModule = module;
818 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) {
819 final EffectiveStatement<?, ?> parent = deque.peekFirst();
820 return parent != null ? pushTypedef(parent, nodeIdentifier) : pushFirstTypedef(nodeIdentifier);
823 private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull EffectiveStatement<?, ?> parent,
824 final @NonNull QName nodeIdentifier) {
825 final TypedefEffectiveStatement ret = parent.get(TypedefNamespace.class, nodeIdentifier)
826 .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier));
831 private @NonNull TypedefEffectiveStatement pushFirstTypedef(final @NonNull QName nodeIdentifier) {
832 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
833 final TypedefEffectiveStatement ret = pushTypedef(module, nodeIdentifier);
834 currentModule = module;
838 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
839 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
840 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
844 // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
845 // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
846 private Iterator<QName> simplePathFromRoot() {
847 return clean ? iterateQNames() : reconstructQNames();
850 private Iterator<QName> iterateQNames() {
851 return Iterators.transform(deque.descendingIterator(), stmt -> {
852 final Object argument = stmt.argument();
853 verify(argument instanceof QName, "Unexpected statement %s", stmt);
854 return (QName) argument;
858 // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
859 // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
861 private Iterator<QName> reconstructQNames() {
862 // Let's walk all statements and decipher them into a temporary stack
863 final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
864 final Iterator<EffectiveStatement<?, ?>> it = deque.descendingIterator();
865 while (it.hasNext()) {
866 final EffectiveStatement<?, ?> stmt = it.next();
867 // Order of checks is significant
868 if (stmt instanceof DataTreeEffectiveStatement) {
869 tmp.resolveDataTreeSteps(((DataTreeEffectiveStatement<?>) stmt).argument());
870 } else if (stmt instanceof ChoiceEffectiveStatement) {
871 tmp.resolveChoiceSteps(((ChoiceEffectiveStatement) stmt).argument());
872 } else if (stmt instanceof SchemaTreeEffectiveStatement) {
873 tmp.enterSchemaTree(((SchemaTreeEffectiveStatement<?> )stmt).argument());
874 } else if (stmt instanceof GroupingEffectiveStatement) {
875 tmp.enterGrouping(((GroupingEffectiveStatement) stmt).argument());
876 } else if (stmt instanceof TypedefEffectiveStatement) {
877 tmp.enterTypedef(((TypedefEffectiveStatement) stmt).argument());
879 throw new VerifyException("Unexpected statement " + stmt);
883 // if the sizes match, we did not jump through hoops. let's remember that for future.
884 clean = deque.size() == tmp.deque.size();
885 return tmp.iterateQNames();
888 private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
889 final EffectiveStatement<?, ?> parent = deque.peekFirst();
890 if (parent instanceof ChoiceEffectiveStatement) {
891 resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
893 enterSchemaTree(nodeIdentifier);
897 private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
898 final @NonNull QName nodeIdentifier) {
899 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
900 if (stmt instanceof CaseEffectiveStatement) {
901 final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
902 final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
903 if (found instanceof ChoiceEffectiveStatement) {
910 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
913 private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
914 final EffectiveStatement<?, ?> parent = deque.peekFirst();
915 if (parent != null) {
916 verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
917 resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
921 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
922 resolveDataTreeSteps(module, nodeIdentifier);
923 currentModule = module;
926 private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
927 final @NonNull QName nodeIdentifier) {
928 // The algebra of identifiers in 'schema tree versus data tree':
929 // - data tree parents are always schema tree parents
930 // - data tree children are always schema tree children
932 // that implies that a data tree parent must satisfy schema tree queries with data tree children,
933 // so a successful lookup of 'data tree parent -> child' and 'schema tree parent -> child' has to be the same
934 // for a direct lookup.
935 final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
936 if (found instanceof DataTreeEffectiveStatement) {
937 // ... and it did, we are done
942 // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
943 // and employ a recursive match.
944 final Deque<EffectiveStatement<QName, ?>> match = new ArrayDeque<>();
945 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
946 if (stmt instanceof ChoiceEffectiveStatement
947 && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
948 match.descendingIterator().forEachRemaining(deque::push);
953 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
956 private static boolean searchCase(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
957 final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
959 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
960 if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) {
961 result.push((DataTreeEffectiveStatement<?>) stmt);
964 if (stmt instanceof ChoiceEffectiveStatement
965 && searchChoice(result, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
973 private static boolean searchChoice(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
974 final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
976 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
977 if (stmt instanceof CaseEffectiveStatement
978 && searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) {
986 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
988 throw new IllegalStateException("Cannot execute on empty stack");
993 private static @NonNull IllegalArgumentException notPresent(final @NonNull EffectiveStatement<?, ?> parent,
994 final @NonNull String name, final QName nodeIdentifier) {
995 return new IllegalArgumentException(name + " " + nodeIdentifier + " not present in " + describeParent(parent));
998 private static @NonNull String describeParent(final @NonNull EffectiveStatement<?, ?> parent) {
999 // Add just enough information to be useful without being overly-verbose. Note we want to expose namespace
1000 // information, so that we understand what revisions we are dealing with
1001 if (parent instanceof SchemaTreeEffectiveStatement) {
1002 return "schema parent " + parent.argument();
1003 } else if (parent instanceof GroupingEffectiveStatement) {
1004 return "grouping " + parent.argument();
1005 } else if (parent instanceof ModuleEffectiveStatement) {
1006 final var module = (ModuleEffectiveStatement) parent;
1007 return "module " + module.argument().bindTo(module.localQNameModule());
1009 // Shorthand for QNames, should provide enough context
1010 final Object arg = parent.argument();
1011 return "parent " + (arg instanceof QName ? arg : parent);