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>
}
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;
}
genCtx.get(module).addChildNodeType(notification, notificationInterface);
// Notification object
+ groupingsToGenTypes(module, notification.getGroupings());
+ addImplementedInterfaceFromUses(notification, notificationInterface);
resolveDataSchemaNodes(module, basePackageName, notificationInterface, notificationInterface,
notification.getChildNodes());
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);
}
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);
it.addImplementsType(augmentable(it));
}
- if (schemaNode instanceof DataNodeContainer) {
- addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it);
- }
-
return it;
}
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 {
return INSTANCE;
}
- «module.classBody»
+ «classBody(module, MODULE_INFO_CLASS_NAME)»
}
'''
return '''
}
- 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»
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");
sb.append("]");
return sb.toString();
}
+
+ «generateSubInfo(m)»
+
'''
def getSourcePath() {
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»
+ '''
+
}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+ }
+
+}
--- /dev/null
+module import-module {
+ yang-version 1;
+ namespace "yang:test:import";
+ prefix "i";
+
+ revision "2013-11-19" {
+ }
+
+
+ container imp-cont {}
+
+}
--- /dev/null
+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 {
+ }
+
+}
--- /dev/null
+submodule submodule1 {
+
+ belongs-to main-module {
+ prefix m;
+ }
+
+ revision 2014-04-02 {
+ }
+
+
+ typedef sub1-type {
+ type string;
+ }
+
+ container sub1-cont {}
+
+}
--- /dev/null
+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;
+ }
+ }
+
+}
--- /dev/null
+submodule submodule3 {
+
+ belongs-to main-module {
+ prefix m;
+ }
+
+ revision 2014-06-30 {
+ }
+
+
+ container sub3-cont {}
+
+}
import java.util.List;
import java.util.Set;
-
import javax.annotation.concurrent.Immutable;
/**
*/
Set<ModuleImport> getImports();
+ Set<Module> getSubmodules();
+
/**
* Returns <code>FeatureDefinition</code> instances which contain data from
* <b>feature</b> statements defined in the module.
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;
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;
}
}
}
- 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;
}
}
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<>();
buildChildren();
+ // SUBMODULES
+ for (ModuleBuilder submodule : addedSubmodules) {
+ submodules.add(submodule.build());
+ }
+
// FEATURES
for (FeatureBuilder fb : addedFeatures) {
features.add(fb.build());
includedModules.put(name, revision);
}
+ public void addSubmodule(final ModuleBuilder submodule) {
+ addedSubmodules.add(submodule);
+ }
+
protected String getSource() {
return source;
}
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;
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(),
return imports;
}
+ @Override
+ public Set<Module> getSubmodules() {
+ return submodules;
+ }
+
@Override
public Set<FeatureDefinition> getFeatures() {
return features;
}
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());