# HG changeset patch # User Jaroslav Tulach # Date 1355215004 -3600 # Node ID 885acca2fa0bcdc2cec51ded16f516a9f8a79910 # Parent 58bd38caf70dfe17bf896141386a3091f73a76d1 Creating Bck2Brwsr entrypoint to for those who wish to generate their JavaScript based Java VM diff -r 58bd38caf70d -r 885acca2fa0b vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java Tue Dec 11 09:36:44 2012 +0100 @@ -0,0 +1,106 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** Build your own virtual machine! Use methods in this class to generate + * a skeleton JVM in JavaScript that contains pre-compiled classes of your + * choice. The generated script defines one JavaScript method that can + * be used to bootstrap and load the virtual machine:
+ * var vm = bck2brwsr();
+ * var main = vm.loadClass('org.your.pkg.Main');
+ * main.main__V_3Ljava_lang_String_2(null);
+ * 
+ * In case one wants to initialize the virtual machine with ability to + * load classes lazily when needed, one can provide a loader function to + * when creating the virtual machine:
+ * var vm = bck2brwsr(function(resource) { 
+ *   return null; // byte[] for the resource
+ * });
+ * 
+ * In this scenario, when a request for a unknown class is made, the loader + * function is asked for its byte code and the system dynamically transforms + * it to JavaScript. + * + * @author Jaroslav Tulach + */ +public final class Bck2Brwsr { + private Bck2Brwsr() { + } + + /** Generates virtual machine from bytes served by a resources + * provider. + * + * @param out the output to write the generated JavaScript to + * @param resources provider of class files to use + * @param classes additional classes to include in the generated script + * @throws IOException I/O exception can be thrown when something goes wrong + */ + public static void generate(Appendable out, Resources resources, String... classes) throws IOException { + StringArray arr = StringArray.asList(classes); + arr.add(VM.class.getName().replace('.', '/')); + VM.compile(resources, out, arr); + } + + /** Generates virtual machine from bytes served by a class loader. + * + * @param out the output to write the generated JavaScript to + * @param loader class loader to load needed classes from + * @param classes additional classes to include in the generated script + * @throws IOException I/O exception can be thrown when something goes wrong + */ + public static void generate(Appendable out, final ClassLoader loader, String... classes) throws IOException { + class R implements Resources { + @Override + public InputStream get(String name) throws IOException { + Enumeration en = loader.getResources(name); + URL u = null; + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + return u.openStream(); + } + } + generate(out, new R(), classes); + } + + /** Provider of resources (classes and other files). The + * {@link #generate(java.lang.Appendable, org.apidesign.vm4brwsr.Bck2Brwsr.Resources, java.lang.String[]) + * generator method} will call back here for all classes needed during + * translation to JavaScript. + */ + public interface Resources { + /** Loads given resource (class or other file like image). The + * resource name to load bytes for the {@link String} class + * would be "java/lang/String.class". + * + * @param resource path to resource to load + * @return the input stream for the resource + * @throws IOException can be thrown if the loading fails on some error + * or the file cannot be found + */ + public InputStream get(String resource) throws IOException; + } +} diff -r 58bd38caf70d -r 885acca2fa0b vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Fri Dec 07 14:10:37 2012 +0100 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Tue Dec 11 09:36:44 2012 +0100 @@ -29,7 +29,7 @@ * * @author Jaroslav Tulach */ -public abstract class ByteCodeToJavaScript { +abstract class ByteCodeToJavaScript { private ClassData jc; final Appendable out; diff -r 58bd38caf70d -r 885acca2fa0b vm/src/main/java/org/apidesign/vm4brwsr/GenJS.java --- a/vm/src/main/java/org/apidesign/vm4brwsr/GenJS.java Fri Dec 07 14:10:37 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,229 +0,0 @@ -/** - * Back 2 Browser Bytecode Translator - * Copyright (C) 2012 Jaroslav Tulach - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. Look for COPYING file in the top folder. - * If not, see http://opensource.org/licenses/GPL-2.0. - */ -package org.apidesign.vm4brwsr; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Enumeration; - -/** Generator of JavaScript from bytecode of classes on classpath of the VM. - * - * @author Jaroslav Tulach - */ -class GenJS extends ByteCodeToJavaScript { - public GenJS(Appendable out) { - super(out); - } - - static { - // uses VMLazy to load dynamic classes - VMLazy.init(); - } - - static void compile(Appendable out, String... names) throws IOException { - compile(out, StringArray.asList(names)); - } - static void compile(ClassLoader l, Appendable out, String... names) throws IOException { - compile(l, out, StringArray.asList(names)); - } - static void compile(Appendable out, StringArray names) throws IOException { - compile(GenJS.class.getClassLoader(), out, names); - } - static void compile(ClassLoader l, Appendable out, StringArray names) throws IOException { - new GenJS(out).doCompile(l, names); - } - protected void doCompile(ClassLoader l, StringArray names) throws IOException { - out.append("(function VM(global) {"); - out.append("\n var vm = {};"); - StringArray processed = new StringArray(); - StringArray initCode = new StringArray(); - for (String baseClass : names.toArray()) { - references.add(baseClass); - for (;;) { - String name = null; - for (String n : references.toArray()) { - if (processed.contains(n)) { - continue; - } - name = n; - } - if (name == null) { - break; - } - InputStream is = loadClass(l, name); - if (is == null) { - throw new IOException("Can't find class " + name); - } - try { - String ic = compile(is); - processed.add(name); - initCode.add(ic == null ? "" : ic); - } catch (RuntimeException ex) { - if (out instanceof CharSequence) { - CharSequence seq = (CharSequence)out; - int lastBlock = seq.length(); - while (lastBlock-- > 0) { - if (seq.charAt(lastBlock) == '{') { - break; - } - } - throw new IOException("Error while compiling " + name + "\n" - + seq.subSequence(lastBlock + 1, seq.length()), ex - ); - } else { - throw new IOException("Error while compiling " + name + "\n" - + out, ex - ); - } - } - } - - for (String resource : scripts.toArray()) { - while (resource.startsWith("/")) { - resource = resource.substring(1); - } - InputStream emul = l.getResourceAsStream(resource); - if (emul == null) { - throw new IOException("Can't find " + resource); - } - readResource(emul, out); - } - scripts = new StringArray(); - - StringArray toInit = StringArray.asList(references.toArray()); - toInit.reverse(); - - for (String ic : toInit.toArray()) { - int indx = processed.indexOf(ic); - if (indx >= 0) { - out.append(initCode.toArray()[indx]).append("\n"); - initCode.toArray()[indx] = ""; - } - } - } - out.append( - " global.bck2brwsr = function() {\n" - + " var args = arguments;\n" - + " var loader = {};\n" - + " loader.vm = vm;\n" - + " loader.loadClass = function(name) {\n" - + " var attr = name.replace__Ljava_lang_String_2CC(name, '.','_');\n" - + " var fn = vm[attr];\n" - + " if (fn) return fn(false);\n" - + " return vm.org_apidesign_vm4brwsr_VMLazy(false).\n" - + " load___3Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2(loader, name, args);\n" - + " }\n" - + " return loader;\n" - + " };\n"); - out.append("}(this));"); - } - private static void readResource(InputStream emul, Appendable out) throws IOException { - try { - int state = 0; - for (;;) { - int ch = emul.read(); - if (ch == -1) { - break; - } - if (ch < 0 || ch > 255) { - throw new IOException("Invalid char in emulation " + ch); - } - switch (state) { - case 0: - if (ch == '/') { - state = 1; - } else { - out.append((char)ch); - } - break; - case 1: - if (ch == '*') { - state = 2; - } else { - out.append('/').append((char)ch); - state = 0; - } - break; - case 2: - if (ch == '*') { - state = 3; - } - break; - case 3: - if (ch == '/') { - state = 0; - } else { - state = 2; - } - break; - } - } - } finally { - emul.close(); - } - } - - private static InputStream loadClass(ClassLoader l, String name) throws IOException { - Enumeration en = l.getResources(name + ".class"); - URL u = null; - while (en.hasMoreElements()) { - u = en.nextElement(); - } - if (u == null) { - throw new IOException("Can't find " + name); - } - if (u.toExternalForm().contains("rt.jar!")) { - throw new IOException("No emulation for " + u); - } - return u.openStream(); - } - - static String toString(String name) throws IOException { - StringBuilder sb = new StringBuilder(); - compile(sb, name); - return sb.toString().toString(); - } - - private StringArray scripts = new StringArray(); - private StringArray references = new StringArray(); - - @Override - protected boolean requireReference(String cn) { - if (references.contains(cn)) { - return false; - } - references.add(cn); - return true; - } - - @Override - protected void requireScript(String resourcePath) { - scripts.add(resourcePath); - } - - @Override - String assignClass(String className) { - return "vm." + className + " = "; - } - - @Override - String accessClass(String className) { - return "vm." + className; - } -} diff -r 58bd38caf70d -r 885acca2fa0b vm/src/main/java/org/apidesign/vm4brwsr/Main.java --- a/vm/src/main/java/org/apidesign/vm4brwsr/Main.java Fri Dec 07 14:10:37 2012 +0100 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/Main.java Tue Dec 11 09:36:44 2012 +0100 @@ -32,6 +32,7 @@ public static void main(String... args) throws IOException { if (args.length < 2) { + System.err.println("Bck2Brwsr Translator from Java(tm) to JavaScript, (c) Jaroslav Tulach 2012"); System.err.println("Usage: java -cp ... -jar ... java/lang/Class org/your/App ..."); return; } @@ -39,7 +40,7 @@ Writer w = new BufferedWriter(new FileWriter(args[0])); StringArray classes = StringArray.asList(args); classes.delete(0); - GenJS.compile(w, classes); + Bck2Brwsr.generate(w, Main.class.getClassLoader(), classes.toArray()); w.close(); } } diff -r 58bd38caf70d -r 885acca2fa0b vm/src/main/java/org/apidesign/vm4brwsr/VM.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/VM.java Tue Dec 11 09:36:44 2012 +0100 @@ -0,0 +1,207 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; + +/** Generator of JavaScript from bytecode of classes on classpath of the VM. + * + * @author Jaroslav Tulach + */ +class VM extends ByteCodeToJavaScript { + public VM(Appendable out) { + super(out); + } + + static { + // uses VMLazy to load dynamic classes + VMLazy.init(); + } + + static void compile(Bck2Brwsr.Resources l, Appendable out, StringArray names) throws IOException { + new VM(out).doCompile(l, names); + } + protected void doCompile(Bck2Brwsr.Resources l, StringArray names) throws IOException { + out.append("(function VM(global) {"); + out.append("\n var vm = {};"); + StringArray processed = new StringArray(); + StringArray initCode = new StringArray(); + for (String baseClass : names.toArray()) { + references.add(baseClass); + for (;;) { + String name = null; + for (String n : references.toArray()) { + if (processed.contains(n)) { + continue; + } + name = n; + } + if (name == null) { + break; + } + InputStream is = loadClass(l, name); + if (is == null) { + throw new IOException("Can't find class " + name); + } + try { + String ic = compile(is); + processed.add(name); + initCode.add(ic == null ? "" : ic); + } catch (RuntimeException ex) { + if (out instanceof CharSequence) { + CharSequence seq = (CharSequence)out; + int lastBlock = seq.length(); + while (lastBlock-- > 0) { + if (seq.charAt(lastBlock) == '{') { + break; + } + } + throw new IOException("Error while compiling " + name + "\n" + + seq.subSequence(lastBlock + 1, seq.length()), ex + ); + } else { + throw new IOException("Error while compiling " + name + "\n" + + out, ex + ); + } + } + } + + for (String resource : scripts.toArray()) { + while (resource.startsWith("/")) { + resource = resource.substring(1); + } + InputStream emul = l.get(resource); + if (emul == null) { + throw new IOException("Can't find " + resource); + } + readResource(emul, out); + } + scripts = new StringArray(); + + StringArray toInit = StringArray.asList(references.toArray()); + toInit.reverse(); + + for (String ic : toInit.toArray()) { + int indx = processed.indexOf(ic); + if (indx >= 0) { + out.append(initCode.toArray()[indx]).append("\n"); + initCode.toArray()[indx] = ""; + } + } + } + out.append( + " global.bck2brwsr = function() {\n" + + " var args = arguments;\n" + + " var loader = {};\n" + + " loader.vm = vm;\n" + + " loader.loadClass = function(name) {\n" + + " var attr = name.replace__Ljava_lang_String_2CC(name, '.','_');\n" + + " var fn = vm[attr];\n" + + " if (fn) return fn(false);\n" + + " return vm.org_apidesign_vm4brwsr_VMLazy(false).\n" + + " load___3Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2(loader, name, args);\n" + + " }\n" + + " return loader;\n" + + " };\n"); + out.append("}(this));"); + } + private static void readResource(InputStream emul, Appendable out) throws IOException { + try { + int state = 0; + for (;;) { + int ch = emul.read(); + if (ch == -1) { + break; + } + if (ch < 0 || ch > 255) { + throw new IOException("Invalid char in emulation " + ch); + } + switch (state) { + case 0: + if (ch == '/') { + state = 1; + } else { + out.append((char)ch); + } + break; + case 1: + if (ch == '*') { + state = 2; + } else { + out.append('/').append((char)ch); + state = 0; + } + break; + case 2: + if (ch == '*') { + state = 3; + } + break; + case 3: + if (ch == '/') { + state = 0; + } else { + state = 2; + } + break; + } + } + } finally { + emul.close(); + } + } + + private static InputStream loadClass(Bck2Brwsr.Resources l, String name) throws IOException { + return l.get(name + ".class"); // NOI18N + } + + static String toString(String name) throws IOException { + StringBuilder sb = new StringBuilder(); +// compile(sb, name); + return sb.toString().toString(); + } + + private StringArray scripts = new StringArray(); + private StringArray references = new StringArray(); + + @Override + protected boolean requireReference(String cn) { + if (references.contains(cn)) { + return false; + } + references.add(cn); + return true; + } + + @Override + protected void requireScript(String resourcePath) { + scripts.add(resourcePath); + } + + @Override + String assignClass(String className) { + return "vm." + className + " = "; + } + + @Override + String accessClass(String className) { + return "vm." + className; + } +} diff -r 58bd38caf70d -r 885acca2fa0b vm/src/test/java/org/apidesign/vm4brwsr/BytesLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm/src/test/java/org/apidesign/vm4brwsr/BytesLoader.java Tue Dec 11 09:36:44 2012 +0100 @@ -0,0 +1,58 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.TreeSet; + +/** + * + * @author Jaroslav Tulach + */ +public final class BytesLoader { + private static Set requested = new TreeSet(); + + public byte[] get(String name) throws IOException { + if (!requested.add(name)) { + throw new IllegalStateException("Requested for second time: " + name); + } + InputStream is = BytesLoader.class.getClassLoader().getResourceAsStream(name); + if (is == null) { + throw new IOException("Can't find " + name); + } + byte[] arr = new byte[is.available()]; + int len = is.read(arr); + if (len != arr.length) { + throw new IOException("Read only " + len + " wanting " + arr.length); + } + /* + System.err.print("loader['" + name + "'] = ["); + for (int i = 0; i < arr.length; i++) { + if (i > 0) { + System.err.print(", "); + } + System.err.print(arr[i]); + } + System.err.println("]"); + */ + return arr; + } + +} diff -r 58bd38caf70d -r 885acca2fa0b vm/src/test/java/org/apidesign/vm4brwsr/CompareVMs.java --- a/vm/src/test/java/org/apidesign/vm4brwsr/CompareVMs.java Fri Dec 07 14:10:37 2012 +0100 +++ b/vm/src/test/java/org/apidesign/vm4brwsr/CompareVMs.java Tue Dec 11 09:36:44 2012 +0100 @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import javax.script.Invocable; +import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import org.testng.Assert; @@ -98,26 +99,11 @@ return; } StringBuilder sb = new StringBuilder(); - class SkipMe extends GenJS { - - public SkipMe(Appendable out) { - super(out); - } - - @Override - protected boolean requireReference(String cn) { - if (cn.contains("CompareVMs")) { - return true; - } - return super.requireReference(cn); - } - } - SkipMe sm = new SkipMe(sb); - sm.doCompile(CompareVMs.class.getClassLoader(), StringArray.asList( - clazz.getName().replace('.', '/'))); + Bck2Brwsr.generate(sb, CompareVMs.class.getClassLoader()); ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine js = sem.getEngineByExtension("js"); + js.getContext().setAttribute("loader", new BytesLoader(), ScriptContext.ENGINE_SCOPE); Object res = js.eval(sb.toString()); Assert.assertTrue(js instanceof Invocable, "It is invocable object: " + res); diff -r 58bd38caf70d -r 885acca2fa0b vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java --- a/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java Fri Dec 07 14:10:37 2012 +0100 +++ b/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java Tue Dec 11 09:36:44 2012 +0100 @@ -20,6 +20,9 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -289,7 +292,7 @@ if (sb == null) { sb = new StringBuilder(); } - GenJS.compile(sb, names); + Bck2Brwsr.generate(sb, new EmulationResources(), names); ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine js = sem.getEngineByExtension("js"); if (eng != null) { @@ -314,4 +317,21 @@ w.close(); return new StringBuilder(f.getPath()); } + private static class EmulationResources implements Bck2Brwsr.Resources { + @Override + public InputStream get(String name) throws IOException { + Enumeration en = StaticMethodTest.class.getClassLoader().getResources(name); + URL u = null; + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + if (u.toExternalForm().contains("rt.jar!")) { + throw new IOException("No emulation for " + u); + } + return u.openStream(); + } + } } diff -r 58bd38caf70d -r 885acca2fa0b vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java --- a/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Fri Dec 07 14:10:37 2012 +0100 +++ b/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Tue Dec 11 09:36:44 2012 +0100 @@ -17,10 +17,6 @@ */ package org.apidesign.vm4brwsr; -import java.io.IOException; -import java.io.InputStream; -import java.util.Set; -import java.util.TreeSet; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; @@ -53,9 +49,9 @@ ScriptEngine[] arr = { null }; code = StaticMethodTest.compileClass(sb, arr, - "org/apidesign/vm4brwsr/GenJS" + "org/apidesign/vm4brwsr/VM" ); - arr[0].getContext().setAttribute("loader", new FindBytes(), ScriptContext.ENGINE_SCOPE); + arr[0].getContext().setAttribute("loader", new BytesLoader(), ScriptContext.ENGINE_SCOPE); codeSeq = sb; } @@ -88,35 +84,4 @@ } assertEquals(ret, expRes, msg + "was: " + ret + "\n" + StaticMethodTest.dumpJS(codeSeq)); } - - public static final class FindBytes { - private static Set requested = new TreeSet(); - - public byte[] get(String name) throws IOException { - if (!requested.add(name)) { - throw new IllegalStateException("Requested for second time: " + name); - } - - InputStream is = VMLazyTest.class.getClassLoader().getResourceAsStream(name); - if (is == null) { - throw new IOException("Can't find " + name); - } - byte[] arr = new byte[is.available()]; - int len = is.read(arr); - if (len != arr.length) { - throw new IOException("Read only " + len + " wanting " + arr.length); - } - /* - System.err.print("loader['" + name + "'] = ["); - for (int i = 0; i < arr.length; i++) { - if (i > 0) { - System.err.print(", "); - } - System.err.print(arr[i]); - } - System.err.println("]"); - */ - return arr; - } - } }