Invert SchemaInferenceStack.deque
[yangtools.git] / model / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / SchemaInferenceStack.java
index 4a6c97a2113a23305be0c63bc617c1d68bd0bab1..0f9520487754266af0b722dcd98091bfe0eadf49 100644 (file)
@@ -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<EffectiveStatement<?, ?>> 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<EffectiveStatement<?, ?>> 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.
+     *
+     * <p>
+     * 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,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();
@@ -563,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();
         }
@@ -597,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);
         }
@@ -622,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()));
     }
 
     /**
@@ -633,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.<QName>builderWithExpectedSize(deque.size())
-            .addAll(simplePathFromRoot())
-            .build());
+        return Absolute.of(simplePathFromRoot());
     }
 
     /**
@@ -647,12 +769,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
      */
     @Deprecated
     public @NonNull SchemaPath toSchemaPath() {
-        SchemaPath ret = SchemaPath.ROOT;
-        final Iterator<QName> it = simplePathFromRoot();
-        while (it.hasNext()) {
-            ret = ret.createChild(it.next());
-        }
-        return ret;
+        return SchemaPath.create(simplePathFromRoot(), true);
     }
 
     /**
@@ -663,16 +780,16 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
      */
     @Deprecated
     public @NonNull Iterator<QName> 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);
     }
 
@@ -681,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;
     }
@@ -695,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);
     }
 
@@ -707,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;
     }
 
@@ -721,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);
     }
 
@@ -733,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;
     }
@@ -748,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;
     }
 
@@ -778,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<QName> simplePathFromRoot() {
-        return clean ? iterateQNames() : reconstructQNames();
+    private Collection<QName> simplePathFromRoot() {
+        return clean ? qnames() : reconstructQNames();
     }
 
-    private Iterator<QName> iterateQNames() {
-        return Iterators.transform(deque.descendingIterator(), stmt -> {
+    private Collection<QName> qnames() {
+        return Collections2.transform(deque, stmt -> {
             final Object argument = stmt.argument();
             verify(argument instanceof QName, "Unexpected statement %s", stmt);
             return (QName) argument;
@@ -793,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<QName> reconstructQNames() {
+    private Collection<QName> 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<EffectiveStatement<?, ?>> it = deque.descendingIterator();
+        final Iterator<EffectiveStatement<?, ?>> it = deque.iterator();
         while (it.hasNext()) {
             final EffectiveStatement<?, ?> stmt = it.next();
             // Order of checks is significant
@@ -816,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 {
@@ -836,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;
                 }
             }
@@ -846,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);
@@ -870,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<EffectiveStatement<QName, ?>> match = new ArrayDeque<>();
+        final var match = new ArrayDeque<EffectiveStatement<QName, ?>>();
         for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
             if (stmt instanceof ChoiceEffectiveStatement
                 && searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
-                match.descendingIterator().forEachRemaining(deque::push);
+                deque.addAll(match);
                 return;
             }
         }
@@ -888,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<EffectiveStatement<QName, ?>> result,
+    private static boolean searchCase(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> 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
@@ -901,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<EffectiveStatement<QName, ?>> result,
+    private static boolean searchChoice(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> 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;
     }
 
@@ -924,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);
+        }
+    }
 }