JavaCompilerSupport.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.databind.codegen;

import gov.nist.secauto.metaschema.core.util.CollectionUtil;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.Writer;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

public class JavaCompilerSupport {
  private static final Logger LOGGER = LogManager.getLogger(JavaCompilerSupport.class);

  @NonNull
  private final Path classDir;
  @NonNull
  private final Set<String> classPath = new LinkedHashSet<>();
  @NonNull
  private final Set<String> modulePath = new LinkedHashSet<>();
  @NonNull
  private final Set<String> rootModuleNames = new LinkedHashSet<>();

  public JavaCompilerSupport(@NonNull Path classDir) {
    this.classDir = classDir;
  }

  public Set<String> getClassPath() {
    return classPath;
  }

  public Set<String> getModulePath() {
    return modulePath;
  }

  public Set<String> getRootModuleNames() {
    return rootModuleNames;
  }

  public void addToClassPath(@NonNull String entry) {
    classPath.add(entry);
  }

  public void addToModulePath(@NonNull String entry) {
    modulePath.add(entry);
  }

  public void addRootModule(@NonNull String entry) {
    rootModuleNames.add(entry);
  }

  @NonNull
  protected List<String> generateCompilerOptions() {
    List<String> options = new LinkedList<>();
    // options.add("-verbose");
    // options.add("-g");
    options.add("-d");
    options.add(classDir.toString());

    if (!classPath.isEmpty()) {
      options.add("-classpath");
      options.add(classPath.stream()
          .collect(Collectors.joining(":")));
    }

    if (!modulePath.isEmpty()) {
      options.add("-p");
      options.add(modulePath.stream()
          .collect(Collectors.joining(":")));
    }

    return options;
  }

  /**
   * Generate and compile Java classes.
   *
   * @param classFiles
   *          the files to compile
   * @param compileOut
   *          a Writer for additional output from the compiler; use System.err if
   *          null
   * @return information about the generated classes
   * @throws IOException
   *           if an error occurred while compiling the classes
   * @throws IllegalArgumentException
   *           if any of the options are invalid, or if any of the given
   *           compilation units are of other kind than
   *           {@link javax.tools.JavaFileObject.Kind#SOURCE}
   */

  public CompilationResult compile(@NonNull List<Path> classFiles, @Nullable Writer compileOut) throws IOException {
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    List<JavaFileObject> compilationUnits;
    try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {

      compilationUnits = classFiles.stream()
          .map(fileManager::getJavaFileObjects)
          .map(CollectionUtil::toList)
          .flatMap(List::stream)
          .collect(Collectors.toUnmodifiableList());

      List<String> options = generateCompilerOptions();

      if (LOGGER.isDebugEnabled()) {
        LOGGER.atDebug().log("Using options: {}", options);
      }

      JavaCompiler.CompilationTask task = compiler.getTask(
          compileOut,
          fileManager,
          diagnostics,
          options,
          null,
          compilationUnits);

      task.addModules(rootModuleNames);

      return new CompilationResult(task.call(), diagnostics);
    }
  }

  public static final class CompilationResult {
    private final boolean successful;
    @NonNull
    private final DiagnosticCollector<JavaFileObject> diagnostics;

    private CompilationResult(boolean successful, @NonNull DiagnosticCollector<JavaFileObject> diagnostics) {
      this.successful = successful;
      this.diagnostics = diagnostics;
    }

    public boolean isSuccessful() {
      return successful;
    }

    public DiagnosticCollector<?> getDiagnostics() {
      return diagnostics;
    }
  }
}