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