jaroslav@106: /** jaroslav@106: * Back 2 Browser Bytecode Translator jaroslav@106: * Copyright (C) 2012 Jaroslav Tulach jaroslav@106: * jaroslav@106: * This program is free software: you can redistribute it and/or modify jaroslav@106: * it under the terms of the GNU General Public License as published by jaroslav@106: * the Free Software Foundation, version 2 of the License. jaroslav@106: * jaroslav@106: * This program is distributed in the hope that it will be useful, jaroslav@106: * but WITHOUT ANY WARRANTY; without even the implied warranty of jaroslav@106: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the jaroslav@106: * GNU General Public License for more details. jaroslav@106: * jaroslav@106: * You should have received a copy of the GNU General Public License jaroslav@106: * along with this program. Look for COPYING file in the top folder. jaroslav@106: * If not, see http://opensource.org/licenses/GPL-2.0. jaroslav@106: */ jaroslav@29: package org.apidesign.vm4brwsr; jaroslav@29: jaroslav@29: import java.io.IOException; jaroslav@29: import java.io.InputStream; jaroslav@1495: import org.apidesign.bck2brwsr.core.JavaScriptBody; lubomir@1029: import org.apidesign.vm4brwsr.ByteCodeParser.ClassData; lubomir@1083: import org.apidesign.vm4brwsr.ByteCodeParser.FieldData; lubomir@1083: import org.apidesign.vm4brwsr.ByteCodeParser.MethodData; jaroslav@29: jaroslav@29: /** Generator of JavaScript from bytecode of classes on classpath of the VM. jaroslav@29: * jaroslav@29: * @author Jaroslav Tulach jaroslav@29: */ lubomir@1020: abstract class VM extends ByteCodeToJavaScript { lubomir@1084: protected final ClassDataCache classDataCache; lubomir@1084: lubomir@1084: private final Bck2Brwsr.Resources resources; lubomir@1029: private final ExportedSymbols exportedSymbols; lubomir@1085: private final StringArray invokerMethods; jaroslav@1587: private final StringArray asBinary; lubomir@1029: jaroslav@1587: private VM( jaroslav@1587: Appendable out, Bck2Brwsr.Resources resources, jaroslav@1587: StringArray explicitlyExported, StringArray asBinary jaroslav@1587: ) { jtulach@162: super(out); lubomir@1029: this.resources = resources; lubomir@1084: this.classDataCache = new ClassDataCache(resources); jaroslav@1491: this.exportedSymbols = new ExportedSymbols(resources, explicitlyExported); lubomir@1085: this.invokerMethods = new StringArray(); jaroslav@1587: this.asBinary = asBinary; lubomir@869: } lubomir@869: jaroslav@277: static { jaroslav@277: // uses VMLazy to load dynamic classes jaroslav@671: boolean assertsOn = false; jaroslav@671: assert assertsOn = true; jaroslav@671: if (assertsOn) { jaroslav@671: VMLazy.init(); jaroslav@1550: ClassPath.init(); jaroslav@671: } jaroslav@277: } jaroslav@399: jaroslav@399: @Override jaroslav@399: boolean debug(String msg) throws IOException { jaroslav@399: return false; jaroslav@399: } jaroslav@277: jaroslav@1491: static void compile(Appendable out, jaroslav@1493: Bck2Brwsr config jaroslav@1491: ) throws IOException { jaroslav@1583: String[] both = config.classes().toArray(); jaroslav@1491: lubomir@1143: final StringArray fixedNames = new StringArray(); jaroslav@1584: fixedNames.add(Class.class.getName().replace('.', '/')); jaroslav@1584: fixedNames.add(ArithmeticException.class.getName().replace('.', '/')); jaroslav@1584: jaroslav@1584: VM vm; jaroslav@1584: if (config.isExtension()) { jaroslav@1584: fixedNames.add(VM.class.getName().replace('.', '/')); jaroslav@1587: vm = new Extension(out, jaroslav@1587: config.getResources(), both, config.exported(), jaroslav@1587: config.allResources() jaroslav@1587: ); jaroslav@1584: } else { jaroslav@1584: if (config.includeVM()) { jaroslav@1584: fixedNames.add(VM.class.getName().replace('.', '/')); jaroslav@1584: } jaroslav@1587: vm = new Standalone(out, jaroslav@1587: config.getResources(), config.exported(), jaroslav@1587: config.allResources() jaroslav@1587: ); jaroslav@1584: } jaroslav@1587: vm.doCompile(fixedNames.addAndNew(both)); jaroslav@29: } lubomir@869: jaroslav@1587: private void doCompile(StringArray names) throws IOException { lubomir@1020: generatePrologue(); jaroslav@1513: append( jaroslav@1508: "\n var invoker = {};"); lubomir@1029: generateBody(names); lubomir@1085: for (String invokerMethod: invokerMethods.toArray()) { jaroslav@1513: append("\n invoker." + invokerMethod + " = function(target) {" jaroslav@1508: + "\n return function() {" jaroslav@1508: + "\n return target['" + invokerMethod + "'].apply(target, arguments);" jaroslav@1508: + "\n };" jaroslav@1508: + "\n };" jaroslav@1508: ); lubomir@1085: } jaroslav@1495: jaroslav@1495: for (String r : asBinary.toArray()) { jaroslav@1558: append("\n ").append(getExportsObject()).append("['registerResource']('"); jaroslav@1513: append(r).append("', '"); jaroslav@1495: InputStream is = this.resources.get(r); jaroslav@1495: byte[] arr = new byte[is.available()]; jaroslav@1520: int offset = 0; jaroslav@1520: for (;;) { jaroslav@1520: if (offset == arr.length) { jaroslav@1520: byte[] tmp = new byte[arr.length * 2]; jaroslav@1520: System.arraycopy(arr, 0, tmp, 0, arr.length); jaroslav@1520: arr = tmp; jaroslav@1520: } jaroslav@1520: int len = is.read(arr, offset, arr.length - offset); jaroslav@1520: if (len == -1) { jaroslav@1520: break; jaroslav@1520: } jaroslav@1520: offset += len; jaroslav@1520: } jaroslav@1520: if (offset != arr.length) { jaroslav@1520: byte[] tmp = new byte[offset]; jaroslav@1520: System.arraycopy(arr, 0, tmp, 0, offset); jaroslav@1520: arr = tmp; jaroslav@1495: } jaroslav@1513: append(btoa(arr)); jaroslav@1513: append("');"); jaroslav@1495: } jaroslav@1495: jaroslav@1513: append("\n"); lubomir@1020: generateEpilogue(); lubomir@869: } lubomir@869: jaroslav@1495: @JavaScriptBody(args = { "arr" }, body = "return btoa(arr);") jaroslav@1495: private static String btoa(byte[] arr) { jaroslav@1495: return javax.xml.bind.DatatypeConverter.printBase64Binary(arr); jaroslav@1495: } jaroslav@1495: lubomir@1020: protected abstract void generatePrologue() throws IOException; lubomir@1020: lubomir@1020: protected abstract void generateEpilogue() throws IOException; lubomir@1020: lubomir@1094: protected abstract String getExportsObject(); lubomir@1029: lubomir@1094: protected abstract boolean isExternalClass(String className); lubomir@1029: lubomir@1029: @Override lubomir@1029: protected final void declaredClass(ClassData classData, String mangledName) lubomir@1029: throws IOException { lubomir@1029: if (exportedSymbols.isExported(classData)) { jaroslav@1513: append("\n").append(getExportsObject()).append("['") lubomir@1029: .append(mangledName) lubomir@1029: .append("'] = ") lubomir@1029: .append(accessClass(mangledName)) lubomir@1029: .append(";\n"); lubomir@1029: } lubomir@1029: } lubomir@1029: lubomir@1094: protected String generateClass(String className) throws IOException { lubomir@1094: ClassData classData = classDataCache.getClassData(className); lubomir@1094: if (classData == null) { lubomir@1094: throw new IOException("Can't find class " + className); lubomir@1094: } lubomir@1094: return compile(classData); lubomir@1094: } lubomir@1094: lubomir@1083: @Override lubomir@1083: protected void declaredField(FieldData fieldData, lubomir@1083: String destObject, lubomir@1083: String mangledName) throws IOException { lubomir@1083: if (exportedSymbols.isExported(fieldData)) { lubomir@1083: exportMember(destObject, mangledName); lubomir@1083: } lubomir@1083: } lubomir@1083: lubomir@1083: @Override lubomir@1083: protected void declaredMethod(MethodData methodData, lubomir@1083: String destObject, lubomir@1083: String mangledName) throws IOException { lubomir@1087: if (isHierarchyExported(methodData)) { lubomir@1083: exportMember(destObject, mangledName); lubomir@1083: } lubomir@1083: } lubomir@1083: lubomir@1083: private void exportMember(String destObject, String memberName) lubomir@1083: throws IOException { jaroslav@1513: append("\n").append(destObject).append("['") lubomir@1083: .append(memberName) lubomir@1083: .append("'] = ") lubomir@1083: .append(destObject).append(".").append(memberName) lubomir@1083: .append(";\n"); lubomir@1083: } lubomir@1083: lubomir@1029: private void generateBody(StringArray names) throws IOException { jtulach@162: StringArray processed = new StringArray(); jtulach@162: StringArray initCode = new StringArray(); jaroslav@1344: StringArray skipClass = new StringArray(); jtulach@162: for (String baseClass : names.toArray()) { jaroslav@272: references.add(baseClass); jaroslav@97: for (;;) { jaroslav@97: String name = null; jaroslav@272: for (String n : references.toArray()) { jaroslav@1344: if (skipClass.contains(n)) { jaroslav@1344: continue; jaroslav@1344: } jtulach@162: if (processed.contains(n)) { jaroslav@97: continue; jaroslav@97: } jaroslav@97: name = n; jaroslav@97: } jaroslav@97: if (name == null) { jaroslav@97: break; jaroslav@97: } jaroslav@1513: InputStream is = resources.get(name + ".class"); jaroslav@97: if (is == null) { jaroslav@1509: lazyReference(this, name); jaroslav@1344: skipClass.add(name); jaroslav@1344: continue; jaroslav@97: } jaroslav@97: try { lubomir@1029: String ic = generateClass(name); jtulach@162: processed.add(name); jtulach@162: initCode.add(ic == null ? "" : ic); jaroslav@97: } catch (RuntimeException ex) { jaroslav@1509: throw new IOException("Error while compiling " + name + "\n", ex); jaroslav@97: } jaroslav@29: } jaroslav@97: jaroslav@272: for (String resource : scripts.toArray()) { jaroslav@250: while (resource.startsWith("/")) { jaroslav@250: resource = resource.substring(1); jaroslav@250: } lubomir@1029: InputStream emul = resources.get(resource); jaroslav@250: if (emul == null) { jaroslav@250: throw new IOException("Can't find " + resource); jaroslav@250: } jaroslav@1509: readResource(emul, this); jaroslav@1587: asBinary.remove(resource); jaroslav@250: } jaroslav@272: scripts = new StringArray(); lubomir@1020: jaroslav@272: StringArray toInit = StringArray.asList(references.toArray()); jtulach@162: toInit.reverse(); jtulach@129: jtulach@162: for (String ic : toInit.toArray()) { jtulach@162: int indx = processed.indexOf(ic); jtulach@162: if (indx >= 0) { jaroslav@833: final String theCode = initCode.toArray()[indx]; jaroslav@833: if (!theCode.isEmpty()) { jaroslav@1509: append(theCode).append("\n"); jaroslav@833: } jtulach@162: initCode.toArray()[indx] = ""; jaroslav@91: } jaroslav@91: } jaroslav@29: } jaroslav@29: } lubomir@1020: jaroslav@90: private static void readResource(InputStream emul, Appendable out) throws IOException { jaroslav@90: try { jaroslav@90: int state = 0; jaroslav@90: for (;;) { jaroslav@90: int ch = emul.read(); jaroslav@90: if (ch == -1) { jaroslav@90: break; jaroslav@90: } jaroslav@90: if (ch < 0 || ch > 255) { jaroslav@90: throw new IOException("Invalid char in emulation " + ch); jaroslav@90: } jaroslav@90: switch (state) { jaroslav@90: case 0: jaroslav@90: if (ch == '/') { jaroslav@90: state = 1; jaroslav@90: } else { jaroslav@90: out.append((char)ch); jaroslav@90: } jaroslav@90: break; jaroslav@90: case 1: jaroslav@90: if (ch == '*') { jaroslav@90: state = 2; jaroslav@90: } else { jaroslav@90: out.append('/').append((char)ch); jaroslav@90: state = 0; jaroslav@90: } jaroslav@90: break; jaroslav@90: case 2: jaroslav@90: if (ch == '*') { jaroslav@90: state = 3; jaroslav@90: } jaroslav@90: break; jaroslav@90: case 3: jaroslav@90: if (ch == '/') { jaroslav@90: state = 0; jaroslav@90: } else { jaroslav@90: state = 2; jaroslav@90: } jaroslav@90: break; jaroslav@90: } jaroslav@90: } jaroslav@90: } finally { jaroslav@90: emul.close(); jaroslav@90: } jaroslav@90: } jaroslav@93: jaroslav@136: static String toString(String name) throws IOException { jaroslav@136: StringBuilder sb = new StringBuilder(); jaroslav@298: // compile(sb, name); jaroslav@136: return sb.toString().toString(); jaroslav@136: } jtulach@162: jtulach@162: private StringArray scripts = new StringArray(); jtulach@162: private StringArray references = new StringArray(); jtulach@162: jtulach@162: @Override jtulach@162: protected boolean requireReference(String cn) { jtulach@162: if (references.contains(cn)) { jtulach@162: return false; jtulach@162: } jtulach@162: references.add(cn); jtulach@162: return true; jtulach@162: } jtulach@162: jtulach@162: @Override jtulach@162: protected void requireScript(String resourcePath) { jtulach@162: scripts.add(resourcePath); jtulach@162: } jaroslav@274: jaroslav@274: @Override jaroslav@274: String assignClass(String className) { jaroslav@274: return "vm." + className + " = "; jaroslav@274: } jaroslav@274: jaroslav@274: @Override jaroslav@274: String accessClass(String className) { jaroslav@274: return "vm." + className; jaroslav@274: } lubomir@869: lubomir@869: @Override lubomir@1085: protected String accessField(String object, String mangledName, lubomir@1085: String[] fieldInfoName) throws IOException { lubomir@1085: final FieldData field = lubomir@1085: classDataCache.findField(fieldInfoName[0], lubomir@1085: fieldInfoName[1], lubomir@1085: fieldInfoName[2]); lubomir@1085: return accessNonVirtualMember(object, mangledName, lubomir@1085: (field != null) ? field.cls : null); lubomir@1085: } lubomir@1085: lubomir@1085: @Override lubomir@1085: protected String accessStaticMethod( lubomir@1085: String object, lubomir@1085: String mangledName, lubomir@1085: String[] fieldInfoName) throws IOException { lubomir@1085: final MethodData method = lubomir@1085: classDataCache.findMethod(fieldInfoName[0], lubomir@1085: fieldInfoName[1], lubomir@1085: fieldInfoName[2]); lubomir@1085: return accessNonVirtualMember(object, mangledName, lubomir@1085: (method != null) ? method.cls : null); lubomir@1085: } lubomir@1085: lubomir@1085: @Override lubomir@1085: protected String accessVirtualMethod( lubomir@1085: String object, lubomir@1085: String mangledName, lubomir@1085: String[] fieldInfoName) throws IOException { lubomir@1085: final ClassData referencedClass = lubomir@1084: classDataCache.getClassData(fieldInfoName[0]); lubomir@1085: final MethodData method = lubomir@1085: classDataCache.findMethod(referencedClass, lubomir@1085: fieldInfoName[1], lubomir@1085: fieldInfoName[2]); lubomir@1085: lubomir@1085: if ((method != null) lubomir@1094: && !isExternalClass(method.cls.getClassName()) lubomir@1085: && (((method.access & ByteCodeParser.ACC_FINAL) != 0) lubomir@1085: || ((referencedClass.getAccessFlags() lubomir@1085: & ByteCodeParser.ACC_FINAL) != 0) lubomir@1087: || !isHierarchyExported(method))) { lubomir@1085: return object + "." + mangledName; lubomir@1084: } lubomir@1084: lubomir@1085: return accessThroughInvoker(object, mangledName); lubomir@1085: } lubomir@1085: lubomir@1085: private String accessThroughInvoker(String object, String mangledName) { lubomir@1085: if (!invokerMethods.contains(mangledName)) { lubomir@1085: invokerMethods.add(mangledName); lubomir@1085: } lubomir@1085: return "invoker." + mangledName + '(' + object + ')'; lubomir@1085: } lubomir@1085: lubomir@1087: private boolean isHierarchyExported(final MethodData methodData) lubomir@1087: throws IOException { lubomir@1087: if (exportedSymbols.isExported(methodData)) { lubomir@1087: return true; lubomir@1087: } lubomir@1087: if ((methodData.access & (ByteCodeParser.ACC_PRIVATE lubomir@1087: | ByteCodeParser.ACC_STATIC)) != 0) { lubomir@1087: return false; lubomir@1087: } lubomir@1087: lubomir@1087: final ExportedMethodFinder exportedMethodFinder = lubomir@1087: new ExportedMethodFinder(exportedSymbols); lubomir@1087: lubomir@1087: classDataCache.findMethods( lubomir@1087: methodData.cls, lubomir@1087: methodData.getName(), lubomir@1087: methodData.getInternalSig(), lubomir@1087: exportedMethodFinder); lubomir@1087: lubomir@1087: return (exportedMethodFinder.getFound() != null); lubomir@1087: } lubomir@1087: lubomir@1094: private String accessNonVirtualMember(String object, lubomir@1094: String mangledName, lubomir@1094: ClassData declaringClass) { lubomir@1094: return ((declaringClass != null) lubomir@1094: && !isExternalClass(declaringClass.getClassName())) lubomir@1094: ? object + "." + mangledName lubomir@1094: : object + "['" + mangledName + "']"; lubomir@1084: } lubomir@1084: lubomir@1087: private static final class ExportedMethodFinder lubomir@1087: implements ClassDataCache.TraversalCallback { lubomir@1087: private final ExportedSymbols exportedSymbols; lubomir@1087: private MethodData found; lubomir@1087: lubomir@1087: public ExportedMethodFinder(final ExportedSymbols exportedSymbols) { lubomir@1087: this.exportedSymbols = exportedSymbols; lubomir@1087: } lubomir@1087: lubomir@1087: @Override lubomir@1087: public boolean traverse(final MethodData methodData) { lubomir@1087: try { lubomir@1087: if (exportedSymbols.isExported(methodData)) { lubomir@1087: found = methodData; lubomir@1087: return false; lubomir@1087: } lubomir@1087: } catch (final IOException e) { lubomir@1087: } lubomir@1087: lubomir@1087: return true; lubomir@1087: } lubomir@1087: lubomir@1087: public MethodData getFound() { lubomir@1087: return found; lubomir@1087: } lubomir@1087: } lubomir@1087: lubomir@1020: private static final class Standalone extends VM { jaroslav@1587: private Standalone(Appendable out, jaroslav@1587: Bck2Brwsr.Resources resources, jaroslav@1587: StringArray explicitlyExported, StringArray asBinary jaroslav@1587: ) { jaroslav@1587: super(out, resources, explicitlyExported, asBinary); lubomir@1020: } lubomir@1020: lubomir@1020: @Override lubomir@1020: protected void generatePrologue() throws IOException { jaroslav@1513: append("(function VM(global) {var fillInVMSkeleton = function(vm) {"); lubomir@1020: } lubomir@1020: lubomir@1020: @Override lubomir@1020: protected void generateEpilogue() throws IOException { jaroslav@1513: append( lubomir@1020: " return vm;\n" lubomir@1020: + " };\n" lubomir@1029: + " var extensions = [];\n" jaroslav@1580: + " function replaceAll(s, target, replacement) {\n" jaroslav@1580: + " var pos = 0;\n" jaroslav@1580: + " for (;;) {\n" jaroslav@1580: + " var indx = s.indexOf(target, pos);\n" jaroslav@1580: + " if (indx === -1) {\n" jaroslav@1580: + " return s;\n" jaroslav@1580: + " }\n" jaroslav@1580: + " pos = indx + replacement.length;\n" jaroslav@1580: + " s = s.substring(0, indx) + replacement + s.substring(indx + target.length);\n" jaroslav@1580: + " }\n" jaroslav@1580: + " }\n" jaroslav@1514: + " function mangleClass(name) {\n" jaroslav@1580: + " name = replaceAll(name, '_', '_1');\n" jaroslav@1580: + " name = replaceAll(name, '.', '_');\n" jaroslav@1580: + " return name;\n" jaroslav@1514: + " };\n" lubomir@1020: + " global.bck2brwsr = function() {\n" lubomir@1020: + " var args = Array.prototype.slice.apply(arguments);\n" jaroslav@1495: + " var resources = {};\n" jaroslav@1495: + " function registerResource(n, a64) {\n" jaroslav@1495: + " var str = atob(a64);\n" jaroslav@1495: + " var arr = [];\n" jaroslav@1496: + " for (var i = 0; i < str.length; i++) {\n" jaroslav@1496: + " var ch = str.charCodeAt(i) & 0xff;\n" jaroslav@1496: + " if (ch > 127) ch -= 256;\n" jaroslav@1496: + " arr.push(ch);\n" jaroslav@1496: + " }\n" jaroslav@1495: + " if (!resources[n]) resources[n] = [arr];\n" jaroslav@1495: + " else resources[n].push(arr);\n" jaroslav@1495: + " }\n" jaroslav@1495: + " var vm = fillInVMSkeleton({ 'registerResource' : registerResource });\n" jaroslav@1584: + " function initVM() {\n" jaroslav@1584: + " var clsArray = vm['java_lang_reflect_Array'];\n" jaroslav@1584: + " if (clsArray) clsArray(false);\n" jaroslav@1584: + " }\n" lubomir@1029: + " for (var i = 0; i < extensions.length; ++i) {\n" lubomir@1029: + " extensions[i](vm);\n" lubomir@1029: + " }\n" jaroslav@1558: + " vm['registerResource'] = null;\n" jaroslav@1489: + " var knownExtensions = extensions.length;\n" lubomir@1020: + " var loader = {};\n" jaroslav@1577: + " var loadBytes = function(name, skip) {\n" jaroslav@1577: + " skip = typeof skip == 'number' ? skip : 0;\n" jaroslav@1577: + " var arr = resources[name];\n" jaroslav@1577: + " if (arr) {\n" jaroslav@1577: + " var arrSize = arr.length;\n" jaroslav@1577: + " if (skip < arrSize) return arr[skip];\n" jaroslav@1577: + " skip -= arrSize;\n" jaroslav@1577: + " } else {\n" jaroslav@1577: + " var arrSize = 0;\n" jaroslav@1577: + " };\n" jaroslav@1581: + " for (var i = 0; i < args.length; i++) {\n" jaroslav@1581: + " var at = args[i];\n" jaroslav@1581: + " var ret;\n" jaroslav@1581: + " if (typeof at === 'function') ret = at(name, skip);\n" jaroslav@1581: + " else ret = vm['org_apidesign_vm4brwsr_ClassPath'](false).\n" jaroslav@1581: + " loadBytes___3BLjava_lang_String_2Ljava_lang_Object_2II(name, args, i, skip);\n" jaroslav@1581: + " if (ret !== null) return ret;\n" jaroslav@1581: + " }\n" jaroslav@1577: + " while (knownExtensions < extensions.length) {\n" jaroslav@1577: + " vm['registerResource'] = registerResource;\n" jaroslav@1577: + " extensions[knownExtensions++](vm);\n" jaroslav@1577: + " vm['registerResource'] = null;\n" jaroslav@1584: + " initVM();\n" jaroslav@1577: + " }\n" jaroslav@1577: + " var arr = resources[name];\n" jaroslav@1577: + " return (arr && arr.length > arrSize) ? arr[arrSize] : null;\n" jaroslav@1577: + " }\n" jaroslav@1578: + " var reload = function(name, arr, keep) {;\n" jaroslav@1578: + " if (!keep) {\n" jaroslav@1578: + " var attr = mangleClass(name);\n" jaroslav@1578: + " delete vm[attr];\n" jaroslav@1578: + " }\n" jaroslav@1578: + " return vm['org_apidesign_vm4brwsr_VMLazy'](false)\n" jaroslav@1578: + " ['load__Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2_3B']\n" jaroslav@1579: + " (vm, name, args, arr);\n" jaroslav@1578: + " };\n" lubomir@1020: + " loader.loadClass = function(name) {\n" jaroslav@1514: + " var attr = mangleClass(name);\n" lubomir@1020: + " var fn = vm[attr];\n" lubomir@1020: + " if (fn) return fn(false);\n" jaroslav@1489: + " try {\n" jaroslav@1584: + " var arr = loadBytes(replaceAll(name, '.', '/') + '.class');\n" jaroslav@1578: + " return reload(name, arr, true);\n" jaroslav@1489: + " } catch (err) {\n" jaroslav@1489: + " fn = vm[attr];\n" jaroslav@1489: + " if (fn) return fn(false);\n" jaroslav@1489: + " throw err;\n" jaroslav@1489: + " }\n" lubomir@1020: + " }\n" jaroslav@1578: + " if (vm['loadClass']) {\n" lubomir@1020: + " throw 'Cannot initialize the bck2brwsr VM twice!';\n" lubomir@1020: + " }\n" jaroslav@1578: + " vm['loadClass'] = loader.loadClass;\n" jaroslav@1578: + " vm['_reload'] = reload;\n" jaroslav@1578: + " vm['loadBytes'] = loadBytes;\n" jaroslav@1584: + " initVM();\n" lubomir@1020: + " return loader;\n" lubomir@1020: + " };\n"); jaroslav@1513: append( jaroslav@1489: " global.bck2brwsr.registerExtension = function(extension) {\n" lubomir@1029: + " extensions.push(extension);\n" jaroslav@1489: + " return null;\n" lubomir@1029: + " };\n"); jaroslav@1513: append("}(this));"); lubomir@1020: } lubomir@1020: lubomir@1020: @Override lubomir@1094: protected String getExportsObject() { lubomir@1094: return "vm"; lubomir@1029: } lubomir@1029: lubomir@1029: @Override lubomir@1094: protected boolean isExternalClass(String className) { lubomir@1094: return false; lubomir@1020: } lubomir@1020: } lubomir@1020: lubomir@1020: private static final class Extension extends VM { lubomir@1094: private final StringArray extensionClasses; lubomir@1094: lubomir@1094: private Extension(Appendable out, Bck2Brwsr.Resources resources, jaroslav@1587: String[] extClassesArray, StringArray explicitlyExported, jaroslav@1587: StringArray asBinary jaroslav@1587: ) { jaroslav@1587: super(out, resources, explicitlyExported, asBinary); lubomir@1094: this.extensionClasses = StringArray.asList(extClassesArray); lubomir@1020: } lubomir@1020: lubomir@1020: @Override lubomir@1020: protected void generatePrologue() throws IOException { jaroslav@1513: append("bck2brwsr.registerExtension(function(exports) {\n" lubomir@1020: + " var vm = {};\n"); jaroslav@1542: append(" function link(n) {\n" jaroslav@1542: + " return function() {\n" jaroslav@1542: + " var cls = n['replace__Ljava_lang_String_2CC']" jaroslav@1542: + "('/', '_').toString();\n" jaroslav@1542: + " var dot = n['replace__Ljava_lang_String_2CC']" jaroslav@1542: + "('/', '.').toString();\n" jaroslav@1542: + " exports.loadClass(dot);\n" jaroslav@1542: + " vm[cls] = exports[cls];\n" jaroslav@1542: + " return vm[cls](arguments);\n" jaroslav@1542: + " };\n" jaroslav@1542: + " };\n" jaroslav@1542: ); lubomir@1020: } lubomir@1020: lubomir@1020: @Override lubomir@1020: protected void generateEpilogue() throws IOException { jaroslav@1513: append("});"); lubomir@1020: } lubomir@1020: lubomir@1020: @Override lubomir@1029: protected String generateClass(String className) throws IOException { lubomir@1094: if (isExternalClass(className)) { jaroslav@1513: append("\n").append(assignClass( lubomir@1029: className.replace('/', '_'))) jaroslav@1542: .append("link('") lubomir@1029: .append(className) jaroslav@1542: .append("');"); lubomir@1029: lubomir@1029: return null; lubomir@1029: } lubomir@1029: lubomir@1094: return super.generateClass(className); lubomir@1029: } lubomir@1029: lubomir@1029: @Override lubomir@1029: protected String getExportsObject() { lubomir@1020: return "exports"; lubomir@1020: } lubomir@1094: lubomir@1094: @Override lubomir@1094: protected boolean isExternalClass(String className) { lubomir@1094: return !extensionClasses.contains(className); lubomir@1094: } lubomir@869: } jaroslav@1367: jaroslav@1367: private static void lazyReference(Appendable out, String n) throws IOException { jaroslav@1367: String cls = n.replace('/', '_'); jaroslav@1367: String dot = n.replace('/', '.'); jaroslav@1367: jaroslav@1367: out.append("\nvm.").append(cls).append(" = function() {"); jaroslav@1367: out.append("\n var instance = arguments.length == 0 || arguments[0] === true;"); jaroslav@1367: out.append("\n delete vm.").append(cls).append(";"); jaroslav@1367: out.append("\n var c = vm.loadClass('").append(dot).append("');"); jaroslav@1367: out.append("\n return vm.").append(cls).append("(instance);"); jaroslav@1367: out.append("\n}"); jaroslav@1367: } jaroslav@29: }