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=86e026ecffa4c9a7026617d2d9e7cf29779da961;hpb=b67b827e357ae40ae26e4bfec35b76ef5ee67967;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 86e026ecff..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; @@ -55,6 +59,8 @@ import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; 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; @@ -64,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 @@ -114,7 +121,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex @Override public List> statementPath() { - return ImmutableList.copyOf(deque.descendingIterator()); + return ImmutableList.copyOf(deque); } /** @@ -127,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; @@ -138,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, @@ -163,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; } /** @@ -222,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; } /** @@ -244,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 @@ -261,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; @@ -291,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()); } /** @@ -305,13 +394,24 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex } /** - * Check if the stack is in instantiated context. This indicates the stack is non-empty and there is no grouping - * (or similar construct) present in the stack. + * 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 grouping, true otherwise. + * @return False if the stack is empty or contains a statement which is not a {@link SchemaTreeEffectiveStatement}, + * true otherwise. */ public boolean inInstantiatedContext() { - return groupingDepth == 0 && !deque.isEmpty(); + return groupingDepth == 0 && !deque.isEmpty() + && deque.stream().allMatch(SchemaTreeEffectiveStatement.class::isInstance); + } + + /** + * Check if the stack is in a {@code grouping} context. + * + * @return False if the stack contains a grouping. + */ + public boolean inGrouping() { + return groupingDepth != 0; } /** @@ -329,28 +429,33 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex * {@link #enterSchemaTree(QName)}, except it handles the use case where traversal ignores actual {@code case} * intermediate schema tree children. * - * @param nodeIdentifier Node identifier of the grouping to enter + * @param nodeIdentifier Node identifier of the choice to enter * @return Resolved choice * @throws NullPointerException if {@code nodeIdentifier} is null * @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) { @@ -360,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); } /** @@ -427,6 +532,46 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex return pushData(requireNonNull(nodeIdentifier)); } + /** + * 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 typedef + * @throws NullPointerException if {@code nodeIdentifier} is null + * @throws IllegalArgumentException if the corresponding typedef cannot be found + */ + public @NonNull TypedefEffectiveStatement enterTypedef(final QName nodeIdentifier) { + 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. * @@ -434,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; } @@ -457,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(); @@ -534,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(); } @@ -568,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); } @@ -593,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())); } /** @@ -604,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()); } /** @@ -618,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); } /** @@ -634,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); } @@ -652,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; } @@ -666,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); } @@ -678,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; } @@ -692,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); } @@ -704,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; } @@ -718,6 +864,26 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex return ret; } + private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) { + 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(() -> notPresent(parent, "Typedef", nodeIdentifier)); + deque.addLast(ret); + return ret; + } + + private @NonNull TypedefEffectiveStatement pushFirstTypedef(final @NonNull QName nodeIdentifier) { + final ModuleEffectiveStatement module = getModule(nodeIdentifier); + final TypedefEffectiveStatement ret = pushTypedef(module, nodeIdentifier); + currentModule = module; + return ret; + } + private @NonNull ModuleEffectiveStatement getModule(final @NonNull QName nodeIdentifier) { final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(nodeIdentifier.getModule()); checkArgument(module != null, "Module for %s not found", nodeIdentifier); @@ -726,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; @@ -741,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 @@ -756,18 +926,23 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex tmp.enterSchemaTree(((SchemaTreeEffectiveStatement )stmt).argument()); } else if (stmt instanceof GroupingEffectiveStatement) { tmp.enterGrouping(((GroupingEffectiveStatement) stmt).argument()); + } else if (stmt instanceof TypedefEffectiveStatement) { + tmp.enterTypedef(((TypedefEffectiveStatement) stmt).argument()); } else { throw new VerifyException("Unexpected statement " + stmt); } } // 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 { @@ -782,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; } } @@ -792,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); @@ -816,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; } } @@ -834,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 @@ -847,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; } @@ -870,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); + } + } }