Use parallelStream() to generate files
[mdsal.git] / binding / maven-sal-api-gen-plugin / src / main / java / org / opendaylight / mdsal / binding / maven / api / gen / plugin / CodeGeneratorImpl.java
index 0eaa68d00b439e1c81f44608db2f38432b662b01..499e030836b7297eb60a0fb9487f4bcb9cbb7672 100644 (file)
@@ -7,10 +7,18 @@
  */
 package org.opendaylight.mdsal.binding.maven.api.gen.plugin;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.Table;
+import com.google.common.collect.Table.Cell;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
@@ -18,23 +26,27 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ForkJoinPool;
 import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import org.apache.maven.project.MavenProject;
-import org.opendaylight.mdsal.binding.generator.api.BindingGenerator;
 import org.opendaylight.mdsal.binding.generator.impl.BindingGeneratorImpl;
-import org.opendaylight.mdsal.binding.generator.util.BindingGeneratorUtil;
 import org.opendaylight.mdsal.binding.java.api.generator.GeneratorJavaFile;
+import org.opendaylight.mdsal.binding.java.api.generator.GeneratorJavaFile.FileKind;
 import org.opendaylight.mdsal.binding.java.api.generator.YangModuleInfoTemplate;
 import org.opendaylight.mdsal.binding.model.api.Type;
-import org.opendaylight.yangtools.yang.binding.BindingMapping;
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator;
 import org.opendaylight.yangtools.yang2sources.spi.BuildContextAware;
 import org.opendaylight.yangtools.yang2sources.spi.MavenProjectAware;
@@ -43,6 +55,9 @@ import org.slf4j.LoggerFactory;
 import org.sonatype.plexus.build.incremental.BuildContext;
 
 public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContextAware, MavenProjectAware {
+    public static final String CONFIG_PERSISTENT_SOURCES_DIR = "persistentSourcesDir";
+    public static final String CONFIG_IGNORE_DUPLICATE_FILES = "ignoreDuplicateFiles";
+
     private static final Logger LOG = LoggerFactory.getLogger(CodeGeneratorImpl.class);
     private static final String FS = File.separator;
     private BuildContext buildContext;
@@ -52,36 +67,88 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
     private File resourceBaseDir;
 
     @Override
-    public Collection<File> generateSources(final SchemaContext context, final File outputDir,
+    public Collection<File> generateSources(final EffectiveModelContext context, final File outputDir,
             final Set<Module> yangModules, final Function<Module, Optional<String>> moduleResourcePathResolver)
                     throws IOException {
         final File outputBaseDir;
 
         outputBaseDir = outputDir == null ? getDefaultOutputBaseDir() : outputDir;
 
-        final BindingGenerator bindingGenerator = new BindingGeneratorImpl(true);
-        final List<Type> types = bindingGenerator.generateTypes(context, yangModules);
-        final GeneratorJavaFile generator = new GeneratorJavaFile(buildContext, types);
+        // Step one: determine binding types which we are generating
+        final Stopwatch sw = Stopwatch.createStarted();
+        final List<Type> types = new BindingGeneratorImpl().generateTypes(context, yangModules);
+        LOG.info("Found {} Binding types in {}", types.size(), sw);
+
+        final GeneratorJavaFile generator = new GeneratorJavaFile(types);
 
         File persistentSourcesDir = null;
+        boolean ignoreDuplicateFiles = true;
         if (additionalConfig != null) {
-            String persistenSourcesPath = additionalConfig.get("persistentSourcesDir");
+            String persistenSourcesPath = additionalConfig.get(CONFIG_PERSISTENT_SOURCES_DIR);
             if (persistenSourcesPath != null) {
                 persistentSourcesDir = new File(persistenSourcesPath);
             }
+            String ignoreDuplicateFilesString = additionalConfig.get(CONFIG_IGNORE_DUPLICATE_FILES);
+            if (ignoreDuplicateFilesString != null) {
+                ignoreDuplicateFiles = Boolean.parseBoolean(ignoreDuplicateFilesString);
+            }
         }
         if (persistentSourcesDir == null) {
             persistentSourcesDir = new File(projectBaseDir, "src" + FS + "main" + FS + "java");
         }
 
-        List<File> result = generator.generateToFile(outputBaseDir, persistentSourcesDir);
+        final Table<FileKind, String, Supplier<String>> generatedFiles = generator.generateFileContent(
+            ignoreDuplicateFiles);
+
+        // Step two: create generation tasks for each target file and group them by parent directory
+        final ListMultimap<Path, GenerationTask> dirs = MultimapBuilder.hashKeys().arrayListValues().build();
+        for (Cell<FileKind, String, Supplier<String>> cell : generatedFiles.cellSet()) {
+            final File target;
+            switch (cell.getRowKey()) {
+                case PERSISTENT:
+                    target = new File(persistentSourcesDir, cell.getColumnKey());
+                    if (target.exists()) {
+                        LOG.debug("Skipping existing persistent {}", target);
+                        continue;
+                    }
+                    break;
+                case TRANSIENT:
+                    target = new File(outputBaseDir, cell.getColumnKey());
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported file type in " + cell);
+            }
+
+            dirs.put(target.getParentFile().toPath(), new GenerationTask(buildContext, target, cell.getValue()));
+        }
+        LOG.info("Generating {} Binding source files into {} directories", dirs.size(), dirs.keySet().size());
+
+        // Step three: submit parent directory creation tasks (via parallelStream()) and wait for them to complete
+        sw.reset().start();
+        dirs.keySet().parallelStream().forEach(path -> {
+            try {
+                Files.createDirectories(path);
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to create " + path, e);
+            }
+        });
+        LOG.debug("Parent directories created in {}", sw);
 
+        // Step four: submit all code generation tasks (via parallelStream()) and wait for them to complete
+        final ListeningExecutorService service = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool());
+        sw.reset().start();
+        final List<File> result = dirs.values().parallelStream()
+                .map(GenerationTask::generateFile)
+                .collect(Collectors.toList());
+        LOG.debug("{} Binding source type files generated in {}", result.size(), sw);
+
+        // Step five: generate auxiliary files
         result.addAll(generateModuleInfos(outputBaseDir, yangModules, context, moduleResourcePathResolver));
         return result;
     }
 
     private Collection<? extends File> generateModuleInfos(final File outputBaseDir, final Set<Module> yangModules,
-            final SchemaContext context, final Function<Module, Optional<String>> moduleResourcePathResolver) {
+            final EffectiveModelContext context, final Function<Module, Optional<String>> moduleResourcePathResolver) {
         Builder<File> result = ImmutableSet.builder();
         Builder<String> bindingProviders = ImmutableSet.builder();
         for (Module module : yangModules) {
@@ -90,7 +157,7 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
             Set<File> moduleInfoProviders = generateYangModuleInfo(outputBaseDir, module, context,
                 moduleResourcePathResolver, currentProvidersBuilder);
             ImmutableSet<String> currentProviders = currentProvidersBuilder.build();
-            LOG.info("Adding ModuleInfo providers {}", currentProviders);
+            LOG.debug("Adding ModuleInfo providers {}", currentProviders);
             bindingProviders.addAll(currentProviders);
             result.addAll(moduleInfoProviders);
         }
@@ -117,12 +184,12 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
         File outputBaseDir;
         outputBaseDir = new File(DEFAULT_OUTPUT_BASE_DIR_PATH);
         setOutputBaseDirAsSourceFolder(outputBaseDir, mavenProject);
-        LOG.debug("Adding " + outputBaseDir.getPath() + " as compile source root");
+        LOG.debug("Adding {} as compile source root", outputBaseDir.getPath());
         return outputBaseDir;
     }
 
     private static void setOutputBaseDirAsSourceFolder(final File outputBaseDir, final MavenProject mavenProject) {
-        Preconditions.checkNotNull(mavenProject, "Maven project needs to be set in this phase");
+        requireNonNull(mavenProject, "Maven project needs to be set in this phase");
         mavenProject.addCompileSourceRoot(outputBaseDir.getPath());
     }
 
@@ -144,13 +211,13 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
 
     @Override
     public void setBuildContext(final BuildContext buildContext) {
-        this.buildContext = Preconditions.checkNotNull(buildContext);
+        this.buildContext = requireNonNull(buildContext);
     }
 
-    private Set<File> generateYangModuleInfo(final File outputBaseDir, final Module module, final SchemaContext ctx,
-            final Function<Module, Optional<String>> moduleResourcePathResolver,
+    private Set<File> generateYangModuleInfo(final File outputBaseDir, final Module module,
+            final EffectiveModelContext ctx, final Function<Module, Optional<String>> moduleResourcePathResolver,
             final Builder<String> providerSourceSet) {
-        Builder<File> generatedFiles = ImmutableSet.<File> builder();
+        Builder<File> generatedFiles = ImmutableSet.builder();
 
         final YangModuleInfoTemplate template = new YangModuleInfoTemplate(module, ctx, moduleResourcePathResolver);
         String moduleInfoSource = template.generate();
@@ -160,7 +227,7 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
         String providerSource = template.generateModelProvider();
 
         final File packageDir = GeneratorJavaFile.packageToDirectory(outputBaseDir,
-                BindingGeneratorUtil.moduleNamespaceToPackageName(module));
+            BindingMapping.getRootPackageName(module.getQNameModule()));
 
         generatedFiles.add(writeJavaSource(packageDir, BindingMapping.MODULE_INFO_CLASS_NAME, moduleInfoSource));
         generatedFiles
@@ -180,26 +247,46 @@ public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContext
         return file;
     }
 
+    @SuppressWarnings("checkstyle:illegalCatch")
     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)) {
+        try (OutputStream stream = buildContext.newFileOutputStream(file)) {
+            try (Writer fw = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
+                try (BufferedWriter bw = new BufferedWriter(fw)) {
                     bw.write(source);
                 }
             } catch (Exception e) {
-                LOG.error("Could not write file: {}",file,e);
+                LOG.error("Could not write file: {}", file, e);
             }
         } catch (Exception e) {
-            LOG.error("Could not create file: {}",file,e);
+            LOG.error("Could not create file: {}", file, e);
         }
         return file;
     }
 
-    @Override
-    public Collection<File> generateSources(final SchemaContext context, final File outputBaseDir, final Set<Module> currentModules)
-            throws IOException {
-        return generateSources(context, outputBaseDir, currentModules,
-            m -> Optional.of("/" + m.getModuleSourcePath().replace(File.separator, "/")));
-    }
+    private static final class GenerationTask {
+        private final BuildContext buildContext;
+        private final Supplier<String> contentSupplier;
+        private final File target;
+
+        GenerationTask(final BuildContext buildContext, final File target, final Supplier<String> contentSupplier) {
+            this.buildContext = requireNonNull(buildContext);
+            this.target = requireNonNull(target);
+            this.contentSupplier = requireNonNull(contentSupplier);
+        }
 
+        File generateFile() {
+            try {
+                try (OutputStream stream = buildContext.newFileOutputStream(target)) {
+                    try (Writer fw = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
+                        try (BufferedWriter bw = new BufferedWriter(fw)) {
+                            bw.write(contentSupplier.get());
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to generate file " + target, e);
+            }
+            return target;
+        }
+    }
 }