Just added scriptlet application to http://scripting.dev.java.net
A Scriptlet is a Java applet written in JavaScript. With scriptlets, developers
use a predefined applet class (called com.sun.scriptlet.Scriptlet)
and define an applet param which identifies a JavaScript. The JavaScript
code can define one or more script functions to "override"
applet's methods such as start, stop, init, destroy or paint.
By just defining paint function, scriptlet becomes equivalent to the new canvas tag.
But unlike canvas tag, scriptlet functions can access the entire
JDK API. The current applet object is exposed as first argument
to scriptlet's functions. For example, using this applet argument, scriptlets can play audio (by calling play method). With just the knowledge of JavaScript and Java API, very nice applets can be written.
Using
Java-to-Javascript Communication between Java to
browser's built-in JavaScript engine, a global variable named window
is defined. This window variable can be used by scriptlet scripts to access browser's script objects. For example, window.location.href returns the URL of the currently visited web page. Similarly window.document can be used to get the DOM document object of the current HTML page. Few simple scriptlets are in the applications/scriptlet/src/scriptlets directory of the scripting project.
Phobos is a lightweight, scripting-friendly, web application environment running on the Java platform. Primary language used in Phobos project is JavaScript - but it is possible to use any JSR-223 compliant language.
Sometimes you may even want to use more than one language in the same application. For example, there is a calculator sample application in Phobos. This application has a simple HTML GUI for a four-function calculator. The add, subtract, multiply and divide operations of this application are implemented as JavaScript number arithmetic - which is same as Java's double precision arithmetic. For example, Let us assume that you want to extend this application to do Java BigDecimal arithmetic instead of double arithmetic. Ofcourse, you can call Java API for BigDecimal arithmetic from JavaScript. But, you can implement this feature very easily using the JEP script engine!
I did the following experiment after expanding and installing the Phobos sample calculator.war under Apache Tomcat [I used Tomcat 5.5].
I copied the following jar files to webapp's lib directory
WEB-INF/lib:
The last three jar files can be downloaded from http://www.singularsys.com/jep/.
If CVS checked out complete sources from scripting.dev.java.net, these
jar files are under scripting/engines/jep/lib directory.
WEB-INF/application/startup.js.
Note that this script executes when the calculator application is
started for the first time. In this script, I've created a script
engine for JEP and stored it in application object.
// application startup script
// creata a script engine manager
application.smanager = new Packages.javax.script.ScriptEngineManager();
// create a Java Math Expression Parser (JEP) script engine
application.jepEngine = application.smanager.getEngineByName("jep");
/* Set the JEP mode to be "bigreal" - with this mode JEP engine
* arithmetic is done using java.math.BigDecimal objects
* rather than double values.
*/
application.jepEngine.eval("mode(\"bigreal\")");
After that, I made the following changes to WEB-INF/application/controller/calculator.js:
Mainly, the calculator's compute function is modified
as follows:
POST: function() {
var value = request.getParameter("value");
var operand = request.getParameter("operand");
var operator = request.getParameter("operator")
/*
* Who needs the switch statement?
*/
value = ({ add: function(x,y) { return application.jepEngine.eval(x + "+" +y); },
subtract: function(x,y) { return application.jepEngine.eval(x + "-" + y); },
multiply: function(x,y) { return application.jepEngine.eval(x + "*" + y); },
divide: function(x,y) { return y == 0 ? 0 : application.jepEngine.eval(x + "/" + y); },
}[operator])(value, operand);
[... more code deleted for brevity ...]
Instead of converting the request parameters as JavaScript
numbers (like the original sample did), I create and evaluate JEP expressions
in add, subtract, multiply and divide methods. After making the above
changes, I started Tomcat and visited http://localhost:8080/calculator.
Now, with the same calculator interface, the user can do BigDecimal
arithmetic! What's more - we can easily change JEP "mode" to - say vector
arithmetic.
If we make the following change to startup.js,
application.jepEngine.eval("mode(\"vector\")");
then the calculator supports vector/matrix/tensor arithmetic! For example, we can enter [3232.343243, 4324] and [3432, 3434.353454] as input values and choose "add" option to do vector addition.
Another example of mixing languages: you may use JavaScript Templates script engine to generate dynamic HTML content using TrimPath JavaScript Templates
With JSR-223 API, it is eary to call between scripts written in different scripting languages - provided there are JSR-223 script engines for the languages involved. It is very easy call eval. If you want more closer co-operation between scripts - like calling a function implemented in another scripting language or implementing a Java interface in script (which could be called from other scripting language or from Java), then there is Invocable interface.
Java Math Expressions Parser supports BigInteger, BigDecimal, rational, complex, vector/matrix/tensor expressions. Just added JSR-223 script engine for that. As usual, the sources are available at http://scripting.dev.java.net.
I'd like to add a script engine (or extend one of the existing engines like JEP engine) to support interval arithmetic. I came across Frink - but it is not open source (nor the interfaces seem available). Other library that I came across is this: http://interval.sourceforge.net.
JDK 6 contains javax.script (JSR-223) API and a Rhino based sample JavaScript engine. http://scripting.dev.java.net project has been created to develop JSR-223 engines for other languages and useful, demo applications that use scripting.
Just added two more script engines to http://scripting.dev.java.net. Unlike other script engines, this script engine is implemented in JavaScript. These engines implement JSP/ASP/PHP-like templating for JavaScript.
It should be possible to use these script engines in Java code as well - because these implement javax.script API as required. But, you have to create a JavaScript engine and eval the corresponding scripts. And then call ejsScriptEngineFactory or jstScriptEngineFactory function to create javax.script.ScriptEngineFactory
for these engines.
It should be possible to use the jst and ejs engines with Phobos web application framework. [though I am yet to test that part!]
I came across these other language implementations on JavaScript:
Also, it seems that JavaScript 2 would be implemented as a translater that translates js2 to js1 (atleast to startwith).
JDK 6 build 96 contains a new sample application called "scriptpad". Scriptpad is a simple notepad-like editor written more or less completely in JavaScript. There is a simple driver code in Java - which uses javax.script API. This sample demonstrates Java-to-JavaScript communication as well.
Just added JSR-223 script engine for Jaskell (http://jaskell.codehaus.org/). Jaskell in author's words:
Jaskell is a functional scripting programming language that runs in JVM. The name "Jaskell" stands for Java-Haskell, Haskell being the famous pure functional programming language.
Jaskell is a lazy functional scripting language for Java. It features higher-order function, function currying, string interpolation, lazy evaluation, monad, dynamic typing, simple syntax and semantics etc.
As usual, engine source and binary are available at https://scripting.dev.java.net/
You may have used Java-to-JavaScript communication in the context of the Java plugin. This API allows a Java applet to call/use JavaScript interpreter of the web browser.
When you hammer, everything looks like a nail
I've implemented JSR-223 script engine by wrapping netscape.javascript API. As usual, it is available
at https://scripting.dev.java.net
This script engine uses the JavaScript interpreter embedded in web browsers. Unlike the other script engines, this script engine is not expected to work on all settings. This engine works only inside the browsers - which effectively means only Java applets can make use of this script engine. I call this script engine as "browserjs" engine.
Because this script engine uses LiveConnect, the applet's APPLET tag has to
include mayscript="true" attribute so that browser's window object can
be obtained. Also, user has to set Applet object as a variable named
"applet" in the current ScriptContext.
javax.script
API.
import java.applet.*;
import javax.script.*;
public class ScriptClient extends Applet {
private ScriptEngine engine;
public void init() {
ClassLoader myloader = getClass().getClassLoader();
ScriptEngineManager manager = new ScriptEngineManager(myloader);
engine = manager.getEngineByName("BrowserJS");
try {
engine.put("applet", this);
// eval alert..
engine.eval("alert('hello world');");
// call alert...
((Invocable)engine).invokeFunction("alert", "hello, world!");
} catch (ScriptException exp) {
throw new RuntimeException(exp);
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(nsme);
}
}
}
How does this "browserjs" JavaScript script engine compares to the bundled
Rhino-based JavaScript engine in
JDK 6?
window, window.document etc.- you
have to call JSObject methods.
In addition to adding this "new" script engine, I've updated scripting.dev.java.net for the following:
As I wrote in my earlier post, I am running OpenSolaris - Nevada
build 38 on my laptop [And Venky promptly
pasted "I boot OpenSolaris" sticker on it
]. With Nevada, there
is a Java API for
DTrace.
I thought of playing with this API (/usr/share/lib/java/javadoc/dtrace/html/fast.html)
There are simple Java examples under /usr/share/lib/java/javadoc/dtrace/examples.
But, these days I prefer to do exploratory programming
with JavaScript that is bundled in Java SE 6
- particularly if I'm going to write a simple class with just the main method!.
The DTrace Java API has the notion of "consumers" - think of consumer as a particular DTrace session. With a consumer, you register one or more "listeners" and then compile one or more D-scripts. Wait for events in your listener after calling "go" on the consumer...
File: test.js
// import package org.opensolaris.os.dtrace
importPackage(org.opensolaris.os.dtrace);
// create a LocalConsumer
var consumer = new LocalConsumer();
/*
* Register a callback listener. The listener is of the interface
* type org.opensolaris.os.dtrace.ConsumerListener. But, in JavaScript
* we can pass a function and that will be wrapped as interface!
*/
consumer.addConsumerListener(function (evt) {
// if we get probe data, then print it
if (evt instanceof DataEvent) {
println(evt.probeData);
}
});
// open consumer, no special options passed
consumer.open([]);
/*
* Note that jrunscript passed "arguments" array that has command
* line arguments to the current JavaScript.
*
* We take the first command line argument as D-script file to use
* and pass the remaining arguments are passed as macro arguments
* (which can be accessed as $1, $2... in the D-script)
*/
// Compile the D-script file
consumer.compile(new java.io.File(arguments(0), arguments.slice(1));
// enable the consumer
consumer.enable();
// "start" the consumer so that we get notifications in the
// callback registered above
consumer.go();
To run the above script, I used the following command line:
jrunscript -cp /usr/share/lib/java/dtrace.jar -f test.js hello.d
Note that I've put dtrace.jar in the classpath. "hello.d" is the D-script passed as first argument.
File: hello.d:
:::BEGIN {
trace("hello, world");
exit(0);
}
When we run the above script, "hello, world" is printed as expected. How about accessing DTrace aggregates? Consider the following simple D-script that populates an aggregate.
File: histo.d
// object-alloc probe is fired whenever a Java object is allocated
hotspot$1:::object-alloc {
// arg1 for this probe is name of the Java class
// whose object is being allocated
// arg2 is length of the class name string
self->str = (string) copyin(arg1, arg2);
self->str[arg2] = '\0';
// arg3 is size of the object being allocated
// We update an aggregate whose key is the class name
// and the value is total size of objects allocated so far.
@histo[(string) self->str] = sum(arg3);
}
How about a script that prints the above histogram once every second? I copied "test.js" to "histo.js" and added the following lines:
File: histo.js
// same code as in "test.js" followed by these lines..
var a;
do {
java.lang.Thread.sleep(1000);
// get all aggregations from consumer
a = consumer.aggregate;
if (! a.asMap().empty()) {
// get "histo" aggregation
var histo = a.getAggregation("histo");
// get iterator of AggregationRecords
var itr = histo.records.iterator();
while (itr.hasNext()) {
var rec = itr.next();
// in our aggreation, the key tuple has only one component
// - the class name.
println(rec.tuple.get(0) + '\t' + rec.value);
}
}
println("===========================================");
} while (consumer.isRunning());
To enable object-alloc probe, we have to start your Java application
with -XX:+ExtendedDTraceProbes flag. I started the Java2D demo
application (bundled with JDK) with the following command line:
java -XX:+ExtendedDTraceProbes $JDK_HOME/demo/jfc/Java2D/Java2Demo.jar
Then, I ran the above "histo.js" script with the following command line:
jrunscript -cp /usr/share/lib/java/dtrace.jar -f histo.js histo.d 790
where 790 is the process id above mentioned Java2D demo app. (Note: you can find process ids of all your Java apps using jps).
Of course, it is not much fun in just printing the histogram by JavaScript
- you may as well do the same with D-script itself. But, you may want to
show histogram in a fancy GUI or do some "client side" post-processing
of trace data and so on. You can write such programs using DTrace
Java API in Java or
any of the scripting
languages for the Java Platform. With languages that support
UNIX shell style heredocs
(for example,
Groovy, Jython)
it is easier to embed D-script as a string and use
Consumer.compile(String program, String... args) that accepts
D-script as a String rather than file. But, what if you don't want to
code but visualize using DTrace? You may want to check out
Chime!
What is doesNotUnderstand method? From Reflective Facilities in Smalltalk-80 by Brian Foote and Ralph E. Johnson:
When a message is sent to a Smalltalk-80 object, the method dictionaries associated with that object's class and its superclasses are searched at runtime. If none of these classes implement a method for a given message, the Smalltalk virtual machine sends the object the message doesNotUnderstand:. The original message selector and message arguments are bundled together in a Message object and passed as the argument to doesNotUnderstand:. The default method for this message is stored in class Object. This method invokes the Smalltalk debugger, since sending an object a message it does not implement is usually a sign of programmer error. However, objects that override doesNotUnderstand: can intercept unimplemented message at runtime, and process them as they see fit.
Java being a statically typed language, javac will not compile if a method can not be found at compile time. Time to look at some code...
class Main {
public static void main(String[] args) {
// see Person class below..
Person p = new Person();
System.out.println("Starting...");
// call methods that are defined in Person class
p.work();
p.greet();
// call methods that are not defined in Person
// or it's superclass
p.surfTheNet();
p.writeBlog();
}
}
class Person {
public void work() {
System.out.println("Okay, I'll work tomorrow!");
}
public void greet() {
System.out.println("Hello, World!");
}
public Object invokeMethod(String name, Object args) {
System.out.println("Why are you calling " + name + "?");
}
}
If you attempted javac against above program, you will get
Main.java:15: cannot find symbol
symbol : method surfTheNet()
location: class Person
p.surfTheNet();
^
Main.java:16: cannot find symbol
symbol : method writeBlog()
location: class Person
p.writeBlog();
^
2 errors
You may have heard and/or used dynamic proxies with Java. With dynamic proxies, you implement all methods of one or more interfaces by a single invoke of an InvocationHandler.
Imagine if you could do something similar for classes as well (not just for interfaces). i.e., all method calls on objects of your class will end-up calling a single (special) "invokeMethod" method. Or think of something slightly restricted. If a method of matching name (and signature) is found in your class (or any of the superclasses) then that method will be called - if no such method is found, then a special method called "invokeMethod" will be called Groovy supports both the options.
Output with above program from Groovy is as follows:Starting... Okay, I'll work tomorrow! Hello, World! Why are you calling surfTheNet? Why are you calling writeBlog?
i.e., For the methods not found in Person class, Groovy calls invokeMethod method. Now, what if you want to "trap" all method calls, regardless of whether the method is defined in your class or not. It turns out that you can that in Groovy. We make the following change to Person class:
class Person implements GroovyInterceptable {
public void work() {
System.out.println("Okay, I'll work tomorrow!");
}
public void greet() {
System.out.println("Hello, World!");
}
public Object invokeMethod(String name, Object args) {
System.out.println("Why are you calling " + name + "?");
}
}
Output from Groovy after the above change is as follows:
Starting... Why are you calling work? Why are you calling greet? Why are you calling surfTheNet? Why are you calling writeBlog?
When you implement groovy.lang.GroovyInterceptable interface, you are essentially saying that for any method call on the objects of this class, call invokeMethod method.
What about properties? You can implement special methods getProperty(String) and putProperty(String, Object) methods for properties. These are used in the implementation of groovy.util.GroovyMBean - which I mentioned in my blog entry titled Groovier jconsole.
Actually, Groovy's Meta Object Protocol is slightly more complex than what I described. But then that is a topic for another blog entry ....
Let us look at a simple class called "Person".
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public Person() {
this.name = "";
}
public boolean equals(Object o) {
System.out.println("Huh! not comparable!");
return false;
}
// This does not override java.lang.Object.equals(Object) method
// See also: Java(TM) Puzzlers: Traps, Pitfalls, and Corner Cases
// Puzzle 58 (It is easy to overload when you intend to override!)
public boolean equals(Person emp) {
return name.equals(emp.name);
}
public static void main(String[] args) {
Object e1 = new Person("Sundar");
Object e2 = new Person("Rajan");
System.out.println(e1.equals(e2));
System.out.println(e1.equals(e1));
System.out.println(e1.equals("Sundar"));
}
}
Output of the above program is as below:
Huh! not comparable! false Huh! not comparable! false Huh! not comparable! falseThis is because
equals(Object) is selected for all equals calls in the main method. If we change the main as
public static void main(String[] args) {
Person e1 = new Person("Sundar");
Person e2 = new Person("Rajan");
System.out.println(e1.equals(e2));
System.out.println(e1.equals(e1));
System.out.println(e1.equals("Sundar"));
}
We get this output:
false true Huh! not comparable! false
This is because javac's overload resolution mechanism has selected the "right" method (at the compile time) for us!
Let us check the output of the unmodified (i.e., main not changed as above) with Groovy. I'll use
jrunscript -cp ../build/groovy-engine.jar;../lib/groovy-all-1.0-JSR-06.jar -l groovy Person.java
false
true
Huh! not comparable!
false
Groovy invokes the "correct" method in all cases. This is because Groovy supports multimethods. The method actully invoked depends not only on the "receiver" - but, also on the dynamic type of the arguments. For each equals call, the correct equals method is chosen (regardless of compiled time type declared in the program).
If you want to know more about "binary methods" (single argument methods where argument type is same as that of the "receiver" - like the equals method), then you may want to refer to On Binary Methods
Apache DB Derby is co-bundled with Mustang (Java SE 6). And Mozilla Rhino based JSR-223 script engine for JavaScript is also bundled with Mustang. How about accessing Derby database from JavaScript? I tried the following script...
// constructor to create DB object. Accepts user, password and DB name
function DB(user, password, db) {
var driver = new org.apache.derby.jdbc.EmbeddedDriver();
var props = new java.util.Properties();
props.put("user", user);
props.put("password", password);
this.conn = java.sql.DriverManager.getConnection("jdbc:derby:" +
db + ";create=true", props);
}
// wraps the ResultSet as an Iterator
DB.prototype.query = function (str) {
var stat = this.conn.createStatement();
var rs = stat.executeQuery(str);
return new java.util.Iterator() {
hasNext: function() {
return rs.next();
},
next: function() {
return new JSAdapter() {
__has__: function (name) {
try {
rs.findColumn(name);
return true;
} catch (e) {
println(e);
return false;
}
},
__get__: function (name) {
return rs.getObject(name);
}
};
},
remove: function() {
rs.deleteRow();
}
};
}
// execute query and update
DB.prototype.execute = function (str) {
var stat = this.conn.createStatement();
return stat.execute(str);
}
DB.prototype.update = function (str) {
var stat = this.conn.createStatement();
return this.conn.executeUpdate(str);
}
// dispose the connection
DB.prototype.dispose = function() {
this.conn.close();
}
// simple "main" function that uses the above db "API"!
function main() {
var db = new DB("user1", "user1", "derbyDB");
db.execute("create table derbyDB(num int, addr varchar(40))");
db.execute("insert into derbyDB values (1956,'Webster St.')");
db.execute("insert into derbyDB values (1910,'Union St.')");
db.execute(
"update derbyDB set num=180, addr='Grand Ave.' where num=1956");
var res = db.query(
"SELECT num, addr FROM derbyDB ORDER BY num");
// because of JSAdapter, we can access
// columns as "fields" of the "row object".
while (res.hasNext()) {
var row = res.next();
println(row.num + "\t" + row.addr);
}
db.dispose();
}
main();
With the above code, I got the following output:
jrunscript -cp D:\jdk1.6.0\db\lib\derby.jar -f db.js
180 Grand Ave.
1910 Union St.
Note that I've put derby.jar in the classpath so that Derby classes can be accessed from the scripts.
I blogged about script shell plugin for jconsole. With script shell plugin, you can use JavaScript to view/analyze JMX MBeans. This is particularly useful if you are going to debug your own MBeans -- although jconsole is a generic JMX client, you can use scripting to debug your MBeans. But, what if your scripting language of choice is not JavaScript? How about Groovy? It turns out that it is possible to use any language for which you have JSR-223 script engine.
jconsole -J-Dcom.sun.demo.jconsole.console.language=groovy -pluginpath $JDK_HOME/demo/scripting/jconsole-plugin/jconsole-plugin.jar
With -J-Dcom.sun.demo.jconsole.console.language=groovy option, you are specifying to use Groovy as the language for script shell plugin. You will see something like this:
There is MBean support in Groovy -- you may want to refer to the class groovy.util.GroovyMBean. Here is the Groovy "session" with jconsole
Note: plugin is a pre-defined variable of type JConsolePlugin)
groovy>plugin.context
pid: 1876 java2demo.jar
groovy>conn = plugin.context.getMBeanServerConnection()
javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@16c1bce
groovy>def objectName(str) { return new javax.management.ObjectName(str); }
groovy>def mbean(str) { return new groovy.util.GroovyMBean(conn, objectName(str)); }
groovy>classLoading = mbean("java.lang:type=ClassLoading")
MBean Name:
java.lang:type=ClassLoading
Attributes:
(r) int LoadedClassCount
(r) long TotalLoadedClassCount
(r) long UnloadedClassCount
(rw) boolean Verbose
groovy>classLoading.LoadedClassCount
2571
groovy>classLoading.UnloadedClassCount
105
groovy>memoryMBean = mbean("java.lang:type=Memory")
MBean Name:
java.lang:type=Memory
Attributes:
(r) javax.management.openmbean.CompositeData HeapMemoryUsage
(r) javax.management.openmbean.CompositeData NonHeapMemoryUsage
(r) int ObjectPendingFinalizationCount
(rw) boolean Verbose
Operations:
void gc()
groovy>
You can customize further by defining a file named jconsole.groovy under your home directory. This will be
loaded by script shell plugin just after initialization. You can define classes, functions in jconsole.groovy that could be called interactively in groovy script shell prompt.