Add SchemaInferenceStack.enterChoice() 36/95136/3
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 12 Feb 2021 00:16:51 +0000 (01:16 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 12 Feb 2021 09:33:03 +0000 (10:33 +0100)
yang-data-impl's SchemaTracker has a use case where it needs to track
choice statements, but does not track case statements. This is quite
usual for things interacting with NormalizedNodes -- and therefore we
provide an adequate utility in SchemaInferenceStack.

JIRA: YANGTOOLS-1235
Change-Id: Ie0936f2dad16c938fa502efe250e368f6440279a
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1231Test.java
yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java

index 5c687d6230cf7f1e2e79fba9cba8bdd14721ca48..d25ab53e56db5a20a5bb21aad1576373529f32c9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
@@ -48,4 +48,26 @@ public class YT1231Test {
         assertThat(stack.enterSchemaTree(BAR), instanceOf(CaseEffectiveStatement.class));
         assertEquals(Absolute.of(FOO, BAR, BAR), stack.toSchemaNodeIdentifier());
     }
+
+    @Test
+    public void testEnterChoice() {
+        final EffectiveModelContext context = YangParserTestUtils.parseYangResource("/yt1231.yang");
+        final SchemaInferenceStack stack = new SchemaInferenceStack(context);
+
+        // Simple
+        assertThat(stack.enterDataTree(FOO), instanceOf(ContainerEffectiveStatement.class));
+        assertEquals(Absolute.of(FOO), stack.toSchemaNodeIdentifier());
+        assertThat(stack.enterChoice(BAR), instanceOf(ChoiceEffectiveStatement.class));
+        assertEquals(Absolute.of(FOO, BAR), stack.toSchemaNodeIdentifier());
+
+        // Has to cross choice -> case -> choice
+        assertThat(stack.enterChoice(BAZ), instanceOf(ChoiceEffectiveStatement.class));
+        assertEquals(Absolute.of(FOO, BAR, BAR, BAZ), stack.toSchemaNodeIdentifier());
+
+        // Now the same with just case -> choice
+        stack.exit();
+        assertThat(stack.enterSchemaTree(BAR), instanceOf(CaseEffectiveStatement.class));
+        assertThat(stack.enterChoice(BAZ), instanceOf(ChoiceEffectiveStatement.class));
+        assertEquals(Absolute.of(FOO, BAR, BAR, BAZ), stack.toSchemaNodeIdentifier());
+    }
 }
index b9eb648ece14a4c57ba880131d929dec05c87fb7..7c0a32a02693221b8596b104f63cb5ca0a55fc0e 100644 (file)
@@ -21,6 +21,7 @@ import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Mutable;
@@ -183,6 +184,51 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
         clean = true;
     }
 
+    /**
+     * Lookup a {@code choice} by its node identifier and push it to the stack. This step is very similar to
+     * {@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
+     * @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<QName, ?> parent = deque.peek();
+        if (parent instanceof ChoiceEffectiveStatement) {
+            return enterChoice((ChoiceEffectiveStatement) parent, nodeIdentifier);
+        }
+
+        // Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
+        final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeIdentifier);
+        if (result instanceof ChoiceEffectiveStatement) {
+            return (ChoiceEffectiveStatement) result;
+        }
+        exit();
+        throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
+    }
+
+    // choice -> choice transition, we have to deal with intermediate case nodes
+    private @NonNull ChoiceEffectiveStatement enterChoice(final ChoiceEffectiveStatement parent,
+            final QName nodeIdentifier) {
+        for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
+            if (stmt instanceof CaseEffectiveStatement) {
+                final Optional<ChoiceEffectiveStatement> optMatch = ((CaseEffectiveStatement) stmt)
+                    .findSchemaTreeNode(nodeIdentifier)
+                    .filter(ChoiceEffectiveStatement.class::isInstance)
+                    .map(ChoiceEffectiveStatement.class::cast);
+                if (optMatch.isPresent()) {
+                    final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
+                    deque.push(match);
+                    clean = false;
+                    return (ChoiceEffectiveStatement) match;
+                }
+            }
+        }
+        throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
+    }
+
     /**
      * Lookup a {@code grouping} by its node identifier and push it to the stack.
      *
@@ -383,7 +429,9 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
             final EffectiveStatement<QName, ?> stmt = it.next();
             // Order of checks is significant
             if (stmt instanceof DataTreeEffectiveStatement) {
-                tmp.resolveSchemaTreeSteps(stmt.argument());
+                tmp.resolveDataTreeSteps(stmt.argument());
+            } else if (stmt instanceof ChoiceEffectiveStatement) {
+                tmp.resolveChoiceSteps(stmt.argument());
             } else if (stmt instanceof SchemaTreeEffectiveStatement) {
                 tmp.enterSchemaTree(stmt.argument());
             } else if (stmt instanceof GroupingEffectiveStatement) {
@@ -398,20 +446,45 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
         return tmp.deque.descendingIterator();
     }
 
-    private void resolveSchemaTreeSteps(final @NonNull QName nodeIdentifier) {
+    private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
+        final EffectiveStatement<?, ?> parent = deque.peekFirst();
+        if (parent instanceof ChoiceEffectiveStatement) {
+            resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
+        } else {
+            enterSchemaTree(nodeIdentifier);
+        }
+    }
+
+    private void resolveChoiceSteps(final @NonNull ChoiceEffectiveStatement parent,
+            final @NonNull QName nodeIdentifier) {
+        for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
+            if (stmt instanceof CaseEffectiveStatement) {
+                final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
+                final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
+                if (found instanceof ChoiceEffectiveStatement) {
+                    deque.push(caze);
+                    deque.push(found);
+                    return;
+                }
+            }
+        }
+        throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
+    }
+
+    private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
         final EffectiveStatement<?, ?> parent = deque.peekFirst();
         if (parent != null) {
             verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
-            resolveSchemaTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>)parent, nodeIdentifier);
+            resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
             return;
         }
 
         final ModuleEffectiveStatement module = getModule(nodeIdentifier);
-        resolveSchemaTreeSteps(module, nodeIdentifier);
+        resolveDataTreeSteps(module, nodeIdentifier);
         currentModule = module;
     }
 
-    private void resolveSchemaTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
+    private void resolveDataTreeSteps(final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent,
             final @NonNull QName nodeIdentifier) {
         // The algebra of identifiers in 'schema tree versus data tree':
         // - data tree parents are always schema tree parents