/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 'use strict'; var Override = {}; function JavaException(className, message) { this.javaClassName = className; this.message = message; } JavaException.prototype = Object.create(Error.prototype); function boolReturnType(ret) { var value; if (ret) { value = 1; } else { value = 0; } return value; } boolReturnType.slotSize = 1; function doubleReturnType(ret) { return ret; } doubleReturnType.slotSize = 2; function voidReturnType(ret) { // no-op } voidReturnType.slotSize = 0; function stringReturnType(ret) { var value; if (typeof ret === "string") { value = util.newString(ret); } else { // already a native string or null value = ret; } return value; } stringReturnType.slotSize = 1; function defaultReturnType(ret) { return ret; } defaultReturnType.slotSize = 1; function intReturnType(ret) { var value = ret | 0; return value; } intReturnType.slotSize = 1; function getReturnFunction(sig) { var retType = sig.substring(sig.lastIndexOf(")") + 1); var fxn; switch (retType) { case 'V': fxn = voidReturnType; break; case 'I': fxn = intReturnType; break; case 'Z': fxn = boolReturnType; break; case 'J': case 'D': fxn = doubleReturnType; break; case 'Ljava/lang/String;': fxn = stringReturnType; break; default: fxn = defaultReturnType; break; } return fxn; } function executePromise(ret, doReturn, ctx, key) { ret.then(function(res) { if (Instrument.profiling) { Instrument.exitAsyncNative(key, ret); } var stack = ctx.current().stack; var convertedValue = doReturn(res); switch (doReturn.slotSize) { case 0: break; case 1: stack.push(convertedValue); break; case 2: stack.push2(convertedValue); break; } }, function(e) { ctx.raiseException(e.javaClassName, e.message); }).then(ctx.start.bind(ctx)); if (Instrument.profiling) { Instrument.enterAsyncNative(key, ret); } throwPause(); } /** * A simple wrapper for overriding JVM functions to avoid logic errors * and simplify implementation: * * - Arguments are pushed off the stack based upon the signature of the * function. * * - The return value is automatically pushed back onto the stack, if * the method signature does not return void. * * - The object reference ("this") is automatically bound to `fn`. * * - JavaException instances are caught and propagated as Java exceptions; JS TypeError propagates as a NullPointerException. * * @param {object} object * Native or Override. * @param {string} key * The fully-qualified JVM method signature. * @param {function(args)} fn * A function taking any number of args. */ function createAlternateImpl(object, key, fn, usesPromise) { var retType = key[key.length - 1]; var numArgs = Signature.getINSlots(key.substring(key.lastIndexOf(".") + 1)) + 1; var doReturn = getReturnFunction(key); var postExec = usesPromise ? executePromise : doReturn; object[key] = function() { try { var args = Array.prototype.slice.apply(arguments); args.push($.ctx); var ret = fn.apply(this, args); return postExec(ret, doReturn, $.ctx, key); } catch(e) { if (e === VM.Pause || e === VM.Yield) { throwHelper(e); } else if (e.name === "TypeError") { // JavaScript's TypeError is analogous to a NullPointerException. console.log(e.stack); $.ctx.raiseExceptionAndYield("java/lang/NullPointerException", e); } else if (e.javaClassName) { $.ctx.raiseExceptionAndYield(e.javaClassName, e.message); } else { console.error(e, e.stack); $.ctx.raiseExceptionAndYield("java/lang/RuntimeException", e); } } }; } Override.create = createAlternateImpl.bind(null, Override); Override.create("com/ibm/oti/connection/file/Connection.decode.(Ljava/lang/String;)Ljava/lang/String;", function(string) { return decodeURIComponent(string.str); }); Override.create("com/ibm/oti/connection/file/Connection.encode.(Ljava/lang/String;)Ljava/lang/String;", function(string) { return string.str.replace(/[^a-zA-Z0-9-_\.!~\*\\'()/:]/g, encodeURIComponent); }); Override.create("java/lang/Math.min.(II)I", function(a, b) { return Math.min(a, b); }); Override.create("java/io/ByteArrayOutputStream.write.([BII)V", function(b, off, len) { if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)) { throw new JavaException("java/lang/IndexOutOfBoundsException"); } if (len == 0) { return; } var count = this.klass.classInfo.getField("I.count.I").get(this); var buf = this.klass.classInfo.getField("I.buf.[B").get(this); var newcount = count + len; if (newcount > buf.length) { var newbuf = J2ME.newByteArray(Math.max(buf.length << 1, newcount)); newbuf.set(buf); buf = newbuf; this.klass.classInfo.getField("I.buf.[B").set(this, buf); } buf.set(b.subarray(off, off + len), count); this.klass.classInfo.getField("I.count.I").set(this, newcount); }); Override.create("java/io/ByteArrayOutputStream.write.(I)V", function(value) { var count = this.klass.classInfo.getField("I.count.I").get(this); var buf = this.klass.classInfo.getField("I.buf.[B").get(this); var newcount = count + 1; if (newcount > buf.length) { var newbuf = J2ME.newByteArray(Math.max(buf.length << 1, newcount)); newbuf.set(buf); buf = newbuf; this.klass.classInfo.getField("I.buf.[B").set(this, buf); } buf[count] = value; this.klass.classInfo.getField("I.count.I").set(this, newcount); }); Override.create("java/io/ByteArrayInputStream..([B)V", function(buf) { if (!buf) { throw new JavaException("java/lang/NullPointerException"); } this.buf = buf; this.pos = this.mark = 0; this.count = buf.length; }); Override.create("java/io/ByteArrayInputStream..([BII)V", function(buf, offset, length) { if (!buf) { throw new JavaException("java/lang/NullPointerException"); } this.buf = buf; this.pos = this.mark = offset; this.count = (offset + length <= buf.length) ? (offset + length) : buf.length; }); Override.create("java/io/ByteArrayInputStream.read.()I", function() { return (this.pos < this.count) ? (this.buf[this.pos++] & 0xFF) : -1; }); Override.create("java/io/ByteArrayInputStream.read.([BII)I", function(b, off, len) { if (!b) { throw new JavaException("java/lang/NullPointerException"); } if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)) { throw new JavaException("java/lang/IndexOutOfBoundsException"); } if (this.pos >= this.count) { return -1; } if (this.pos + len > this.count) { len = this.count - this.pos; } if (len === 0) { return 0; } b.set(this.buf.subarray(this.pos, this.pos + len), off); this.pos += len; return len; }); Override.create("java/io/ByteArrayInputStream.skip.(J)J", function(long) { var n = long.toNumber(); if (this.pos + n > this.count) { n = this.count - this.pos; } if (n < 0) { return Long.fromNumber(0); } this.pos += n; return Long.fromNumber(n); }); Override.create("java/io/ByteArrayInputStream.available.()I", function() { return this.count - this.pos; }); Override.create("java/io/ByteArrayInputStream.mark.(I)V", function(readAheadLimit) { this.mark = this.pos; }); Override.create("java/io/ByteArrayInputStream.reset.()V", function() { this.pos = this.mark; }); // The following Permissions methods are overriden to avoid expensive calls to // DomainPolicy.loadValues. This has the added benefit that we avoid many other // computations. Override.create("com/sun/midp/security/Permissions.forDomain.(Ljava/lang/String;)[[B", function(name) { // NUMBER_OF_PERMISSIONS = PermissionsStrings.PERMISSION_STRINGS.length + 2 // The 2 is the two hardcoded MIPS and AMS permissions. var NUMBER_OF_PERMISSIONS = 61; var ALLOW = 1; var maximums = J2ME.newByteArray(NUMBER_OF_PERMISSIONS); var defaults = J2ME.newByteArray(NUMBER_OF_PERMISSIONS); for (var i = 0; i < NUMBER_OF_PERMISSIONS; i++) { maximums[i] = defaults[i] = ALLOW; } var permissions = J2ME.newArray(J2ME.PrimitiveArrayClassInfo.B.klass, 2); permissions[0] = maximums; permissions[1] = defaults; return permissions; }); // Always return true to make Java think the MIDlet domain is trusted. Override.create("com/sun/midp/security/Permissions.isTrusted.(Ljava/lang/String;)Z", function(name) { return true; }); // Returns the ID of the permission. The callers will use this ID to check the // permission in the permissions array returned by Permissions::forDomain. Override.create("com/sun/midp/security/Permissions.getId.(Ljava/lang/String;)I", function(name) { return 0; }); // The Java code that uses this method doesn't actually use the return value, but // passes it to Permissions.getId. So we can return anything. Override.create("com/sun/midp/security/Permissions.getName.(I)Ljava/lang/String;", function(id) { return "com.sun.midp"; });