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.yang.common.AbstractQName;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.common.QNameModule;
33 import org.opendaylight.yangtools.yang.common.UnqualifiedQName;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
37 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.PathExpression;
39 import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
40 import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
41 import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
42 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
43 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
44 import org.opendaylight.yangtools.yang.model.api.TypeAware;
45 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
48 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
55 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
56 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
57 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
58 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
60 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
61 import org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference;
62 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
63 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep;
64 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
65 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
66 import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
69 * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
70 * is conceptually a stack, tracking {@link EffectiveStatement}s encountered along traversal.
73 * This is meant to be a replacement concept for the use of {@link SchemaPath} in various places, notably
74 * in {@link SchemaContextUtil} methods.
77 * This class is designed for single-threaded uses and does not make any guarantees around concurrent access.
80 public final class SchemaInferenceStack implements Mutable, EffectiveModelContextProvider, LeafrefResolver {
82 * Semantic binding of {@link EffectiveStatementInference} produced by {@link SchemaInferenceStack}. Sequence of
83 * {@link #statementPath()} is implementation-specific.
86 public static final class Inference extends AbstractEffectiveStatementInference<EffectiveStatement<?, ?>> {
87 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
88 private final ModuleEffectiveStatement currentModule;
89 private final int groupingDepth;
90 private final boolean clean;
92 Inference(final @NonNull EffectiveModelContext modelContext, final ArrayDeque<EffectiveStatement<?, ?>> deque,
93 final ModuleEffectiveStatement currentModule, final int groupingDepth, final boolean clean) {
95 this.deque = requireNonNull(deque);
96 this.currentModule = currentModule;
97 this.groupingDepth = groupingDepth;
102 * Create a new stack backed by an effective model and set up to point and specified data tree node.
104 * @param effectiveModel EffectiveModelContext to which this stack is attached
105 * @param qnames Data tree path qnames
106 * @return A new stack
107 * @throws NullPointerException if any argument is null or path contains a null element
108 * @throws IllegalArgumentException if a path element cannot be found
110 public static @NonNull Inference ofDataTreePath(final EffectiveModelContext effectiveModel,
111 final QName... qnames) {
112 return SchemaInferenceStack.ofDataTreePath(effectiveModel, qnames).toInference();
116 public List<EffectiveStatement<?, ?>> statementPath() {
117 return ImmutableList.copyOf(deque.descendingIterator());
121 * Convert this inference into a {@link SchemaInferenceStack}.
123 * @return A new stack
125 public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
126 return new SchemaInferenceStack(getEffectiveModelContext(), deque, currentModule, groupingDepth, clean);
130 private final @NonNull EffectiveModelContext effectiveModel;
131 private final ArrayDeque<EffectiveStatement<?, ?>> deque;
133 private @Nullable ModuleEffectiveStatement currentModule;
134 private int groupingDepth;
136 // True if there were only steps along grouping and schema tree, hence it is consistent with SchemaNodeIdentifier
137 // False if we have evidence of a data tree lookup succeeding
138 private boolean clean;
140 private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
141 this.deque = new ArrayDeque<>(expectedSize);
142 this.effectiveModel = requireNonNull(effectiveModel);
146 private SchemaInferenceStack(final SchemaInferenceStack source) {
147 this.deque = source.deque.clone();
148 this.effectiveModel = source.effectiveModel;
149 this.currentModule = source.currentModule;
150 this.groupingDepth = source.groupingDepth;
151 this.clean = source.clean;
154 private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
155 final ArrayDeque<EffectiveStatement<?, ?>> deque, final ModuleEffectiveStatement currentModule,
156 final int groupingDepth, final boolean clean) {
157 this.effectiveModel = requireNonNull(effectiveModel);
158 this.deque = deque.clone();
159 this.currentModule = currentModule;
160 this.groupingDepth = groupingDepth;
164 private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
165 this.effectiveModel = requireNonNull(effectiveModel);
166 this.deque = new ArrayDeque<>();
171 * Create a new empty stack backed by an effective model.
173 * @param effectiveModel EffectiveModelContext to which this stack is attached
174 * @return A new stack
175 * @throws NullPointerException if {@code effectiveModel} is null
177 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
178 return new SchemaInferenceStack(effectiveModel);
182 * Create a new stack backed by an effective model, pointing to specified schema node identified by
185 * @param effectiveModel EffectiveModelContext to which this stack is attached
186 * @return A new stack
187 * @throws NullPointerException if {@code effectiveModel} is null
188 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model
190 public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel, final Absolute path) {
191 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
192 path.getNodeIdentifiers().forEach(ret::enterSchemaTree);
197 * Create a new stack from an {@link EffectiveStatementInference}.
199 * @param inference Inference to use for initialization
200 * @return A new stack
201 * @throws NullPointerException if {@code inference} is null
202 * @throws IllegalArgumentException if {@code inference} implementation is not supported
204 public static @NonNull SchemaInferenceStack ofInference(final EffectiveStatementInference inference) {
205 if (inference.statementPath().isEmpty()) {
206 return new SchemaInferenceStack(inference.getEffectiveModelContext());
207 } else if (inference instanceof SchemaTreeInference) {
208 return ofInference((SchemaTreeInference) inference);
209 } else if (inference instanceof Inference) {
210 return ((Inference) inference).toSchemaInferenceStack();
212 throw new IllegalArgumentException("Unsupported Inference " + inference);
217 * Create a new stack from an {@link SchemaTreeInference}.
219 * @param inference SchemaTreeInference to use for initialization
220 * @return A new stack
221 * @throws NullPointerException if {@code inference} is null
222 * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
224 public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
225 return of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
229 * Create a new stack backed by an effective model and set up to point and specified data tree node.
231 * @param effectiveModel EffectiveModelContext to which this stack is attached
232 * @return A new stack
233 * @throws NullPointerException if any argument is null or path contains a null element
234 * @throws IllegalArgumentException if a path element cannot be found
236 public static @NonNull SchemaInferenceStack ofDataTreePath(final EffectiveModelContext effectiveModel,
237 final QName... path) {
238 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
239 for (QName qname : path) {
240 ret.enterDataTree(qname);
246 * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
247 * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}.
249 * @param effectiveModel EffectiveModelContext to which this stack is attached
250 * @return A new stack
251 * @throws NullPointerException {@code effectiveModel} is null
252 * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
256 public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
257 final SchemaPath path) {
258 checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
259 final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
260 path.getPathFromRoot().forEach(ret::enterSchemaTree);
265 public EffectiveModelContext getEffectiveModelContext() {
266 return effectiveModel;
270 * Create a deep copy of this object.
272 * @return An isolated copy of this object
274 public @NonNull SchemaInferenceStack copy() {
275 return new SchemaInferenceStack(this);
279 * Check if this stack is empty.
281 * @return True if this stack has not entered any node.
283 public boolean isEmpty() {
284 return deque.isEmpty();
288 * Return the statement at the top of the stack.
290 * @return Top statement
291 * @throws IllegalStateException if the stack is empty
293 public @NonNull EffectiveStatement<?, ?> currentStatement() {
294 return checkNonNullState(deque.peekFirst());
298 * Return current module the stack has entered.
300 * @return Current module
301 * @throws IllegalStateException if the stack is empty
303 public @NonNull ModuleEffectiveStatement currentModule() {
304 return checkNonNullState(currentModule);
308 * Check if the stack is in instantiated context. This indicates the stack is non-empty and there is no grouping
309 * (or similar construct) present in the stack.
311 * @return False if the stack is empty or contains a grouping, true otherwise.
313 public boolean inInstantiatedContext() {
314 return groupingDepth == 0 && !deque.isEmpty();
318 * Reset this stack to empty state.
320 public void clear() {
322 currentModule = null;
328 * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
329 * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case}
330 * intermediate schema tree children.
332 * @param nodeIdentifier Node identifier of the grouping to enter
333 * @return Resolved choice
334 * @throws NullPointerException if {@code nodeIdentifier} is null
335 * @throws IllegalArgumentException if the corresponding choice cannot be found
337 public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
338 final EffectiveStatement<?, ?> parent = deque.peek();
339 if (parent instanceof ChoiceEffectiveStatement) {
340 return enterChoice((ChoiceEffectiveStatement) parent, nodeIdentifier);
343 // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
344 final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeIdentifier);
345 if (result instanceof ChoiceEffectiveStatement) {
346 return (ChoiceEffectiveStatement) result;
349 throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
352 // choice -> choice transition, we have to deal with intermediate case nodes
353 private @NonNull ChoiceEffectiveStatement enterChoice(final ChoiceEffectiveStatement parent,
354 final QName nodeIdentifier) {
355 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
356 if (stmt instanceof CaseEffectiveStatement) {
357 final Optional<ChoiceEffectiveStatement> optMatch = ((CaseEffectiveStatement) stmt)
358 .findSchemaTreeNode(nodeIdentifier)
359 .filter(ChoiceEffectiveStatement.class::isInstance)
360 .map(ChoiceEffectiveStatement.class::cast);
361 if (optMatch.isPresent()) {
362 final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
365 return (ChoiceEffectiveStatement) match;
369 throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
373 * Lookup a {@code grouping} by its node identifier and push it to the stack.
375 * @param nodeIdentifier Node identifier of the grouping to enter
376 * @return Resolved grouping
377 * @throws NullPointerException if {@code nodeIdentifier} is null
378 * @throws IllegalArgumentException if the corresponding grouping cannot be found
380 public @NonNull GroupingEffectiveStatement enterGrouping(final QName nodeIdentifier) {
381 return pushGrouping(requireNonNull(nodeIdentifier));
385 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
387 * @param nodeIdentifier Node identifier of the schema tree child to enter
388 * @return Resolved schema tree child
389 * @throws NullPointerException if {@code nodeIdentifier} is null
390 * @throws IllegalArgumentException if the corresponding child cannot be found
392 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final QName nodeIdentifier) {
393 return pushSchema(requireNonNull(nodeIdentifier));
397 * Lookup a {@code schema tree} node by its schema node identifier and push it to the stack.
399 * @param nodeIdentifier Schema node identifier of the schema tree node to enter
400 * @return Resolved schema tree node
401 * @throws NullPointerException if {@code nodeIdentifier} is null
402 * @throws IllegalArgumentException if the corresponding node cannot be found
404 public @NonNull SchemaTreeEffectiveStatement<?> enterSchemaTree(final SchemaNodeIdentifier nodeIdentifier) {
405 if (nodeIdentifier instanceof Absolute) {
409 final Iterator<QName> it = nodeIdentifier.getNodeIdentifiers().iterator();
410 SchemaTreeEffectiveStatement<?> ret;
412 ret = enterSchemaTree(it.next());
413 } while (it.hasNext());
419 * Lookup a {@code schema tree} child by its node identifier and push it to the stack.
421 * @param nodeIdentifier Node identifier of the date tree child to enter
422 * @return Resolved date tree child
423 * @throws NullPointerException if {@code nodeIdentifier} is null
424 * @throws IllegalArgumentException if the corresponding child cannot be found
426 public @NonNull DataTreeEffectiveStatement<?> enterDataTree(final QName nodeIdentifier) {
427 return pushData(requireNonNull(nodeIdentifier));
431 * Pop the current statement from the stack.
433 * @return Previous statement
434 * @throws NoSuchElementException if this stack is empty
436 public @NonNull EffectiveStatement<?, ?> exit() {
437 final EffectiveStatement<?, ?> prev = deque.pop();
438 if (prev instanceof GroupingEffectiveStatement) {
441 if (deque.isEmpty()) {
442 currentModule = null;
449 * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
450 * subsequent {@link #enterDataTree(QName)} will find it again.
452 * @return Previous statement
453 * @throws NoSuchElementException if this stack is empty
454 * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
455 * a {@link DataTreeAwareEffectiveStatement}
457 public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
458 final EffectiveStatement<?, ?> child = exit();
459 checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
460 final EffectiveStatement<?, ?> parent = deque.peekFirst();
461 checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
462 return (DataTreeEffectiveStatement<?>) child;
467 public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
468 final SchemaInferenceStack tmp = copy();
470 LeafrefTypeDefinition current = type;
472 final EffectiveStatement<?, ?> resolved = tmp.resolvePathExpression(current.getPathStatement());
473 checkState(resolved instanceof TypeAware, "Unexpected result %s resultion of %s", resolved, type);
474 final TypeDefinition<?> result = ((TypedDataSchemaNode) resolved).getType();
475 if (result instanceof LeafrefTypeDefinition) {
476 checkArgument(result != type, "Resolution of %s loops back onto itself via %s", type, current);
477 current = (LeafrefTypeDefinition) result;
485 * Resolve a {@link PathExpression}.
488 * Note if this method throws, this stack may be in an undefined state.
490 * @param path Requested path
491 * @return Resolved schema tree child
492 * @throws NullPointerException if {@code path} is null
493 * @throws IllegalArgumentException if the target node cannot be found
494 * @throws VerifyException if path expression is invalid
496 public @NonNull EffectiveStatement<?, ?> resolvePathExpression(final PathExpression path) {
497 final Steps steps = path.getSteps();
498 if (steps instanceof LocationPathSteps) {
499 return resolveLocationPath(((LocationPathSteps) steps).getLocationPath());
500 } else if (steps instanceof DerefSteps) {
501 return resolveDeref((DerefSteps) steps);
503 throw new VerifyException("Unhandled steps " + steps);
507 private @NonNull EffectiveStatement<?, ?> resolveDeref(final DerefSteps deref) {
508 final EffectiveStatement<?, ?> leafRefSchemaNode = currentStatement();
509 final YangLocationPath.Relative derefArg = deref.getDerefArgument();
510 final EffectiveStatement<?, ?> derefStmt = resolveLocationPath(derefArg);
511 checkArgument(derefStmt != null, "Cannot find deref(%s) target node %s in context of %s",
512 derefArg, leafRefSchemaNode);
513 checkArgument(derefStmt instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg,
516 // We have a deref() target, decide what to do about it
517 final TypeDefinition<?> targetType = ((TypedDataSchemaNode) derefStmt).getType();
518 if (targetType instanceof InstanceIdentifierTypeDefinition) {
519 // Static inference breaks down, we cannot determine where this points to
520 // FIXME: dedicated exception, users can recover from it, derive from IAE
521 throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType);
524 // deref() is defined only for instance-identifier and leafref types, handle the latter
525 checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType);
527 final PathExpression dereferencedLeafRefPath = ((LeafrefTypeDefinition) targetType).getPathStatement();
528 EffectiveStatement<?, ?> derefNode = resolvePathExpression(dereferencedLeafRefPath);
529 checkArgument(derefStmt != null, "Can not find target node of dereferenced node %s", derefStmt);
530 checkArgument(derefNode instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref,
531 dereferencedLeafRefPath);
532 return resolveLocationPath(deref.getRelativePath());
535 private @NonNull EffectiveStatement<?, ?> resolveLocationPath(final YangLocationPath path) {
536 // get the default namespace before we clear and loose our deque
537 final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peek().argument()).getModule();
538 if (path.isAbsolute()) {
542 EffectiveStatement<?, ?> current = null;
543 for (Step step : path.getSteps()) {
544 final YangXPathAxis axis = step.getAxis();
547 verify(step instanceof AxisStep, "Unexpected parent step %s", step);
549 current = exitToDataTree();
550 } catch (IllegalStateException | NoSuchElementException e) {
551 throw new IllegalArgumentException("Illegal parent access in " + path, e);
555 verify(step instanceof QNameStep, "Unexpected child step %s", step);
556 current = enterChild((QNameStep) step, defaultNamespace);
559 throw new VerifyException("Unexpected step " + step);
563 return verifyNotNull(current);
566 private @NonNull EffectiveStatement<?, ?> enterChild(final QNameStep step, final QNameModule defaultNamespace) {
567 final AbstractQName toResolve = step.getQName();
569 if (toResolve instanceof QName) {
570 qname = (QName) toResolve;
571 } else if (toResolve instanceof UnqualifiedQName) {
572 checkArgument(defaultNamespace != null, "Can not find target module of step %s", step);
573 qname = ((UnqualifiedQName) toResolve).bindTo(defaultNamespace);
575 throw new VerifyException("Unexpected child step QName " + toResolve);
577 return enterDataTree(qname);
581 * Return an {@link Inference} equivalent of current state.
583 * @return An {@link Inference}
585 public @NonNull Inference toInference() {
586 return new Inference(effectiveModel, deque.clone(), currentModule, groupingDepth, clean);
590 * Return an {@link SchemaTreeInference} equivalent of current state.
592 * @return An {@link SchemaTreeInference}
593 * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
595 public @NonNull SchemaTreeInference toSchemaTreeInference() {
596 return DefaultSchemaTreeInference.of(getEffectiveModelContext(), toSchemaNodeIdentifier());
600 * Convert current state into an absolute schema node identifier.
602 * @return Absolute schema node identifier representing current state
603 * @throws IllegalStateException if current state is not instantiated
605 public @NonNull Absolute toSchemaNodeIdentifier() {
606 checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
607 return Absolute.of(ImmutableList.<QName>builderWithExpectedSize(deque.size())
608 .addAll(simplePathFromRoot())
613 * Convert current state into a SchemaPath.
615 * @return Absolute SchemaPath representing current state
616 * @throws IllegalStateException if current state is not instantiated
617 * @deprecated This method is meant only for interoperation with SchemaPath-based APIs.
620 public @NonNull SchemaPath toSchemaPath() {
621 SchemaPath ret = SchemaPath.ROOT;
622 final Iterator<QName> it = simplePathFromRoot();
623 while (it.hasNext()) {
624 ret = ret.createChild(it.next());
630 * Return an iterator along {@link SchemaPath#getPathFromRoot()}. This method is a faster equivalent of
631 * {@code toSchemaPath().getPathFromRoot().iterator()}.
633 * @return An unmodifiable iterator
636 public @NonNull Iterator<QName> schemaPathIterator() {
637 return Iterators.unmodifiableIterator(simplePathFromRoot());
641 public String toString() {
642 return MoreObjects.toStringHelper(this).add("stack", deque).toString();
645 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
646 final EffectiveStatement<?, ?> parent = deque.peekFirst();
647 return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
650 private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull EffectiveStatement<?, ?> parent,
651 final @NonNull QName nodeIdentifier) {
652 final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
653 .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
655 .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present"));
661 private @NonNull GroupingEffectiveStatement pushFirstGrouping(final @NonNull QName nodeIdentifier) {
662 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
663 final GroupingEffectiveStatement ret = pushGrouping(module, nodeIdentifier);
664 currentModule = module;
668 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
669 final EffectiveStatement<?, ?> parent = deque.peekFirst();
670 return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
673 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final EffectiveStatement<?, ?> parent,
674 final @NonNull QName nodeIdentifier) {
675 checkState(parent instanceof SchemaTreeAwareEffectiveStatement, "Cannot descend schema tree at %s", parent);
676 return pushSchema((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
679 private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
680 final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
681 final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier).orElseThrow(
682 () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present"));
687 private @NonNull SchemaTreeEffectiveStatement<?> pushFirstSchema(final @NonNull QName nodeIdentifier) {
688 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
689 final SchemaTreeEffectiveStatement<?> ret = pushSchema(module, nodeIdentifier);
690 currentModule = module;
694 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
695 final EffectiveStatement<?, ?> parent = deque.peekFirst();
696 return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
699 private @NonNull DataTreeEffectiveStatement<?> pushData(final EffectiveStatement<?, ?> parent,
700 final @NonNull QName nodeIdentifier) {
701 checkState(parent instanceof DataTreeAwareEffectiveStatement, "Cannot descend data tree at %s", parent);
702 return pushData((DataTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
705 private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
706 final @NonNull QName nodeIdentifier) {
707 final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier).orElseThrow(
708 () -> new IllegalArgumentException("Data tree child " + nodeIdentifier + " not present"));
714 private @NonNull DataTreeEffectiveStatement<?> pushFirstData(final @NonNull QName nodeIdentifier) {
715 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
716 final DataTreeEffectiveStatement<?> ret = pushData(module, nodeIdentifier);
717 currentModule = module;
721 private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) {
722 final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule());
723 checkArgument(module != null, "Module for %s not found", nodeIdentifier);
727 // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
728 // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
729 private Iterator<QName> simplePathFromRoot() {
730 return clean ? iterateQNames() : reconstructQNames();
733 private Iterator<QName> iterateQNames() {
734 return Iterators.transform(deque.descendingIterator(), stmt -> {
735 final Object argument = stmt.argument();
736 verify(argument instanceof QName, "Unexpected statement %s", stmt);
737 return (QName) argument;
741 // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
742 // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
744 private Iterator<QName> reconstructQNames() {
745 // Let's walk all statements and decipher them into a temporary stack
746 final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
747 final Iterator<EffectiveStatement<?, ?>> it = deque.descendingIterator();
748 while (it.hasNext()) {
749 final EffectiveStatement<?, ?> stmt = it.next();
750 // Order of checks is significant
751 if (stmt instanceof DataTreeEffectiveStatement) {
752 tmp.resolveDataTreeSteps(((DataTreeEffectiveStatement<?>) stmt).argument());
753 } else if (stmt instanceof ChoiceEffectiveStatement) {
754 tmp.resolveChoiceSteps(((ChoiceEffectiveStatement) stmt).argument());
755 } else if (stmt instanceof SchemaTreeEffectiveStatement) {
756 tmp.enterSchemaTree(((SchemaTreeEffectiveStatement<?> )stmt).argument());
757 } else if (stmt instanceof GroupingEffectiveStatement) {
758 tmp.enterGrouping(((GroupingEffectiveStatement) stmt).argument());
760 throw new VerifyException("Unexpected statement " + stmt);
764 // if the sizes match, we did not jump through hoops. let's remember that for future.
765 clean = deque.size() == tmp.deque.size();
766 return tmp.iterateQNames();
769 private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
770 final EffectiveStatement<?, ?> parent = deque.peekFirst();
771 if (parent instanceof ChoiceEffectiveStatement) {
772 resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
774 enterSchemaTree(nodeIdentifier);
778 private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
779 final @NonNull QName nodeIdentifier) {
780 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
781 if (stmt instanceof CaseEffectiveStatement) {
782 final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
783 final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
784 if (found instanceof ChoiceEffectiveStatement) {
791 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
794 private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
795 final EffectiveStatement<?, ?> parent = deque.peekFirst();
796 if (parent != null) {
797 verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
798 resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
802 final ModuleEffectiveStatement module = getModule(nodeIdentifier);
803 resolveDataTreeSteps(module, nodeIdentifier);
804 currentModule = module;
807 private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
808 final @NonNull QName nodeIdentifier) {
809 // The algebra of identifiers in 'schema tree versus data tree':
810 // - data tree parents are always schema tree parents
811 // - data tree children are always schema tree children
813 // that implies that a data tree parent must satisfy schema tree queries with data tree children,
814 // so a successful lookup of 'data tree parent -> child' and 'schema tree parent -> child' has to be the same
815 // for a direct lookup.
816 final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
817 if (found instanceof DataTreeEffectiveStatement) {
818 // ... and it did, we are done
823 // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
824 // and employ a recursive match.
825 final Deque<EffectiveStatement<QName, ?>> match = new ArrayDeque<>();
826 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
827 if (stmt instanceof ChoiceEffectiveStatement
828 && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
829 match.descendingIterator().forEachRemaining(deque::push);
834 throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
837 private static boolean searchCase(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
838 final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
840 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
841 if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) {
842 result.push((DataTreeEffectiveStatement<?>) stmt);
845 if (stmt instanceof ChoiceEffectiveStatement
846 && searchChoice(result, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
854 private static boolean searchChoice(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
855 final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
857 for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
858 if (stmt instanceof CaseEffectiveStatement
859 && searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) {
867 private static <T> @NonNull T checkNonNullState(final @Nullable T obj) {
869 throw new IllegalStateException("Cannot execute on empty stack");