Merge "BUG-1537: improved YangModuleInfo."
authorTony Tkacik <ttkacik@cisco.com>
Mon, 25 Aug 2014 10:32:33 +0000 (10:32 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 25 Aug 2014 10:32:33 +0000 (10:32 +0000)
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 befe3e88e02c494e48b287d8089cf2f05eb8fd08..87f15ab128b1ad26a53993aa7851637e2c3250a9 100644 (file)
@@ -311,16 +311,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;
     }
 
@@ -561,6 +562,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());
 
@@ -705,9 +708,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);
     }
 
@@ -1205,6 +1209,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);
@@ -1639,10 +1644,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 c7ebf66d2d3195fa9bcc7f5d7dba2cba3467cbfe..759e779eed26e29a079b7e37681515022e8a160a 100644 (file)
@@ -33,10 +33,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;
@@ -817,11 +820,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;
@@ -849,28 +858,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());