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}