BUG-1537: improved YangModuleInfo. 34/10134/2
authorMartin Vitez <mvitez@cisco.com>
Thu, 21 Aug 2014 13:14:41 +0000 (15:14 +0200)
committerMartin Vitez <mvitez@cisco.com>
Fri, 22 Aug 2014 12:57:40 +0000 (14:57 +0200)
YangModuleInfo is generated also for submodules in form as inner class in YangModuleInfo class of
module to which it belongs. Method YangModuleInfo.getImportedModules() returns also YangModuleInfo
classes of submodules which this module includes.
Added getSubmodules() method to Module interface.
Added test.

Change-Id: Id949835d960eee3197d249f7a83be2975a63d6b3
Signed-off-by: Martin Vitez <mvitez@cisco.com>
13 files changed:
code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/BindingGeneratorImpl.java
code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/YangModuleInfoTemplate.xtend
code-generator/maven-sal-api-gen-plugin/src/test/java/org/opendaylight/yangtools/yang/unified/doc/generator/maven/YangModuleInfoCompilationTest.java [new file with mode: 0644]
code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/import-module.yang [new file with mode: 0644]
code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/main-module.yang [new file with mode: 0644]
code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule1.yang [new file with mode: 0644]
code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule2.yang [new file with mode: 0644]
code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule3.yang [new file with mode: 0644]
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/Module.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/builder/impl/BuilderUtils.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/builder/impl/ModuleBuilder.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/builder/impl/ModuleImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java

index 9c66f8d6a6bcb91f04d25ffb95fe59d5dc17428a..f42678e7c287d60c30ca69e1dceab8700d9a1305 100644 (file)
@@ -285,16 +285,17 @@ public class BindingGeneratorImpl implements BindingGenerator {
         }
         final String packageName = packageNameForGeneratedType(basePackageName, node.getPath());
         final GeneratedTypeBuilder genType = addDefaultInterfaceDefinition(packageName, node, childOf);
-        genType.addComment(node.getDescription());
-        genType.setDescription(createDescription(node, genType.getFullyQualifiedName()));
-        genType.setModuleName(module.getName());
-        genType.setReference(node.getReference());
-        genType.setSchemaPath(node.getPath().getPathFromRoot());
         if (node instanceof DataNodeContainer) {
             genCtx.get(module).addChildNodeType(node, genType);
             groupingsToGenTypes(module, ((DataNodeContainer) node).getGroupings());
             processUsesAugments((DataNodeContainer) node, module);
+            addImplementedInterfaceFromUses((DataNodeContainer) node, genType);
         }
+        genType.addComment(node.getDescription());
+        genType.setDescription(createDescription(node, genType.getFullyQualifiedName()));
+        genType.setModuleName(module.getName());
+        genType.setReference(node.getReference());
+        genType.setSchemaPath(node.getPath().getPathFromRoot());
         return genType;
     }
 
@@ -535,6 +536,8 @@ public class BindingGeneratorImpl implements BindingGenerator {
                 genCtx.get(module).addChildNodeType(notification, notificationInterface);
 
                 // Notification object
+                groupingsToGenTypes(module, notification.getGroupings());
+                addImplementedInterfaceFromUses(notification, notificationInterface);
                 resolveDataSchemaNodes(module, basePackageName, notificationInterface, notificationInterface,
                         notification.getChildNodes());
 
@@ -680,9 +683,10 @@ public class BindingGeneratorImpl implements BindingGenerator {
     private void groupingToGenType(final String basePackageName, final GroupingDefinition grouping, final Module module) {
         final String packageName = packageNameForGeneratedType(basePackageName, grouping.getPath());
         final GeneratedTypeBuilder genType = addDefaultInterfaceDefinition(packageName, grouping);
+        groupingsToGenTypes(module, grouping.getGroupings());
+        addImplementedInterfaceFromUses(grouping, genType);
         genCtx.get(module).addGroupingType(grouping.getPath(), genType);
         resolveDataSchemaNodes(module, basePackageName, genType, genType, grouping.getChildNodes());
-        groupingsToGenTypes(module, grouping.getGroupings());
         processUsesAugments(grouping, module);
     }
 
@@ -1180,6 +1184,7 @@ public class BindingGeneratorImpl implements BindingGenerator {
             if (caseNode != null && !caseNode.isAddedByUses() && !caseNode.isAugmenting()) {
                 final String packageName = packageNameForGeneratedType(basePackageName, caseNode.getPath());
                 final GeneratedTypeBuilder caseTypeBuilder = addDefaultInterfaceDefinition(packageName, caseNode);
+                addImplementedInterfaceFromUses(caseNode, caseTypeBuilder);
                 caseTypeBuilder.addImplementsType(refChoiceType);
                 genCtx.get(module).addCaseType(caseNode.getPath(), caseTypeBuilder);
                 genCtx.get(module).addChoiceToCaseMapping(refChoiceType, caseTypeBuilder, caseNode);
@@ -1614,10 +1619,6 @@ public class BindingGeneratorImpl implements BindingGenerator {
             it.addImplementsType(augmentable(it));
         }
 
-        if (schemaNode instanceof DataNodeContainer) {
-            addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it);
-        }
-
         return it;
     }
 
index 4feee7dbc0766bf47c25a0aed3e5e5a227a15515..08f19bdd70d45d42a1f30168437ec47fed887cf3 100644 (file)
@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableSet
 import static org.opendaylight.yangtools.yang.binding.BindingMapping.*
 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider
 import com.google.common.base.Preconditions
+import org.opendaylight.yangtools.yang.binding.BindingMapping
 
 class YangModuleInfoTemplate {
 
@@ -72,7 +73,7 @@ class YangModuleInfoTemplate {
                     return INSTANCE;
                 }
 
-                «module.classBody»
+                «classBody(module, MODULE_INFO_CLASS_NAME)»
             }
         '''
         return '''
@@ -96,10 +97,12 @@ class YangModuleInfoTemplate {
 
     }
 
-    private def CharSequence classBody(Module m) '''
-        private «MODULE_INFO_CLASS_NAME»() {
-            «IF m.imports.size != 0»
+    private def CharSequence classBody(Module m, String className) '''
+        private «className»() {
+            «IF !m.imports.empty || !m.submodules.empty»
                 «Set.importedName»<«YangModuleInfo.importedName»> set = new «HashSet.importedName»<>();
+            «ENDIF»
+            «IF !m.imports.empty»
                 «FOR imp : m.imports»
                     «val name = imp.moduleName»
                     «val rev = imp.revision»
@@ -116,10 +119,18 @@ class YangModuleInfoTemplate {
                         set.add(«BindingGeneratorUtil.moduleNamespaceToPackageName(ctx.findModuleByName(name, rev))».«MODULE_INFO_CLASS_NAME».getInstance());
                     «ENDIF»
                 «ENDFOR»
-                importedModules = «ImmutableSet.importedName».copyOf(set);
-            «ELSE»
+            «ENDIF»
+            «IF !m.submodules.empty»
+                «FOR submodule : m.submodules»
+                    set.add(«BindingMapping.getClassName(submodule.name)»Info.getInstance());
+                «ENDFOR»
+            «ENDIF»
+            «IF m.imports.empty && m.submodules.empty»
                 importedModules = «Collections.importedName».emptySet();
+            «ELSE»
+                importedModules = «ImmutableSet.importedName».copyOf(set);
             «ENDIF»
+
             «InputStream.importedName» stream = «MODULE_INFO_CLASS_NAME».class.getResourceAsStream(resourcePath);
             if (stream == null) {
                 throw new IllegalStateException("Resource '" + resourcePath + "' is missing");
@@ -172,6 +183,9 @@ class YangModuleInfoTemplate {
             sb.append("]");
             return sb.toString();
         }
+
+        «generateSubInfo(m)»
+
     '''
 
     def getSourcePath() {
@@ -286,4 +300,27 @@ class YangModuleInfoTemplate {
         return builder.toString();
     }
 
+    private def generateSubInfo(Module module) '''
+        «FOR submodule : module.submodules»
+            private static final class «BindingMapping.getClassName(submodule.name)»Info implements «YangModuleInfo.importedName» {
+
+                private static final «YangModuleInfo.importedName» INSTANCE = new «BindingMapping.getClassName(submodule.name)»Info();
+
+                private final «String.importedName» name = "«submodule.name»";
+                private final «String.importedName» namespace = "«submodule.namespace.toString»";
+                «val DateFormat df = new SimpleDateFormat("yyyy-MM-dd")»
+                private final «String.importedName» revision = "«df.format(submodule.revision)»";
+                private final «String.importedName» resourcePath = "/«submodule.moduleSourcePath.replace(java.io.File.separatorChar, '/')»";
+
+                private final «Set.importedName»<YangModuleInfo> importedModules;
+
+                public static «YangModuleInfo.importedName» getInstance() {
+                    return INSTANCE;
+                }
+
+                «classBody(submodule, BindingMapping.getClassName(submodule.name + "Info"))»
+            }
+        «ENDFOR»
+    '''
+
 }
diff --git a/code-generator/maven-sal-api-gen-plugin/src/test/java/org/opendaylight/yangtools/yang/unified/doc/generator/maven/YangModuleInfoCompilationTest.java b/code-generator/maven-sal-api-gen-plugin/src/test/java/org/opendaylight/yangtools/yang/unified/doc/generator/maven/YangModuleInfoCompilationTest.java
new file mode 100644 (file)
index 0000000..1208f19
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. 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.unified.doc.generator.maven;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangContextParser;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.sonatype.plexus.build.incremental.DefaultBuildContext;
+
+/**
+ * Test correct generation of YangModuleInfo class.
+ *
+ */
+// TODO: most of private static methods are copied from
+// binding-java-api-generator project - reorganize compilation tests
+public class YangModuleInfoCompilationTest {
+    public static final String FS = File.separator;
+    private static final String BASE_PKG = "org.opendaylight.yang.gen.v1";
+
+    private static final String TEST_PATH = "target" + FS + "test";
+    private static final File TEST_DIR = new File(TEST_PATH);
+
+    private static final String GENERATOR_OUTPUT_PATH = TEST_PATH + FS + "src";
+    private static final File GENERATOR_OUTPUT_DIR = new File(GENERATOR_OUTPUT_PATH);
+    private static final String COMPILER_OUTPUT_PATH = TEST_PATH + FS + "bin";
+    private static final File COMPILER_OUTPUT_DIR = new File(COMPILER_OUTPUT_PATH);
+
+    @BeforeClass
+    public static void createTestDirs() {
+        if (TEST_DIR.exists()) {
+            deleteTestDir(TEST_DIR);
+        }
+        assertTrue(GENERATOR_OUTPUT_DIR.mkdirs());
+        assertTrue(COMPILER_OUTPUT_DIR.mkdirs());
+    }
+
+    @Test
+    public void compilationTest() throws Exception {
+        final File sourcesOutputDir = new File(GENERATOR_OUTPUT_PATH + FS + "yang");
+        assertTrue("Failed to create test file '" + sourcesOutputDir + "'", sourcesOutputDir.mkdirs());
+        final File compiledOutputDir = new File(COMPILER_OUTPUT_PATH + FS + "yang");
+        assertTrue("Failed to create test file '" + compiledOutputDir + "'", compiledOutputDir.mkdirs());
+
+        generateTestSources("/yang-module-info", sourcesOutputDir);
+
+        // Test if $YangModuleInfoImpl.java file is generated
+        final String BASE_PATH = "org" + FS + "opendaylight" + FS + "yang" + FS + "gen" + FS + "v1";
+        final String NS_TEST = BASE_PATH + FS + "yang" + FS + "test" + FS + "main" + FS + "rev140630";
+        File parent = new File(sourcesOutputDir, NS_TEST);
+        File keyArgs = new File(parent, "$YangModuleInfoImpl.java");
+        assertTrue(keyArgs.exists());
+
+        // Test if sources are compilable
+        testCompilation(sourcesOutputDir, compiledOutputDir);
+
+        // Create URLClassLoader
+        File[] roots = File.listRoots();
+        URL[] urls = new URL[roots.length + 1];
+        for (int i = 0; i < roots.length; i++) {
+            urls[i] = roots[i].toURI().toURL();
+
+        }
+        urls[roots.length] = compiledOutputDir.toURI().toURL();
+        ClassLoader loader = new URLClassLoader(urls);
+
+        // Load class
+        Class<?> yangModuleInfoClass = Class.forName(BASE_PKG + ".yang.test.main.rev140630.$YangModuleInfoImpl", true,
+                loader);
+
+        // Test generated $YangModuleInfoImpl class
+        assertFalse(yangModuleInfoClass.isInterface());
+        Method getInstance = assertContainsMethod(yangModuleInfoClass, YangModuleInfo.class, "getInstance");
+        Object yangModuleInfo = getInstance.invoke(null);
+
+        // Test getImportedModules method
+        Method getImportedModules = assertContainsMethod(yangModuleInfoClass, Set.class, "getImportedModules");
+        Object importedModules = getImportedModules.invoke(yangModuleInfo);
+        assertTrue(importedModules instanceof Set);
+
+        YangModuleInfo infoImport = null;
+        YangModuleInfo infoSub1 = null;
+        YangModuleInfo infoSub2 = null;
+        YangModuleInfo infoSub3 = null;
+        for (Object importedModule : (Set<?>) importedModules) {
+            assertTrue(importedModule instanceof YangModuleInfo);
+            YangModuleInfo ymi = (YangModuleInfo) importedModule;
+            String name = ymi.getName();
+
+            switch (name) {
+            case "import-module":
+                infoImport = ymi;
+                break;
+            case "submodule1":
+                infoSub1 = ymi;
+                break;
+            case "submodule2":
+                infoSub2 = ymi;
+                break;
+            case "submodule3":
+                infoSub3 = ymi;
+            }
+        }
+        assertNotNull(infoImport);
+        assertNotNull(infoSub1);
+        assertNotNull(infoSub2);
+        assertNotNull(infoSub3);
+
+        cleanUp(sourcesOutputDir, compiledOutputDir);
+    }
+
+    private void generateTestSources(String resourceDirPath, File sourcesOutputDir) throws Exception {
+        final List<File> sourceFiles = getSourceFiles(resourceDirPath);
+        YangContextParser parser = new YangParserImpl();
+        final SchemaContext context = parser.parseFiles(sourceFiles);
+        CodeGeneratorImpl codegen = new CodeGeneratorImpl();
+        codegen.setBuildContext(new DefaultBuildContext());
+        codegen.generateSources(context, sourcesOutputDir, context.getModules());
+    }
+
+    private static void testCompilation(File sourcesOutputDir, File compiledOutputDir) {
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+        List<File> filesList = getJavaFiles(sourcesOutputDir);
+        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(filesList);
+        Iterable<String> options = Arrays.asList("-d", compiledOutputDir.getAbsolutePath());
+        boolean compiled = compiler.getTask(null, null, null, options, null, compilationUnits).call();
+        assertTrue(compiled);
+    }
+
+    private static List<File> getJavaFiles(File directory) {
+        List<File> result = new ArrayList<>();
+        File[] filesToRead = directory.listFiles();
+        if (filesToRead != null) {
+            for (File file : filesToRead) {
+                if (file.isDirectory()) {
+                    result.addAll(getJavaFiles(file));
+                } else {
+                    String absPath = file.getAbsolutePath();
+                    if (absPath.endsWith(".java")) {
+                        result.add(file);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    private static List<File> getSourceFiles(String path) throws Exception {
+        final URI resPath = YangModuleInfoCompilationTest.class.getResource(path).toURI();
+        final File sourcesDir = new File(resPath);
+        if (sourcesDir.exists()) {
+            final List<File> sourceFiles = new ArrayList<>();
+            final File[] fileArray = sourcesDir.listFiles();
+            if (fileArray == null) {
+                throw new IllegalArgumentException("Unable to locate files in " + sourcesDir);
+            }
+            sourceFiles.addAll(Arrays.asList(fileArray));
+            return sourceFiles;
+        } else {
+            throw new FileNotFoundException("Testing files were not found(" + sourcesDir.getName() + ")");
+        }
+    }
+
+    private static void deleteTestDir(File file) {
+        if (file.isDirectory()) {
+            File[] filesToDelete = file.listFiles();
+            if (filesToDelete != null) {
+                for (File f : filesToDelete) {
+                    deleteTestDir(f);
+                }
+            }
+        }
+        if (!file.delete()) {
+            throw new RuntimeException("Failed to clean up after test");
+        }
+    }
+
+    private static Method assertContainsMethod(Class<?> clazz, Class<?> returnType, String name, Class<?>... args) {
+        try {
+            Method m = clazz.getDeclaredMethod(name, args);
+            assertEquals(returnType, m.getReturnType());
+            return m;
+        } catch (NoSuchMethodException e) {
+            throw new AssertionError("Method " + name + " with args " + Arrays.toString(args)
+                    + " does not exists in class " + clazz.getSimpleName());
+        }
+    }
+
+    private static void cleanUp(File... resourceDirs) {
+        for (File resourceDir : resourceDirs) {
+            if (resourceDir.exists()) {
+                deleteTestDir(resourceDir);
+            }
+        }
+    }
+
+}
diff --git a/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/import-module.yang b/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/import-module.yang
new file mode 100644 (file)
index 0000000..4c29dcb
--- /dev/null
@@ -0,0 +1,12 @@
+module import-module {
+    yang-version 1;
+    namespace "yang:test:import";
+    prefix "i";
+
+    revision "2013-11-19" {
+    }
+
+
+    container imp-cont {}
+
+}
diff --git a/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/main-module.yang b/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/main-module.yang
new file mode 100644 (file)
index 0000000..c20b20f
--- /dev/null
@@ -0,0 +1,25 @@
+module main-module {
+
+    namespace "yang:test:main";
+    prefix m;
+
+    include submodule1 {
+        revision-date 2014-04-02;
+    }
+
+    include submodule2 {
+        revision-date 2014-06-30;
+    }
+
+    include submodule3 {
+        revision-date 2014-06-30;
+    }
+
+    import import-module {
+        prefix n;
+    }
+
+    revision 2014-06-30 {
+    }
+
+}
diff --git a/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule1.yang b/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule1.yang
new file mode 100644 (file)
index 0000000..02e1478
--- /dev/null
@@ -0,0 +1,17 @@
+submodule submodule1 {
+
+    belongs-to main-module {
+        prefix m;
+    }
+
+    revision 2014-04-02 {
+    }
+
+
+    typedef sub1-type {
+        type string;
+    }
+
+    container sub1-cont {}
+
+}
diff --git a/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule2.yang b/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule2.yang
new file mode 100644 (file)
index 0000000..fcdf5e1
--- /dev/null
@@ -0,0 +1,21 @@
+submodule submodule2 {
+
+    belongs-to main-module {
+        prefix m;
+    }
+
+    include submodule1 {
+        revision-date 2014-04-02;
+    }
+
+    revision 2014-06-30 {
+    }
+
+
+    grouping sub2-gr {
+        leaf sub2-leaf {
+            type string;
+        }
+    }
+
+}
diff --git a/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule3.yang b/code-generator/maven-sal-api-gen-plugin/src/test/resources/yang-module-info/submodule3.yang
new file mode 100644 (file)
index 0000000..2602814
--- /dev/null
@@ -0,0 +1,13 @@
+submodule submodule3 {
+
+    belongs-to main-module {
+        prefix m;
+    }
+
+    revision 2014-06-30 {
+    }
+
+
+    container sub3-cont {}
+
+}
index 23eb68ef1de1cdc268cc06da40b57d2c47a0ab6f..9a28d8fe633a247c75724c767a1a5e702d295473 100644 (file)
@@ -9,7 +9,6 @@ package org.opendaylight.yangtools.yang.model.api;
 
 import java.util.List;
 import java.util.Set;
-
 import javax.annotation.concurrent.Immutable;
 
 /**
@@ -128,6 +127,8 @@ public interface Module extends DataNodeContainer, SourceStreamAware, ModuleIden
      */
     Set<ModuleImport> getImports();
 
+    Set<Module> getSubmodules();
+
     /**
      * Returns <code>FeatureDefinition</code> instances which contain data from
      * <b>feature</b> statements defined in the module.
index 4a61d7b0f6d0eef9458a16fbc1c3becd97d3976c..d2bb65227e8118e779b6f07bc871f4ad2a10983f 100644 (file)
@@ -35,10 +35,13 @@ import java.util.Set;
 import java.util.TreeMap;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.apache.commons.io.IOUtils;
+import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_header_stmtsContext;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_stmtContext;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Namespace_stmtContext;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtsContext;
+import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_header_stmtsContext;
+import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_stmtContext;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
@@ -822,11 +825,17 @@ public final class BuilderUtils {
 
     public static Map<String, TreeMap<Date, URI>> createYangNamespaceContext(
             final Collection<? extends ParseTree> modules, final Optional<SchemaContext> context) {
-        Map<String, TreeMap<Date, URI>> map = new HashMap<>();
+        Map<String, TreeMap<Date, URI>> namespaceContext = new HashMap<>();
+        Set<Submodule_stmtContext> submodules = new HashSet<>();
+        // first read ParseTree collection and separate modules and submodules
         for (ParseTree module : modules) {
             for (int i = 0; i < module.getChildCount(); i++) {
                 ParseTree moduleTree = module.getChild(i);
-                if (moduleTree instanceof Module_stmtContext) {
+                if (moduleTree instanceof Submodule_stmtContext) {
+                    // put submodule context to separate collection
+                    submodules.add((Submodule_stmtContext) moduleTree);
+                } else if (moduleTree instanceof Module_stmtContext) {
+                    // get name, revision and namespace from module
                     Module_stmtContext moduleCtx = (Module_stmtContext) moduleTree;
                     final String moduleName = ParserListenerUtils.stringFromNode(moduleCtx);
                     Date rev = null;
@@ -854,28 +863,56 @@ public final class BuilderUtils {
                             }
                         }
                     }
-                    TreeMap<Date, URI> revToNs = map.get(moduleName);
+                    // update namespaceContext
+                    TreeMap<Date, URI> revToNs = namespaceContext.get(moduleName);
                     if (revToNs == null) {
                         revToNs = new TreeMap<>();
                         revToNs.put(rev, namespace);
-                        map.put(moduleName, revToNs);
+                        namespaceContext.put(moduleName, revToNs);
                     }
                     revToNs.put(rev, namespace);
                 }
             }
         }
+        // after all ParseTree-s are parsed update namespaceContext with modules
+        // from SchemaContext
         if (context.isPresent()) {
             for (Module module : context.get().getModules()) {
-                TreeMap<Date, URI> revToNs = map.get(module.getName());
+                TreeMap<Date, URI> revToNs = namespaceContext.get(module.getName());
                 if (revToNs == null) {
                     revToNs = new TreeMap<>();
                     revToNs.put(module.getRevision(), module.getNamespace());
-                    map.put(module.getName(), revToNs);
+                    namespaceContext.put(module.getName(), revToNs);
                 }
                 revToNs.put(module.getRevision(), module.getNamespace());
             }
         }
-        return map;
+        // when all modules are processed, traverse submodules and update
+        // namespaceContext with mapping for submodules
+        for (Submodule_stmtContext submodule : submodules) {
+            final String moduleName = ParserListenerUtils.stringFromNode(submodule);
+            for (int i = 0; i < submodule.getChildCount(); i++) {
+                ParseTree subHeaderCtx = submodule.getChild(i);
+                if (subHeaderCtx instanceof Submodule_header_stmtsContext) {
+                    for (int j = 0; j < subHeaderCtx.getChildCount(); j++) {
+                        ParseTree belongsCtx = subHeaderCtx.getChild(j);
+                        if (belongsCtx instanceof Belongs_to_stmtContext) {
+                            final String belongsTo = ParserListenerUtils.stringFromNode(belongsCtx);
+                            TreeMap<Date, URI> ns = namespaceContext.get(belongsTo);
+                            if (ns == null) {
+                                throw new YangParseException(moduleName, submodule.getStart().getLine(), String.format(
+                                        "Unresolved belongs-to statement: %s", belongsTo));
+                            }
+                            // submodule get namespace and revision from module
+                            TreeMap<Date, URI> subNs = new TreeMap<>();
+                            subNs.put(ns.firstKey(), ns.firstEntry().getValue());
+                            namespaceContext.put(moduleName, subNs);
+                        }
+                    }
+                }
+            }
+        }
+        return namespaceContext;
     }
 
 }
index 3d43e0025f8c98de7ec0e604e646420fa83da5f8..5c4afe6ae17b49e99535784d7e4ab2a74c28f26a 100644 (file)
@@ -79,7 +79,9 @@ public class ModuleBuilder extends AbstractDocumentedDataNodeContainerBuilder im
     final Map<String, ModuleImport> imports = new HashMap<>();
     final Map<String, ModuleBuilder> importedModules = new HashMap<>();
 
-    private final Map<String, Date> includedModules = new HashMap<>();
+    final Set<ModuleBuilder> addedSubmodules = new HashSet<>();
+    final Set<Module> submodules = new HashSet<>();
+    final Map<String, Date> includedModules = new HashMap<>();
 
     private final Set<AugmentationSchema> augments = new LinkedHashSet<>();
     private final List<AugmentationSchemaBuilder> augmentBuilders = new ArrayList<>();
@@ -171,6 +173,11 @@ public class ModuleBuilder extends AbstractDocumentedDataNodeContainerBuilder im
 
         buildChildren();
 
+        // SUBMODULES
+        for (ModuleBuilder submodule : addedSubmodules) {
+            submodules.add(submodule.build());
+        }
+
         // FEATURES
         for (FeatureBuilder fb : addedFeatures) {
             features.add(fb.build());
@@ -374,6 +381,10 @@ public class ModuleBuilder extends AbstractDocumentedDataNodeContainerBuilder im
         includedModules.put(name, revision);
     }
 
+    public void addSubmodule(final ModuleBuilder submodule) {
+        addedSubmodules.add(submodule);
+    }
+
     protected String getSource() {
         return source;
     }
index d2c3729871227954435671f78d27bece1e06a8f9..00018b8dda1128e20704ac15f5b89d9fff0859e4 100644 (file)
@@ -36,6 +36,7 @@ public final class ModuleImpl extends AbstractDocumentedDataNodeContainer implem
     private final String organization;
     private final String contact;
     private final Set<ModuleImport> imports;
+    private final Set<Module> submodules;
     private final Set<FeatureDefinition> features;
     private final Set<NotificationDefinition> notifications;
     private final Set<AugmentationSchema> augmentations;
@@ -61,6 +62,7 @@ public final class ModuleImpl extends AbstractDocumentedDataNodeContainer implem
         this.name = checkNotNull(name, "Missing name");
         this.sourcePath = sourcePath; //TODO: can this be nullable?
         this.imports = ImmutableSet.<ModuleImport> copyOf(builder.imports.values());
+        this.submodules = ImmutableSet.<Module> copyOf(builder.submodules);
         this.prefix = builder.getPrefix();
 
         this.qnameModule = QNameModule.create(builder.getNamespace(),
@@ -125,6 +127,11 @@ public final class ModuleImpl extends AbstractDocumentedDataNodeContainer implem
         return imports;
     }
 
+    @Override
+    public Set<Module> getSubmodules() {
+        return submodules;
+    }
+
     @Override
     public Set<FeatureDefinition> getFeatures() {
         return features;
index 1ed5986df5c75d4f53f162f2988ad2a4f844256a..38055d908df31608a5a3d100c8976fc69f0c182c 100644 (file)
@@ -503,6 +503,7 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     private void addSubmoduleToModule(final ModuleBuilder submodule, final ModuleBuilder module) {
+        module.addSubmodule(submodule);
         submodule.setParent(module);
         module.getDirtyNodes().addAll(submodule.getDirtyNodes());
         module.getImports().putAll(submodule.getImports());