« October 2006 »
SunMonTueWedThuFriSat
1
2
5
8
10
13
14
15
17
19
20
21
23
25
27
29
    
       
Today
XML

Blog::Navigation

GetJava Download Button
Get the Source
Personal Blog

Blog::Referers

Today's Page Hits: 857

Powered by Roller Weblogger.
« Previous day (Oct 4, 2006) | Main | Next day (Oct 6, 2006) »
20061006 Friday October 06, 2006

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:

I used all these through JSR-223 script engines for these languages.

Updates:

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



( Oct 06 2006, 03:42:59 PM IST ) Permalink Comments [9] del.icio.us | furl | simpy | slashdot | technorati | digg

Copyright (C) 2005, A. Sundararajan's Weblog