From c6e3f104f035b0d7aee05fdc75d11e1a2de50f3c Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 19 May 2019 21:39:47 +0200 Subject: [PATCH] Populate SubmoduleEffectiveModule with import namespaces 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 --- .../api/stmt/SubmoduleEffectiveStatement.java | 5 + .../export/DeclaredStatementFormatter.java | 10 +- .../yang/model/export/ExportUtils.java | 68 ------- .../model/export/ModuleNamespaceContext.java | 11 -- .../model/export/StatementPrefixResolver.java | 176 ++++++++++++++++++ .../yang/model/export/YangTextSnippet.java | 10 +- .../model/export/YangTextSnippetIterator.java | 14 +- .../model/export/YangTextSnippetTest.java | 30 ++- .../bugs/yt992/module1@2019-05-17.yang | 14 ++ .../yt992/module1submodule1@2019-05-17.yang | 22 +++ .../bugs/yt992/module2@2019-05-17.yang | 18 ++ .../rfc7950/stmt/AbstractEffectiveModule.java | 19 ++ .../module/ModuleEffectiveStatementImpl.java | 12 +- .../SubmoduleEffectiveStatementImpl.java | 34 +++- 14 files changed, 331 insertions(+), 112 deletions(-) delete mode 100644 yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ExportUtils.java create mode 100644 yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/StatementPrefixResolver.java create mode 100644 yang/yang-model-export/src/test/resources/bugs/yt992/module1@2019-05-17.yang create mode 100644 yang/yang-model-export/src/test/resources/bugs/yt992/module1submodule1@2019-05-17.yang create mode 100644 yang/yang-model-export/src/test/resources/bugs/yt992/module2@2019-05-17.yang diff --git a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/SubmoduleEffectiveStatement.java b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/SubmoduleEffectiveStatement.java index 79c95a8299..953285a96a 100644 --- a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/SubmoduleEffectiveStatement.java +++ b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/SubmoduleEffectiveStatement.java @@ -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 { diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/DeclaredStatementFormatter.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/DeclaredStatementFormatter.java index 040f0c9a9d..e0efa40e7b 100644 --- a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/DeclaredStatementFormatter.java +++ b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/DeclaredStatementFormatter.java @@ -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 index 585cfeac6a..0000000000 --- a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ExportUtils.java +++ /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 statementPrefix(final Map 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 match = null; - for (Entry 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); - } -} diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ModuleNamespaceContext.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ModuleNamespaceContext.java index c74562e5a6..08c505e87e 100644 --- a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ModuleNamespaceContext.java +++ b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/ModuleNamespaceContext.java @@ -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 prefixAndNamespaceForStatement(final QName stmtName) { - final Optional prefix = ExportUtils.statementPrefix(moduleToPrefix, stmtName); - if (!prefix.isPresent()) { - return YIN_PREFIX_AND_NAMESPACE; - } - - return new SimpleImmutableEntry<>(prefix.get(), stmtName.getNamespace().toString()); - } - Map 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 index 0000000000..34332c7b66 --- /dev/null +++ b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/StatementPrefixResolver.java @@ -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, String>> statements; + + Conflict(final Collection, 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 lookup; + + private StatementPrefixResolver(final Map map) { + this.lookup = ImmutableMap.copyOf(map); + } + + private StatementPrefixResolver(final ImmutableMap map) { + this.lookup = requireNonNull(map); + } + + static StatementPrefixResolver forModule(final ModuleEffectiveStatement module) { + final Map imports = module.getAll(QNameModuleToPrefixNamespace.class); + final Collection 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>> 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 builder = ImmutableMap.builderWithExpectedSize(prefixToNamespaces.size()); + + // ... first resolve unambiguous mappings ... + final Iterator>>> it = + prefixToNamespaces.entrySet().iterator(); + while (it.hasNext()) { + final Entry>> entry = it.next(); + final Multimap> 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, String>> conflicts = ArrayListMultimap.create(); + for (Entry>> entry : prefixToNamespaces.entrySet()) { + for (Entry> 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 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 match = null; + for (Entry 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 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>> map, + final Map imports, final EffectiveStatement stmt) { + for (Entry entry : imports.entrySet()) { + map.computeIfAbsent(entry.getValue(), key -> ArrayListMultimap.create()).put(entry.getKey(), stmt); + } + } +} diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippet.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippet.java index 9355e1e049..abdefd7f72 100644 --- a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippet.java +++ b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippet.java @@ -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 mapper; + private final StatementPrefixResolver resolver; private final DeclaredStatement statement; private final boolean omitDefaultStatements; - YangTextSnippet(final DeclaredStatement statement, final Map 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 diff --git a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetIterator.java b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetIterator.java index b97e0c5c12..78a5e61f4d 100644 --- a/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetIterator.java +++ b/yang/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetIterator.java @@ -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 strings = new ArrayDeque<>(8); // Let's be modest, 16-level deep constructs are not exactly common. private final Deque>> stack = new ArrayDeque<>(8); - private final Map namespaces; private final Set ignoredStatements; + private final StatementPrefixResolver resolver; private final boolean omitDefaultStatements; - YangTextSnippetIterator(final DeclaredStatement stmt, final Map namespaces, + YangTextSnippetIterator(final DeclaredStatement stmt, final StatementPrefixResolver resolver, final Set 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 prefix = ExportUtils.statementPrefix(namespaces, stmtName); + final Optional 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()); diff --git a/yang/yang-model-export/src/test/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetTest.java b/yang/yang-model-export/src/test/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetTest.java index f1740cb470..206c939bd1 100644 --- a/yang/yang-model-export/src/test/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetTest.java +++ b/yang/yang-model-export/src/test/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetTest.java @@ -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 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 index 0000000000..77d25ff3bb --- /dev/null +++ b/yang/yang-model-export/src/test/resources/bugs/yt992/module1@2019-05-17.yang @@ -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 index 0000000000..fd3150195d --- /dev/null +++ b/yang/yang-model-export/src/test/resources/bugs/yt992/module1submodule1@2019-05-17.yang @@ -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 index 0000000000..129f420f68 --- /dev/null +++ b/yang/yang-model-export/src/test/resources/bugs/yt992/module2@2019-05-17.yang @@ -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 diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/AbstractEffectiveModule.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/AbstractEffectiveModule.java index 7f66ecd4e6..8eeb12bf3f 100644 --- a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/AbstractEffectiveModule.java +++ b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/AbstractEffectiveModule.java @@ -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; @@ -42,13 +44,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 @@ -285,4 +291,17 @@ public abstract class AbstractEffectiveModule ctx, + final Builder 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()); + }); + } } diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleEffectiveStatementImpl.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleEffectiveStatementImpl.java index 9e4df502fb..7bda11fac5 100644 --- a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleEffectiveStatementImpl.java +++ b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/module/ModuleEffectiveStatementImpl.java @@ -30,7 +30,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; @@ -40,7 +39,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; @@ -64,15 +62,7 @@ final class ModuleEffectiveStatementImpl extends AbstractEffectiveModule 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 tmp = Maps.newLinkedHashMapWithExpectedSize(prefixToModule.size() + 1); diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/submodule/SubmoduleEffectiveStatementImpl.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/submodule/SubmoduleEffectiveStatementImpl.java index 594ec2d5cd..c3d1d6731c 100644 --- a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/submodule/SubmoduleEffectiveStatementImpl.java +++ b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/submodule/SubmoduleEffectiveStatementImpl.java @@ -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.Optional; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; @@ -21,7 +25,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; @@ -36,7 +44,8 @@ import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule implements SubmoduleEffectiveStatement, MutableStatement { - + private final ImmutableMap prefixToModule; + private final ImmutableMap namespaceToPrefix; private final QNameModule qnameModule; private Set>> submoduleContexts; @@ -50,6 +59,16 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule prefixToModuleBuilder = ImmutableMap.builder(); + appendPrefixes(ctx, prefixToModuleBuilder); + prefixToModule = prefixToModuleBuilder.build(); + + final Map tmp = Maps.newLinkedHashMapWithExpectedSize(prefixToModule.size()); + for (Entry e : prefixToModule.entrySet()) { + tmp.putIfAbsent(e.getValue().localQNameModule(), e.getKey()); + } + namespaceToPrefix = ImmutableMap.copyOf(tmp); + final Optional submoduleRevision = findFirstEffectiveSubstatementArgument( RevisionEffectiveStatement.class); this.qnameModule = QNameModule.create(belongsToModuleQName.getNamespace(), submoduleRevision).intern(); @@ -87,6 +106,19 @@ final class SubmoduleEffectiveStatementImpl extends AbstractEffectiveModule> Optional> getNamespaceContents( + final @NonNull Class namespace) { + if (PrefixToEffectiveModuleNamespace.class.equals(namespace)) { + return Optional.of((Map) prefixToModule); + } + if (QNameModuleToPrefixNamespace.class.equals(namespace)) { + return Optional.of((Map) namespaceToPrefix); + } + return super.getNamespaceContents(namespace); + } + @Override public Set getSubmodules() { checkState(sealed, "Attempt to get base submodules from unsealed submodule effective statement %s", -- 2.36.6