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=refs%2Fchanges%2F17%2F100517%2F8;hp=75a8f50d675c67e92362066117e3ecd31d7a62ec;hpb=0ae668cc926c52d354b562292e4196f9b0c0dea2;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 75a8f50d67..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; @@ -27,10 +30,11 @@ import java.util.Optional; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.concepts.Mutable; +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; @@ -56,6 +60,7 @@ import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absol import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.TypedefNamespace; import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference; @@ -65,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 @@ -115,7 +121,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex @Override public List> statementPath() { - return ImmutableList.copyOf(deque.descendingIterator()); + return ImmutableList.copyOf(deque); } /** @@ -128,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; @@ -139,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, @@ -164,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; } /** @@ -223,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; } /** @@ -245,7 +300,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex /** * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute - * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}. + * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()} interpreted as a schema node identifier. * * @param effectiveModel EffectiveModelContext to which this stack is attached * @return A new stack @@ -262,6 +317,39 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex return ret; } + /** + * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute + * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}, interpreted as a series of steps along primarily + * schema tree, with grouping namespace being the alternative lookup. + * + * @param effectiveModel EffectiveModelContext to which this stack is attached + * @return A new stack + * @throws NullPointerException {@code effectiveModel} is null + * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an + * absolute path. + */ + @Deprecated + public static @NonNull SchemaInferenceStack ofSchemaPath(final EffectiveModelContext effectiveModel, + final SchemaPath path) { + checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path); + final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel); + + for (QName step : path.getPathFromRoot()) { + try { + ret.enterSchemaTree(step); + } catch (IllegalArgumentException schemaEx) { + try { + ret.enterGrouping(step); + } catch (IllegalArgumentException ex) { + ex.addSuppressed(schemaEx); + throw ex; + } + } + } + + return ret; + } + @Override public EffectiveModelContext getEffectiveModelContext() { return effectiveModel; @@ -292,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()); } /** @@ -306,7 +394,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } /** - * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only screma + * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only schema * tree statements in the stack. * * @return False if the stack is empty or contains a statement which is not a {@link SchemaTreeEffectiveStatement}, @@ -347,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) { @@ -372,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); } /** @@ -443,7 +536,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * Lookup a {@code typedef} by its node identifier and push it to the stack. * * @param nodeIdentifier Node identifier of the typedef to enter - * @return Resolved choice + * @return Resolved typedef * @throws NullPointerException if {@code nodeIdentifier} is null * @throws IllegalArgumentException if the corresponding typedef cannot be found */ @@ -451,6 +544,34 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex return pushTypedef(requireNonNull(nodeIdentifier)); } + /** + * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name. + * + * @param namespace Module namespace in which to lookup the template + * @param name Template name + * @return Resolved yang-data + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if the corresponding yang-data cannot be found + * @throws IllegalStateException if this stack is not empty + */ + public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) { + final EffectiveStatement parent = deque.peekLast(); + checkState(parent == null, "Cannot lookup yang-data in a non-empty stack"); + + final String templateName = requireNonNull(name); + final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(requireNonNull(namespace)); + checkArgument(module != null, "Module for %s not found", namespace); + + final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class) + .filter(stmt -> templateName.equals(stmt.argument())) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace)); + deque.addLast(ret); + currentModule = module; + return ret; + } + /** * Pop the current statement from the stack. * @@ -458,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; } @@ -481,12 +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); - final EffectiveStatement parent = deque.peekFirst(); + EffectiveStatement parent = deque.peekLast(); + while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) { + 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(); @@ -558,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(); } @@ -592,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); } @@ -617,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())); } /** @@ -628,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()); } /** @@ -642,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); } /** @@ -658,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); } @@ -676,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; } @@ -690,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); } @@ -702,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; } @@ -716,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); } @@ -728,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; } @@ -743,18 +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) { - // TODO: 8.0.0: revisit this once we have TypedefNamespace working - final TypedefEffectiveStatement ret = parent.streamEffectiveSubstatements(TypedefEffectiveStatement.class) - .filter(stmt -> nodeIdentifier.equals(stmt.argument())) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present")); - deque.push(ret); + final TypedefEffectiveStatement ret = parent.get(TypedefNamespace.class, nodeIdentifier) + .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier)); + deque.addLast(ret); return ret; } @@ -773,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; @@ -788,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 @@ -811,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 { @@ -831,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; } } @@ -841,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); @@ -865,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; } } @@ -883,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 @@ -896,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; } @@ -919,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); + } + } }