package org.jetbrains.jps.incremental.java; import org.jetbrains.jps.incremental.CompileContext; import javax.tools.*; import java.io.File; import java.util.*; import java.util.concurrent.ExecutorService; /** * @author Eugene Zhuravlev * Date: 9/23/11 */ public class EmbeddedJavac { private final JavaCompiler myCompiler; private final ExecutorService myTaskRunner; //private final SequentialTaskExecutor mySequentialTaskExecutor; private final List<ClassPostProcessor> myClassProcessors = new ArrayList<ClassPostProcessor>(); private static final Set<String> FILTERED_OPTIONS = new HashSet<String>(Arrays.<String>asList( "-d", "-classpath", "-cp", "-bootclasspath" )); public static interface DiagnosticOutputConsumer extends DiagnosticListener<JavaFileObject> { void outputLineAvailable(String line); } public static interface OutputFileConsumer { void save(OutputFileObject fileObject); } public static interface ClassPostProcessor { void process(CompileContext context, OutputFileObject out); } public EmbeddedJavac(ExecutorService taskRunner) { myTaskRunner = taskRunner; //mySequentialTaskExecutor = new SequentialTaskExecutor(taskRunner); myCompiler = ToolProvider.getSystemJavaCompiler(); } public void addClassProcessor(ClassPostProcessor processor) { myClassProcessors.add(processor); } public boolean compile(Collection<String> options, final Collection<File> sources, Collection<File> classpath, Collection<File> platformClasspath, Map<File, Set<File>> outputDirToRoots, CompileContext compileContext, final DiagnosticOutputConsumer outConsumer, final OutputFileConsumer outputSink) { final FileManagerContext context = new FileManagerContext(compileContext, outConsumer, outputSink); // todo for (File outputDir : outputDirToRoots.keySet()) { outputDir.mkdirs(); } final JavacFileManager fileManager = new JavacFileManager(context); fileManager.setOutputDirectories(outputDirToRoots); if (!classpath.isEmpty()) { if (!fileManager.setLocation(StandardLocation.CLASS_PATH, classpath)) { return false; } } if (!platformClasspath.isEmpty()) { if (!fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, platformClasspath)) { return false; } } //todo setup file manager to support multiple outputs final LineOutputWriter out = new LineOutputWriter() { protected void lineAvailable(String line) { outConsumer.outputLineAvailable(line); } }; try { final JavaCompiler.CompilationTask task = myCompiler.getTask( out, fileManager, outConsumer, filterOptionList(options), null, fileManager.toJavaFileObjects(sources) ); return task.call(); } finally { context.ensurePendingTasksCompleted(); fileManager.cleanupResources(); } } private static Collection<String> filterOptionList(final Collection<String> options) { if (options.isEmpty()) { return options; } final List<String> result = new ArrayList<String>(); boolean skip = false; for (String option : options) { if (FILTERED_OPTIONS.contains(option)) { skip = true; continue; } if (!skip) { result.add(option); } skip = false; } return result; } private class FileManagerContext implements JavacFileManager.Context { private final StandardJavaFileManager myStdManager; private final CompileContext myCompileContext; private final DiagnosticOutputConsumer myOutConsumer; private final OutputFileConsumer myOutputFileSink; private int myTasksInProgress = 0; private final Object myCounterLock = new Object(); public FileManagerContext(CompileContext compileContext, DiagnosticOutputConsumer outConsumer, OutputFileConsumer sink) { myCompileContext = compileContext; myOutConsumer = outConsumer; myOutputFileSink = sink != null? sink : new OutputFileConsumer() { public void save(OutputFileObject fileObject) { throw new RuntimeException("Output sink for compiler was not specified"); } }; myStdManager = myCompiler.getStandardFileManager(outConsumer, Locale.US, null); } public StandardJavaFileManager getStandardFileManager() { return myStdManager; } public void reportMessage(final Diagnostic.Kind kind, String message) { myOutConsumer.report(new PlainMessageDiagnostic(kind, message)); } public void consumeOutputFile(final OutputFileObject cls) { incTaskCount(); myTaskRunner.submit(new Runnable() { public void run() { try { runProcessors(cls); } finally { //mySequentialTaskExecutor.submit(new Runnable() { // public void run() { try { myOutputFileSink.save(cls); } finally { decTaskCount(); } //} //}); } } }); } private void decTaskCount() { synchronized (myCounterLock) { myTasksInProgress = Math.max(0, myTasksInProgress - 1); if (myTasksInProgress == 0) { myCounterLock.notifyAll(); } } } private void incTaskCount() { synchronized (myCounterLock) { myTasksInProgress++; } } public void ensurePendingTasksCompleted() { synchronized (myCounterLock) { while (myTasksInProgress > 0) { try { myCounterLock.wait(); } catch (InterruptedException ignored) { } } } } private void runProcessors(OutputFileObject cls) { for (ClassPostProcessor processor : myClassProcessors) { processor.process(myCompileContext, cls); } } } }