Refactor AbstractEffectiveModule 70/85970/10
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 26 Nov 2019 11:27:57 +0000 (12:27 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 27 Nov 2019 00:34:43 +0000 (01:34 +0100)
AbstractEffectiveModule has a number of special cases in its
constructor, effectively adding behavior based on expected subclass.

Furthermore its layout does not correctly propagate declared/effective
substatements towards superclass, leading to partially-populated
data/schema tree namespaces.

This patch moves the special handling bits to subclasses, making
AbstractEffectiveModule properly agnostic of whether it is used as
a ModuleEffectiveStatement or SubmoduleEffectiveStatement.

For the mechanics of including submodule effective statements into
the main module we introduce ModuleStmtContext, which performs the
semantic addition, thus all StmtContexts are properly visible as if
they were declared in the main module.

JIRA: YANGTOOLS-1042
Change-Id: Ice9609c2db82981ec9156d076f8a02bc637181b9
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/AbstractEffectiveModule.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleEffectiveStatementImpl.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleStmtContext.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/submodule/SubmoduleEffectiveStatementImpl.java
yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/stmt/YT1042Test.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/test/resources/bugs/YT1042/bar.yang [new file with mode: 0644]
yang/yang-parser-rfc7950/src/test/resources/bugs/YT1042/foo.yang [new file with mode: 0644]

index 411615c44f78bac1626709af2f6f0bbb062258a2..85721fee9a7de9465a996d22d1f1fb6527be0466 100644 (file)
@@ -7,16 +7,13 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.stmt;
 
-import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -26,7 +23,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.concepts.SemVer;
 import org.opendaylight.yangtools.openconfig.model.api.OpenConfigVersionEffectiveStatement;
@@ -34,7 +30,6 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.common.YangVersion;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Deviation;
 import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition;
@@ -45,31 +40,24 @@ import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.UsesNode;
-import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
 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.BelongsToEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ContactEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.OrganizationEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.PrefixEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.PrefixStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.YangVersionEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.compat.NotificationNodeContainerCompat;
-import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
 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.source.IncludedSubmoduleNameToModuleCtx;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 @Beta
 public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String>> extends
-        AbstractSchemaEffectiveDocumentedNode<String, D> implements Module, MutableStatement,
+        AbstractSchemaEffectiveDocumentedNode<String, D> implements Module,
         NotificationNodeContainerCompat<String, D> {
     private final String name;
     private final String prefix;
@@ -92,32 +80,13 @@ public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String
     private final ImmutableSet<DataSchemaNode> publicChildNodes;
     private final SemVer semanticVersion;
 
-    private Set<StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>>
-        submoduleContextsToBuild;
-    private ImmutableSet<Module> submodules;
-    private boolean sealed;
-
-    protected AbstractEffectiveModule(final StmtContext<String, D, ? extends EffectiveStatement<String, ?>> ctx) {
+    protected AbstractEffectiveModule(
+            final @NonNull StmtContext<String, D, ? extends EffectiveStatement<String, ?>> ctx,
+            final @NonNull String prefix) {
         super(ctx);
 
         this.name = argument();
-
-        final EffectiveStatement<?, ?> parentOfPrefix;
-        if (ctx.getPublicDefinition() == YangStmtMapping.SUBMODULE) {
-            final Optional<BelongsToEffectiveStatement> optParent =
-                    findFirstEffectiveSubstatement(BelongsToEffectiveStatement.class);
-            SourceException.throwIf(!optParent.isPresent(), ctx.getStatementSourceReference(),
-                    "Unable to find belongs-to statement in submodule %s.", ctx.getStatementArgument());
-            parentOfPrefix = optParent.get();
-        } else {
-            parentOfPrefix = this;
-        }
-
-        final Optional<@NonNull PrefixEffectiveStatement> prefixStmt = parentOfPrefix.findFirstEffectiveSubstatement(
-            PrefixEffectiveStatement.class);
-        SourceException.throwIf(!prefixStmt.isPresent(), ctx.getStatementSourceReference(),
-                "Unable to resolve prefix for module or submodule %s.", ctx.getStatementArgument());
-        this.prefix = prefixStmt.get().argument();
+        this.prefix = requireNonNull(prefix);
         this.yangVersion = findFirstEffectiveSubstatementArgument(YangVersionEffectiveStatement.class)
                 .orElse(YangVersion.VERSION_1);
         this.semanticVersion = findFirstEffectiveSubstatementArgument(OpenConfigVersionEffectiveStatement.class)
@@ -127,65 +96,6 @@ public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String
         this.contact = findFirstEffectiveSubstatementArgument(ContactEffectiveStatement.class)
                 .orElse(null);
 
-        // init submodules and substatements of submodules
-        final List<EffectiveStatement<?, ?>> substatementsOfSubmodules;
-        final Map<String, StmtContext<?, ?, ?>> includedSubmodulesMap = ctx
-                .getAllFromCurrentStmtCtxNamespace(IncludedSubmoduleNameToModuleCtx.class);
-
-        if (includedSubmodulesMap == null || includedSubmodulesMap.isEmpty()) {
-            this.submodules = ImmutableSet.of();
-            this.submoduleContextsToBuild = ImmutableSet.of();
-            substatementsOfSubmodules = ImmutableList.of();
-        } else if (YangStmtMapping.MODULE.equals(ctx.getPublicDefinition())) {
-            /*
-             * Aggregation of substatements from submodules should be done only
-             * for modules. In case of submodules it does not make sense because
-             * of possible circular chains of includes between submodules.
-             */
-            final Set<Module> submodulesInit = new HashSet<>();
-            final List<EffectiveStatement<?, ?>> substatementsOfSubmodulesInit = new ArrayList<>();
-            for (final StmtContext<?, ?, ?> submoduleCtx : includedSubmodulesMap.values()) {
-                final EffectiveStatement<?, ?> submodule = submoduleCtx.buildEffective();
-                Verify.verify(submodule instanceof SubmoduleEffectiveStatement);
-                Verify.verify(submodule instanceof Module, "Submodule statement %s is not a Module", submodule);
-                submodulesInit.add((Module) submodule);
-                substatementsOfSubmodulesInit.addAll(submodule.effectiveSubstatements().stream()
-                        .filter(sub -> sub instanceof SchemaNode || sub instanceof DataNodeContainer)
-                        .collect(Collectors.toList()));
-            }
-
-            this.submodules = ImmutableSet.copyOf(submodulesInit);
-            this.submoduleContextsToBuild = ImmutableSet.of();
-            substatementsOfSubmodules = ImmutableList.copyOf(substatementsOfSubmodulesInit);
-        } else {
-            /*
-             * Because of possible circular chains of includes between submodules we can
-             * collect only submodule contexts here and then build them during
-             * sealing of this statement.
-             */
-            final Set<StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>>
-                submoduleContextsInit = new HashSet<>();
-            for (final StmtContext<?, ?, ?> submoduleCtx : includedSubmodulesMap.values()) {
-                submoduleContextsInit.add(
-                    (StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>)submoduleCtx);
-            }
-
-            this.submoduleContextsToBuild = ImmutableSet.copyOf(submoduleContextsInit);
-            substatementsOfSubmodules = ImmutableList.of();
-        }
-
-        if (!submoduleContextsToBuild.isEmpty()) {
-            ((Mutable<?, ?, ?>) ctx).addMutableStmtToSeal(this);
-            sealed = false;
-        } else {
-            sealed = true;
-        }
-
-        // init substatements collections
-        final List<EffectiveStatement<?, ?>> effectiveSubstatements = new ArrayList<>();
-        effectiveSubstatements.addAll(effectiveSubstatements());
-        effectiveSubstatements.addAll(substatementsOfSubmodules);
-
         final List<UnknownSchemaNode> unknownNodesInit = new ArrayList<>();
         final Set<AugmentationSchemaNode> augmentationsInit = new LinkedHashSet<>();
         final Set<ModuleImport> importsInit = new HashSet<>();
@@ -202,7 +112,7 @@ public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String
         final Set<TypeDefinition<?>> mutableTypeDefinitions = new LinkedHashSet<>();
         final Set<DataSchemaNode> mutablePublicChildNodes = new LinkedHashSet<>();
 
-        for (final EffectiveStatement<?, ?> effectiveStatement : effectiveSubstatements) {
+        for (final EffectiveStatement<?, ?> effectiveStatement : effectiveSubstatements()) {
             if (effectiveStatement instanceof UnknownSchemaNode) {
                 unknownNodesInit.add((UnknownSchemaNode) effectiveStatement);
             }
@@ -312,13 +222,6 @@ public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String
         return imports;
     }
 
-    @Override
-    public Set<Module> getSubmodules() {
-        checkState(sealed, "Attempt to get base submodules from unsealed submodule effective statement %s",
-            getQNameModule());
-        return submodules;
-    }
-
     @Override
     public Set<FeatureDefinition> getFeatures() {
         return features;
@@ -402,13 +305,10 @@ public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String
                 .toString();
     }
 
-    @Override
-    public void seal() {
-        if (!sealed) {
-            submodules = ImmutableSet.copyOf(Iterables.transform(submoduleContextsToBuild,
-                ctx -> (Module) ctx.buildEffective()));
-            submoduleContextsToBuild = ImmutableSet.of();
-            sealed = true;
-        }
+    protected static final @NonNull String findPrefix(final @NonNull StmtContext<?, ?, ?> ctx,
+            final String type, final String name) {
+        return SourceException.throwIfNull(
+            StmtContextUtils.firstAttributeOf(ctx.declaredSubstatements(), PrefixStatement.class),
+            ctx.getStatementSourceReference(), "Unable to resolve prefix for %s %s.", type, name);
     }
 }
index eb0810db857b194a9afefdccabad60258d0bf4b1..ad883e20a6a83fd17cc011a6d07549bbd12ab461 100644 (file)
@@ -11,6 +11,7 @@ import static com.google.common.base.Verify.verifyNotNull;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -19,6 +20,7 @@ import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.model.api.stmt.ExtensionEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ExtensionEffectiveStatementNamespace;
@@ -52,10 +54,13 @@ final class ModuleEffectiveStatementImpl extends AbstractEffectiveModule<ModuleS
     private final ImmutableMap<String, ModuleEffectiveStatement> prefixToModule;
     private final ImmutableMap<QNameModule, String> namespaceToPrefix;
     private final @NonNull QNameModule qnameModule;
+    private final ImmutableSet<Module> submodules;
 
-    ModuleEffectiveStatementImpl(final StmtContext<String, ModuleStatement, ModuleEffectiveStatement> ctx) {
-        super(ctx);
-        qnameModule = verifyNotNull(ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx));
+    private ModuleEffectiveStatementImpl(final @NonNull ModuleStmtContext ctx) {
+        super(ctx, findPrefix(ctx.delegate(), "module", ctx.getStatementArgument()));
+        submodules = ctx.getSubmodules();
+
+        qnameModule = verifyNotNull(ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.delegate()));
 
         final String localPrefix = findFirstEffectiveSubstatementArgument(PrefixEffectiveStatement.class).get();
         final Builder<String, ModuleEffectiveStatement> prefixToModuleBuilder = ImmutableMap.builder();
@@ -78,10 +83,10 @@ final class ModuleEffectiveStatementImpl extends AbstractEffectiveModule<ModuleS
         }
         namespaceToPrefix = ImmutableMap.copyOf(tmp);
 
-        final Map<String, StmtContext<?, ?, ?>> submodules =
+        final Map<String, StmtContext<?, ?, ?>> includedSubmodules =
                 ctx.getAllFromCurrentStmtCtxNamespace(IncludedSubmoduleNameToModuleCtx.class);
-        nameToSubmodule = submodules == null ? ImmutableMap.of()
-                : ImmutableMap.copyOf(Maps.transformValues(submodules,
+        nameToSubmodule = includedSubmodules == null ? ImmutableMap.of()
+                : ImmutableMap.copyOf(Maps.transformValues(includedSubmodules,
                     submodule -> (SubmoduleEffectiveStatement) submodule.buildEffective()));
 
         final Map<QName, StmtContext<?, ExtensionStatement, ExtensionEffectiveStatement>> extensions =
@@ -98,6 +103,10 @@ final class ModuleEffectiveStatementImpl extends AbstractEffectiveModule<ModuleS
                 : ImmutableMap.copyOf(Maps.transformValues(identities, StmtContext::buildEffective));
     }
 
+    ModuleEffectiveStatementImpl(final StmtContext<String, ModuleStatement, ModuleEffectiveStatement> ctx) {
+        this(ModuleStmtContext.create(ctx));
+    }
+
     @Override
     public @NonNull QNameModule localQNameModule() {
         return qnameModule;
@@ -108,6 +117,11 @@ final class ModuleEffectiveStatementImpl extends AbstractEffectiveModule<ModuleS
         return qnameModule;
     }
 
+    @Override
+    public ImmutableSet<Module> getSubmodules() {
+        return submodules;
+    }
+
     @Override
     @SuppressWarnings("unchecked")
     public <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends Map<K, V>> getNamespaceContents(
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleStmtContext.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleStmtContext.java
new file mode 100644 (file)
index 0000000..0e7e70b
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2019 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,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.module;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ForwardingObject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.CopyHistory;
+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.source.IncludedSubmoduleNameToModuleCtx;
+import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
+
+/**
+ * A concentrator {@link StmtContext}, which makes it appear as though as all effective statements in submodules
+ * are included in it.
+ */
+final class ModuleStmtContext extends ForwardingObject
+        implements StmtContext<String, ModuleStatement, ModuleEffectiveStatement> {
+
+    private final @NonNull StmtContext<String, ModuleStatement, ModuleEffectiveStatement> delegate;
+    private final @NonNull ImmutableList<StmtContext<?, ?, ?>> effectiveSubstatements;
+    private final @NonNull ImmutableSet<Module> submodules;
+
+    private ModuleStmtContext(final StmtContext<String, ModuleStatement, ModuleEffectiveStatement> delegate,
+            final Collection<StmtContext<?, ?, ?>> submodules) {
+        this.delegate = requireNonNull(delegate);
+
+        final List<StmtContext<?, ?, ?>> statements = new ArrayList<>(delegate.effectiveSubstatements());
+        final Set<Module> subs = new LinkedHashSet<>(submodules.size());
+        for (StmtContext<?, ?, ?> submoduleCtx : submodules) {
+            final EffectiveStatement<?, ?> submodule = submoduleCtx.buildEffective();
+            verify(submodule instanceof Module, "Submodule statement %s is not a Module", submodule);
+            subs.add((Module) submodule);
+
+            for (StmtContext<?, ?, ?> stmt : submoduleCtx.allSubstatements()) {
+                if (stmt.isSupportedByFeatures()) {
+                    final EffectiveStatement<?, ?> effective = stmt.buildEffective();
+                    if (effective instanceof SchemaNode || effective instanceof DataNodeContainer) {
+                        statements.add(stmt);
+                    }
+                }
+            }
+        }
+
+        this.effectiveSubstatements = ImmutableList.copyOf(statements);
+        this.submodules = ImmutableSet.copyOf(subs);
+    }
+
+    static @NonNull ModuleStmtContext create(
+            final StmtContext<String, ModuleStatement, ModuleEffectiveStatement> delegate) {
+        final Map<String, StmtContext<?, ?, ?>> includedSubmodules = delegate.getAllFromCurrentStmtCtxNamespace(
+            IncludedSubmoduleNameToModuleCtx.class);
+        return new ModuleStmtContext(delegate, includedSubmodules == null || includedSubmodules.isEmpty()
+                ? ImmutableList.of() : includedSubmodules.values());
+    }
+
+    @Override
+    protected @NonNull StmtContext<String, ModuleStatement, ModuleEffectiveStatement> delegate() {
+        return delegate;
+    }
+
+    ImmutableSet<Module> getSubmodules() {
+        return submodules;
+    }
+
+    @Override
+    public ImmutableList<StmtContext<?, ?, ?>> effectiveSubstatements() {
+        return effectiveSubstatements;
+    }
+
+    @Override
+    public ModuleEffectiveStatement buildEffective() {
+        throw new UnsupportedOperationException("Attempted to instantiate proxy context " + this);
+    }
+
+    @Override
+    public ModuleStatement buildDeclared() {
+        return delegate.buildDeclared();
+    }
+
+    @Override
+    public StatementSource getStatementSource() {
+        return delegate.getStatementSource();
+    }
+
+    @Override
+    public StatementSourceReference getStatementSourceReference() {
+        return delegate.getStatementSourceReference();
+    }
+
+    @Override
+    public StatementDefinition getPublicDefinition() {
+        return delegate.getPublicDefinition();
+    }
+
+    @Override
+    public StmtContext<?, ?, ?> getParentContext() {
+        return delegate.getParentContext();
+    }
+
+    @Override
+    public String rawStatementArgument() {
+        return delegate.rawStatementArgument();
+    }
+
+    @Override
+    public @Nullable String getStatementArgument() {
+        return delegate.getStatementArgument();
+    }
+
+    @Override
+    public @NonNull Optional<SchemaPath> getSchemaPath() {
+        return delegate.getSchemaPath();
+    }
+
+    @Override
+    public boolean isConfiguration() {
+        return delegate.isConfiguration();
+    }
+
+    @Override
+    public boolean isEnabledSemanticVersioning() {
+        return delegate.isEnabledSemanticVersioning();
+    }
+
+    @Override
+    public <K, V, T extends K, N extends IdentifierNamespace<K, V>> @NonNull V getFromNamespace(final Class<N> type,
+            final T key) {
+        return delegate.getFromNamespace(type, key);
+    }
+
+    @Override
+    public <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getAllFromNamespace(final Class<N> type) {
+        return delegate.getAllFromNamespace(type);
+    }
+
+    @Override
+    public <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getAllFromCurrentStmtCtxNamespace(
+            final Class<N> type) {
+        return delegate.getAllFromCurrentStmtCtxNamespace(type);
+    }
+
+    @Override
+    public StmtContext<?, ?, ?> getRoot() {
+        return delegate.getRoot();
+    }
+
+    @Override
+    public Collection<? extends StmtContext<?, ?, ?>> declaredSubstatements() {
+        return delegate.declaredSubstatements();
+    }
+
+    @Override
+    public boolean isSupportedToBuildEffective() {
+        return delegate.isSupportedToBuildEffective();
+    }
+
+    @Override
+    public boolean isSupportedByFeatures() {
+        return delegate.isSupportedByFeatures();
+    }
+
+    @Override
+    public Collection<? extends StmtContext<?, ?, ?>> getEffectOfStatement() {
+        return delegate.getEffectOfStatement();
+    }
+
+    @Override
+    public CopyHistory getCopyHistory() {
+        return delegate.getCopyHistory();
+    }
+
+    @Override
+    public Optional<StmtContext<?, ?, ?>> getOriginalCtx() {
+        return delegate.getOriginalCtx();
+    }
+
+    @Override
+    public Optional<? extends StmtContext<?, ?, ?>> getPreviousCopyCtx() {
+        return delegate.getPreviousCopyCtx();
+    }
+
+    @Override
+    public ModelProcessingPhase getCompletedPhase() {
+        return delegate.getCompletedPhase();
+    }
+
+    @Override
+    public @NonNull YangVersion getRootVersion() {
+        return delegate.getRootVersion();
+    }
+}
index a53155396152912e26850fafc4013adfc400bae4..90edc788679f0b4922332fcf3193b458723d0b6f 100644 (file)
@@ -7,27 +7,45 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.submodule;
 
+import static com.google.common.base.Preconditions.checkState;
 import static org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils.firstAttributeOf;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.util.HashSet;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
 import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.AbstractEffectiveModule;
+import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
 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.StmtContextUtils;
+import org.opendaylight.yangtools.yang.parser.spi.source.IncludedSubmoduleNameToModuleCtx;
 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<SubmoduleStatement>
-        implements SubmoduleEffectiveStatement {
+        implements SubmoduleEffectiveStatement, MutableStatement {
 
     private final QNameModule qnameModule;
 
+    private Set<StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>> submoduleContexts;
+    private ImmutableSet<Module> submodules;
+    private boolean sealed;
+
     SubmoduleEffectiveStatementImpl(final StmtContext<String, SubmoduleStatement, SubmoduleEffectiveStatement> ctx) {
-        super(ctx);
+        super(ctx, findSubmodulePrefix(ctx));
 
         final String belongsToModuleName = firstAttributeOf(ctx.declaredSubstatements(), BelongsToStatement.class);
         final QNameModule belongsToModuleQName = ctx.getFromNamespace(ModuleNameToModuleQName.class,
@@ -36,6 +54,33 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<Subm
         final Optional<Revision> submoduleRevision = findFirstEffectiveSubstatementArgument(
             RevisionEffectiveStatement.class);
         this.qnameModule = QNameModule.create(belongsToModuleQName.getNamespace(), submoduleRevision).intern();
+
+        /*
+         * Because of possible circular chains of includes between submodules we can
+         * collect only submodule contexts here and then build them during
+         * sealing of this statement.
+         */
+        final Map<String, StmtContext<?, ?, ?>> includedSubmodulesMap = ctx.getAllFromCurrentStmtCtxNamespace(
+            IncludedSubmoduleNameToModuleCtx.class);
+        if (includedSubmodulesMap != null) {
+            final Set<StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>>
+                submoduleContextsInit = new HashSet<>();
+            for (final StmtContext<?, ?, ?> submoduleCtx : includedSubmodulesMap.values()) {
+                submoduleContextsInit.add(
+                    (StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>)submoduleCtx);
+            }
+            submoduleContexts = ImmutableSet.copyOf(submoduleContextsInit);
+        } else {
+            submoduleContexts = ImmutableSet.of();
+        }
+
+        if (!submoduleContexts.isEmpty()) {
+            ((Mutable<?, ?, ?>) ctx).addMutableStmtToSeal(this);
+            sealed = false;
+        } else {
+            submodules = ImmutableSet.of();
+            sealed = true;
+        }
     }
 
     @Override
@@ -43,6 +88,13 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<Subm
         return qnameModule;
     }
 
+    @Override
+    public Set<Module> getSubmodules() {
+        checkState(sealed, "Attempt to get base submodules from unsealed submodule effective statement %s",
+            qnameModule);
+        return submodules;
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(getName(), getYangVersion(), qnameModule);
@@ -60,4 +112,22 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<Subm
         return Objects.equals(getName(), other.getName()) && qnameModule.equals(other.qnameModule)
                 && Objects.equals(getYangVersion(), other.getYangVersion());
     }
+
+    @Override
+    public void seal() {
+        if (!sealed) {
+            submodules = ImmutableSet.copyOf(Iterables.transform(submoduleContexts,
+                ctx -> (Module) ctx.buildEffective()));
+            submoduleContexts = ImmutableSet.of();
+            sealed = true;
+        }
+    }
+
+    private static @NonNull String findSubmodulePrefix(final StmtContext<String, ?, ?> ctx) {
+        final String name = ctx.getStatementArgument();
+        final StmtContext<?, ?, ?> belongsTo = SourceException.throwIfNull(
+                StmtContextUtils.findFirstDeclaredSubstatement(ctx, BelongsToStatement.class),
+                ctx.getStatementSourceReference(), "Unable to find belongs-to statement in submodule %s.", name);
+        return findPrefix(belongsTo, "submodule", name);
+    }
 }
diff --git a/yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/stmt/YT1042Test.java b/yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/stmt/YT1042Test.java
new file mode 100644 (file)
index 0000000..390ecda
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 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,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.stmt;
+
+import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
+
+public class YT1042Test {
+    @Test
+    public void testSubmoduleConflict() throws Exception {
+        try {
+            StmtTestUtils.parseYangSources("/bugs/YT1042");
+            fail("Sechema assembly should have failed");
+        } catch (ReactorException e) {
+            final Throwable cause = e.getCause();
+            assertThat(cause, isA(SourceException.class));
+            assertThat(cause.getMessage(),
+                startsWith("Cannot add data tree child with name (foo)foo, a conflicting child already exists"));
+        }
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/test/resources/bugs/YT1042/bar.yang b/yang/yang-parser-rfc7950/src/test/resources/bugs/YT1042/bar.yang
new file mode 100644 (file)
index 0000000..bdc3acd
--- /dev/null
@@ -0,0 +1,13 @@
+submodule bar {
+    belongs-to foo {
+        prefix foo;
+    }
+
+    choice bar {
+        case foo {
+            leaf foo {
+                type string;
+            }
+        }
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/test/resources/bugs/YT1042/foo.yang b/yang/yang-parser-rfc7950/src/test/resources/bugs/YT1042/foo.yang
new file mode 100644 (file)
index 0000000..0249391
--- /dev/null
@@ -0,0 +1,10 @@
+module foo {
+    namespace foo;
+    prefix foo;
+
+    include bar;
+
+    container foo {
+
+    }
+}