001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.codegen;
007
008import gov.nist.secauto.metaschema.core.util.CollectionUtil;
009
010import org.apache.logging.log4j.LogManager;
011import org.apache.logging.log4j.Logger;
012
013import java.io.IOException;
014import java.io.Writer;
015import java.nio.file.Path;
016import java.util.LinkedHashSet;
017import java.util.LinkedList;
018import java.util.List;
019import java.util.Set;
020import java.util.stream.Collectors;
021
022import javax.tools.DiagnosticCollector;
023import javax.tools.JavaCompiler;
024import javax.tools.JavaFileObject;
025import javax.tools.StandardJavaFileManager;
026import javax.tools.ToolProvider;
027
028import edu.umd.cs.findbugs.annotations.NonNull;
029import edu.umd.cs.findbugs.annotations.Nullable;
030
031public class JavaCompilerSupport {
032  private static final Logger LOGGER = LogManager.getLogger(JavaCompilerSupport.class);
033
034  @NonNull
035  private final Path classDir;
036  @NonNull
037  private final Set<String> classPath = new LinkedHashSet<>();
038  @NonNull
039  private final Set<String> modulePath = new LinkedHashSet<>();
040  @NonNull
041  private final Set<String> rootModuleNames = new LinkedHashSet<>();
042
043  public JavaCompilerSupport(@NonNull Path classDir) {
044    this.classDir = classDir;
045  }
046
047  public Set<String> getClassPath() {
048    return classPath;
049  }
050
051  public Set<String> getModulePath() {
052    return modulePath;
053  }
054
055  public Set<String> getRootModuleNames() {
056    return rootModuleNames;
057  }
058
059  public void addToClassPath(@NonNull String entry) {
060    classPath.add(entry);
061  }
062
063  public void addToModulePath(@NonNull String entry) {
064    modulePath.add(entry);
065  }
066
067  public void addRootModule(@NonNull String entry) {
068    rootModuleNames.add(entry);
069  }
070
071  @NonNull
072  protected List<String> generateCompilerOptions() {
073    List<String> options = new LinkedList<>();
074    // options.add("-verbose");
075    // options.add("-g");
076    options.add("-d");
077    options.add(classDir.toString());
078
079    if (!classPath.isEmpty()) {
080      options.add("-classpath");
081      options.add(classPath.stream()
082          .collect(Collectors.joining(":")));
083    }
084
085    if (!modulePath.isEmpty()) {
086      options.add("-p");
087      options.add(modulePath.stream()
088          .collect(Collectors.joining(":")));
089    }
090
091    return options;
092  }
093
094  /**
095   * Generate and compile Java classes.
096   *
097   * @param classFiles
098   *          the files to compile
099   * @param compileOut
100   *          a Writer for additional output from the compiler; use System.err if
101   *          null
102   * @return information about the generated classes
103   * @throws IOException
104   *           if an error occurred while compiling the classes
105   * @throws IllegalArgumentException
106   *           if any of the options are invalid, or if any of the given
107   *           compilation units are of other kind than
108   *           {@link javax.tools.JavaFileObject.Kind#SOURCE}
109   */
110
111  public CompilationResult compile(@NonNull List<Path> classFiles, @Nullable Writer compileOut) throws IOException {
112    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
113
114    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
115
116    List<JavaFileObject> compilationUnits;
117    try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
118
119      compilationUnits = classFiles.stream()
120          .map(fileManager::getJavaFileObjects)
121          .map(CollectionUtil::toList)
122          .flatMap(List::stream)
123          .collect(Collectors.toUnmodifiableList());
124
125      List<String> options = generateCompilerOptions();
126
127      if (LOGGER.isDebugEnabled()) {
128        LOGGER.atDebug().log("Using options: {}", options);
129      }
130
131      JavaCompiler.CompilationTask task = compiler.getTask(
132          compileOut,
133          fileManager,
134          diagnostics,
135          options,
136          null,
137          compilationUnits);
138
139      task.addModules(rootModuleNames);
140
141      return new CompilationResult(task.call(), diagnostics);
142    }
143  }
144
145  public static final class CompilationResult {
146    private final boolean successful;
147    @NonNull
148    private final DiagnosticCollector<JavaFileObject> diagnostics;
149
150    private CompilationResult(boolean successful, @NonNull DiagnosticCollector<JavaFileObject> diagnostics) {
151      this.successful = successful;
152      this.diagnostics = diagnostics;
153    }
154
155    public boolean isSuccessful() {
156      return successful;
157    }
158
159    public DiagnosticCollector<?> getDiagnostics() {
160      return diagnostics;
161    }
162  }
163}