package platformSpecific.inMemoryCompiler;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.Map.Entry;
import javax.tools.*;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject.Kind;
import facade.L42;
public class InMemoryJavaCompiler {
public static class SourceFile extends SimpleJavaFileObject {
private String contents;
private static URI methForSuper(String s){
try {
return java.net.URI.create(URLEncoder.encode(s,"UTF-8"));
} catch (UnsupportedEncodingException e) { throw new Error(e);}
}
public SourceFile(String className, String contents) {
super(methForSuper(
"string:///" + className.replace('.', '/')+ Kind.SOURCE.extension
), Kind.SOURCE);
this.contents = contents;}
public CharSequence getCharContent(boolean b){return contents;}
}
@SuppressWarnings("serial")
public static class CompilationError extends Exception{
public final MyDiagnosticListener diagnostic;
CompilationError(MyDiagnosticListener diagnostic){
super(diagnostic.toString()); this.diagnostic=diagnostic;}
}
public static class ClassFile extends SimpleJavaFileObject{
private final ByteArrayOutputStream byteCode = new ByteArrayOutputStream();
public final String name;
private byte[] bytes=null;
public ClassFile(String name, Kind kind) {
super(java.net.URI.create("string:///" + name.replace('.', '/')+kind.extension),kind);
this.name=name;
}
private void cacheBytes() {bytes=byteCode.toByteArray(); }
private byte[] getBytes() {if(bytes==null) {cacheBytes();} return bytes;}
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(getBytes());
}
@Override
public OutputStream openOutputStream() throws IOException {
return byteCode;
}
}
private static class MyDiagnosticListener implements DiagnosticListener<JavaFileObject> {
public List<Diagnostic<? extends JavaFileObject>> diagnostic=new ArrayList<>();
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
this.diagnostic.add(diagnostic);
}
public String toString(){
StringBuilder res=new StringBuilder();
for(Diagnostic<? extends JavaFileObject> d: diagnostic){
res.append(d.toString());
res.append("\n\n");
}
return res.toString();
}
}
private static String plugins(){
String result="";
if(L42.pluginPaths==null){
return System.getProperty("java.class.path");
}
for (URL url:L42.pluginPaths){result+=url.toString().substring(6)+System.getProperty("path.separator");}
//TODO: substring 6 is a hack to remove "file:/", do it better.
result+=System.getProperty("java.class.path");
return result;
}
public static class MapClassLoader extends java.security.SecureClassLoader{
public MapClassLoader(HashMap<String,ClassFile> map,ClassLoader env){
super(env);
this.map=map;
}
private final HashMap<String,ClassFile> map;
public HashMap<String,ClassFile> map(){return map;}
private static class SClassFile implements Serializable{
private static final long serialVersionUID = 1L;
public String name;
private byte[] bytes=null;
private Kind kind;
static SClassFile fromCF(ClassFile from){
SClassFile res=new SClassFile();
res.name=from.name;
res.bytes=from.bytes;
res.kind=from.getKind();
return res;
}
ClassFile toCF(){
ClassFile res=new ClassFile(name,kind);
res.bytes=this.bytes;
return res;
}
}
public void saveOnFile(Path file){
HashMap<String, SClassFile> smap =new HashMap<>();
for(Entry<String, ClassFile> e:map().entrySet()){
smap.put(e.getKey(),SClassFile.fromCF(e.getValue()));
}
try (
OutputStream os = Files.newOutputStream(file);
ObjectOutputStream out = new ObjectOutputStream(os);
){
out.writeObject(smap);
}catch(IOException i) {throw new Error(i);}
}
public static MapClassLoader readFromFile(Path file,ClassLoader env){
try (
InputStream is = Files.newInputStream(file);
ObjectInputStream in = new ObjectInputStream(is);
){
Object res = in.readObject();
@SuppressWarnings("unchecked")
HashMap<String,SClassFile> smap=(HashMap<String,SClassFile>)res;
HashMap<String, ClassFile> map =new HashMap<>();
for(Entry<String, SClassFile> e:smap.entrySet()){
map.put(e.getKey(),e.getValue().toCF());
}
return new MapClassLoader(map,env);
}
catch(IOException i) {throw new Error(i);}
catch (ClassNotFoundException e) {throw new Error(e);}
catch (ClassCastException e) {throw new Error(e);}//means file corrupted?
}
@Override
protected Class<?> findClass(String name)throws ClassNotFoundException {
if(!map.containsKey(name)){
return super.findClass(name);
}
ClassFile jclassObject = map.get(name);
return super.defineClass(name, jclassObject.getBytes(), 0, jclassObject.getBytes().length);
}
};
public static MapClassLoader compile(ClassLoader env,List<SourceFile> files) throws CompilationError {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) throw new Error("Only JDK contains the JavaCompiler, you are running a Java JRE");
MyDiagnosticListener diagnisticListenerForErrors=new MyDiagnosticListener();
final HashMap<String,ClassFile> map;
final MapClassLoader classLoader;
if(!(env instanceof MapClassLoader)){
map= new HashMap<>();
classLoader=new MapClassLoader(map,env);
}
else{
classLoader=(MapClassLoader)env;
map=classLoader.map();
}
final ForwardingJavaFileManager<StandardJavaFileManager> classFileManager=
new ForwardingJavaFileManager<StandardJavaFileManager>(compiler.getStandardFileManager(diagnisticListenerForErrors,Locale.ENGLISH, null)){
@Override
public ClassLoader getClassLoader(Location location) {return classLoader;}
@Override
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
ClassFile f = map.get(className);
if (f!=null){return f;}
return super.getJavaFileForInput(location, className, kind);
}
@Override public Iterable<JavaFileObject> list(Location location,
String packageName,
Set<Kind> kinds,
boolean recurse)
throws IOException {
ArrayList<JavaFileObject> list=new ArrayList<>();
if(kinds.contains(Kind.CLASS)){list.addAll(map.values());}
for(JavaFileObject jfo:fileManager.list(location, packageName, kinds, recurse)){
list.add(jfo);
}
return list;
}
@Override public String inferBinaryName(Location location, JavaFileObject file) {
try{
String res;
if(file instanceof ClassFile){res=((ClassFile)file).name;}
else{ res=super.inferBinaryName(location,file);}
return res;
}catch(IllegalArgumentException ill){
throw ill;}
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException{
ClassFile classFile=new ClassFile(className, kind);
map.put(className, classFile);
return classFile;
}
};
if(!compiler.getTask(/*out:*/null,classFileManager,diagnisticListenerForErrors,
/*compilerOptions:*/Arrays.asList("-Xlint:unchecked","-encoding","\"UTF-8\"","-classpath",plugins()),/*StringsClasses??:*/null,files
).call()
){
throw new CompilationError(diagnisticListenerForErrors);
}
return classLoader;
}
}