Make ParserNamespace an identifier
[yangtools.git] / parser / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / meta / UniqueStatementSupport.java
index 2b80ecc48dc29f7003d926d10d717ea535a4f004..01832062bc70f428c6325cefaf52b6b5f4bdd4eb 100644 (file)
@@ -7,17 +7,26 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.meta;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.regex.Pattern;
 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclarationReference;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.LeafEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
 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.SchemaNodeIdentifier.Descendant;
@@ -28,10 +37,17 @@ import org.opendaylight.yangtools.yang.model.ri.stmt.DeclaredStatements;
 import org.opendaylight.yangtools.yang.model.ri.stmt.EffectiveStatements;
 import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
 import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.ArgumentUtils;
+import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
 import org.opendaylight.yangtools.yang.parser.spi.meta.BoundStmtCtx;
 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
+import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceAction;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.Prerequisite;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
 import org.opendaylight.yangtools.yang.parser.spi.meta.SubstatementValidator;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
@@ -63,6 +79,21 @@ public final class UniqueStatementSupport
         return uniqueConstraints;
     }
 
+    @Override
+    public void onStatementAdded(final Mutable<Set<Descendant>, UniqueStatement, UniqueEffectiveStatement> stmt) {
+        // Check whether this statement is in a list statement and if so ...
+        final var list = stmt.coerceParentContext();
+        if (list.producesEffective(ListEffectiveStatement.class)) {
+            final var listParent = list.coerceParentContext();
+            // ... do not allow parent to complete until we have resolved ...
+            final var action = listParent.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
+            // ... we require the list to be completely resolve ...
+            action.requiresCtx(list, ModelProcessingPhase.EFFECTIVE_MODEL);
+            // ... after which we will continue
+            action.apply(new RequireEffectiveList(stmt, list, listParent));
+        }
+    }
+
     @Override
     protected UniqueStatement createDeclared(final BoundStmtCtx<Set<Descendant>> ctx,
             final ImmutableList<DeclaredStatement<?>> substatements) {
@@ -96,4 +127,76 @@ public final class UniqueStatementSupport
         }
         return ImmutableSet.copyOf(uniqueConstraintNodes);
     }
+
+    /**
+     * Inference action to process parent list reaching effective model, i.e. we can tell it is now complete.
+     */
+    private static final class RequireEffectiveList implements InferenceAction {
+        private final StmtContext<Set<Descendant>, ?, ?> unique;
+        private final StmtContext<?, ?, ?> list;
+        private final Mutable<?, ?, ?> parent;
+
+        RequireEffectiveList(final StmtContext<Set<Descendant>, ?, ?> unique, final StmtContext<?, ?, ?> list,
+                final Mutable<?, ?, ?> parent) {
+            this.unique = requireNonNull(unique);
+            this.list = requireNonNull(list);
+            this.parent = requireNonNull(parent);
+        }
+
+        @Override
+        public void apply(final InferenceContext ctx) {
+            if (isApplicable()) {
+                // So now, we have the effective list, we again block its parent from resolving ...
+                final var action = parent.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
+                // ... and before going further ...
+                action.apply(new RequireLeafDescendants(unique,
+                    // ... require that each schema node identifier resolves against the schema tree
+                    Maps.uniqueIndex(unique.getArgument(),
+                        desc -> action.requiresCtxPath(list,
+                            (SchemaTreeNamespace) SchemaTreeNamespace.instance(), desc.getNodeIdentifiers(),
+                            ModelProcessingPhase.EFFECTIVE_MODEL))));
+            }
+        }
+
+        @Override
+        public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
+            InferenceException.throwIf(isApplicable(), unique, "Parent list failed to reach effective model");
+        }
+
+        private boolean isApplicable() {
+            return list.isSupportedToBuildEffective() && unique.isSupportedToBuildEffective();
+        }
+    }
+
+    private static final class RequireLeafDescendants implements InferenceAction {
+        private final Map<Prerequisite<StmtContext<?, ?, ?>>, Descendant> prereqs;
+        private final StmtContext<Set<Descendant>, ?, ?> unique;
+
+        RequireLeafDescendants(final StmtContext<Set<Descendant>, ?, ?> unique,
+                final Map<Prerequisite<StmtContext<?, ?, ?>>, Descendant> prereqs) {
+            this.unique = requireNonNull(unique);
+            this.prereqs = requireNonNull(prereqs);
+
+        }
+
+        @Override
+        public void apply(final InferenceContext ctx) {
+            // All prerequisites have resolved, so now check each ...
+            for (var entry : prereqs.entrySet()) {
+                final var stmt = entry.getKey().resolve(ctx);
+                // ... and if it is not a leaf, report an error
+                SourceException.throwIf(!stmt.producesEffective(LeafEffectiveStatement.class),
+                    unique, "Path %s resolved to non-leaf %s", stmt.publicDefinition().getStatementName());
+            }
+        }
+
+        @Override
+        public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
+            // Report failed descandants
+            final var inv = ImmutableBiMap.copyOf(prereqs);
+            throw new SourceException(unique,
+                "Following components of unique statement argument refer to non-existent nodes: %s",
+                failed.stream().map(inv::get).filter(Objects::nonNull).collect(ImmutableSet.toImmutableSet()));
+        }
+    }
 }