1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.codegen;
7   
8   import gov.nist.secauto.metaschema.core.util.CollectionUtil;
9   
10  import org.apache.logging.log4j.LogManager;
11  import org.apache.logging.log4j.Logger;
12  
13  import java.io.IOException;
14  import java.io.Writer;
15  import java.nio.file.Path;
16  import java.util.LinkedHashSet;
17  import java.util.LinkedList;
18  import java.util.List;
19  import java.util.Set;
20  import java.util.stream.Collectors;
21  
22  import javax.tools.DiagnosticCollector;
23  import javax.tools.JavaCompiler;
24  import javax.tools.JavaFileObject;
25  import javax.tools.StandardJavaFileManager;
26  import javax.tools.ToolProvider;
27  
28  import edu.umd.cs.findbugs.annotations.NonNull;
29  import edu.umd.cs.findbugs.annotations.Nullable;
30  
31  public class JavaCompilerSupport {
32    private static final Logger LOGGER = LogManager.getLogger(JavaCompilerSupport.class);
33  
34    @NonNull
35    private final Path classDir;
36    @NonNull
37    private final Set<String> classPath = new LinkedHashSet<>();
38    @NonNull
39    private final Set<String> modulePath = new LinkedHashSet<>();
40    @NonNull
41    private final Set<String> rootModuleNames = new LinkedHashSet<>();
42  
43    public JavaCompilerSupport(@NonNull Path classDir) {
44      this.classDir = classDir;
45    }
46  
47    public Set<String> getClassPath() {
48      return classPath;
49    }
50  
51    public Set<String> getModulePath() {
52      return modulePath;
53    }
54  
55    public Set<String> getRootModuleNames() {
56      return rootModuleNames;
57    }
58  
59    public void addToClassPath(@NonNull String entry) {
60      classPath.add(entry);
61    }
62  
63    public void addToModulePath(@NonNull String entry) {
64      modulePath.add(entry);
65    }
66  
67    public void addRootModule(@NonNull String entry) {
68      rootModuleNames.add(entry);
69    }
70  
71    @NonNull
72    protected List<String> generateCompilerOptions() {
73      List<String> options = new LinkedList<>();
74      // options.add("-verbose");
75      // options.add("-g");
76      options.add("-d");
77      options.add(classDir.toString());
78  
79      if (!classPath.isEmpty()) {
80        options.add("-classpath");
81        options.add(classPath.stream()
82            .collect(Collectors.joining(":")));
83      }
84  
85      if (!modulePath.isEmpty()) {
86        options.add("-p");
87        options.add(modulePath.stream()
88            .collect(Collectors.joining(":")));
89      }
90  
91      return options;
92    }
93  
94    /**
95     * Generate and compile Java classes.
96     *
97     * @param classFiles
98     *          the files to compile
99     * @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 }