Code Generator Prototype - Binding Specification v2 56/55056/2
authorMartin Ciglan <mciglan@cisco.com>
Fri, 7 Apr 2017 10:53:10 +0000 (12:53 +0200)
committerRobert Varga <nite@hq.sk>
Sat, 15 Apr 2017 17:25:44 +0000 (17:25 +0000)
- provide means for processing generated types
to actual compilable Java files
- generators & renderers adjustments (enum, interface, class, builder)
- code clean up
- bugfix revealed while testing

Change-Id: I1c94e6b53b62cc4b77c36c87b909877c8a072d56
Signed-off-by: Martin Ciglan <mciglan@cisco.com>
(cherry picked from commit 4207ff2998d5b9ddd1483e52e20e29084b444605)

binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/yang/types/TypeProviderImpl.java
binding2/mdsal-binding2-generator-util/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/util/ReferencedTypeImpl.java
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/javav2/java/api/generator/BuilderGenerator.java
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/javav2/java/api/generator/GeneratorJavaFile.java
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/javav2/java/api/generator/renderers/BuilderRenderer.java
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/javav2/java/api/generator/renderers/InterfaceRenderer.java
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/javav2/java/api/generator/renderers/YangModuleInfoTemplateRenderer.java
binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding/javav2/java/api/generator/builderTemplate.scala.txt
binding2/mdsal-binding2-maven-api-gen-plugin/src/main/java/org/opendaylight/mdsal/binding/javav2/maven/api/gen/plugin/CodeGeneratorImpl.java

index ef2109f478994ad8b27be64e89ad5f3214a286b5..a57f29cce67bb94e4fd752effc25242e2746432f 100644 (file)
@@ -140,7 +140,7 @@ public final class TypeProviderImpl implements TypeProvider {
      * @throws IllegalArgumentException
      *             <ul>
      *             <li>if <code>typeDefinition</code> equal null</li>
-     *             <li>if Qname of <code>typeDefinition</code> equal null</li>
+     *             <li>if QName of <code>typeDefinition</code> equal null</li>
      *             <li>if name of <code>typeDefinition</code> equal null</li>
      *             </ul>
      */
@@ -348,7 +348,7 @@ public final class TypeProviderImpl implements TypeProvider {
      *             <ul>
      *             <li>if <code>basePackageName</code> is null</li>
      *             <li>if <code>typedef</code> is null</li>
-     *             <li>if Qname of <code>typedef</code> is null</li>
+     *             <li>if QName of <code>typedef</code> is null</li>
      *             </ul>
      */
     @SuppressWarnings({ "rawtypes", "unchecked" })
index 3ea30de3ea5ff33f5fc6d9923ba4cb9780e86031..03a812e497862e341e7da80601fb5705a48e661c 100644 (file)
@@ -28,6 +28,21 @@ public final class ReferencedTypeImpl extends AbstractBaseType {
         super(packageName, name);
     }
 
+    /**
+     * Creates instance of this class with concrete package name and type name
+     * for already normalized identifier
+     *
+     * @param packageName
+     *            string with the package name
+     * @param name
+     *            string with the name for referenced type
+     * @param isNormalized
+     *            indicates if identifier name is normalized
+     */
+    public ReferencedTypeImpl(String packageName, String name, boolean isNormalized) {
+        super(packageName, name, isNormalized);
+    }
+
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
index d31bb59d0f39899495057397dcd15bccc8aa7e53..f0a17b82f6ae49dae3743aad7b2c99abec46be5e 100644 (file)
@@ -27,6 +27,11 @@ import org.opendaylight.yangtools.concepts.Identifier;
 @Beta
 public final class BuilderGenerator implements CodeGenerator {
 
+    /**
+     * Constant used as suffix for builder name.
+     */
+    public static final String BUILDER = "Builder";
+
     @Override
     public String generate(Type type) {
         if ((type instanceof GeneratedType) && !(type instanceof GeneratedTransferObject)) {
@@ -55,6 +60,6 @@ public final class BuilderGenerator implements CodeGenerator {
 
     @Override
     public Identifier getUnitName(Type type) {
-        return new UnitName(type.getName());
+        return new UnitName(type.getName() + BUILDER);
     }
 }
index 1cf281b6317b5aa14cffae559376758052834f91..ab869983b6ce5d5ffd0c39cb901728df97b66036 100644 (file)
@@ -10,11 +10,22 @@ package org.opendaylight.mdsal.binding.javav2.java.api.generator;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import org.opendaylight.mdsal.binding.javav2.model.api.CodeGenerator;
+import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
+import org.opendaylight.mdsal.binding.javav2.model.api.UnitName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonatype.plexus.build.incremental.BuildContext;
@@ -26,6 +37,7 @@ import org.sonatype.plexus.build.incremental.BuildContext;
 public final class GeneratorJavaFile {
 
     private static final Logger LOG = LoggerFactory.getLogger(GeneratorJavaFile.class);
+    private static final Splitter BSDOT_SPLITTER = Splitter.on(".");
 
     /**
      * List of <code>CodeGenerator</code> instances.
@@ -57,10 +69,150 @@ public final class GeneratorJavaFile {
         this.buildContext = Preconditions.checkNotNull(buildContext);
         this.types = Preconditions.checkNotNull(types);
         generators.add(new EnumGenerator());
-        //TODO: finish generators
-//        generators.add(new InterfaceGenerator());
+        generators.add(new InterfaceGenerator());
+        generators.add(new BuilderGenerator());
+        //TODO: finish generators below
 //        generators.add(new TOGenerator());
-//        generators.add(new BuilderGenerator());
+    }
+
+    /**
+     * Generates <code>List</code> of files for collection of types. All files are stored
+     * to sub-folders of base directory <code>persistentSourcesDirectory</code>. Subdirectories
+     * are generated according to packages to which the type belongs (e. g. if
+     * type belongs to the package <i>org.pcg</i> then in <code>persistentSourcesDirectory</code>
+     * is created directory <i>org</i> which contains <i>pcg</i>).
+     *
+     * @param generatedSourcesDirectory expected output directory for generated sources configured by
+     *            user
+     * @param persistentSourcesDirectory base directory
+     * @return list of generated files
+     * @throws IOException thrown in case of I/O error
+     */
+    public List<File> generateToFile(final File generatedSourcesDirectory, final File persistentSourcesDirectory)
+            throws IOException {
+        final List<File> result = new ArrayList<>();
+        for (Type type : types) {
+            if (type != null) {
+                for (CodeGenerator generator : generators) {
+                    File generatedJavaFile = null;
+                    if (type instanceof GeneratedTransferObject
+                            && ((GeneratedTransferObject) type).isUnionTypeBuilder()) {
+                        File packageDir = packageToDirectory(persistentSourcesDirectory, type.getPackageName());
+                        File file = new File(packageDir, generator.getUnitName(type) + ".java");
+                        if (!file.exists()) {
+                            generatedJavaFile = generateTypeToJavaFile(persistentSourcesDirectory, type, generator);
+                        }
+                    } else {
+                        generatedJavaFile = generateTypeToJavaFile(generatedSourcesDirectory, type, generator);
+                    }
+                    if (generatedJavaFile != null) {
+                        result.add(generatedJavaFile);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Creates the package directory path as concatenation of
+     * <code>parentDirectory</code> and parsed <code>packageName</code>. The
+     * parsing of <code>packageName</code> is realized as replacement of the
+     * package name dots with the file system separator.
+     *
+     * @param parentDirectory
+     *            <code>File</code> object with reference to parent directory
+     * @param packageName
+     *            string with the name of the package
+     * @return <code>File</code> object which refers to the new directory for
+     *         package <code>packageName</code>
+     */
+    public static File packageToDirectory(final File parentDirectory, final String packageName) {
+        if (packageName == null) {
+            throw new IllegalArgumentException("Package Name cannot be NULL!");
+        }
+
+        final StringBuilder dirPathBuilder = new StringBuilder();
+        final Iterator<String> packageElementsItr = BSDOT_SPLITTER.split(packageName).iterator();
+        if (packageElementsItr.hasNext()) {
+            dirPathBuilder.append(packageElementsItr.next());
+        }
+
+        while (packageElementsItr.hasNext()) {
+            dirPathBuilder.append(File.separator);
+            dirPathBuilder.append(packageElementsItr.next());
+        }
+
+        return new File(parentDirectory, dirPathBuilder.toString());
+    }
+
+    /**
+     * Generates <code>File</code> for <code>type</code>. All files are stored
+     * to sub-folders of base directory <code>parentDir</code>. Subdirectories
+     * are generated according to packages to which the type belongs (e. g. if
+     * type belongs to the package <i>org.pcg</i> then in <code>parentDir</code>
+     * is created directory <i>org</i> which contains <i>pcg</i>).
+     *
+     * @param parentDir
+     *            directory where should be the new file generated
+     * @param type
+     *            JAVA <code>Type</code> for which should be JAVA source code
+     *            generated
+     * @param generator
+     *            code generator which is used for generating of the source code
+     * @return file which contains JAVA source code
+     * @throws IOException
+     *             if the error during writing to the file occurs
+     * @throws IllegalArgumentException
+     *             if <code>type</code> equals <code>null</code>
+     * @throws IllegalStateException
+     *             if string with generated code is empty
+     */
+    private File generateTypeToJavaFile(final File parentDir, final Type type, final CodeGenerator generator)
+            throws IOException {
+        if (parentDir == null) {
+            LOG.warn("Parent Directory not specified, files will be generated "
+                    + "accordingly to generated Type package path.");
+        }
+        if (type == null) {
+            LOG.error("Cannot generate Type into Java File because " + "Generated Type is NULL!");
+            throw new IllegalArgumentException("Generated Type Cannot be NULL!");
+        }
+        if (generator == null) {
+            LOG.error("Cannot generate Type into Java File because " + "Code Generator instance is NULL!");
+            throw new IllegalArgumentException("Code Generator Cannot be NULL!");
+        }
+        final File packageDir = packageToDirectory(parentDir, type.getPackageName());
+
+        if (!packageDir.exists()) {
+            packageDir.mkdirs();
+        }
+
+        if (generator.isAcceptable(type)) {
+            final String generatedCode = generator.generate(type);
+            Preconditions.checkState(!generatedCode.isEmpty(), "Generated code should not be empty!");
+            final File file = new File(packageDir, ((UnitName) generator.getUnitName(type)).getValue() + ".java");
+
+            if (file.exists()) {
+                LOG.warn("Naming conflict for type '{}': file with same name already exists and will not be generated.",
+                    type.getFullyQualifiedName());
+                return null;
+            }
+
+            try (final OutputStream stream = buildContext.newFileOutputStream(file)) {
+                try (final Writer fw = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
+                    try (final BufferedWriter bw = new BufferedWriter(fw)) {
+                        bw.write(generatedCode);
+                    }
+                } catch (IOException e) {
+                    LOG.error("Failed to write generate output into {}", file.getPath(), e);
+                    throw e;
+                }
+            }
+            return file;
+
+        }
+        return null;
     }
 
 }
index 21b82f3c0e85d85251ede05001d194f3e0b4de66..ef6e926a423a744483ae52a112fae85f308dd218 100644 (file)
@@ -159,9 +159,9 @@ public class BuilderRenderer extends BaseRenderer {
                             final String aPackage = getPackage(fullyQualifiedName);
                             final String name = getName(fullyQualifiedName);
                             final GeneratedTOBuilderImpl generatedTOBuilder = new GeneratedTOBuilderImpl(aPackage, name);
-                            final ReferencedTypeImpl referencedType = new ReferencedTypeImpl(aPackage, name);
+                            final ReferencedTypeImpl referencedType = new ReferencedTypeImpl(aPackage, name, true);
                             final ReferencedTypeImpl generic = new ReferencedTypeImpl(getType().getPackageName(),
-                                    getType().getName());
+                                    getType().getName(), true);
                             final ParameterizedType parametrizedReturnType = Types.parameterizedTypeFor(referencedType, generic);
                             generatedTOBuilder.addMethod(method.getName()).setReturnType(parametrizedReturnType);
                             augmentField = propertyFromGetter(generatedTOBuilder.toInstance().getMethodDefinitions().get(0));
index 4acc227b95c89a28a2d2b9524649ada777e1fced..565a2571d2547643351259455f206bc6d900c5a2 100644 (file)
@@ -73,24 +73,24 @@ public class InterfaceRenderer extends BaseRenderer {
 
         final String generatedConstants = String.join("\n", strings);
 
-        final List<String> innerClassesBuilder = new ArrayList<>(getType().getEnclosedTypes().size());
+        final List<String> innerClasses = new ArrayList<>(getType().getEnclosedTypes().size());
         for (GeneratedType innerClass : getType().getEnclosedTypes()) {
             if (innerClass instanceof GeneratedTransferObject) {
                 if (((GeneratedTransferObject) innerClass).isUnionType()) {
                     final UnionRenderer unionRenderer = new UnionRenderer((GeneratedTransferObject) innerClass);
-                    innerClassesBuilder.add(unionRenderer.body());
+                    innerClasses.add(unionRenderer.body());
                     this.getImportMap().putAll(unionRenderer.getImportMap());
                 } else {
                     final ClassRenderer classRenderer = new ClassRenderer((GeneratedTransferObject) innerClass);
-                    innerClassesBuilder.add(classRenderer.generateAsInnerClass());
+                    innerClasses.add(classRenderer.generateAsInnerClass());
                     this.getImportMap().putAll(classRenderer.getImportMap());
                 }
             }
         }
-        final String innerClasses = String.join("\n", strings);
+        final String generatedInnerClasses = String.join("\n", innerClasses);
 
         return interfaceTemplate.render(getType(), enums, mainAnnotations, methodList, generatedImports,
-                generatedConstants, innerClasses).body();
+                generatedConstants, generatedInnerClasses).body();
     }
 
     private boolean isAccessor (final MethodSignature maybeGetter) {
index 38cf8d5ae7b69ec6a07ac039c1c9abf2dd2ebdf3..fda8f071c0c29026ffeb3f5372f7672540e70165 100644 (file)
@@ -20,8 +20,10 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.function.Function;
 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
 import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.modelProviderTemplate;
 import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.yangModuleInfoTemplate;
@@ -40,13 +42,16 @@ public class YangModuleInfoTemplateRenderer {
     private final Map<String, String> importMap = new HashMap<>();
     private final String packageName;
     private final String modelBindingProviderName;
+    private final Function<Module, Optional<String>> moduleFilePathResolver;
 
-    public YangModuleInfoTemplateRenderer(final Module module, final SchemaContext ctx) {
+
+    public YangModuleInfoTemplateRenderer(final Module module, final SchemaContext ctx, final Function<Module, Optional<String>> moduleFilePathResolver) {
 
         Preconditions.checkArgument(module != null, "Module must not be null.");
         this.module = module;
         this.ctx = ctx;
         this.packageName = getRootPackageName(module);
+        this.moduleFilePathResolver = moduleFilePathResolver;
 
         final StringBuilder sb = new StringBuilder();
         sb.append(packageName)
@@ -211,4 +216,8 @@ public class YangModuleInfoTemplateRenderer {
         }
         return sorted.lastEntry().getValue();
     }
+
+    public String getModelBindingProviderName() {
+        return modelBindingProviderName;
+    }
 }
\ No newline at end of file
index d4324f22bb9bb547e74b18398827144b07645ff9..e4fa8a075c927347ac44b695fcc632ba09247641 100644 (file)
@@ -98,8 +98,8 @@ public class @{genType.getName}Builder implements @{getSimpleNameForBuilder} <@{
 @generateAugmentField(isPrivate: Boolean) = {
     @if(augmentField != null) {
         @if(isPrivate) {private }
-        @{importedNames.get("map")}<@{importedNames.get("class")}<? extends@{importedNames.get("augmentFieldReturnType")}>,
-        @{importedNames.get("augmentFieldReturnType")}>@{augmentField.getName} = @{importedNames.get("collections")}.emptyMap();
+        @{importedNames.get("map")}<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>,
+        @{importedNames.get("augmentFieldReturnType")}> @{augmentField.getName} = @{importedNames.get("collections")}.emptyMap();
     }
 }
 
@@ -205,7 +205,7 @@ public class @{genType.getName}Builder implements @{getSimpleNameForBuilder} <@{
                 break;
             case 1:
                 final @{importedNames.get("map")}.Entry<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}> e = base.@{augmentField.getName}.entrySet().iterator().next();
-                this.@{augmentField.getName} = @{importedNames.get("collections")}.<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}>singletonMap(e.getKey(), e.getValue());
+                this.@{augmentField.getName} = @{importedNames.get("collections")}.<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}> singletonMap(e.getKey(), e.getValue());
                 break;
             default :
                 this.@{augmentField.getName} = new @{importedNames.get("hashMap")}<>(base.@{augmentField.getName});
index bffcbb011f003c5536bcb016339c8cf4a4a98460..d57917fa689342877f6327dce8315bea17bb14a4 100644 (file)
@@ -9,18 +9,31 @@
 package org.opendaylight.mdsal.binding.javav2.maven.api.gen.plugin;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
 import org.apache.maven.project.MavenProject;
 import org.opendaylight.mdsal.binding.javav2.generator.api.BindingGenerator;
 import org.opendaylight.mdsal.binding.javav2.generator.impl.BindingGeneratorImpl;
+import org.opendaylight.mdsal.binding.javav2.java.api.generator.GeneratorJavaFile;
+import org.opendaylight.mdsal.binding.javav2.java.api.generator.renderers.YangModuleInfoTemplateRenderer;
 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModelBindingProvider;
+import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator;
@@ -34,17 +47,15 @@ import org.sonatype.plexus.build.incremental.BuildContext;
 public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContextAware, MavenProjectAware {
 
     private static final Logger LOG = LoggerFactory.getLogger(CodeGeneratorImpl.class);
+    private static final String FS = File.separator;
     private BuildContext buildContext;
     private File projectBaseDir;
     private Map<String, String> additionalConfig;
     private MavenProject mavenProject;
     private File resourceBaseDir;
 
-    //TODO: bind this all together with BindingGeneratorImpl and GeneratorJavaFile
-
     /**
      * This method is called from mojo (maven goal)
-     *
      * @param context
      *            parsed from YANG files
      * @param outputBaseDir
@@ -52,16 +63,31 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
      *            user
      * @param currentModules
      *            YANG modules parsed from yangFilesRootDir
+     * @param moduleResourcePathResolver
+     *            Function converting a local module to the packaged resource path
      * @return
      * @throws IOException
      */
     @Override
-    public Collection<File> generateSources(SchemaContext context, File outputBaseDir, Set<Module> currentModules) throws IOException {
-
+    public Collection<File> generateSources(SchemaContext context, File outputBaseDir, Set<Module> currentModules, Function<Module, Optional<String>> moduleResourcePathResolver) throws IOException {
         final BindingGenerator bindingGenerator = new BindingGeneratorImpl(true);
         final List<Type> types = bindingGenerator.generateTypes(context, currentModules);
+        final GeneratorJavaFile generator = new GeneratorJavaFile(buildContext, types);
+
+        File persistentSourcesDir = null;
+        if (additionalConfig != null) {
+            String persistentSourcesPath = additionalConfig.get("persistentSourcesDir");
+            if (persistentSourcesPath != null) {
+                persistentSourcesDir = new File(persistentSourcesPath);
+            }
+        }
+        if (persistentSourcesDir == null) {
+            persistentSourcesDir = new File(projectBaseDir, "src" + FS + "main" + FS + "java");
+        }
 
-        List<File> result = new ArrayList<>();
+        List<File> result = generator.generateToFile(outputBaseDir, persistentSourcesDir);
+
+        result.addAll(generateModuleInfos(outputBaseDir, currentModules, context, moduleResourcePathResolver));
         return result;
     }
 
@@ -85,4 +111,87 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
         this.mavenProject = project;
         this.projectBaseDir = project.getBasedir();
     }
+
+    @Override
+    public Collection<File> generateSources(SchemaContext context, File outputBaseDir, Set<Module> currentModules) throws IOException {
+        return generateSources(context, outputBaseDir, currentModules,
+                m -> Optional.of("/" + m.getModuleSourcePath().replace(File.separator, "/")));
+    }
+
+    private Collection<? extends File> generateModuleInfos(final File outputBaseDir, final Set<Module> yangModules,
+                                                           final SchemaContext context, final Function<Module, Optional<String>> moduleResourcePathResolver) {
+        Builder<File> result = ImmutableSet.builder();
+        Builder<String> bindingProviders = ImmutableSet.builder();
+        for (Module module : yangModules) {
+            Builder<String> currentProvidersBuilder = ImmutableSet.builder();
+            Set<File> moduleInfoProviders = generateYangModuleInfo(outputBaseDir, module, context,
+                    moduleResourcePathResolver, currentProvidersBuilder);
+            ImmutableSet<String> currentProviders = currentProvidersBuilder.build();
+            LOG.info("Adding ModuleInfo providers {}", currentProviders);
+            bindingProviders.addAll(currentProviders);
+            result.addAll(moduleInfoProviders);
+        }
+
+        result.add(writeMetaInfServices(resourceBaseDir, YangModelBindingProvider.class, bindingProviders.build()));
+        return result.build();
+    }
+
+    private File writeMetaInfServices(final File outputBaseDir, final Class<YangModelBindingProvider> serviceClass,
+                                      final ImmutableSet<String> services) {
+        File metainfServicesFolder = new File(outputBaseDir, "META-INF" + File.separator + "services");
+        metainfServicesFolder.mkdirs();
+        File serviceFile = new File(metainfServicesFolder, serviceClass.getName());
+
+        String src = Joiner.on('\n').join(services);
+
+        return writeFile(serviceFile, src);
+    }
+
+    private Set<File> generateYangModuleInfo(final File outputBaseDir, final Module module, final SchemaContext ctx,
+           final Function<Module, Optional<String>> moduleResourcePathResolver, final Builder<String> providerSourceSet) {
+
+        Builder<File> generatedFiles = ImmutableSet.builder();
+        final YangModuleInfoTemplateRenderer template = new YangModuleInfoTemplateRenderer(module, ctx,
+                moduleResourcePathResolver);
+        String moduleInfoSource = template.generateTemplate();
+        if (moduleInfoSource.isEmpty()) {
+            throw new IllegalStateException("Generated code should not be empty!");
+        }
+        String providerSource = template.generateModelProvider();
+
+        final File packageDir = GeneratorJavaFile.packageToDirectory(outputBaseDir,
+                BindingMapping.getRootPackageName(module));
+
+        generatedFiles.add(writeJavaSource(packageDir, BindingMapping.MODULE_INFO_CLASS_NAME, moduleInfoSource));
+        generatedFiles
+                .add(writeJavaSource(packageDir, BindingMapping.MODEL_BINDING_PROVIDER_CLASS_NAME, providerSource));
+        providerSourceSet.add(template.getModelBindingProviderName());
+
+        return generatedFiles.build();
+
+    }
+
+    private File writeFile(final File file, final String source) {
+        try (final OutputStream stream = buildContext.newFileOutputStream(file)) {
+            try (final Writer fw = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
+                try (final BufferedWriter bw = new BufferedWriter(fw)) {
+                    bw.write(source);
+                }
+            } catch (Exception e) {
+                LOG.error("Could not write file: {}",file,e);
+            }
+        } catch (Exception e) {
+            LOG.error("Could not create file: {}",file,e);
+        }
+        return file;
+    }
+
+    private File writeJavaSource(final File packageDir, final String className, final String source) {
+        if (!packageDir.exists()) {
+            packageDir.mkdirs();
+        }
+        final File file = new File(packageDir, className + ".java");
+        writeFile(file, source);
+        return file;
+    }
 }