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 java.io.IOException;
011import java.io.StringWriter;
012import java.nio.file.Path;
013import java.util.LinkedHashSet;
014import java.util.LinkedList;
015import java.util.List;
016import java.util.Set;
017import java.util.stream.Collectors;
018
019import javax.tools.DiagnosticCollector;
020import javax.tools.JavaCompiler;
021import javax.tools.JavaFileObject;
022import javax.tools.StandardJavaFileManager;
023import javax.tools.ToolProvider;
024
025import edu.umd.cs.findbugs.annotations.NonNull;
026import edu.umd.cs.findbugs.annotations.Nullable;
027
028public class JavaCompilerSupport {
029  @Nullable
030  private Logger logger;
031  @NonNull
032  private final Path classDir;
033  @NonNull
034  private final Set<String> classPath = new LinkedHashSet<>();
035  @NonNull
036  private final Set<String> modulePath = new LinkedHashSet<>();
037  @NonNull
038  private final Set<String> rootModuleNames = new LinkedHashSet<>();
039
040  public JavaCompilerSupport(@NonNull Path classDir) {
041    this.classDir = classDir;
042  }
043
044  public Set<String> getClassPath() {
045    return classPath;
046  }
047
048  public Set<String> getModulePath() {
049    return modulePath;
050  }
051
052  public Set<String> getRootModuleNames() {
053    return rootModuleNames;
054  }
055
056  public void addToClassPath(@NonNull String entry) {
057    classPath.add(entry);
058  }
059
060  public void addToModulePath(@NonNull String entry) {
061    modulePath.add(entry);
062  }
063
064  public void addRootModule(@NonNull String entry) {
065    rootModuleNames.add(entry);
066  }
067
068  public void setLogger(@NonNull Logger logger) {
069    this.logger = logger;
070  }
071
072  @NonNull
073  protected List<String> generateCompilerOptions() {
074    List<String> options = new LinkedList<>();
075    // options.add("-verbose");
076    // options.add("-g");
077    options.add("-d");
078    options.add(classDir.toString());
079
080    if (!classPath.isEmpty()) {
081      options.add("-classpath");
082      options.add(classPath.stream()
083          .collect(Collectors.joining(":")));
084    }
085
086    if (!modulePath.isEmpty()) {
087      options.add("-p");
088      options.add(modulePath.stream()
089          .collect(Collectors.joining(":")));
090    }
091
092    return options;
093  }
094
095  /**
096   * Generate and compile Java classes.
097   *
098   * @param classFiles
099   *          the files to compile
100   * @return information about the generated classes
101   * @throws IOException
102   *           if an error occurred while compiling the classes
103   * @throws IllegalArgumentException
104   *           if any of the options are invalid, or if any of the given
105   *           compilation units are of other kind than
106   *           {@link javax.tools.JavaFileObject.Kind#SOURCE}
107   */
108
109  public CompilationResult compile(@NonNull List<Path> classFiles) throws IOException {
110    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
111
112    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
113
114    List<JavaFileObject> compilationUnits;
115    try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
116
117      compilationUnits = classFiles.stream()
118          .map(fileManager::getJavaFileObjects)
119          .map(CollectionUtil::toList)
120          .flatMap(List::stream)
121          .collect(Collectors.toUnmodifiableList());
122
123      List<String> options = generateCompilerOptions();
124
125      Logger logger = this.logger;
126      if (logger != null && logger.isDebugEnabled()) {
127        logger.debug(String.format("Using options: %s", options));
128      }
129
130      boolean result;
131      try (StringWriter writer = new StringWriter()) {
132        JavaCompiler.CompilationTask task = compiler.getTask(
133            writer,
134            fileManager,
135            diagnostics,
136            options,
137            null,
138            compilationUnits);
139        task.addModules(rootModuleNames);
140
141        result = task.call();
142        writer.flush();
143        String output = writer.toString();
144        if (!output.isBlank() && logger != null && logger.isInfoEnabled()) {
145          logger.info(String.format("compiler output: %s", writer.toString()));
146        }
147      }
148      return new CompilationResult(result, diagnostics);
149    }
150  }
151
152  public static final class CompilationResult {
153    private final boolean successful;
154    @NonNull
155    private final DiagnosticCollector<JavaFileObject> diagnostics;
156
157    private CompilationResult(boolean successful, @NonNull DiagnosticCollector<JavaFileObject> diagnostics) {
158      this.successful = successful;
159      this.diagnostics = diagnostics;
160    }
161
162    public boolean isSuccessful() {
163      return successful;
164    }
165
166    public DiagnosticCollector<?> getDiagnostics() {
167      return diagnostics;
168    }
169  }
170
171  public interface Logger {
172    boolean isDebugEnabled();
173
174    boolean isInfoEnabled();
175
176    void debug(String msg);
177
178    void info(String msg);
179  }
180}