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 java.util.Objects.requireNonNull;
15 import com.google.common.annotations.Beta;
16 import com.google.common.base.MoreObjects;
17 import com.google.common.base.VerifyException;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.Iterators;
20 import java.util.ArrayDeque;
21 import java.util.Deque;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.NoSuchElementException;
25 import java.util.Optional;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.opendaylight.yangtools.concepts.Mutable;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
31 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
32 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
33 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
34 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
35 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
36 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
37 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
38 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
39 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
40 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
41 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
42 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
43 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
44 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
45 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
46 import org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference;
49 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
50 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
53 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
54 * in {@link SchemaContextUtil} methods.
57 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
60 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider {
62 * Semantic binding of {@link EffectiveStatementInference} produced by {@link SchemaInferenceStack}. Sequence of
63 * {@link #statementPath()} is implementation-specific.
66 public static final class Inference extends AbstractEffectiveStatementInference<EffectiveStatement<?, ?>> {
67 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
68 private final ModuleEffectiveStatement currentModule;
69 private final int groupingDepth;
70 private final boolean clean;
72 Inference(final @NonNull EffectiveModelContext modelContext, final ArrayDeque<EffectiveStatement<?, ?>> deque,
73 final ModuleEffectiveStatement currentModule, final int groupingDepth, final boolean clean) {
75 this.deque = requireNonNull(deque);
76 this.currentModule = currentModule;
77 this.groupingDepth = groupingDepth;
82 public List<EffectiveStatement<?, ?>> statementPath() {
83 return ImmutableList.copyOf(deque.descendingIterator());
87 * Convert this inference into a {@link SchemaInferenceStack}.
91 public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
92 return new SchemaInferenceStack(getEffectiveModelContext(), deque, currentModule, groupingDepth, clean);
96 private final @NonNull EffectiveModelContext effectiveModel;
97 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
99 private @Nullable ModuleEffectiveStatement currentModule;
100 private int groupingDepth;
102 // True if there were only steps along grouping and schema tree, hence it is consistent with SchemaNodeIdentifier
103 // False if we have evidence of a data tree lookup succeeding
104 private boolean clean;
106 private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
107 this.deque = new ArrayDeque<>(expectedSize);
108 this.effectiveModel = requireNonNull(effectiveModel);
112 private SchemaInferenceStack(final SchemaInferenceStack source) {
113 this.deque = source.deque.clone();
114 this.effectiveModel = source.effectiveModel;
115 this.currentModule = source.currentModule;
116 this.groupingDepth = source.groupingDepth;
117 this.clean = source.clean;
120 private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
121 final ArrayDeque<EffectiveStatement<?, ?>> deque, final ModuleEffectiveStatement currentModule,
122 final int groupingDepth, final boolean clean) {
123 this.effectiveModel = requireNonNull(effectiveModel);
124 this.deque = deque.clone();
125 this.currentModule = currentModule;
126 this.groupingDepth = groupingDepth;
130 private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
131 this.effectiveModel = requireNonNull(effectiveModel);
132 this.deque = new ArrayDeque<>();
137 * Create a new empty stack backed by an effective model.
139 * @param effectiveModel EffectiveModelContext to which this stack is attached
140 * @return A new stack
141 * @throws NullPointerException if {@code effectiveModel} is null
143 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
144 return new SchemaInferenceStack(effectiveModel);
148 * Create a new stack backed by an effective model, pointing to specified schema node identified by
151 * @param effectiveModel EffectiveModelContext to which this stack is attached
152 * @return A new stack
153 * @throws NullPointerException if {@code effectiveModel} is null
154 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
156 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
157 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
158 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
163 * Create a new stack from an {@link EffectiveStatementInference}.
165 * @param inference Inference to use for initialization
166 * @return A new stack
167 * @throws NullPointerException if {@code inference} is null
168 * @throws IllegalArgumentException if {@code inference} implementation is not supported
170 public static @NonNull SchemaInferenceStack ofInference(final EffectiveStatementInference inference) {
171 if (inference.statementPath().isEmpty()) {
172 return new SchemaInferenceStack(inference.getEffectiveModelContext());
173 } else if (inference instanceof SchemaTreeInference) {
174 return ofInference((SchemaTreeInference) inference);
175 } else if (inference instanceof Inference) {
176 return ((Inference) inference).toSchemaInferenceStack();
178 throw new IllegalArgumentException("Unsupported Inference " + inference);
183 * Create a new stack from an {@link SchemaTreeInference}.
185 * @param inference SchemaTreeInference to use for initialization
186 * @return A new stack
187 * @throws NullPointerException if {@code inference} is null
188 * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
190 public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
191 return of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
195 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
196 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}.
198 * @param effectiveModel EffectiveModelContext to which this stack is attached
199 * @return A new stack
200 * @throws NullPointerException {@code effectiveModel} is null
201 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
205 public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
206 final SchemaPath path) {
207 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
208 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
209 path.getPathFromRoot().forEach(ret::enterSchemaTree);
214 public EffectiveModelContext getEffectiveModelContext() {
215 return effectiveModel;
219 * Create a deep copy of this object.
221 * @return An isolated copy of this object
223 public @NonNull SchemaInferenceStack copy() {
224 return new SchemaInferenceStack(this);
228 * Check if this stack is empty.
230 * @return True if this stack has not entered any node.
232 public boolean isEmpty() {
233 return deque.isEmpty();
237 * Return the statement at the top of the stack.
239 * @return Top statement
240 * @throws IllegalStateException if the stack is empty
242 public @NonNull EffectiveStatement<?, ?> currentStatement() {
243 return checkNonNullState(deque.peekFirst());
247 * Return current module the stack has entered.
249 * @return Current module
250 * @throws IllegalStateException if the stack is empty
252 public @NonNull ModuleEffectiveStatement currentModule() {
253 return checkNonNullState(currentModule);
257 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there is no grouping
258 * (or similar construct) present in the stack.
260 * @return False if the stack is empty or contains a grouping, true otherwise.
262 public boolean inInstantiatedContext() {
263 return groupingDepth == 0 && !deque.isEmpty();
267 * Reset this stack to empty state.
269 public void clear() {
271 currentModule = null;
277 * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
278 * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case}
279 * intermediate schema tree children.
281 * @param nodeIdentifier Node identifier of the grouping to enter
282 * @return Resolved choice
283 * @throws NullPointerException if {@code nodeIdentifier} is null
284 * @throws IllegalArgumentException if the corresponding choice cannot be found
286 public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
287 final EffectiveStatement<?, ?> parent = deque.peek();
288 if (parent instanceof ChoiceEffectiveStatement) {
289 return enterChoice((ChoiceEffectiveStatement) parent, nodeIdentifier);
292 // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
293 final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeIdentifier);
294 if (result instanceof ChoiceEffectiveStatement) {
295 return (ChoiceEffectiveStatement) result;
298 throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
301 // choice -> choice transition, we have to deal with intermediate case nodes
302 private @NonNull ChoiceEffectiveStatement enterChoice(final ChoiceEffectiveStatement parent,
303 final QName nodeIdentifier) {
304 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
305 if (stmt instanceof CaseEffectiveStatement) {
306 final Optional<ChoiceEffectiveStatement> optMatch = ((CaseEffectiveStatement) stmt)
307 .findSchemaTreeNode(nodeIdentifier)
308 .filter(ChoiceEffectiveStatement.class::isInstance)
309 .map(ChoiceEffectiveStatement.class::cast);
310 if (optMatch.isPresent()) {
311 final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
314 return (ChoiceEffectiveStatement) match;
318 throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
322 * Lookup a {@code grouping} by its node identifier and push it to the stack.
324 * @param nodeIdentifier Node identifier of the grouping to enter
325 * @return Resolved grouping
326 * @throws NullPointerException if {@code nodeIdentifier} is null
327 * @throws IllegalArgumentException if the corresponding grouping cannot be found
329 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
330 return pushGrouping(requireNonNull(nodeIdentifier));
334 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
336 * @param nodeIdentifier Node identifier of the schema tree child to enter
337 * @return Resolved schema tree child
338 * @throws NullPointerException if {@code nodeIdentifier} is null
339 * @throws IllegalArgumentException if the corresponding child cannot be found
341 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
342 return pushSchema(requireNonNull(nodeIdentifier));
346 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
348 * @param nodeIdentifier Node identifier of the date tree child to enter
349 * @return Resolved date tree child
350 * @throws NullPointerException if {@code nodeIdentifier} is null
351 * @throws IllegalArgumentException if the corresponding child cannot be found
353 public @NonNull DataTreeEffectiveStatement<?> enterDataTree(final QName nodeIdentifier) {
354 return pushData(requireNonNull(nodeIdentifier));
358 * Pop the current statement from the stack.
360 * @return Previous statement
361 * @throws NoSuchElementException if this stack is empty
363 public @NonNull EffectiveStatement<?, ?> exit() {
364 final EffectiveStatement<?, ?> prev = deque.pop();
365 if (prev instanceof GroupingEffectiveStatement) {
368 if (deque.isEmpty()) {
369 currentModule = null;
376 * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
377 * subsequent {@link #enterDataTree(QName)} will find it again.
379 * @return Previous statement
380 * @throws NoSuchElementException if this stack is empty
381 * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
382 * a {@link DataTreeAwareEffectiveStatement}
384 public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
385 final EffectiveStatement<?, ?> child = exit();
386 checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
387 final EffectiveStatement<?, ?> parent = deque.peekFirst();
388 checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
389 return (DataTreeEffectiveStatement<?>) child;
393 * Return an {@link Inference} equivalent of current state.
395 * @return An {@link Inference}
397 public @NonNull Inference toInference() {
398 return new Inference(effectiveModel, deque.clone(), currentModule, groupingDepth, clean);
402 * Return an {@link SchemaTreeInference} equivalent of current state.
404 * @return An {@link SchemaTreeInference}
405 * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
407 public @NonNull SchemaTreeInference toSchemaTreeInference() {
408 return DefaultSchemaTreeInference.of(getEffectiveModelContext(), toSchemaNodeIdentifier());
412 * Convert current state into an absolute schema node identifier.
414 * @return Absolute schema node identifier representing current state
415 * @throws IllegalStateException if current state is not instantiated
417 public @NonNull Absolute toSchemaNodeIdentifier() {
418 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
419 return Absolute.of(ImmutableList.<QName>builderWithExpectedSize(deque.size())
420 .addAll(simplePathFromRoot())
425 * Convert current state into a SchemaPath.
427 * @return Absolute SchemaPath representing current state
428 * @throws IllegalStateException if current state is not instantiated
429 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
432 public @NonNull SchemaPath toSchemaPath() {
433 SchemaPath ret = SchemaPath.ROOT;
434 final Iterator<QName> it = simplePathFromRoot();
435 while (it.hasNext()) {
436 ret = ret.createChild(it.next());
442 * Return an iterator along {@link SchemaPath#getPathFromRoot()}. This method is a faster equivalent of
443 * {@code toSchemaPath().getPathFromRoot().iterator()}.
445 * @return An unmodifiable iterator
448 public @NonNull Iterator<QName> schemaPathIterator() {
449 return Iterators.unmodifiableIterator(simplePathFromRoot());
453 public String toString() {
454 return MoreObjects.toStringHelper(this).add("stack", deque).toString();
457 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
458 final EffectiveStatement<?, ?> parent = deque.peekFirst();
459 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
462 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
463 final @NonNull QName nodeIdentifier) {
464 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
465 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
467 .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present"));
473 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
474 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
475 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
476 currentModule = module;
480 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
481 final EffectiveStatement<?, ?> parent = deque.peekFirst();
482 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
485 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<?, ?> parent,
486 final @NonNull QName nodeIdentifier) {
487 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
488 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
491 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
492 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
493 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier).orElseThrow(
494 () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present"));
499 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
500 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
501 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
502 currentModule = module;
506 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
507 final EffectiveStatement<?, ?> parent = deque.peekFirst();
508 return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
511 private @NonNull DataTreeEffectiveStatement<?> pushData(final EffectiveStatement<?, ?> parent,
512 final @NonNull QName nodeIdentifier) {
513 checkState(parent instanceof DataTreeAwareEffectiveStatement, "Cannot descend data tree at %s", parent);
514 return pushData((DataTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
517 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
518 final @NonNull QName nodeIdentifier) {
519 final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier).orElseThrow(
520 () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present"));
526 private @NonNull DataTreeEffectiveStatement<?> pushFirstData(final @NonNull QName nodeIdentifier) {
527 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
528 final DataTreeEffectiveStatement<?> ret = pushData(module, nodeIdentifier);
529 currentModule = module;
533 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
534 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
535 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
539 // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
540 // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
541 private Iterator<QName> simplePathFromRoot() {
542 return clean ? iterateQNames() : reconstructQNames();
545 private Iterator<QName> iterateQNames() {
546 return Iterators.transform(deque.descendingIterator(), stmt -> {
547 final Object argument = stmt.argument();
548 verify(argument instanceof QName, "Unexpected statement %s", stmt);
549 return (QName) argument;
553 // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
554 // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
556 private Iterator<QName> reconstructQNames() {
557 // Let's walk all statements and decipher them into a temporary stack
558 final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
559 final Iterator<EffectiveStatement<?, ?>> it = deque.descendingIterator();
560 while (it.hasNext()) {
561 final EffectiveStatement<?, ?> stmt = it.next();
562 // Order of checks is significant
563 if (stmt instanceof DataTreeEffectiveStatement) {
564 tmp.resolveDataTreeSteps(((DataTreeEffectiveStatement<?>) stmt).argument());
565 } else if (stmt instanceof ChoiceEffectiveStatement) {
566 tmp.resolveChoiceSteps(((ChoiceEffectiveStatement) stmt).argument());
567 } else if (stmt instanceof SchemaTreeEffectiveStatement) {
568 tmp.enterSchemaTree(((SchemaTreeEffectiveStatement<?> )stmt).argument());
569 } else if (stmt instanceof GroupingEffectiveStatement) {
570 tmp.enterGrouping(((GroupingEffectiveStatement) stmt).argument());
572 throw new VerifyException("Unexpected statement " + stmt);
576 // if the sizes match, we did not jump through hoops. let's remember that for future.
577 clean = deque.size() == tmp.deque.size();
578 return tmp.iterateQNames();
581 private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
582 final EffectiveStatement<?, ?> parent = deque.peekFirst();
583 if (parent instanceof ChoiceEffectiveStatement) {
584 resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
586 enterSchemaTree(nodeIdentifier);
590 private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
591 final @NonNull QName nodeIdentifier) {
592 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
593 if (stmt instanceof CaseEffectiveStatement) {
594 final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
595 final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
596 if (found instanceof ChoiceEffectiveStatement) {
603 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
606 private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
607 final EffectiveStatement<?, ?> parent = deque.peekFirst();
608 if (parent != null) {
609 verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
610 resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
614 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
615 resolveDataTreeSteps(module, nodeIdentifier);
616 currentModule = module;
619 private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
620 final @NonNull QName nodeIdentifier) {
621 // The algebra of identifiers in 'schema tree versus data tree':
622 // - data tree parents are always schema tree parents
623 // - data tree children are always schema tree children
625 // that implies that a data tree parent must satisfy schema tree queries with data tree children,
626 // so a successful lookup of 'data tree parent -> child' and 'schema tree parent -> child' has to be the same
627 // for a direct lookup.
628 final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
629 if (found instanceof DataTreeEffectiveStatement) {
630 // ... and it did, we are done
635 // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
636 // and employ a recursive match.
637 final Deque<EffectiveStatement<QName, ?>> match = new ArrayDeque<>();
638 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
639 if (stmt instanceof ChoiceEffectiveStatement
640 && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
641 match.descendingIterator().forEachRemaining(deque::push);
646 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
649 private static boolean searchCase(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
650 final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
652 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
653 if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) {
654 result.push((DataTreeEffectiveStatement<?>) stmt);
657 if (stmt instanceof ChoiceEffectiveStatement
658 && searchChoice(result, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
666 private static boolean searchChoice(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
667 final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
669 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
670 if (stmt instanceof CaseEffectiveStatement
671 && searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) {
679 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
681 throw new IllegalStateException("Cannot execute on empty stack");