Add DefaultSchemaTreeInference.unsafeOf()
[yangtools.git] / model / yang-model-spi / src / main / java / org / opendaylight / yangtools / yang / model / spi / DefaultSchemaTreeInference.java
index 94003e15033603c7b686322e4c89f0aa92972d52..4e01e4551707916f33618ce09e0d0f7940998950 100644 (file)
@@ -10,18 +10,20 @@ package org.opendaylight.yangtools.yang.model.spi;
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import java.util.Iterator;
 import java.util.List;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
-import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
 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.spi.AbstractEffectiveStatementInference.WithPath;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default implementation of a a {@link SchemaTreeInference}. Guaranteed to be consistent with its
@@ -31,43 +33,89 @@ import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInfer
 @NonNullByDefault
 public final class DefaultSchemaTreeInference extends WithPath<SchemaTreeEffectiveStatement<?>>
         implements SchemaTreeInference {
+    private static final String VERIFY_UNSAFE_PROP =
+        "org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference.verifyUnsafeOf";
+    private static final boolean VERIFY_UNSAFE = Boolean.getBoolean(VERIFY_UNSAFE_PROP);
+
+    static {
+        if (VERIFY_UNSAFE) {
+            LoggerFactory.getLogger(DefaultSchemaTreeInference.class)
+                .info("DefaultSchemaTreeInference.unsafeOf() arguments are being verified");
+        }
+    }
+
     private DefaultSchemaTreeInference(final EffectiveModelContext modelContext,
-            final ImmutableList<SchemaTreeEffectiveStatement<?>> path) {
+            final ImmutableList<? extends SchemaTreeEffectiveStatement<?>> path) {
         super(modelContext, path);
     }
 
     /**
-     * Create a new instance.
+     * Create a new instance based on an {@link EffectiveModelContext} and an {@link Absolute} schema node identifier.
      *
      * @param modelContext Associated {@link EffectiveModelContext}
      * @param path An absolute schema node identifier
      * @return A new instance
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if the provided {@code path} cannot be resolved in {@code modelContext}
      */
     public static DefaultSchemaTreeInference of(final EffectiveModelContext modelContext, final Absolute path) {
-        final List<QName> steps = path.getNodeIdentifiers();
-        final QName first = steps.get(0);
-        final ModuleEffectiveStatement module = modelContext.findModuleStatement(first.getModule()).orElseThrow(
+        return new DefaultSchemaTreeInference(modelContext, resolveSteps(modelContext, path.getNodeIdentifiers()));
+    }
+
+    /**
+     * Create a new instance based on an {@link EffectiveModelContext} and a resolved sequence of statements. Provided
+     * statements are expected to have been produced in a validated manner and are normally trusted to be accurate.
+     *
+     * <p>
+     * Run-time verification of {@code path} can be enabled by setting the {@value #VERIFY_UNSAFE_PROP} system property
+     * to {@code true}.
+     *
+     * @param modelContext Associated {@link EffectiveModelContext}
+     * @param path Resolved statement path
+     * @return A new instance
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if {@code path} is empty or when verification is enabled and the {@code path}
+     *                                  does not match the {@code modelContext}'s schema tree
+     */
+    public static DefaultSchemaTreeInference unsafeOf(final EffectiveModelContext modelContext,
+            final ImmutableList<? extends SchemaTreeEffectiveStatement<?>> path) {
+        checkArgument(!path.isEmpty(), "Path must not be empty");
+        return VERIFY_UNSAFE ? verifiedOf(modelContext, path) : new DefaultSchemaTreeInference(modelContext, path);
+    }
+
+    @VisibleForTesting
+    static DefaultSchemaTreeInference verifiedOf(final EffectiveModelContext modelContext,
+            final ImmutableList<? extends SchemaTreeEffectiveStatement<?>> path) {
+        final var resolved = resolveSteps(modelContext, Lists.transform(path, SchemaTreeEffectiveStatement::argument));
+        checkArgument(path.equals(resolved), "Provided path %s is not consistent with resolved path %s", path,
+            resolved);
+        return new DefaultSchemaTreeInference(modelContext, path);
+    }
+
+    private static ImmutableList<SchemaTreeEffectiveStatement<?>> resolveSteps(final EffectiveModelContext modelContext,
+            final List<QName> steps) {
+        final var first = steps.get(0);
+        final var module = modelContext.findModuleStatement(first.getModule()).orElseThrow(
             () -> new IllegalArgumentException("No module for " + first));
 
-        final ImmutableList.Builder<SchemaTreeEffectiveStatement<?>> builder =
-            ImmutableList.builderWithExpectedSize(steps.size());
+        final var builder = ImmutableList.<SchemaTreeEffectiveStatement<?>>builderWithExpectedSize(steps.size());
         SchemaTreeAwareEffectiveStatement<?, ?> parent = module;
         final Iterator<QName> it = steps.iterator();
         while (true) {
-            final QName qname = it.next();
-            final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(qname).orElseThrow(
+            final var qname = it.next();
+            final var found = parent.findSchemaTreeNode(qname).orElseThrow(
                 () -> new IllegalArgumentException("Cannot resolve step " + qname + " in " + builder.build()));
             builder.add(found);
 
-            if (it.hasNext()) {
-                checkArgument(found instanceof SchemaTreeAwareEffectiveStatement, "Cannot resolve steps %s past %s",
-                    steps, found);
-                parent = (SchemaTreeAwareEffectiveStatement<?, ?>) found;
-            } else {
+            if (!it.hasNext()) {
                 break;
             }
+
+            checkArgument(found instanceof SchemaTreeAwareEffectiveStatement, "Cannot resolve steps %s past %s", steps,
+                found);
+            parent = (SchemaTreeAwareEffectiveStatement<?, ?>) found;
         }
 
-        return new DefaultSchemaTreeInference(modelContext, builder.build());
+        return builder.build();
     }
 }