Mercurial Hosting > luan
diff src/luan/impl/Compiled.java @ 1434:56fb5cd8228d
cache compiled code in temp files
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 29 Dec 2019 15:25:07 -0700 |
parents | src/luan/impl/LuanJavaCompiler.java@1a68fc55a80c |
children | 65d4afc9ad07 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/impl/Compiled.java Sun Dec 29 15:25:07 2019 -0700 @@ -0,0 +1,191 @@ +package luan.impl; + +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; +import javax.tools.JavaFileManager; +import javax.tools.StandardJavaFileManager; +import javax.tools.ForwardingJavaFileManager; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; + + +final class Compiled { + private static final Logger logger = LoggerFactory.getLogger(Compiled.class); + + private static class MyJavaFileObject extends SimpleJavaFileObject { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + MyJavaFileObject() { + super(URI.create("whatever"),JavaFileObject.Kind.CLASS); + } + + @Override public OutputStream openOutputStream() { + return baos; + } + + byte[] byteCode(String sourceName) { + byte[] byteCode = baos.toByteArray(); + final int len = sourceName.length(); + int max = byteCode.length-len-3; + outer: + for( int i=0; true; i++ ) { + if( i > max ) + throw new RuntimeException("len="+len); + if( byteCode[i]==1 && (byteCode[i+1] << 8 | 0xFF & byteCode[i+2]) == len ) { + for( int j=i+3; j<i+3+len; j++ ) { + if( byteCode[j] != '$' ) + continue outer; + } + System.arraycopy(sourceName.getBytes(),0,byteCode,i+3,len); + break; + } + } + return byteCode; + } + } + + static Compiled compile(final String className,final String sourceName,final String code) { + final int len = sourceName.length(); + StringBuilder sb = new StringBuilder(sourceName); + for( int i=0; i<len; i++ ) + sb.setCharAt(i,'$'); + JavaFileObject sourceFile = new SimpleJavaFileObject(URI.create(sb.toString()),JavaFileObject.Kind.SOURCE) { + @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return code; + } + @Override public String getName() { + return sourceName; + } + @Override public boolean isNameCompatible(String simpleName,JavaFileObject.Kind kind) { + return true; + } + }; + final Map<String,MyJavaFileObject> map = new HashMap<String,MyJavaFileObject>(); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager sjfm = compiler.getStandardFileManager(null,null,null); + ForwardingJavaFileManager fjfm = new ForwardingJavaFileManager(sjfm) { + @Override public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + if( map.containsKey(className) ) + throw new RuntimeException(className); + MyJavaFileObject classFile = new MyJavaFileObject(); + map.put(className,classFile); + return classFile; + } + }; + StringWriter out = new StringWriter(); + boolean b = compiler.getTask(out, fjfm, null, null, null, Collections.singletonList(sourceFile)).call(); + if( !b ) + throw new RuntimeException("\n"+out+"\ncode:\n"+code+"\n"); + Map<String,byte[]> map2 = new HashMap<String,byte[]>(); + for( Map.Entry<String,MyJavaFileObject> entry : map.entrySet() ) { + map2.put( entry.getKey(), entry.getValue().byteCode(sourceName) ); + } + return new Compiled(className,map2); + } + + + private final String className; + private final Map<String,byte[]> map; + private final Set<String> set = new HashSet<String>(); + + private Compiled(String className,Map<String,byte[]> map) { + this.className = className; + this.map = map; + } + + Class loadClass() { + try { + ClassLoader cl = new ClassLoader() { + @Override protected Class<?> findClass(String name) throws ClassNotFoundException { + if( !set.add(name) ) + logger.error("dup "+name); + byte[] byteCode = map.get(name); + if( byteCode != null ) { + return defineClass(name, byteCode, 0, byteCode.length); + } + return super.findClass(name); + } + }; + return cl.loadClass(className); + } catch(ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + + private static final int VERSION = 1; + private static final File tmpDir; + static { + File f = new File(System.getProperty("java.io.tmpdir")); + tmpDir = new File(f,"luan"); + tmpDir.mkdir(); + if( !tmpDir.exists() ) + throw new RuntimeException(); + } + + static Compiled load(String fileName,String key) { + try { + File f = new File(tmpDir,fileName); + if( !f.exists() ) + return null; + DataInputStream in = new DataInputStream(new FileInputStream(f)); + if( in.readInt() != VERSION ) + return null; + if( !in.readUTF().equals(key) ) + return null; + String className = in.readUTF(); + int n = in.readInt(); + Map<String,byte[]> map = new HashMap<String,byte[]>(); + for( int i=0; i<n; i++ ) { + String s = in.readUTF(); + int len = in.readInt(); + byte[] a = new byte[len]; + in.readFully(a); + map.put(s,a); + } + in.close(); + return new Compiled(className,map); + } catch(IOException e) { + logger.error("load failed",e); + return null; + } + } + + void save(String fileName,String key) { + try { + File f = new File(tmpDir,fileName); + DataOutputStream out = new DataOutputStream(new FileOutputStream(f)); + out.writeInt(VERSION); + out.writeUTF(key); + out.writeUTF(className); + out.writeInt(map.size()); + for( Map.Entry<String,byte[]> entry : map.entrySet() ) { + out.writeUTF( entry.getKey() ); + byte[] a = entry.getValue(); + out.writeInt(a.length); + out.write(a,0,a.length); + } + out.close(); + } catch(IOException e) { + logger.error("save failed",e); + } + } +}