jaroslav@0: /* jaroslav@0: Java 4 Browser Bytecode Translator jaroslav@0: Copyright (C) 2012-2012 Jaroslav Tulach jaroslav@0: jaroslav@0: This program is free software: you can redistribute it and/or modify jaroslav@0: it under the terms of the GNU General Public License as published by jaroslav@0: the Free Software Foundation, version 2 of the License. jaroslav@0: jaroslav@0: This program is distributed in the hope that it will be useful, jaroslav@0: but WITHOUT ANY WARRANTY; without even the implied warranty of jaroslav@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the jaroslav@0: GNU General Public License for more details. jaroslav@0: jaroslav@0: You should have received a copy of the GNU General Public License jaroslav@0: along with this program. Look for COPYING file in the top folder. jaroslav@0: If not, see http://opensource.org/licenses/GPL-2.0. jaroslav@0: */ jaroslav@0: package org.apidesign.java4browser; jaroslav@0: jaroslav@0: import java.io.IOException; jaroslav@0: import java.io.InputStream; jaroslav@0: import java.util.List; jaroslav@2: import static org.netbeans.modules.classfile.ByteCodes.*; jaroslav@4: import org.netbeans.modules.classfile.CPMethodInfo; jaroslav@0: import org.netbeans.modules.classfile.ClassFile; jaroslav@0: import org.netbeans.modules.classfile.Code; jaroslav@0: import org.netbeans.modules.classfile.Method; jaroslav@0: import org.netbeans.modules.classfile.Parameter; jaroslav@0: jaroslav@0: /** Translator of the code inside class files to JavaScript. jaroslav@0: * jaroslav@0: * @author Jaroslav Tulach jaroslav@0: */ jaroslav@0: public final class ByteCodeToJavaScript { jaroslav@0: private final ClassFile jc; jaroslav@0: private final Appendable out; jaroslav@0: jaroslav@0: private ByteCodeToJavaScript(ClassFile jc, Appendable out) { jaroslav@0: this.jc = jc; jaroslav@0: this.out = out; jaroslav@0: } jaroslav@0: jaroslav@0: /** Converts a given class file to a JavaScript version. jaroslav@0: * @param fileName the name of the file we are reading jaroslav@0: * @param classFile input stream with code of the .class file jaroslav@0: * @param out a {@link StringBuilder} or similar to generate the output to jaroslav@0: * @throws IOException if something goes wrong during read or write or translating jaroslav@0: */ jaroslav@0: public static void compile(String fileName, InputStream classFile, Appendable out) throws IOException { jaroslav@0: ClassFile jc = new ClassFile(classFile, true); jaroslav@0: ByteCodeToJavaScript compiler = new ByteCodeToJavaScript(jc, out); jaroslav@0: for (Method m : jc.getMethods()) { jaroslav@0: if (m.isStatic()) { jaroslav@0: compiler.generateStaticMethod(m); jaroslav@0: } jaroslav@0: } jaroslav@0: } jaroslav@0: private void generateStaticMethod(Method m) throws IOException { jaroslav@1: out.append("\nfunction ").append( jaroslav@0: jc.getName().getExternalName().replace('.', '_') jaroslav@0: ).append('_').append( jaroslav@0: m.getName() jaroslav@0: ); jaroslav@0: out.append(m.getReturnType()); jaroslav@0: List args = m.getParameters(); jaroslav@0: for (Parameter t : args) { jaroslav@0: out.append(t.getDescriptor()); jaroslav@0: } jaroslav@0: out.append('('); jaroslav@0: String space = ""; jaroslav@2: for (int index = 0, i = 0; i < args.size(); i++) { jaroslav@0: out.append(space); jaroslav@2: out.append("arg").append(String.valueOf(index)); jaroslav@0: space = ","; jaroslav@2: final String desc = args.get(i).getDescriptor(); jaroslav@3: if ("D".equals(desc) || "J".equals(desc)) { jaroslav@2: index += 2; jaroslav@2: } else { jaroslav@2: index++; jaroslav@2: } jaroslav@0: } jaroslav@5: out.append(") {").append("\n"); jaroslav@0: final Code code = m.getCode(); jaroslav@0: int len = code.getMaxLocals(); jaroslav@5: for (int index = args.size(), i = args.size(); i < len; i++) { jaroslav@5: out.append(" var "); jaroslav@5: out.append("arg").append(String.valueOf(i)).append(";\n"); jaroslav@0: } jaroslav@0: out.append(";\n var stack = new Array("); jaroslav@0: out.append(Integer.toString(code.getMaxStack())); jaroslav@0: out.append(");\n"); jaroslav@0: produceCode(code.getByteCodes()); jaroslav@0: out.append("}"); jaroslav@0: } jaroslav@0: jaroslav@0: private void produceCode(byte[] byteCodes) throws IOException { jaroslav@4: out.append("\nvar gt = 0;\nfor(;;) switch(gt) {\n"); jaroslav@0: for (int i = 0; i < byteCodes.length; i++) { jaroslav@0: int prev = i; jaroslav@4: out.append(" case " + i).append(": "); jaroslav@0: final int c = (byteCodes[i] + 256) % 256; jaroslav@0: switch (c) { jaroslav@2: case bc_aload_0: jaroslav@2: case bc_iload_0: jaroslav@2: case bc_lload_0: jaroslav@2: case bc_fload_0: jaroslav@2: case bc_dload_0: jaroslav@0: out.append("stack.push(arg0);"); jaroslav@0: break; jaroslav@2: case bc_aload_1: jaroslav@2: case bc_iload_1: jaroslav@2: case bc_lload_1: jaroslav@2: case bc_fload_1: jaroslav@2: case bc_dload_1: jaroslav@0: out.append("stack.push(arg1);"); jaroslav@0: break; jaroslav@2: case bc_aload_2: jaroslav@2: case bc_iload_2: jaroslav@2: case bc_lload_2: jaroslav@2: case bc_fload_2: jaroslav@2: case bc_dload_2: jaroslav@2: out.append("stack.push(arg2);"); jaroslav@2: break; jaroslav@3: case bc_aload_3: jaroslav@3: case bc_iload_3: jaroslav@3: case bc_lload_3: jaroslav@3: case bc_fload_3: jaroslav@3: case bc_dload_3: jaroslav@3: out.append("stack.push(arg3);"); jaroslav@3: break; jaroslav@3: case bc_iload: jaroslav@3: case bc_lload: jaroslav@3: case bc_fload: jaroslav@3: case bc_dload: jaroslav@3: case bc_aload: { jaroslav@3: final int indx = (byteCodes[++i] + 256) % 256; jaroslav@3: out.append("stack.push(arg").append(indx + ");"); jaroslav@3: break; jaroslav@3: } jaroslav@5: case bc_istore_0: jaroslav@5: case bc_lstore_0: jaroslav@5: case bc_fstore_0: jaroslav@5: case bc_dstore_0: jaroslav@5: out.append("arg0 = stack.pop();"); jaroslav@5: break; jaroslav@5: case bc_istore_1: jaroslav@5: case bc_lstore_1: jaroslav@5: case bc_fstore_1: jaroslav@5: case bc_dstore_1: jaroslav@5: out.append("arg1 = stack.pop();"); jaroslav@5: break; jaroslav@5: case bc_istore_2: jaroslav@5: case bc_lstore_2: jaroslav@5: case bc_fstore_2: jaroslav@5: case bc_dstore_2: jaroslav@5: out.append("arg2 = stack.pop();"); jaroslav@5: break; jaroslav@5: case bc_istore_3: jaroslav@5: case bc_lstore_3: jaroslav@5: case bc_fstore_3: jaroslav@5: case bc_dstore_3: jaroslav@5: out.append("arg3 = stack.pop();"); jaroslav@5: break; jaroslav@2: case bc_iadd: jaroslav@2: case bc_ladd: jaroslav@2: case bc_fadd: jaroslav@2: case bc_dadd: jaroslav@0: out.append("stack.push(stack.pop() + stack.pop());"); jaroslav@0: break; jaroslav@2: case bc_isub: jaroslav@2: case bc_lsub: jaroslav@2: case bc_fsub: jaroslav@2: case bc_dsub: jaroslav@3: out.append("{ var tmp = stack.pop(); stack.push(stack.pop() - tmp); }"); jaroslav@2: break; jaroslav@2: case bc_imul: jaroslav@2: case bc_lmul: jaroslav@2: case bc_fmul: jaroslav@2: case bc_dmul: jaroslav@1: out.append("stack.push(stack.pop() * stack.pop());"); jaroslav@1: break; jaroslav@3: case bc_idiv: jaroslav@3: case bc_ldiv: jaroslav@3: out.append("{ var tmp = stack.pop(); stack.push(Math.floor(stack.pop() / tmp)); }"); jaroslav@3: break; jaroslav@3: case bc_fdiv: jaroslav@3: case bc_ddiv: jaroslav@3: out.append("{ var tmp = stack.pop(); stack.push(stack.pop() / tmp); }"); jaroslav@3: break; jaroslav@5: case bc_iinc: { jaroslav@5: final int varIndx = (byteCodes[++i] + 256) % 256; jaroslav@5: final int incrBy = (byteCodes[++i] + 256) % 256; jaroslav@5: if (incrBy == 1) { jaroslav@5: out.append("arg" + varIndx).append("++;"); jaroslav@5: } else { jaroslav@5: out.append("arg" + varIndx).append(" += " + incrBy).append(";"); jaroslav@5: } jaroslav@5: break; jaroslav@5: } jaroslav@2: case bc_ireturn: jaroslav@2: case bc_lreturn: jaroslav@2: case bc_freturn: jaroslav@2: case bc_dreturn: jaroslav@0: out.append("return stack.pop();"); jaroslav@1: break; jaroslav@2: case bc_i2l: jaroslav@2: case bc_i2f: jaroslav@2: case bc_i2d: jaroslav@2: case bc_l2i: jaroslav@3: // max int check? jaroslav@2: case bc_l2f: jaroslav@2: case bc_l2d: jaroslav@3: case bc_f2d: jaroslav@3: case bc_d2f: jaroslav@3: out.append("/* number conversion */"); jaroslav@3: break; jaroslav@2: case bc_f2i: jaroslav@2: case bc_f2l: jaroslav@2: case bc_d2i: jaroslav@2: case bc_d2l: jaroslav@3: out.append("stack.push(Math.floor(stack.pop()));"); jaroslav@3: break; jaroslav@2: case bc_i2b: jaroslav@2: case bc_i2c: jaroslav@2: case bc_i2s: jaroslav@2: out.append("/* number conversion */"); jaroslav@2: break; jaroslav@4: case bc_iconst_0: jaroslav@4: case bc_dconst_0: jaroslav@4: case bc_lconst_0: jaroslav@4: case bc_fconst_0: jaroslav@4: out.append("stack.push(0);"); jaroslav@4: break; jaroslav@4: case bc_iconst_1: jaroslav@4: case bc_lconst_1: jaroslav@4: case bc_fconst_1: jaroslav@4: case bc_dconst_1: jaroslav@4: out.append("stack.push(1);"); jaroslav@4: break; jaroslav@4: case bc_iconst_2: jaroslav@4: case bc_fconst_2: jaroslav@4: out.append("stack.push(2);"); jaroslav@4: break; jaroslav@4: case bc_iconst_3: jaroslav@4: out.append("stack.push(3);"); jaroslav@4: break; jaroslav@4: case bc_iconst_4: jaroslav@4: out.append("stack.push(4);"); jaroslav@4: break; jaroslav@4: case bc_iconst_5: jaroslav@4: out.append("stack.push(5);"); jaroslav@4: break; jaroslav@4: case bc_if_icmpeq: { jaroslav@4: i = generateIf(byteCodes, i, "=="); jaroslav@4: break; jaroslav@4: } jaroslav@4: case bc_if_icmpne: jaroslav@4: i = generateIf(byteCodes, i, "!="); jaroslav@4: break; jaroslav@4: case bc_if_icmplt: jaroslav@4: i = generateIf(byteCodes, i, ">"); jaroslav@4: break; jaroslav@4: case bc_if_icmple: jaroslav@4: i = generateIf(byteCodes, i, ">="); jaroslav@4: break; jaroslav@4: case bc_if_icmpgt: jaroslav@4: i = generateIf(byteCodes, i, "<"); jaroslav@4: break; jaroslav@4: case bc_if_icmpge: jaroslav@4: i = generateIf(byteCodes, i, "<="); jaroslav@4: break; jaroslav@5: case bc_goto: { jaroslav@5: int indx = i + readIntArg(byteCodes, i); jaroslav@5: out.append("gt = " + indx).append("; continue;"); jaroslav@5: i += 2; jaroslav@5: break; jaroslav@5: } jaroslav@4: case bc_invokestatic: { jaroslav@4: int methodIndex = readIntArg(byteCodes, i); jaroslav@4: CPMethodInfo mi = (CPMethodInfo) jc.getConstantPool().get(methodIndex); jaroslav@4: boolean[] hasReturn = { false }; jaroslav@4: StringBuilder signature = new StringBuilder(); jaroslav@4: int cnt = countArgs(mi.getDescriptor(), hasReturn, signature); jaroslav@4: jaroslav@4: if (hasReturn[0]) { jaroslav@4: out.append("stack.push("); jaroslav@4: } jaroslav@4: out.append(mi.getClassName().getInternalName().replace('/', '_')); jaroslav@4: out.append('_'); jaroslav@4: out.append(mi.getName()); jaroslav@4: out.append(signature.toString()); jaroslav@4: out.append('('); jaroslav@4: String sep = ""; jaroslav@4: for (int j = 0; j < cnt; j++) { jaroslav@4: out.append(sep); jaroslav@4: out.append("stack.pop()"); jaroslav@4: sep = ", "; jaroslav@4: } jaroslav@4: out.append(")"); jaroslav@4: if (hasReturn[0]) { jaroslav@4: out.append(")"); jaroslav@4: } jaroslav@4: out.append(";"); jaroslav@4: i += 2; jaroslav@4: break; jaroslav@4: } jaroslav@0: } jaroslav@3: out.append(" /*"); jaroslav@0: for (int j = prev; j <= i; j++) { jaroslav@0: out.append(" "); jaroslav@0: final int cc = (byteCodes[j] + 256) % 256; jaroslav@0: out.append(Integer.toString(cc)); jaroslav@0: } jaroslav@0: out.append("*/\n"); jaroslav@0: } jaroslav@4: out.append("}\n"); jaroslav@4: } jaroslav@4: jaroslav@4: private int generateIf(byte[] byteCodes, int i, final String test) throws IOException { jaroslav@4: int indx = i + readIntArg(byteCodes, i); jaroslav@4: out.append("if (stack.pop() ").append(test).append(" stack.pop()) { gt = " + indx); jaroslav@4: out.append("; continue; }"); jaroslav@4: return i + 2; jaroslav@4: } jaroslav@4: jaroslav@4: private int readIntArg(byte[] byteCodes, int offsetInstruction) { jaroslav@5: final int indxHi = byteCodes[offsetInstruction + 1] << 8; jaroslav@5: final int indxLo = byteCodes[offsetInstruction + 2]; jaroslav@5: return (indxHi & 0xffffff00) | (indxLo & 0xff); jaroslav@4: } jaroslav@4: jaroslav@4: private static int countArgs(String descriptor, boolean[] hasReturnType, StringBuilder sig) { jaroslav@4: int cnt = 0; jaroslav@4: int i = 0; jaroslav@4: Boolean count = null; jaroslav@4: while (i < descriptor.length()) { jaroslav@4: char ch = descriptor.charAt(i++); jaroslav@4: switch (ch) { jaroslav@4: case '(': jaroslav@4: count = true; jaroslav@4: continue; jaroslav@4: case ')': jaroslav@4: count = false; jaroslav@4: continue; jaroslav@4: case 'B': jaroslav@4: case 'C': jaroslav@4: case 'D': jaroslav@4: case 'F': jaroslav@4: case 'I': jaroslav@4: case 'J': jaroslav@4: case 'S': jaroslav@4: case 'Z': jaroslav@4: if (count) { jaroslav@4: cnt++; jaroslav@4: sig.append(ch); jaroslav@4: } else { jaroslav@4: hasReturnType[0] = true; jaroslav@4: sig.insert(0, ch); jaroslav@4: } jaroslav@4: continue; jaroslav@4: case 'V': jaroslav@4: assert !count; jaroslav@4: hasReturnType[0] = false; jaroslav@4: sig.insert(0, 'V'); jaroslav@4: continue; jaroslav@4: case 'L': jaroslav@4: i = descriptor.indexOf(';', i); jaroslav@4: if (count) { jaroslav@4: cnt++; jaroslav@4: } else { jaroslav@4: hasReturnType[0] = true; jaroslav@4: } jaroslav@4: continue; jaroslav@4: case '[': jaroslav@4: //arrays++; jaroslav@4: continue; jaroslav@4: default: jaroslav@4: break; // invalid character jaroslav@4: } jaroslav@4: } jaroslav@4: return cnt; jaroslav@0: } jaroslav@0: }