DefaultCompiler.java
/*-
* #%L
* io.earcam.instrumental.compile
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.instrumental.compile;
import static java.util.stream.Collectors.toList;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.processing.Processor;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import io.earcam.instrumental.compile.SourceSource.SourceSink;
final class DefaultCompiler implements Compiler, SourceSink {
private static class NoopWriter extends Writer {
@Override
public void write(char[] cbuf, int off, int len)
{
/* NoOp */
}
@Override
public void flush()
{
/* NoOp */
}
@Override
public void close()
{
/* NoOp */
}
}
private final List<FileObjectProvider> providers = new ArrayList<>();
private final List<Processor> processors = new ArrayList<>();
private final List<String> options = new ArrayList<>();
private final List<String> sourceCodes = new ArrayList<>();
private final List<Path> sourceFiles = new ArrayList<>();
private Charset encoding = Charset.defaultCharset();
private Locale locale = Locale.getDefault();
private DiagnosticListener<? super JavaFileObject> diagnosticListener;
private Writer compilerOutput = new NoopWriter();
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private boolean throwOnError = true;
@Override
public void sink(Path path)
{
sourceFiles.add(path);
}
@Override
public void sink(String text)
{
sourceCodes.add(text);
}
@Override
public <T> T compile(CompilationTarget<T> output)
{
StandardJavaFileManager standard = compiler.getStandardFileManager(diagnosticListener, locale, encoding);
StandardForwardingJavaFileManager fileManager = new CustomJavaFileManager(standard, providers);
JavaFileManager configuredManager = output.configureOutputFileManager(fileManager);
List<JavaFileObject> javaFiles = inputJavaFileObjects(fileManager);
Iterable<String> classesForAnnotationProcessing = null;
CompilationTask task = compiler.getTask(compilerOutput, configuredManager, diagnosticListener, options, classesForAnnotationProcessing, javaFiles);
task.setLocale(locale);
task.setProcessors(processors);
if(!task.call() && throwOnError) {
throw new IllegalStateException("Compilation did not complete without errors and `throwOnError` set to true");
}
return output.get();
}
private List<JavaFileObject> inputJavaFileObjects(StandardJavaFileManager fileManager)
{
List<JavaFileObject> javaFiles = this.sourceCodes.stream().map(StringJavaFileObject::new).collect(toList());
if(!sourceFiles.isEmpty()) {
fileManager.getJavaFileObjectsFromFiles(sourceFiles.stream().map(Path::toFile).collect(Collectors.toList())).forEach(javaFiles::add);
}
return javaFiles;
}
@Override
public Compiler sources(Iterable<SourceSource> sources)
{
sources.forEach(s -> s.drain(this));
return this;
}
@Override
public Compiler localisedTo(Locale locale)
{
this.locale = locale;
return this;
}
@Override
public Compiler encodedAs(Charset encoding)
{
this.encoding = encoding;
return this;
}
@Override
public Compiler withOption(String option)
{
options.add(option);
return this;
}
@Override
public Compiler usingCompiler(String spi)
{
return usingCompiler(loadCompiler(spi));
}
private static JavaCompiler loadCompiler(String spiCompilerClassName)
{
ServiceLoader<JavaCompiler> serviceLoader = ServiceLoader.load(JavaCompiler.class);
return stream(serviceLoader)
.filter(c -> c.getClass().getCanonicalName().equals(spiCompilerClassName))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static <T> Stream<T> stream(Iterable<T> iterable)
{
return StreamSupport.stream(iterable.spliterator(), false);
}
@Override
public Compiler usingCompiler(JavaCompiler implementation)
{
compiler = implementation;
return this;
}
@Override
public Compiler processedBy(Processor annotationProcessor)
{
processors.add(annotationProcessor);
return this;
}
@Override
public Compiler loggingTo(Writer compilerOutputWriter)
{
this.compilerOutput = compilerOutputWriter;
return this;
}
@Override
public Compiler consumingDiagnositics(DiagnosticListener<? super JavaFileObject> listener)
{
this.diagnosticListener = listener;
return this;
}
@Override
public Compiler ignoreCompilationErrors()
{
this.throwOnError = false;
return this;
}
@Override
public Compiler withDependencies(FileObjectProvider provider)
{
providers.add(provider);
return this;
}
}