package org.smoothbuild.builtin.java.javac;
import static java.nio.charset.Charset.defaultCharset;
import static org.smoothbuild.builtin.java.javac.PackagedJavaFileObjects.classesFromJars;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.smoothbuild.io.fs.base.FileSystemException;
import org.smoothbuild.lang.message.ErrorMessage;
import org.smoothbuild.lang.message.WarningMessage;
import org.smoothbuild.lang.plugin.Container;
import org.smoothbuild.lang.plugin.Required;
import org.smoothbuild.lang.plugin.SmoothFunction;
import org.smoothbuild.lang.value.Array;
import org.smoothbuild.lang.value.Blob;
import org.smoothbuild.lang.value.SFile;
import org.smoothbuild.lang.value.SString;
public class JavacFunction {
@SmoothFunction
public static Array<SFile> javac(Container container, @Required Array<SFile> sources,
Array<Blob> libs, SString source, SString target) {
return new Worker(container, sources, libs, source, target).execute();
}
private static class Worker {
private static final Set<String> SOURCE_VALUES = unmodifiableSet("1.3", "1.4", "1.5", "5",
"1.6", "6", "1.7", "7", "1.8", "8");
private static final Set<String> TARGET_VALUES = unmodifiableSet("1.1", "1.2", "1.3", "1.4",
"1.5", "5", "1.6", "6", "1.7", "7", "1.8", "8");
private final JavaCompiler compiler;
private final Container container;
private final Array<SFile> sources;
private final Array<Blob> libs;
private final SString source;
private final SString target;
public Worker(Container container,
Array<SFile> sources,
Array<Blob> libs,
SString source,
SString target) {
this.compiler = ToolProvider.getSystemJavaCompiler();
this.container = container;
this.sources = sources;
this.libs = libs;
this.source = source;
this.target = target;
}
public Array<SFile> execute() {
if (compiler == null) {
throw new ErrorMessage("Couldn't find JavaCompiler implementation. "
+ "You have to run Smooth tool using JDK (not JVM). Only JDK contains java compiler.");
}
return compile(sources);
}
public Array<SFile> compile(Array<SFile> files) {
// prepare arguments for compilation
StringWriter additionalCompilerOutput = new StringWriter();
LoggingDiagnosticListener diagnostic = new LoggingDiagnosticListener(container);
Iterable<String> options = options();
SandboxedJavaFileManager fileManager = fileManager(diagnostic);
try {
Iterable<InputSourceFile> inputSourceFiles = toJavaFiles(files);
/*
* Java compiler fails miserably when there's no java files.
*/
if (!inputSourceFiles.iterator().hasNext()) {
container.log(new WarningMessage("Param 'sources' is empty list."));
return container.create().arrayBuilder(SFile.class).build();
}
// run compilation task
CompilationTask task =
compiler.getTask(additionalCompilerOutput, fileManager, diagnostic, options, null,
inputSourceFiles);
boolean success = task.call();
// tidy up
if (!success && !diagnostic.errorReported()) {
container.log(new ErrorMessage(
"Internal error: Compilation failed but JavaCompiler reported no error message."));
}
String additionalInfo = additionalCompilerOutput.toString();
if (!additionalInfo.isEmpty()) {
container.log(new WarningMessage(additionalInfo));
}
return fileManager.resultClassfiles();
} finally {
try {
fileManager.close();
} catch (IOException e) {
throw new FileSystemException(e);
}
}
}
private Iterable<String> options() {
List<String> result = new ArrayList<>();
if (!source.value().isEmpty()) {
String sourceArg = source.value();
if (!SOURCE_VALUES.contains(sourceArg)) {
throw new ErrorMessage(
"Parameter source has illegal value = '" + sourceArg + "'.\n"
+ "Only following values are allowed " + SOURCE_VALUES + "\n");
}
result.add("-source");
result.add(sourceArg);
}
if (!target.value().isEmpty()) {
String targetArg = target.value();
if (!TARGET_VALUES.contains(targetArg)) {
throw new ErrorMessage("Parameter target has illegal value = '" + targetArg + "'.\n"
+ "Only following values are allowed " + TARGET_VALUES);
}
result.add("-target");
result.add(targetArg);
}
return result;
}
private SandboxedJavaFileManager fileManager(LoggingDiagnosticListener diagnostic) {
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostic, null, defaultCharset());
Iterable<InputClassFile> libsClasses = classesFromJars(container, libs);
return new SandboxedJavaFileManager(fileManager, container, libsClasses);
}
private static Iterable<InputSourceFile> toJavaFiles(Iterable<SFile> sourceFiles) {
ArrayList<InputSourceFile> result = new ArrayList<>();
for (SFile file : sourceFiles) {
result.add(new InputSourceFile(file));
}
return result;
}
}
public static <T> Set<T> unmodifiableSet(T... elements) {
Set<T> set = new HashSet<>(elements.length);
Collections.addAll(set, elements);
return Collections.unmodifiableSet(set);
}
}