Java Integration: JavaScript, Groovy and JRuby

When using scripting and dynamically typed languages with Java platform, you would want to take advantage of Java platform API. We know that there is Java API for (nearly) everything under the "Sun" ;-). In this blog entry, we will see how to access Java from JavaScript, Groovy and JRuby.

Note: I used the following versions to test my scripts:

  • Rhino 1.6R2 based JavaScript engine that is bundled with JDK 6.
  • Groovy 1.0 JSR-06.
  • JRuby 0.9.0.
I used all these through JSR-223 script engines for these languages.

Updates:

  • Jochen "blackdrag" Theodorou notes that the Groovy overload resolution can be forced to select a specific method in future - check out Groovy 1.0.
  • Nick Sieger, Thomas E Enebo and Charles Oliver Nutter note that JRuby's Java integration is improving - check out JRuby 0.9.1+.

Feature JavaScript Groovy JRuby
Importing Java packages importPackage function.

Examples:

importPackage(java.awt);
importPackage(javax.swing);

Rhino has a built-in variable by the name Packages. You have to use Package.javax.swing to refer to javax.swing package. But, "java" is a short-cut for "Packages.java". In JDK 6, short-cuts have been added to all commonly used package prefixes as well (like javax, org, com, net etc.). If you are using Rhino standalone, you can eval the following:

var javax = Packages.javax;
var com = Packages.com;
var net = Packages.net;
var edu = Packages.edu;

Note: java.lang package is not automatically imported in Java (that would result conflicts - example: JavaScript Object vs. Java's java.lang.Object).

If you are importing too many packages and classes, you may be polluting the global namespace. You can avoid that by using JavaImporter function.


var guiPkgs = new JavaImporter(
     java.awt, java.awt.event, javax.swing);

with (guiPkgs) {
   // here you can access all classes
   // in java.awt, java.awt.event and
   // javax.swing classes by simple names
}

// outside the above "with" statement
// you can't use simple class names.

Same as Java!

import java.awt.\*;
import javax.swing.\*;

By default, Groovy imports java.lang, java.util, java.io, java.net, groovy.lang, groovy.util packages.
You can not import all classes of a package in top-level scope. Instead, you have to import within a module.

module MyJava
  include_package 'java.util'
end

$o = MyJava::Hashtable.new
puts $o

Importing specific class(es) importClass function.

importClass(java.util.HashMap);
importClass(javax.swing.JFrame);

Same as Java!

import javax.swing.JFrame;

By default, Groovy imports java.math.BigInteger and java.math.BigDecimal classes.
include_class(classname_or_list_of_classnames) {|package, name| optional_renaming_block }

require 'java'

include_class('javax.swing.JFrame')
include_class ["JFrame"].map {
 |e| "javax.swing." + e
}

Note: The Java import packages/class is improving. See also:
Type alias (Referring Java class with different name)

var Format = java.text.SimpleTextFormat;
// use "Format" as class name below


import java.text.SimpleTextFormat
 as Format;
// use "Format" as class name below

include_class(classname_or_list_of_classnames) { |package, name| optional_renaming_block }

require 'java'

include_class('java.lang.String') {
 |package,name| "J#{name}" 
}

# after this you can use "JString"

Creating a Java object

importClass(javax.swing.JFrame);
var f = new JFrame("hello");


import javax.swing.JFrame;
f = new JFrame("hello");


include_class('javax.swing.JFrame');
f = JFrame.new('hello');

Calling instance methods

var f = new javax.swing.JFrame("hello");
f.setSize(100, 100);
f.setVisible(true);


f = new javax.swing.JFrame("hello");
f.setSize(100, 100);
f.setVisible(true);


include_class('javax.swing.JFrame');
f = JFrame.new('hello');
f.setSize(100, 100);
f.setVisible(true);

Calling static methods java.lang.System.exit(0) System.exit(0) include_class('java.lang.System'); System.exit(0)
JavaBean support

f = new javax.swing.JFrame("hello");
f.setSize(100, 100);
// calls setVisible
f.visible = true;
// calls getTitle for "title" access
println(f.title);


f = new javax.swing.JFrame("hello");
f.setSize(100, 100);
// calls setVisible
f.visible = true;
// calls getTitle
println(f.title);


require 'java';
include_class('javax.swing.JFrame');
f = JFrame.new("hello");
# calls setVisible
f.visible = true;
# calls getTitle
puts f.title

instanceof check

importClass(javax.swing.JFrame);
var f = new JFrame("hello");
// prints true
println(f instanceof JFrame);


import javax.swing.JFrame;
f = new JFrame("hello");
// prints true
println(f instanceof JFrame);


require 'java';
include_class('javax.swing.JFrame');
f = JFrame.new("hello");
# prints true
puts f.kind_of?(JFrame)

Java overloaded resolution

In most cases, Rhino selects proper overload variant automatically. But, if you want to force selection of a particular method, you can use the following:


var out = java.lang.System.out;
out["println(double)"](3.14);

Essentially, we specify full singature of the method as a string. Note that JavaScript objects are associative arrays - it is always possible to use property/method name within square bracket (as a string). This is same as ".field" or ".method" syntax.

Groovy selects the "most appropriate" overload automatically (see also Mutlimethods in Groovy). I am not sure if there is any way to force a particular overload to be called.. Update: Jochen "blackdrag" Theodorou notes that the overload resolution will be improved in Groovy 1.0. For now, you can use java reflection API directly.

require 'java';
include_class('java.lang.System');

System.out.println("hello");

# the following don't print numbers
# I am not sure how to resolve to
# a particular overload variant

# System.out.println(33.33);
# System.out.println(2)

I think it should be possible to use low-level reflection of JRuby - may be I can do this little less verbose than what is below??


require 'java';

cl = Java::JavaClass.for_name(
   "java.io.PrintStream");

m = cl.java_method(:println, :double)
include_class('java.lang.System')
include_class('java.lang.Double')
d = Double.new(3.141596)

m.invoke(System.out.java_object,
     d.java_object)
Handling Java exceptions

var Stream = java.io.FileInputStream
try {
  s = new Stream("non_existent");
} catch (e) {
  // handle FileNotFoundException
}

It is possible to have multiple catch classes like in Java. JavaScript error objects have "javaException" property - which points to the actual underlying Java exception (if error was thrown due to a Java exception). You can do instanceof check on the underlying java exception.


var Stream = java.io.FileInputStream
try {
  s = new Stream("myfile");
} catch (fnfe
  if fnfe.javaException instanceof 
     java.io.FileNotFoundException) {
  println("file not found");
  fnfe.javaException.printStackTrace();
} catch (ioe
  if ioe.javaException instanceof
     java.io.IOException) {
  println("some io error");
  ioe.javaException.printStackTrace();
}

Exception handling is same as Java.

try {
  s = new java.io.FileInputStream("myfile");
} catch (FileNotFoundException fnfe) {
  println("file not found");
  fnfe.printStackTrace();
} catch (IOException ioe) {
  println("some io error");
  ioe.printStackTrace();
}


require 'java';

include_class("java.io.FileInputStream")
include_class("java.io.FileNotFoundException")
include_class("java.io.IOException")

begin
 f = FileInputStream.new("myfile");
rescue FileNotFoundException=>fnfe
 puts fnfe.backtrace
rescue IOException=>ioe
 puts ioe.backtrace
end

Creating Java Arrays There is no "direct" syntax. But, whenever a Java array is needed (say as an argument to a Java method call), JavaScript array can be passed. Script arrays are automatically converted to Java arrays as needed. If you want you can use java.lang.reflect.Array

// create a Java String[] of
// 3 elements
var s = java.lang.reflect.Array.
   newInstance(java.lang.String, 3);
println(s);

Same as Java!

s = new String[3];
println(s);

Update: Seems like no direct support. Nick Sieger notes that arrays can be created with easier syntax: (Thanks!)

include_class("java.lang.String") {
  "JString"
}

s = JString[].new(2)
puts s

I had the following code before Nick Sieger's comment - You can use java.lang.reflect.Array directly:

include_class("java.lang.reflect.Array") {
  "JArray"
}

include_class("java.lang.String") {
  "JString" 
}

s = JArray.newInstance(JString, 2);
puts s

Accessing Java Arrays Same as Java! usual [] and .length are supported.

// create a Java String[] of
// 3 elements
var s = java.lang.reflect.Array.
   newInstance(java.lang.String, 3);
s[0] = "hello";
s[1] = "world";
s[2] = "JavaScript";
println(s.length);
println(s[0]);
println(s[1]);
println(s[2]);

Same as Java!

s = new String[3];
s[0] = "hello";
s[1] = "world";
s[2] = "Groovy";
println(s.length);
println(s[0]);
println(s[1]);
println(s[2]);

Same as Java!

include_class("java.lang.reflect.Array") {
  "JArray"
}

include_class("java.lang.String") {
  "JString" 
}

s = JArray.newInstance(JString, 3);
s[0] = "hello";
s[1] = "world";
s[2] = "JRuby";
puts s.length;
puts s[0];
puts s[1];
puts s[2];

Implementing a Java interface Use Java anonymous class-like syntax:

var r = new java.lang.Runnable() {
  run: function() {
     println("I'm run method");
  }
};

println(r instanceof java.lang.Runnable);
new java.lang.Thread(r).start();

For single method interfaces, you can pass JavaScript function directly:

function f() {
 println("I'm function f");
}

new java.lang.Thread(f).start();

Similar to Java - but anonymous classes are not supported (yet). There is an RFE to allow auto-conversion of Closure to single-method interfaces

class MyRunnable implements Runnable {
  void run() {
    println("I'm run method");
  }
}

r = new MyRunnable();
println(r instanceof java.lang.Runnable);
new java.lang.Thread(r).start();


require 'java';
include_class('java.lang.Runnable');

class MyRunnable < Runnable
 def run
   puts "I'm run method"
 end
end

r = MyRunnable.new
puts r.kind_of?(Runnable)

include_class('java.lang.Thread') {
 |e| "JThread" }

JThread.new(r).start

Another example:

require 'java'

include_class "java.awt.event.ActionListener"

class MyActionListener < ActionListener
 def actionPerformed(event)
   puts event
 end
end

Charles Oliver Nutter notes that there is also a more "anonymous" way to implement interface:

require 'java';

include_class('java.lang.Runnable');

r = Runnable.new { puts "I'm running"; }
puts r.kind_of?(Runnable)

Implementing multiple interfaces

Rhino standalone download supports implementing multiple interfaces through the use of JavaAdapter. But, the bundled JavaScript engine in JDK 6 does not support this. See also: JDK 6 release notes - scripting section. But, it is possible to use java.lang.reflect.Proxy as shown below:


importClass(java.lang.Runnable);
importClass(java.util.concurrent.Callable);

Proxy = java.lang.reflect.Proxy;

r = new Proxy.newProxyInstance(
     null,
     [ Runnable, Callable ],
     new java.lang.reflect.InvocationHandler() {
        invoke: function(obj, name, args) {
          println(name + " called"); 
        }
     }
    );

println(r instanceof Runnable);
println(r instanceof Callable);

r.run();
r.call();

Same as Java!

import java.util.concurrent.Callable;

class MyClass 
 implements Runnable, Callable {
 void run() {
   println("run called");
 }

 Object call() {
   println("call called");
 }
}

r = new MyClass();
println(r instanceof Callable);
println(r instanceof Runnable);
r.run();
r.call();

It appears that it is not possible currently. See also: Interfaces Should Be Modules I think you can use java.lang.reflect.Proxy trick for now. But, when I tried the following I got StackOverflowError! I've filed a bug (Attempting to create a java.lang.reflect.Proxy instance results in StackOverflow)
require 'java';

include_class('java.lang.reflect.InvocationHandler');
include_class('java.lang.reflect.Proxy') {
  |x| "JProxy" 
};

include_class('java.lang.Runnable');
include_class('java.util.concurrent.Callable');
include_class("java.lang.reflect.Array") {
  |x| "JArray"
};

include_class("java.lang.Class") {
  |x| "JClass" 
}

class MyRunnable < InvocationHandler
 def invoke(obj, name, args) 
   puts name + " called"
 end
end

include_class('java.lang.Runnable')

types = JArray.newInstance(JClass, 2);
types[0] = Runnable
types[1] = Callable

m = MyRunnable.new
r = JProxy.newProxyInstance(nil, types, m);

puts r.kind_of?(Runnable)
puts r.kind_of?(Callable)

Extending a Java class Rhino standalone download supports extending a java class through JavaAdapter. But, the bundled JavaScript engine in JDK 6 does not support this. Supporting this requires .class file generation -- which has been removed in JDK 6 (for security and footprint reasons). See also: JDK 6 release notes - scripting section. Same as Java!

import java.awt.\*;
import java.awt.event.\*;

class MyListener extends WindowAdapter {
  // override just one method
  void windowClosing(WindowEvent evt) {
    println("Why did you close?");
    System.exit(0);     
  }
}

f = new Frame();
f.addWindowListener(new MyListener());
f.setVisible(true);

This requires generation of .class files. As of now, JRuby is interpreted only. And so extending a Java class and passing it to Java API is not possible.

References

Comments:

nice work again, thx. One small thing for "Java overloaded resolution". We are changing this till 1.0. That means I will be able to force the method selection by using casts. If you have a class Foo extends Bar and the overloaded methods func(Bar) and func(Foo), then func((Bar)x) will call func(Bar) even if x is a Foo. This is not yet implemented, but it will be soon.

Posted by Jochen "blackdrag" Theodorou on October 06, 2006 at 08:58 AM IST #

Hi "blackdrag": Thanks for the info on overload resolution. [That was fast! RSS event alerter looking for the word "Groovy"?!)

Posted by A. Sundararajan on October 06, 2006 at 09:07 AM IST #

Here are a couple of corrections to the JRuby examples for you.

Importing classes

As you note, the syntax is improving for this. In the 0.9.1 release you will be able to do any of the following:

require 'java'

java.lang.String.new
JString = java.lang.String; JString.new
OtherClass = Java::some.other.package.OtherClass; OtherClass.new


Creating arrays

You can create new arrays like so:

JString[].new(2)


Implementing multiple interfaces and extending classes

There is a patch to allow Java classes to be extended, so this should be working within the next couple of releases (see JRUBY-71). Also, there has been discussion on the list lately concerning an approach for multiple interface implementation, so I would expect that to be available soon as well.

Posted by Nick Sieger on October 06, 2006 at 05:41 PM IST #

# 0.9.1 of JRuby will be out in a week or so and these are some updates
#  based on trunk today:

require 'java'

# Importing specific classes:

JFrame = javax.swing.JFrame

# or long-hand when top-level package is not common:

JFrame = Java::javax.swing.JFrame

# Java overload resolution (your example works now):

System = java.lang.System

System.out.println(33.33)
System.out.println(2)

# Creating arrays:

s = java.lang.String[].new(3)
puts s 

Extending concrete classes hs a patch but we are not dealing with it until 0.9.1 comes out. We are also going to be improving array syntax in the near future since it is not friendly enough.

Posted by Thomas E Enebo on October 06, 2006 at 05:51 PM IST #

Hi Nick Sieger and Thomas E Enebo: Thank you very much for the explanations!

Posted by A. Sundararajan on October 06, 2006 at 06:02 PM IST #

JRuby also has a more "anonymous" way to implement interfaces as well:
ActionListener = java.awt.event.ActionListener
FocusListener = java.awt.event.FocusListener

al = ActionListener.new { puts "hello" }
al.actionPerformed(nil) => "hello"

fl = FocusListener.new(:focusGained) { |sym, \*args| ... }
fl.focusGained(nil) => ...
# fl.focusLost(nil) produces a NameError; it's not implemented
The eventual support for implementing multiple interfaces and concrete classes will likely look as follows:
#simple cases
class X < Interface1; end # implementation
class Y < Concrete1; end # specialization

#composite types
class X < composite(Interface1, Interface2); end
class Y < composite(Concrete1, Interface1, Interface2); end
# class Z < composite(Concrete1, Concrete2) produces an error
We are actively working to improve the java integration before a 1.0 release, so suggestions from the other language camps are very welcome (so long as they feel "Ruby" enough :)

Posted by Charles Oliver Nutter on October 06, 2006 at 11:53 PM IST #

hehe, just coincidence. I am looking at your blog on a regular base. Oh, and maybe a info for you... possibly you know that already, but Groovy does also have the "as" syntax. Which is a type transformation operation. for exmaple you can transform lists in arrays. [1,2,3] as int[]. That creates an int[] initialized with the numbers 1,2 and 3. The type transformation syntax is capable of doing much more. And when we implement the closures conversion to interface we will use that syntax there too. And one last thing... is it personal taste or out of a habit that you always write ";"?

Posted by Jochen on October 07, 2006 at 10:21 PM IST #

Hi Jochen: Thanks for reading my blog regularly. Yes, I looked at the "as" syntax. Regarding semicolon (in programs?): it may be a habit of years of C, C++ and Java programming. Also, automatic semicolon insertion with some languages (such as JavaScript) has been recognized as a source of problems - please refer to jslint and JS 2.0. I'd prefer some non-whitespace statement terminator - be it ';' or '.' (as in Smalltalk).

Posted by A. Sundararajan on October 08, 2006 at 04:41 AM IST #

One interesting problem I've run into with the "agile" languages is whether or not they support inner classes. Some believe the main use for them is for anonymous inner classes, which closures help with. But no, inner classes are also great for architectural reasons like providing nested name spaces and the like. I specifically ran into this while trying to use Processing.org's great graphics system, using Jython to script within that system. Its a subtle but important integration issue. Owen

Posted by Owen Densmore on October 08, 2006 at 04:03 PM IST #

Post a Comment:
Comments are closed for this entry.
About

sundararajan

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks
Links

No bookmarks in folder

Blogroll

No bookmarks in folder

News

No bookmarks in folder