package jhydra.core.scripting.java;
import jhydra.core.scripting.CompileErrorReport;
import jhydra.core.scripting.IBaseScript;
import jhydra.core.scripting.IScriptCompiler;
import jhydra.core.scripting.ScriptType;
import jhydra.core.scripting.exceptions.*;
import jhydra.core.scripting.scriptinfo.IScriptInfo;
import org.apache.commons.io.IOUtils;
import javax.tools.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
//Package access only
class DynamicJavaCompiler implements IScriptCompiler {
//TODO: Get this into config
private final String classOutputFolder = "temp";
public DynamicJavaCompiler(){
final File file = new File(classOutputFolder);
establishDirectory(file);
}
@Override
public IBaseScript getCompiledScript(IScriptInfo scriptInfo) throws ScriptFatalException, CompileErrorException{
final String filePath = scriptInfo.getFilePath();
final String className = scriptInfo.getClassName();
if(!fileExists(filePath)){
throw new ScriptNotExistException(className);
}
final ScriptType scriptType = scriptInfo.getType();
if(scriptType!=ScriptType.JAVA){
final String message = "File attempted for java compilation is not a java file! File path: " + filePath;
throw new ScriptInputLoadingException(filePath, className, message);
}
compile(filePath, className);
return getScriptFromCompileOutput(className, filePath);
}
private IBaseScript getScriptFromCompileOutput(String className, String filePath) throws ScriptFatalException{
try{
final File file = new File(classOutputFolder);
final URL url = file.toURI().toURL();
final URL[] urls = new URL[]{url};
final ClassLoader loader = new URLClassLoader(urls);
final Class thisClass = loader.loadClass(className);
return (IBaseScript)thisClass.newInstance();
}
catch(IllegalAccessException e){
throw new NonPublicScriptClassException(filePath, e);
}
catch(ClassNotFoundException e){
throw new ClassNotInScriptFileException(filePath, e);
}
catch(InstantiationException e){
throw new ScriptInstantiationException(filePath, e);
}
catch(MalformedURLException e){
throw new ScriptOutputLoadingException(className, e);
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void establishDirectory(File file){
if(!file.exists()){
file.mkdirs();
}
}
private void compile(String fileName, String className) throws ScriptFatalException, CompileErrorException{
try{
final JavaFileObject file = getJavaFileObject(className, fileName);
final List<Diagnostic<? extends JavaFileObject>> diagnostics = compile(Arrays.asList(file));
evaluateDiagnostics(fileName, diagnostics);
}
catch(CompileErrorException e){
throw e;
}
catch(Exception e){
throw new ScriptInputLoadingException(fileName, className, e);
}
}
private Boolean fileExists(String fileName){
try{
final File file = new File(fileName);
return file.isFile();
}
catch(Exception e){
return false;
}
}
private void evaluateDiagnostics(String fileName, List<Diagnostic<? extends JavaFileObject>> diagnostics)
throws CompileErrorException{
final List<CompileErrorReport> reports = new ArrayList<>();
if(diagnostics.size() > 0){
final CompileErrorReport report = new CompileErrorReport(fileName, diagnostics);
reports.add(report);
}
for(Diagnostic diagnostic : diagnostics){
if(diagnostic.getKind() == Diagnostic.Kind.ERROR){
final String message = "Java compiler found error(s) in script named " + fileName;
throw new CompileErrorException(message, reports);
}
}
}
private List<Diagnostic<? extends JavaFileObject>> compile(Iterable<JavaFileObject> files)
throws IOException {
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
try(final StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnosticCollector,Locale.ENGLISH,null)){
final List<String> options = new ArrayList<>();
options.add("-d");
options.add(classOutputFolder);
options.add("-verbose");
compiler.getTask(null, fileManager, diagnosticCollector, options, null, files).call();
return diagnosticCollector.getDiagnostics();
}
}
private JavaFileObject getJavaFileObject(String className, String fileName) throws IOException{
final String sourceCode = loadSourceCode(fileName);
return new InMemoryJavaFileObject(className, sourceCode);
}
private String loadSourceCode(String fileName) throws IOException{
try (FileInputStream inputStream = new FileInputStream(fileName)) {
return IOUtils.toString(inputStream);
}
}
}