Populate SubmoduleEffectiveModule with import namespaces 21/87321/1
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 19 May 2019 19:39:47 +0000 (21:39 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 31 Jan 2020 11:06:44 +0000 (12:06 +0100)
In case we are exporting a submodule, we need to find matching
imports -- just as they are constructed for ModuleEffectiveStatement.

This is then used in YangTextSnippet with appropriate resolver,
adding an explicit test.

JIRA: YANGTOOLS-992
Change-Id: Ie86cd8ff50f598fe1868f576f9f7e5a8ab2e5c5d
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
14 files changed:
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/SubmoduleEffectiveStatement.java
yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/DeclaredStatementFormatter.java
yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ExportUtils.java [deleted file]
yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ModuleNamespaceContext.java
yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/StatementPrefixResolver.java [new file with mode: 0644]
yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippet.java
yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetIterator.java
yang/yang-model-export/src/test/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetTest.java
yang/yang-model-export/src/test/resources/bugs/yt992/module1@2019-05-17.yang [new file with mode: 0644]
yang/yang-model-export/src/test/resources/bugs/yt992/module1submodule1@2019-05-17.yang [new file with mode: 0644]
yang/yang-model-export/src/test/resources/bugs/yt992/module2@2019-05-17.yang [new file with mode: 0644]
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/submodule/SubmoduleEffectiveStatementImpl.java

index 79c95a82991483aa923c34d15a5f8b8016eb3d5b..953285a96acd4a975713dbc00179543b85791297 100644 (file)
@@ -10,6 +10,11 @@ package org.opendaylight.yangtools.yang.model.api.stmt;
 import com.google.common.annotations.Beta;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 
+/**
+ * Representation of {@code submodule} statement. Note that implementations of this interface are required to provide
+ * {@link ModuleEffectiveStatement.PrefixToEffectiveModuleNamespace} and
+ * {@link ModuleEffectiveStatement.QNameModuleToPrefixNamespace} namespaces.
+ */
 @Beta
 public interface SubmoduleEffectiveStatement extends EffectiveStatement<String, SubmoduleStatement> {
 
index 040f0c9a9d5c3f6e58bf87228749e57ef8ffaf01..e0efa40e7bd7b65bf282af1a80d0dd5d65369e6f 100644 (file)
@@ -18,7 +18,7 @@ import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement.QNameModuleToPrefixNamespace;
+import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
 
 /**
  * Utility class for formatting {@link DeclaredStatement}s.
@@ -48,7 +48,13 @@ public final class DeclaredStatementFormatter implements Immutable {
      */
     public YangTextSnippet toYangTextSnippet(final ModuleEffectiveStatement module,
             final DeclaredStatement<?> statement) {
-        return new YangTextSnippet(statement, module.findAll(QNameModuleToPrefixNamespace.class), ignoredStatements,
+        return new YangTextSnippet(statement, StatementPrefixResolver.forModule(module), ignoredStatements,
+            omitDefaultStatements);
+    }
+
+    public YangTextSnippet toYangTextSnippet(final SubmoduleEffectiveStatement submodule,
+            final DeclaredStatement<?> statement) {
+        return new YangTextSnippet(statement, StatementPrefixResolver.forSubmodule(submodule), ignoredStatements,
             omitDefaultStatements);
     }
 
diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ExportUtils.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ExportUtils.java
deleted file mode 100644 (file)
index 585cfea..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2018 Pantheon Technologies, 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.model.export;
-
-import static com.google.common.base.Verify.verify;
-
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.common.Revision;
-import org.opendaylight.yangtools.yang.common.YangConstants;
-
-/**
- * Internal shared helpers.
- * @author Robert Varga
- *
- */
-final class ExportUtils {
-    private ExportUtils() {
-        // Hidden on purpose
-    }
-
-    static Optional<String> statementPrefix(final Map<QNameModule, String> namespaces, final QName stmtName) {
-        final QNameModule namespace = stmtName.getModule();
-        if (YangConstants.RFC6020_YIN_MODULE.equals(namespace)) {
-            return Optional.empty();
-        }
-
-        // Non-default namespace, a prefix is needed
-        @Nullable String prefix = namespaces.get(namespace);
-        if (prefix == null && !namespace.getRevision().isPresent()) {
-            // FIXME: this is an artifact of commonly-bound statements in parser, which means a statement's name
-            //        does not have a Revision. We'll need to find a solution to this which is acceptable. There
-            //        are multiple ways of fixing this:
-            //        - perhaps EffectiveModuleStatement should be giving us a statement-to-EffectiveModule map?
-            //        - or DeclaredStatement should provide the prefix?
-            //        The second one seems cleaner, as that means we would not have perform any lookup at all...
-            Entry<QNameModule, @NonNull String> match = null;
-            for (Entry<QNameModule, @NonNull String> entry : namespaces.entrySet()) {
-                final QNameModule ns = entry.getKey();
-                if (namespace.equals(ns.withoutRevision()) && (match == null
-                        || Revision.compare(match.getKey().getRevision(), ns.getRevision()) < 0)) {
-                    match = entry;
-                }
-            }
-
-            if (match != null) {
-                prefix = match.getValue();
-            }
-        }
-
-        if (prefix == null) {
-            throw new IllegalArgumentException("Failed to find prefix for statement " + stmtName);
-        }
-
-        verify(!prefix.isEmpty(), "Empty prefix for statement %s", stmtName);
-        return Optional.of(prefix);
-    }
-}
index c74562e5a66fcf2a08ad6b06daf4efbcd5fbc124..08c505e87ea8ef266f6d0efd841e72f92515418d 100644 (file)
@@ -20,11 +20,9 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Optional;
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
 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.common.YangConstants;
 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
@@ -111,15 +109,6 @@ final class ModuleNamespaceContext implements NamespaceContext {
         return new SimpleImmutableEntry<>(prefix, module.getNamespace().toString());
     }
 
-    Entry<String, String> prefixAndNamespaceForStatement(final QName stmtName) {
-        final Optional<String> prefix = ExportUtils.statementPrefix(moduleToPrefix, stmtName);
-        if (!prefix.isPresent()) {
-            return YIN_PREFIX_AND_NAMESPACE;
-        }
-
-        return new SimpleImmutableEntry<>(prefix.get(), stmtName.getNamespace().toString());
-    }
-
     Map<String, String> prefixesAndNamespaces() {
         return Maps.transformValues(prefixToModule, module -> module.localQNameModule().getNamespace().toString());
     }
diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/StatementPrefixResolver.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/StatementPrefixResolver.java
new file mode 100644 (file)
index 0000000..34332c7
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * 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.model.export;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.YangConstants;
+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.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement.NameToEffectiveSubmoduleNamespace;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement.QNameModuleToPrefixNamespace;
+import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
+
+/**
+ * Utility resolver to disambiguate imports.
+ */
+final class StatementPrefixResolver {
+    private static final class Conflict {
+        private final Collection<Entry<DeclaredStatement<?>, String>> statements;
+
+        Conflict(final Collection<Entry<DeclaredStatement<?>, String>> entries) {
+            this.statements = requireNonNull(entries);
+        }
+
+        @Nullable String findPrefix(final DeclaredStatement<?> stmt) {
+            return statements.stream().filter(entry -> contains(entry.getKey(), stmt)).findFirst().map(Entry::getValue)
+                    .orElse(null);
+        }
+
+        private static boolean contains(final DeclaredStatement<?> haystack, final DeclaredStatement<?> needle) {
+            if (haystack == needle) {
+                return true;
+            }
+            for (DeclaredStatement<?> child : haystack.declaredSubstatements()) {
+                if (contains(child, needle)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private final Map<QNameModule, ?> lookup;
+
+    private StatementPrefixResolver(final Map<QNameModule, String> map) {
+        this.lookup = ImmutableMap.copyOf(map);
+    }
+
+    private StatementPrefixResolver(final ImmutableMap<QNameModule, ?> map) {
+        this.lookup = requireNonNull(map);
+    }
+
+    static StatementPrefixResolver forModule(final ModuleEffectiveStatement module) {
+        final Map<QNameModule, String> imports = module.getAll(QNameModuleToPrefixNamespace.class);
+        final Collection<SubmoduleEffectiveStatement> submodules = module.getAll(
+            NameToEffectiveSubmoduleNamespace.class).values();
+        if (submodules.isEmpty()) {
+            // Simple: it's just the module
+            return new StatementPrefixResolver(imports);
+        }
+
+        // Stage one: check what everyone thinks about imports
+        final Map<String, Multimap<QNameModule, EffectiveStatement<?, ?>>> prefixToNamespaces = new HashMap<>();
+        indexPrefixes(prefixToNamespaces, imports, module);
+        for (SubmoduleEffectiveStatement submodule : submodules) {
+            indexPrefixes(prefixToNamespaces, submodule.getAll(QNameModuleToPrefixNamespace.class), submodule);
+        }
+
+        // Stage two: resolve first order of conflicts, potentially completely resolving mappings...
+        final Builder<QNameModule, Object> builder = ImmutableMap.builderWithExpectedSize(prefixToNamespaces.size());
+
+        // ... first resolve unambiguous mappings ...
+        final Iterator<Entry<String, Multimap<QNameModule, EffectiveStatement<?, ?>>>> it =
+                prefixToNamespaces.entrySet().iterator();
+        while (it.hasNext()) {
+            final Entry<String, Multimap<QNameModule, EffectiveStatement<?, ?>>> entry = it.next();
+            final Multimap<QNameModule, EffectiveStatement<?, ?>> modules = entry.getValue();
+            if (modules.size() == 1) {
+                builder.put(modules.keys().iterator().next(), entry.getKey());
+                it.remove();
+            }
+        }
+
+        // .. check for any remaining conflicts ...
+        if (!prefixToNamespaces.isEmpty()) {
+            final Multimap<QNameModule, Entry<DeclaredStatement<?>, String>> conflicts = ArrayListMultimap.create();
+            for (Entry<String, Multimap<QNameModule, EffectiveStatement<?, ?>>> entry : prefixToNamespaces.entrySet()) {
+                for (Entry<QNameModule, EffectiveStatement<?, ?>> namespace : entry.getValue().entries()) {
+                    conflicts.put(namespace.getKey(), new SimpleImmutableEntry<>(namespace.getValue().getDeclared(),
+                            entry.getKey()));
+                }
+            }
+
+            builder.putAll(Maps.transformValues(conflicts.asMap(), Conflict::new));
+        }
+
+        return new StatementPrefixResolver(builder.build());
+    }
+
+    static StatementPrefixResolver forSubmodule(final SubmoduleEffectiveStatement submodule) {
+        return new StatementPrefixResolver(submodule.getAll(QNameModuleToPrefixNamespace.class));
+    }
+
+    Optional<String> findPrefix(final DeclaredStatement<?> stmt) {
+        final QNameModule module = stmt.statementDefinition().getStatementName().getModule();
+        if (YangConstants.RFC6020_YIN_MODULE.equals(module)) {
+            return Optional.empty();
+        }
+
+        final Object obj = lookup.get(module);
+        if (obj != null) {
+            return decodeEntry(obj, stmt);
+        }
+        if (module.getRevision().isPresent()) {
+            throw new IllegalArgumentException("Failed to find prefix for statement " + stmt);
+        }
+
+        // FIXME: this is an artifact of commonly-bound statements in parser, which means a statement's name
+        //        does not have a Revision. We'll need to find a solution to this which is acceptable. There
+        //        are multiple ways of fixing this:
+        //        - perhaps EffectiveModuleStatement should be giving us a statement-to-EffectiveModule map?
+        //        - or DeclaredStatement should provide the prefix?
+        //        The second one seems cleaner, as that means we would not have perform any lookup at all...
+        Entry<QNameModule, ?> match = null;
+        for (Entry<QNameModule, ?> entry : lookup.entrySet()) {
+            final QNameModule ns = entry.getKey();
+            if (module.equals(ns.withoutRevision()) && (match == null
+                    || Revision.compare(match.getKey().getRevision(), ns.getRevision()) < 0)) {
+                match = entry;
+            }
+        }
+
+        return match == null ? null : decodeEntry(match.getValue(), stmt);
+    }
+
+    private static Optional<String> decodeEntry(final Object entry, final DeclaredStatement<?> stmt) {
+        if (entry instanceof String) {
+            return Optional.of((String)entry);
+        }
+        verify(entry instanceof Conflict, "Unexpected entry %s", entry);
+        final String prefix = ((Conflict) entry).findPrefix(stmt);
+        checkArgument(prefix != null, "Failed to find prefix for statement %s", stmt);
+        verify(!prefix.isEmpty(), "Empty prefix for statement %s", stmt);
+        return Optional.of(prefix);
+    }
+
+    private static void indexPrefixes(final Map<String, Multimap<QNameModule, EffectiveStatement<?, ?>>> map,
+            final Map<QNameModule, String> imports, final EffectiveStatement<?, ?> stmt) {
+        for (Entry<QNameModule, String> entry : imports.entrySet()) {
+            map.computeIfAbsent(entry.getValue(), key -> ArrayListMultimap.create()).put(entry.getKey(), stmt);
+        }
+    }
+}
index 9355e1e0490fed0cb2d065795a37a6e092c3aced..abdefd7f722201ee8ec056923544be6152d142ea 100644 (file)
@@ -13,7 +13,6 @@ import static org.eclipse.jdt.annotation.DefaultLocation.RETURN_TYPE;
 
 import com.google.common.annotations.Beta;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
@@ -23,7 +22,6 @@ import java.util.stream.StreamSupport;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.opendaylight.yangtools.concepts.Immutable;
-import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 
@@ -42,21 +40,21 @@ import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 @NonNullByDefault({ PARAMETER, RETURN_TYPE })
 public final class YangTextSnippet implements Immutable, Iterable<@NonNull String> {
     private final Set<@NonNull StatementDefinition> ignoredStatements;
-    private final Map<QNameModule, @NonNull String> mapper;
+    private final StatementPrefixResolver resolver;
     private final DeclaredStatement<?> statement;
     private final boolean omitDefaultStatements;
 
-    YangTextSnippet(final DeclaredStatement<?> statement, final Map<QNameModule, @NonNull String> namespaces,
+    YangTextSnippet(final DeclaredStatement<?> statement, final StatementPrefixResolver resolver,
             final Set<@NonNull StatementDefinition> ignoredStatements, final boolean omitDefaultStatements) {
         this.statement = requireNonNull(statement);
-        this.mapper = requireNonNull(namespaces);
+        this.resolver = requireNonNull(resolver);
         this.ignoredStatements = requireNonNull(ignoredStatements);
         this.omitDefaultStatements = omitDefaultStatements;
     }
 
     @Override
     public Iterator<@NonNull String> iterator() {
-        return new YangTextSnippetIterator(statement, mapper, ignoredStatements, omitDefaultStatements);
+        return new YangTextSnippetIterator(statement, resolver, ignoredStatements, omitDefaultStatements);
     }
 
     @Override
index b97e0c5c125f8b7fd1ca28958410ccb5ed20d6c2..78a5e61f4dfbc36986d744396cc2c1f4c00f493c 100644 (file)
@@ -20,15 +20,12 @@ import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Queue;
 import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
 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.StatementDefinition;
@@ -127,13 +124,13 @@ final class YangTextSnippetIterator extends AbstractIterator<@NonNull String> {
     private final Queue<String> strings = new ArrayDeque<>(8);
     // Let's be modest, 16-level deep constructs are not exactly common.
     private final Deque<Iterator<? extends DeclaredStatement<?>>> stack = new ArrayDeque<>(8);
-    private final Map<QNameModule, @NonNull String> namespaces;
     private final Set<StatementDefinition> ignoredStatements;
+    private final StatementPrefixResolver resolver;
     private final boolean omitDefaultStatements;
 
-    YangTextSnippetIterator(final DeclaredStatement<?> stmt, final Map<QNameModule, @NonNull String> namespaces,
+    YangTextSnippetIterator(final DeclaredStatement<?> stmt, final StatementPrefixResolver resolver,
         final Set<StatementDefinition> ignoredStatements, final boolean omitDefaultStatements) {
-        this.namespaces = requireNonNull(namespaces);
+        this.resolver = requireNonNull(resolver);
         this.ignoredStatements = requireNonNull(ignoredStatements);
         this.omitDefaultStatements = omitDefaultStatements;
         pushStatement(requireNonNull(stmt));
@@ -194,13 +191,12 @@ final class YangTextSnippetIterator extends AbstractIterator<@NonNull String> {
         addIndent();
 
         // Add statement prefixed with namespace if needed
-        final QName stmtName = def.getStatementName();
-        final Optional<String> prefix = ExportUtils.statementPrefix(namespaces, stmtName);
+        final Optional<String> prefix = resolver.findPrefix(stmt);
         if (prefix.isPresent()) {
             strings.add(prefix.get());
             strings.add(":");
         }
-        strings.add(stmtName.getLocalName());
+        strings.add(def.getStatementName().getLocalName());
 
         // Add argument, quoted and properly indented if need be
         addArgument(def, stmt.rawArgument());
index f1740cb470f4a56b24e2c48456227d7f6709d9ad..206c939bd1c228c25b6a8eee1c36dc7f9f300209 100644 (file)
@@ -9,25 +9,47 @@ package org.opendaylight.yangtools.yang.model.export;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter.defaultInstance;
 
+import java.util.Collection;
 import org.junit.Test;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement.NameToEffectiveSubmoduleNamespace;
+import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
 public class YangTextSnippetTest {
     @Test
     public void testNotification() {
         final SchemaContext schema = YangParserTestUtils.parseYangResource("/bugs/bug2444/yang/notification.yang");
+        assertFormat(schema.getModules());
+    }
+
+    @Test
+    public void testSubmoduleNamespaces() throws Exception {
+        SchemaContext schema = YangParserTestUtils.parseYangResourceDirectory("/bugs/yt992");
+        assertFormat(schema.getModules());
+    }
 
-        for (Module module : schema.getModules()) {
+    private static void assertFormat(final Collection<? extends Module> modules) {
+        for (Module module : modules) {
             assertTrue(module instanceof ModuleEffectiveStatement);
             final ModuleEffectiveStatement stmt = (ModuleEffectiveStatement) module;
+            assertNotNull(formatModule(stmt));
 
-            final String str = DeclaredStatementFormatter.defaultInstance().toYangTextSnippet(stmt, stmt.getDeclared())
-                    .toString();
-            assertNotNull(str);
+            for (SubmoduleEffectiveStatement substmt : stmt.getAll(NameToEffectiveSubmoduleNamespace.class).values()) {
+                assertNotNull(formatSubmodule(substmt));
+            }
         }
     }
+
+    private static String formatModule(final ModuleEffectiveStatement stmt) {
+        return defaultInstance().toYangTextSnippet(stmt, stmt.getDeclared()).toString();
+    }
+
+    private static String formatSubmodule(final SubmoduleEffectiveStatement stmt) {
+        return defaultInstance().toYangTextSnippet(stmt, stmt.getDeclared()).toString();
+    }
 }
diff --git a/yang/yang-model-export/src/test/resources/bugs/yt992/module1@2019-05-17.yang b/yang/yang-model-export/src/test/resources/bugs/yt992/module1@2019-05-17.yang
new file mode 100644 (file)
index 0000000..77d25ff
--- /dev/null
@@ -0,0 +1,14 @@
+module module1 {
+    yang-version "1.1";
+    namespace "urn:example:module1";
+    prefix "module1";
+
+    include module1submodule1;
+
+    revision "2019-05-17" {
+    }
+
+    container cont1 {
+        uses submodule-grouping;
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-model-export/src/test/resources/bugs/yt992/module1submodule1@2019-05-17.yang b/yang/yang-model-export/src/test/resources/bugs/yt992/module1submodule1@2019-05-17.yang
new file mode 100644 (file)
index 0000000..fd31501
--- /dev/null
@@ -0,0 +1,22 @@
+submodule module1submodule1 {
+    yang-version "1.1";
+    belongs-to "module1" {
+        prefix "module1";
+    }
+
+    import module2 {
+        prefix "module2";
+    }
+
+    revision "2019-05-17" {
+    }
+
+    grouping submodule-grouping {
+        uses module2:grouping1;
+
+        leaf leaf2 {
+            type string;
+            module2:ext1 "param1";
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-model-export/src/test/resources/bugs/yt992/module2@2019-05-17.yang b/yang/yang-model-export/src/test/resources/bugs/yt992/module2@2019-05-17.yang
new file mode 100644 (file)
index 0000000..129f420
--- /dev/null
@@ -0,0 +1,18 @@
+module module2 {
+    yang-version "1.1";
+    namespace "urn:example:module2";
+    prefix "module2";
+
+    revision "2019-05-17" {
+    }
+
+    grouping grouping1 {
+        leaf leaf1 {
+            type string;
+        }
+    }
+
+    extension ext1 {
+        argument "parameter";
+    }
+}
\ No newline at end of file
index f12301403e8b8eb6c52451106093b4ef79e1905b..73279b0c774b5105703913f98ca844fa1682cf78 100644 (file)
@@ -7,11 +7,13 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.stmt;
 
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
 import java.net.URI;
 import java.util.ArrayList;
@@ -41,13 +43,17 @@ import org.opendaylight.yangtools.yang.model.api.UsesNode;
 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.ContactEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
 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.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.StmtContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
+import org.opendaylight.yangtools.yang.parser.spi.source.ImportPrefixToModuleCtx;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 @Beta
@@ -284,4 +290,17 @@ public abstract class AbstractEffectiveModule<D extends DeclaredStatement<String
             StmtContextUtils.firstAttributeOf(ctx.declaredSubstatements(), PrefixStatement.class),
             ctx.getStatementSourceReference(), "Unable to resolve prefix for %s %s.", type, name);
     }
+
+    // Alright. this is quite ugly
+    protected final void appendPrefixes(final StmtContext<?, ?, ?> ctx,
+            final Builder<String, ModuleEffectiveStatement> builder) {
+        streamEffectiveSubstatements(ImportEffectiveStatement.class)
+            .map(imp -> imp.findFirstEffectiveSubstatementArgument(PrefixEffectiveStatement.class).get())
+            .forEach(pfx -> {
+                final StmtContext<?, ?, ?> importedCtx =
+                        verifyNotNull(ctx.getFromNamespace(ImportPrefixToModuleCtx.class, pfx),
+                            "Failed to resolve prefix %s", pfx);
+                builder.put(pfx, (ModuleEffectiveStatement) importedCtx.buildEffective());
+            });
+    }
 }
index ad883e20a6a83fd17cc011a6d07549bbd12ab461..2a8f56cc6418dee76b14e05eb78bd05098d75cae 100644 (file)
@@ -31,7 +31,6 @@ import org.opendaylight.yangtools.yang.model.api.stmt.FeatureStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.IdentityEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.IdentityEffectiveStatementNamespace;
 import org.opendaylight.yangtools.yang.model.api.stmt.IdentityStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.PrefixEffectiveStatement;
@@ -41,7 +40,6 @@ import org.opendaylight.yangtools.yang.parser.spi.ExtensionNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.FeatureNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.IdentityNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
-import org.opendaylight.yangtools.yang.parser.spi.source.ImportPrefixToModuleCtx;
 import org.opendaylight.yangtools.yang.parser.spi.source.IncludedSubmoduleNameToModuleCtx;
 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
 
@@ -65,15 +63,7 @@ final class ModuleEffectiveStatementImpl extends AbstractEffectiveModule<ModuleS
         final String localPrefix = findFirstEffectiveSubstatementArgument(PrefixEffectiveStatement.class).get();
         final Builder<String, ModuleEffectiveStatement> prefixToModuleBuilder = ImmutableMap.builder();
         prefixToModuleBuilder.put(localPrefix, this);
-
-        streamEffectiveSubstatements(ImportEffectiveStatement.class)
-                .map(imp -> imp.findFirstEffectiveSubstatementArgument(PrefixEffectiveStatement.class).get())
-                .forEach(prefix -> {
-                    final StmtContext<?, ?, ?> importedCtx =
-                            verifyNotNull(ctx.getFromNamespace(ImportPrefixToModuleCtx.class, prefix),
-                                "Failed to resolve prefix %s", prefix);
-                    prefixToModuleBuilder.put(prefix, (ModuleEffectiveStatement) importedCtx.buildEffective());
-                });
+        appendPrefixes(ctx, prefixToModuleBuilder);
         prefixToModule = prefixToModuleBuilder.build();
 
         final Map<QNameModule, String> tmp = Maps.newLinkedHashMapWithExpectedSize(prefixToModule.size() + 1);
index 90edc788679f0b4922332fcf3193b458723d0b6f..2289e9b5c68aed092f4fbb58a60527708fabffea 100644 (file)
@@ -10,10 +10,14 @@ 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.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -22,7 +26,11 @@ 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.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement.PrefixToEffectiveModuleNamespace;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement.QNameModuleToPrefixNamespace;
 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;
@@ -37,7 +45,8 @@ import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<SubmoduleStatement>
         implements SubmoduleEffectiveStatement, MutableStatement {
-
+    private final ImmutableMap<String, ModuleEffectiveStatement> prefixToModule;
+    private final ImmutableMap<QNameModule, String> namespaceToPrefix;
     private final QNameModule qnameModule;
 
     private Set<StmtContext<?, SubmoduleStatement, EffectiveStatement<String, SubmoduleStatement>>> submoduleContexts;
@@ -51,6 +60,16 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<Subm
         final QNameModule belongsToModuleQName = ctx.getFromNamespace(ModuleNameToModuleQName.class,
                 belongsToModuleName);
 
+        final Builder<String, ModuleEffectiveStatement> prefixToModuleBuilder = ImmutableMap.builder();
+        appendPrefixes(ctx, prefixToModuleBuilder);
+        prefixToModule = prefixToModuleBuilder.build();
+
+        final Map<QNameModule, String> tmp = Maps.newLinkedHashMapWithExpectedSize(prefixToModule.size());
+        for (Entry<String, ModuleEffectiveStatement> e : prefixToModule.entrySet()) {
+            tmp.putIfAbsent(e.getValue().localQNameModule(), e.getKey());
+        }
+        namespaceToPrefix = ImmutableMap.copyOf(tmp);
+
         final Optional<Revision> submoduleRevision = findFirstEffectiveSubstatementArgument(
             RevisionEffectiveStatement.class);
         this.qnameModule = QNameModule.create(belongsToModuleQName.getNamespace(), submoduleRevision).intern();
@@ -88,6 +107,19 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule<Subm
         return qnameModule;
     }
 
+    @Override
+    @SuppressWarnings("unchecked")
+    public <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends Map<K, V>> getNamespaceContents(
+            final @NonNull Class<N> namespace) {
+        if (PrefixToEffectiveModuleNamespace.class.equals(namespace)) {
+            return Optional.of((Map<K, V>) prefixToModule);
+        }
+        if (QNameModuleToPrefixNamespace.class.equals(namespace)) {
+            return Optional.of((Map<K, V>) namespaceToPrefix);
+        }
+        return super.getNamespaceContents(namespace);
+    }
+
     @Override
     public Set<Module> getSubmodules() {
         checkState(sealed, "Attempt to get base submodules from unsealed submodule effective statement %s",