X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=model%2Fyang-model-util%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fmodel%2Futil%2FSchemaInferenceStack.java;h=0f9520487754266af0b722dcd98091bfe0eadf49;hb=502ec3268436a6f662fae661462baf3c2f73b5ad;hp=78a70e66909ab0cffe8155a8b064c6d4a84e690b;hpb=8b69871cf4487aa003432bf9c205d312c3b61305;p=yangtools.git diff --git a/model/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java b/model/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java index 78a70e6690..0f95204877 100644 --- a/model/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java +++ b/model/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java @@ -14,12 +14,15 @@ import static com.google.common.base.Verify.verifyNotNull; import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.VerifyException; +import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import java.util.ArrayDeque; -import java.util.Deque; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -31,7 +34,7 @@ import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement; import org.opendaylight.yangtools.yang.common.AbstractQName; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; -import org.opendaylight.yangtools.yang.common.UnqualifiedQName; +import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider; import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference; @@ -67,6 +70,7 @@ import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep; import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep; import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step; import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis; +import org.slf4j.LoggerFactory; /** * A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This @@ -117,7 +121,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex @Override public List> statementPath() { - return ImmutableList.copyOf(deque.descendingIterator()); + return ImmutableList.copyOf(deque); } /** @@ -130,6 +134,18 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } } + private static final String VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP = + "org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.verifyDefaultSchemaTreeInference"; + private static final boolean VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE = + Boolean.getBoolean(VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP); + + static { + if (VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE) { + LoggerFactory.getLogger(SchemaInferenceStack.class) + .info("SchemaTreeStack.ofInference(DefaultSchemaTreeInference) argument is being verified"); + } + } + private final @NonNull EffectiveModelContext effectiveModel; private final ArrayDeque> deque; @@ -141,17 +157,17 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex private boolean clean; private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) { - this.deque = new ArrayDeque<>(expectedSize); + deque = new ArrayDeque<>(expectedSize); this.effectiveModel = requireNonNull(effectiveModel); - this.clean = true; + clean = true; } private SchemaInferenceStack(final SchemaInferenceStack source) { - this.deque = source.deque.clone(); - this.effectiveModel = source.effectiveModel; - this.currentModule = source.currentModule; - this.groupingDepth = source.groupingDepth; - this.clean = source.clean; + deque = source.deque.clone(); + effectiveModel = source.effectiveModel; + currentModule = source.currentModule; + groupingDepth = source.groupingDepth; + clean = source.clean; } private SchemaInferenceStack(final EffectiveModelContext effectiveModel, @@ -166,8 +182,8 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex private SchemaInferenceStack(final EffectiveModelContext effectiveModel) { this.effectiveModel = requireNonNull(effectiveModel); - this.deque = new ArrayDeque<>(); - this.clean = true; + deque = new ArrayDeque<>(); + clean = true; } /** @@ -225,7 +241,44 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack */ public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) { - return of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier()); + return inference instanceof DefaultSchemaTreeInference ? ofInference((DefaultSchemaTreeInference) inference) + : of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier()); + } + + /** + * Create a new stack from an {@link DefaultSchemaTreeInference}. The argument is nominally trusted to be an + * accurate representation of the schema tree. + * + *

+ * Run-time verification of {@code inference} can be enabled by setting the + * {@value #VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP} system property to {@code true}. + * + * @param inference DefaultSchemaTreeInference to use for initialization + * @return A new stack + * @throws NullPointerException if {@code inference} is null + * @throws IllegalArgumentException if {@code inference} refers to a missing module or when verification is enabled + * and it does not match its context's scheam tree + */ + public static @NonNull SchemaInferenceStack ofInference(final DefaultSchemaTreeInference inference) { + return VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE ? ofUntrusted(inference) : ofTrusted(inference); + } + + private static @NonNull SchemaInferenceStack ofTrusted(final DefaultSchemaTreeInference inference) { + final var path = inference.statementPath(); + final var ret = new SchemaInferenceStack(inference.getEffectiveModelContext(), path.size()); + ret.currentModule = ret.getModule(path.get(0).argument()); + ret.deque.addAll(path); + return ret; + } + + @VisibleForTesting + static @NonNull SchemaInferenceStack ofUntrusted(final DefaultSchemaTreeInference inference) { + final var ret = of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier()); + if (!Iterables.elementsEqual(ret.deque, inference.statementPath())) { + throw new IllegalArgumentException("Provided " + inference + " is not consistent with resolved path " + + ret.toSchemaTreeInference()); + } + return ret; } /** @@ -327,7 +380,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * @throws IllegalStateException if the stack is empty */ public @NonNull EffectiveStatement currentStatement() { - return checkNonNullState(deque.peekFirst()); + return checkNonNullState(deque.peekLast()); } /** @@ -382,22 +435,27 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * @throws IllegalArgumentException if the corresponding choice cannot be found */ public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) { - final EffectiveStatement parent = deque.peek(); + final QName nodeId = requireNonNull(nodeIdentifier); + final EffectiveStatement parent = deque.peekLast(); if (parent instanceof ChoiceEffectiveStatement) { - return enterChoice((ChoiceEffectiveStatement) parent, nodeIdentifier); + return enterChoice((ChoiceEffectiveStatement) parent, nodeId); } // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error - final SchemaTreeEffectiveStatement result = enterSchemaTree(nodeIdentifier); + final SchemaTreeEffectiveStatement result = enterSchemaTree(nodeId); if (result instanceof ChoiceEffectiveStatement) { return (ChoiceEffectiveStatement) result; } exit(); - throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present"); + + if (parent != null) { + throw notPresent(parent, "Choice", nodeId); + } + throw new IllegalArgumentException("Choice " + nodeId + " not present"); } // choice -> choice transition, we have to deal with intermediate case nodes - private @NonNull ChoiceEffectiveStatement enterChoice(final ChoiceEffectiveStatement parent, + private @NonNull ChoiceEffectiveStatement enterChoice(final @NonNull ChoiceEffectiveStatement parent, final QName nodeIdentifier) { for (EffectiveStatement stmt : parent.effectiveSubstatements()) { if (stmt instanceof CaseEffectiveStatement) { @@ -407,13 +465,13 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex .map(ChoiceEffectiveStatement.class::cast); if (optMatch.isPresent()) { final SchemaTreeEffectiveStatement match = optMatch.orElseThrow(); - deque.push(match); + deque.addLast(match); clean = false; return (ChoiceEffectiveStatement) match; } } } - throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present"); + throw notPresent(parent, "Choice", nodeIdentifier); } /** @@ -497,7 +555,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * @throws IllegalStateException if this stack is not empty */ public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); checkState(parent == null, "Cannot lookup yang-data in a non-empty stack"); final String templateName = requireNonNull(name); @@ -509,7 +567,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex .findFirst() .orElseThrow( () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace)); - deque.push(ret); + deque.addLast(ret); currentModule = module; return ret; } @@ -521,7 +579,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * @throws NoSuchElementException if this stack is empty */ public @NonNull EffectiveStatement exit() { - final EffectiveStatement prev = deque.pop(); + final EffectiveStatement prev = deque.removeLast(); if (prev instanceof GroupingEffectiveStatement) { --groupingDepth; } @@ -544,17 +602,16 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex public @NonNull DataTreeEffectiveStatement exitToDataTree() { final EffectiveStatement child = exit(); checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child); - EffectiveStatement parent = deque.peekFirst(); + EffectiveStatement parent = deque.peekLast(); while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) { - deque.pollFirst(); - parent = deque.peekFirst(); + deque.pollLast(); + parent = deque.peekLast(); } checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent); return (DataTreeEffectiveStatement) child; } - @Override public TypeDefinition resolveLeafref(final LeafrefTypeDefinition type) { final SchemaInferenceStack tmp = copy(); @@ -626,7 +683,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex private @NonNull EffectiveStatement resolveLocationPath(final YangLocationPath path) { // get the default namespace before we clear and loose our deque - final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peek().argument()).getModule(); + final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peekLast().argument()).getModule(); if (path.isAbsolute()) { clear(); } @@ -660,9 +717,9 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex final QName qname; if (toResolve instanceof QName) { qname = (QName) toResolve; - } else if (toResolve instanceof UnqualifiedQName) { + } else if (toResolve instanceof Unqualified) { checkArgument(defaultNamespace != null, "Can not find target module of step %s", step); - qname = ((UnqualifiedQName) toResolve).bindTo(defaultNamespace); + qname = ((Unqualified) toResolve).bindTo(defaultNamespace); } else { throw new VerifyException("Unexpected child step QName " + toResolve); } @@ -685,7 +742,11 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference} */ public @NonNull SchemaTreeInference toSchemaTreeInference() { - return DefaultSchemaTreeInference.of(getEffectiveModelContext(), toSchemaNodeIdentifier()); + checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this); + final var cleanDeque = clean ? deque : reconstructSchemaInferenceStack().deque; + return DefaultSchemaTreeInference.unsafeOf(getEffectiveModelContext(), cleanDeque.stream() + .map(stmt -> (SchemaTreeEffectiveStatement) stmt) + .collect(ImmutableList.toImmutableList())); } /** @@ -696,9 +757,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex */ public @NonNull Absolute toSchemaNodeIdentifier() { checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this); - return Absolute.of(ImmutableList.builderWithExpectedSize(deque.size()) - .addAll(simplePathFromRoot()) - .build()); + return Absolute.of(simplePathFromRoot()); } /** @@ -710,12 +769,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex */ @Deprecated public @NonNull SchemaPath toSchemaPath() { - SchemaPath ret = SchemaPath.ROOT; - final Iterator it = simplePathFromRoot(); - while (it.hasNext()) { - ret = ret.createChild(it.next()); - } - return ret; + return SchemaPath.create(simplePathFromRoot(), true); } /** @@ -726,16 +780,16 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex */ @Deprecated public @NonNull Iterator schemaPathIterator() { - return Iterators.unmodifiableIterator(simplePathFromRoot()); + return Iterators.unmodifiableIterator(simplePathFromRoot().iterator()); } @Override public String toString() { - return MoreObjects.toStringHelper(this).add("stack", deque).toString(); + return MoreObjects.toStringHelper(this).add("path", deque).toString(); } private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier); } @@ -744,8 +798,8 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class) .filter(stmt -> nodeIdentifier.equals(stmt.argument())) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present")); - deque.push(ret); + .orElseThrow(() -> notPresent(parent, "Grouping", nodeIdentifier)); + deque.addLast(ret); ++groupingDepth; return ret; } @@ -758,7 +812,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } private @NonNull SchemaTreeEffectiveStatement pushSchema(final @NonNull QName nodeIdentifier) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier); } @@ -770,9 +824,9 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex private @NonNull SchemaTreeEffectiveStatement pushSchema( final @NonNull SchemaTreeAwareEffectiveStatement parent, final @NonNull QName nodeIdentifier) { - final SchemaTreeEffectiveStatement ret = parent.findSchemaTreeNode(nodeIdentifier).orElseThrow( - () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present")); - deque.push(ret); + final SchemaTreeEffectiveStatement ret = parent.findSchemaTreeNode(nodeIdentifier) + .orElseThrow(() -> notPresent(parent, "Schema tree child ", nodeIdentifier)); + deque.addLast(ret); return ret; } @@ -784,7 +838,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } private @NonNull DataTreeEffectiveStatement pushData(final @NonNull QName nodeIdentifier) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier); } @@ -796,9 +850,9 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex private @NonNull DataTreeEffectiveStatement pushData(final @NonNull DataTreeAwareEffectiveStatement parent, final @NonNull QName nodeIdentifier) { - final DataTreeEffectiveStatement ret = parent.findDataTreeNode(nodeIdentifier).orElseThrow( - () -> new IllegalArgumentException("Data tree child " + nodeIdentifier + " not present")); - deque.push(ret); + final DataTreeEffectiveStatement ret = parent.findDataTreeNode(nodeIdentifier) + .orElseThrow(() -> notPresent(parent, "Data tree child", nodeIdentifier)); + deque.addLast(ret); clean = false; return ret; } @@ -811,15 +865,15 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); return parent != null ? pushTypedef(parent, nodeIdentifier) : pushFirstTypedef(nodeIdentifier); } private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull EffectiveStatement parent, final @NonNull QName nodeIdentifier) { final TypedefEffectiveStatement ret = parent.get(TypedefNamespace.class, nodeIdentifier) - .orElseThrow(() -> new IllegalArgumentException("Typedef " + nodeIdentifier + " not present")); - deque.push(ret); + .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier)); + deque.addLast(ret); return ret; } @@ -838,12 +892,12 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex // Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user // at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'. - private Iterator simplePathFromRoot() { - return clean ? iterateQNames() : reconstructQNames(); + private Collection simplePathFromRoot() { + return clean ? qnames() : reconstructQNames(); } - private Iterator iterateQNames() { - return Iterators.transform(deque.descendingIterator(), stmt -> { + private Collection qnames() { + return Collections2.transform(deque, stmt -> { final Object argument = stmt.argument(); verify(argument instanceof QName, "Unexpected statement %s", stmt); return (QName) argument; @@ -853,10 +907,14 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex // So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series // of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the // clean flag. - private Iterator reconstructQNames() { + private Collection reconstructQNames() { + return reconstructSchemaInferenceStack().qnames(); + } + + private SchemaInferenceStack reconstructSchemaInferenceStack() { // Let's walk all statements and decipher them into a temporary stack final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size()); - final Iterator> it = deque.descendingIterator(); + final Iterator> it = deque.iterator(); while (it.hasNext()) { final EffectiveStatement stmt = it.next(); // Order of checks is significant @@ -876,12 +934,15 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } // if the sizes match, we did not jump through hoops. let's remember that for future. - clean = deque.size() == tmp.deque.size(); - return tmp.iterateQNames(); + if (deque.size() == tmp.deque.size()) { + clean = true; + return this; + } + return tmp; } private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); if (parent instanceof ChoiceEffectiveStatement) { resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier); } else { @@ -896,8 +957,8 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt; final SchemaTreeEffectiveStatement found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null); if (found instanceof ChoiceEffectiveStatement) { - deque.push(caze); - deque.push(found); + deque.addLast(caze); + deque.addLast(found); return; } } @@ -906,7 +967,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) { - final EffectiveStatement parent = deque.peekFirst(); + final EffectiveStatement parent = deque.peekLast(); if (parent != null) { verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent); resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement) parent, nodeIdentifier); @@ -930,17 +991,17 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex final SchemaTreeEffectiveStatement found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null); if (found instanceof DataTreeEffectiveStatement) { // ... and it did, we are done - deque.push(found); + deque.addLast(found); return; } // Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state // and employ a recursive match. - final Deque> match = new ArrayDeque<>(); + final var match = new ArrayDeque>(); for (EffectiveStatement stmt : parent.effectiveSubstatements()) { if (stmt instanceof ChoiceEffectiveStatement && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) { - match.descendingIterator().forEachRemaining(deque::push); + deque.addAll(match); return; } } @@ -948,12 +1009,12 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent); } - private static boolean searchCase(final @NonNull Deque> result, + private static boolean searchCase(final @NonNull ArrayDeque> result, final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) { - result.push(parent); + result.addLast(parent); for (EffectiveStatement stmt : parent.effectiveSubstatements()) { if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) { - result.push((DataTreeEffectiveStatement) stmt); + result.addLast((DataTreeEffectiveStatement) stmt); return true; } if (stmt instanceof ChoiceEffectiveStatement @@ -961,20 +1022,20 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex return true; } } - result.pop(); + result.removeLast(); return false; } - private static boolean searchChoice(final @NonNull Deque> result, + private static boolean searchChoice(final @NonNull ArrayDeque> result, final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) { - result.push(parent); + result.addLast(parent); for (EffectiveStatement stmt : parent.effectiveSubstatements()) { if (stmt instanceof CaseEffectiveStatement && searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) { return true; } } - result.pop(); + result.removeLast(); return false; } @@ -984,4 +1045,26 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } return obj; } + + private static @NonNull IllegalArgumentException notPresent(final @NonNull EffectiveStatement parent, + final @NonNull String name, final QName nodeIdentifier) { + return new IllegalArgumentException(name + " " + nodeIdentifier + " not present in " + describeParent(parent)); + } + + private static @NonNull String describeParent(final @NonNull EffectiveStatement parent) { + // Add just enough information to be useful without being overly-verbose. Note we want to expose namespace + // information, so that we understand what revisions we are dealing with + if (parent instanceof SchemaTreeEffectiveStatement) { + return "schema parent " + parent.argument(); + } else if (parent instanceof GroupingEffectiveStatement) { + return "grouping " + parent.argument(); + } else if (parent instanceof ModuleEffectiveStatement) { + final var module = (ModuleEffectiveStatement) parent; + return "module " + module.argument().bindTo(module.localQNameModule()); + } else { + // Shorthand for QNames, should provide enough context + final Object arg = parent.argument(); + return "parent " + (arg instanceof QName ? arg : parent); + } + } }