Mustang (Java SE 6) is co-bundled with Mozilla Rhino based JavaScript engine as an example implementation for javax.script API.
There are 3 use cases within JDK
There is already an experimental web application environment that uses JavaScript - http://phobos.dev.java.net.
So, we may expect that JavaScript will be employed within the JDK context more often. How about debugging support for JavaScript engine in JDK context? For Firefox, there are atleast two debuggers (that I know of!):
Rhino comes with it's own debugger -- but in Mustang, Rhino's tools are not included. Also, javax.script API does not include debugger API (yet?). For Rhino in Mustang, we have to resort to other ways to debug scripts including good old println/printf style. Few debugging tips and tricks are here...
You can use load function to load your script files (or URLs). In the interactive mode, you can call specific script functions or evaluate various expressions.
Use good old printf/System.out.println/alert/echo style debugging! In Mustang Java, there are built-in print and println functions always defined. If you have scripts that use alert, you can consider defining something like this:
// define alert to be same as println function
var alert = println;
Note that 'print' and 'println' functions print the message to output writer configured in the script context (recall that "context" is a pre-defined variable initialized with the current ScriptContext object). If you configure different output writer (may be, the one that writes messages to a file or socket etc.), you can make all print/println messages to go there.
// viewing function sources in jrunscript prompt.
// built-in functions are shown as "native code"
// But, you can get arity (number of arguments)
// of the function
js> eval.toSource()js> eval.toSource()
function eval() { [native code for eval, arity=1] }
// for script functions toSource() shows full source
js> function add(x, y) { return x + y }
js> add.toSource()
function add(x, y) {return x + y;}
// for Java methods we get signature as part of "source"
js> var v = new java.util.Date()
js> v.getDay.toSource()
function getDay() {/*
int getDay()
*/}
// for overloaded Java methods, toSource() shows signatures
// for all overloads
js> var out = java.lang.System.out
js> out.println.toSource()
function println() {/*
void println(boolean)
void println(char)
void println(int)
void println()
void println(long)
void println(java.lang.Object)
void println(java.lang.String)
void println(char[])
void println(double)
void println(float)
*/}
for(var i in obj) { print(obj[i]); }
like loop.
I frequently print all fields of an object (or elements of array) this way. Note that you will get object's methods (which are function valued properties) as well. To filter these, you can use typeof operator. You can also filter properties using property name pattern/index.
JavaScript functions can be passed as arguments and returned as values. Functions can be called by apply or call functions. Functions are first-class values that can be stored in variables. You can use these to implement JavaScript AOP. You can use "interceptors" to print debug trace output. For eg. function entry/exit can be printed (along with arguments, if desired). For example, the following is a variation of JavaScript AOP referred in above link..
var Aspects = new Object();
// calls "before" interceptor function before calling function
// name specified by fname. The function is expected to be a property
// of object 'obj'
Aspects.addBefore = function(obj, fname, before) {
var oldFunc = obj[fname];
var newFunc = function() {
return oldFunc.apply(this, before(arguments, oldFunc, this));
};
// store oldFunc for restore purpose...
newFunc.oldFunc = oldFunc;
obj[fname] = newFunc;
};
Aspects.restore = function(obj, fname) {
obj[fname] = obj[fname].oldFunc;
}
// tracing function entry
function traceEntry(args, oldFunc, thiz) {
// print the name of the function
print("entering " + oldFunc.name);
// print arguments
var str = "";
for (var i = 0; i < args.length; i++) {
str += args[i] + ", ";
}
print("arguments " + str);
// return argument array - used by oldFunc.
return args;
}
With the above code in a debug library (say "debug.js"), the client code can write something like:
function add(x, y) { return x + y; }
// just add two arguments
add(3, 4);
Aspect.addBefore(this, "add", traceEntry);
// call add -- prints debug output on entry
add(3, 4);
// restore old "add" function without debug output
Aspect.restore(this, "add");
Use objects instead. It is easy to accidentally create globals in JavaScript. For example,
function f() {
x = m(); // forgot 'var' before x, x is global!
// rest of the code...
}
While it may be possible to spot "unwanted" globals by code inspection, we can
do better than that. It is possible to check global variable assignments and accesses
using javax.script.Bindings used for global variable storage.
import javax.script.*;
import java.io.*;
public class Test {
public static void main(String[] args) throws Exception {
// create a new ScriptEngineManager
ScriptEngineManager m = new ScriptEngineManager();
// get JavaScript engine instance
ScriptEngine jsEngine = m.getEngineByName("javascript");
// set a "debug" bindings for global variables
jsEngine.setBindings(new DebugBindings(), ScriptContext.ENGINE_SCOPE);
// eval code from a java.io.Reader object.
jsEngine.eval(new FileReader(args[0]));
}
}
The DebugBindings class could look like
// a simple Bindings implementation that prints debug output
// whenever a variable is accessed or assigned.
import javax.script.*;
public class DebugBindings extends SimpleBindings {
@Override public Object put(String name, Object value) {
Object res = super.put(name, value);
System.out.println("Global assign: " + name);
return res;
}
@Override public Object get(Object key) {
Object res = super.get(key);
System.out.println("Global access: " + key);
return res;
}
}
When running the Test class with the following t.js
script file,
function h() {
// global variable assign
x = 32;
}
h();
we get the following output..
Global assign: context Global assign: print Global assign: println Global access: javax.script.filename Global assign: h Global access: h Global assign: xNote that debug output is printed for function "assignments" as well. Note that global functions are global variables with "function" value.
Because JavaScript is dynamically typed language, we can replace an object with any other object that "looks" like the "original". i.e., the replacement objects should just support same methods, properties -- but can do anything. (If it walks like a duck and quacks like a duck, it must be a duck). You can wrap actual object with a "debuggable" object (whose methods print debug/trace output) -- so long as debug wrapper objects supports same methods, properties. Usually, it is very easy to wrap an object with another object in JavaScript. But, if your object supports properties (a.k.a fields) it becomes tricky to wrap the same - for example, you may want to wrap XMLHttpRequest that has properties. But, you can use JSAdapter and "hook" all property or method access or assignments.
This prevent any other implementation than a map.
By example there is no way to add infinite identifiers like a1, b3, etc. (letters+numerics) to represent a cell in a spreadsheet.
Because bindinds are a map, bindinds must have a size and a way to iterate on it.
Rémi Forax
Posted by 193.50.159.2 on July 05, 2006 at 01:09 PM IST #
Posted by A. Sundararajan on July 06, 2006 at 07:55 PM IST #