content: package ru.yandex.qatools.allure.command;
  
  import io.airlift.airline.Arguments;
  import io.airlift.airline.Command;
  import org.apache.commons.exec.CommandLine;
  import org.apache.commons.exec.DefaultExecutor;
  import org.apache.commons.exec.util.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.nio.file.DirectoryStream;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.jar.Attributes;
  import java.util.jar.JarEntry;
  import java.util.jar.JarOutputStream;
  import java.util.jar.Manifest;
  
  /**
   * @author Artem Eroshenko <eroshenkoam@yandex-team.ru>
   */
  @Command(name = "generate", description = "Generate report")
  public class ReportGenerate extends ReportCommand {
  
      private static final Logger LOGGER = LoggerFactory.getLogger(ReportGenerate.class);
  
      public static final String MAIN = "ru.yandex.qatools.allure.AllureMain";
  
      public static final String JAR_FILES = "*.jar";
  
      @Arguments(title = "Results directories", required = true,
              description = "A list of input directories to be processed")
      public List<String> results = new ArrayList<>();
  
      /**
       * {@inheritDoc}
       */
      @Override
      protected void runUnsafe() throws Exception {
          validateResultsDirectories();
          CommandLine commandLine = createCommandLine();
          new DefaultExecutor().execute(commandLine);
          LOGGER.info("Report successfully generated to the directory <{}>. " +
                  "Use `allure report open` command to show the report.", getReportDirectoryPath());
      }
  
      /**
       * Throws an exception if at least one results directory is missing.
       */
      protected void validateResultsDirectories() {
          for (String result : results) {
              if (Files.notExists(Paths.get(result))) {
                  throw new AllureCommandException(String.format("Report directory <%s> not found.", result));
              }
          }
      }
  
      /**
       * Create a {@link CommandLine} to run bundle with needed arguments.
       */
      private CommandLine createCommandLine() throws IOException {
          return new CommandLine(getJavaExecutablePath())
                  .addArguments(getBundleJavaOptsArgument())
                  .addArgument(getLoggerConfigurationArgument())
                  .addArgument("-jar")
                  .addArgument(getExecutableJar())
                  .addArguments(results.toArray(new String[results.size()]), false)
                  .addArgument(getReportDirectoryPath().toString(), false);
      }
  
      /**
       * Returns the classpath for executable jar.
       */
      protected String getClasspath() throws IOException {
          List<String> classpath = new ArrayList<>();
          classpath.add(getBundleJarPath());
          classpath.addAll(getPluginsPath());
          return StringUtils.toString(classpath.toArray(new String[classpath.size()]), " ");
      }
  
      /**
       * Create an executable jar to generate the report. Created jar contains only
       * allure configuration file.
       */
      protected String getExecutableJar() throws IOException {
          Manifest manifest = new Manifest();
          manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
          manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, MAIN);
          manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, getClasspath());
  
          Path jar = createTempDirectory("exec").resolve("generate.jar");
          try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest)) {
              output.putNextEntry(new JarEntry("allure.properties"));
              Path allureConfig = PROPERTIES.getAllureConfig();
              if (Files.exists(allureConfig)) {
                  byte[] bytes = Files.readAllBytes(allureConfig);
                  output.write(bytes);
              }
              output.closeEntry();
          }
  
          return jar.toAbsolutePath().toString();
      }
  
      /**
       * Returns the bundle jar classpath element.
       */
      protected String getBundleJarPath() throws MalformedURLException {
          Path path = PROPERTIES.getAllureHome().resolve("app/allure-bundle.jar").toAbsolutePath();
          if (Files.notExists(path)) {
              throw new AllureCommandException(String.format("Bundle not found by path <%s>", path));
          }
          return path.toUri().toURL().toString();
      }
  
      /**
       * Returns the plugins classpath elements.
       */
      protected List<String> getPluginsPath() throws IOException {
          List<String> result = new ArrayList<>();
          Path pluginsDirectory = PROPERTIES.getAllureHome().resolve("plugins").toAbsolutePath();
          if (Files.notExists(pluginsDirectory)) {
              return Collections.emptyList();
          }
  
          try (DirectoryStream<Path> plugins = Files.newDirectoryStream(pluginsDirectory, JAR_FILES)) {
              for (Path plugin : plugins) {
                  result.add(plugin.toUri().toURL().toString());
              }
          }
          return result;
      }
  
      /**
       * Get argument to configure log level for bundle.
       */
      protected String getLoggerConfigurationArgument() {
          return String.format("-Dorg.slf4j.simpleLogger.defaultLogLevel=%s",
                  isQuiet() || !isVerbose() ? "error" : "debug");
      }
  
      /**
       * Returns the bundle java options split by space.
       */
      protected String getBundleJavaOptsArgument() {
          return PROPERTIES.getBundleJavaOpts();
      }
  
      /**
       * Returns the path to java executable.
       */
      protected String getJavaExecutablePath() {
          String executableName = isWindows() ? "bin/java.exe" : "bin/java";
          return PROPERTIES.getJavaHome().resolve(executableName).toAbsolutePath().toString();
      }
  
      /**
       * Returns true if operation system is windows, false otherwise.
       */
      protected boolean isWindows() {
          return PROPERTIES.getOsName().contains("win");
      }
  }
  