Module JavaBridge
In: ruby/comm_abstract.rb
ruby/comm_bstream.rb
ruby/comm_xmlrpc.rb
ruby/jbridge.rb
ruby/jlambda.rb

JavaBridge

This module provides a communication facility between Ruby and Java. The current implementation is written by pure Java and pure ruby. You can use ruby language and gain Java power easily.

Simple example and features

Hello world

  require 'yajb/jbridge'
  include JavaBridge
  jimport "javax.swing.*"

  :JOptionPane.jclass.showMessageDialog( nil, "Hello World!")
  # this Java window sometimes shows behind the other window.

GUI

You can use Java GUI components as you use on Java. See sample scripts:

  • gui_simple.rb : Simple GUI (Implementation of java.awt.event interfaces)
  • gui_dialog.rb : JDialog Demo (Handling the Java-AWT thread)
  • gui_table.rb : JTable Demo (Implementation of TableModel)
  • gui_table2.rb : JTable Demo (Implementation of MVC)
  • gui_graphics.rb : Drawing graphics (Implementing JComponent and drawing graphics)

Writing scripts in UTF-8, you can use multibyte code correctly.

Using java library

  • ext_poi.rb : POI Demo (Using external classpath and multibyte handling)

POI sample demonstrates multibyte handling.

Dynamic java code

This package contains the javassist, the byte engineering tool library. You can make the POJO implementation from scratch.

  • jlambda.rb : jlambda programming sample

See javassist website, www.csg.is.titech.ac.jp/~chiba/javassist/index.html

Configuration

When you call a communication method for the first time, this module starts up JVM and constructs the bridge between Ruby and Java. You can configure JVM and Bridge parameters via hash object, as follows:

 JBRIDGE_OPTIONS = {
   :classpath => "$CLASSPATH",   # this record will be evaluated as `echo #{classpath}`.
   :jvm_path =>  "java",         # if you need, set fullpath to the JVM.
                                 # if nil, the jbridge will not start the JVM by itself.
   :jvm_vm_args => "",           # VM argument. ex: "-Xmx128m"
   :jvm_stdout => nil,           # if not nil, jbridge shows STDOUT from JVM.
                                 # In the windows environment, this option causes
                                 # serious slowing down.
   :jvm_log_file => nil,         # output from JVM. ex: "log.txt"
   :jvm_log_level => "normal",   # JVM debug level. [debug,verbose,normal,warning,error]
   :bridge_log => false,         # Ruby debug output. true or false

   :bridge_driver => :bstream,    # communication driver (:xmlrpc or :bstream)

   :xmlrpc_bridge_port_r2j => 9010,  # communication port: ruby -> java
   :xmlrpc_bridge_port_j2r => 9009,  # communication port: java -> ruby
   :xmlrpc_bridge_opened => :kill,
                    # If the port is using, how the program deals the port:
                    # :kill  : try to kill the previous port and open new port
                    # :abort : abort program
                    # :reuse : use the previous port

   :bstream_bridge_port => nil,
                    # communication port for bstream driver.
                    # if nil, the available port will be searched automatically.
 }

:xmlrpc driver is simple implementation to check the communication protocol. Although the XMLRPC communication library is easy to get and use and the implementation, the speed is slow. The slowness is due to the XML parsing and writing.

:bstream driver is second implementation improved the communication speed. This driver tranports binary data form on the TCP socket. So the speed of the :bstream communication is two times faster than that of :xmlrpc driver.

Basic API

JavaBridge

Symbol

  • jnew( [<constructor arguments]… )
  • jclass
  • jext( [<constructor arguments]… )

Notes

Transfer objects and primitive types

The numerical values, String and Array objects are transfered to Java side so as to maintain their precision. (the xmlrpc driver loses the precision of the floating point values because of transfoming text encoding.) The transformation between the Ruby and Java is done as follows:

    Java                Ruby
 ----------------------------------------
   byte(Byte)
   short(Short)
   int(Integer)        Fixnum,Bignum,Float
   long(Long)          (jbridge transforms dynamically)
   float(Float)
   double(Double)
   BigDecimal
   String              String
   Object[]            Array (content objects are applied transformation recursively)
   primitive array     Array (typed array, see text)

The instances of Fixnum, Bignum and Float are encoded by driver and tranfored to Java side. Then, the variable type are detected by its own value size and arguments types of called methods dynamically.

The other objects in Ruby can not be transfer into Java. On the other hand, if the other object in Java are transfered, the proxy objects are created to manipulate the Java objects from the Ruby side. Please see the next section for more details.

The transfer mechanism of the array object is very complecated. Generally, jbridge can not exactly construct an array object in Java, because numerical values never tell its own Java type. (for example, the value of "1" can be hold byte, short, int, long and double in Java.) So, in many cases, the array objects are transformed into Object[] values. Only if an array consists of String, an object of String[] is created in Java.

If you want to transfer an array as a primitive array, you can use typed array notation. The first element in the array specifies the primitive type:

 [:t_int4, 1, 2, 3, 4] (Ruby) --> new int[]{ 1, 2, 3, 4 } (Java).

You can use following type-symbols,

  • :t_int1 => byte[]
  • :t_int2 => short[]
  • :t_int4 => int[]
  • :t_int8 => long[]
  • :t_float => float[]
  • :t_double => double[]
  • :t_decimal => BigDecimal[]
  • :t_string => String[]
  • :t_boolean => boolean[].

Proxy objects and GC

All created java objects, such as created by jnew, jextend, jstatic and return values, are holded by JavaBridge server. Those objects has proxy id to delegate the method calling between the Java and ruby. If a proxy object is garbage collected in the ruby side, the JavaBridge sends an unlink message to the Java side to remove the corresponding object from the repositry in Java.

The overriding ruby objects created by jextend method are never removed automatically, because the JavaBridge can not decide when the object should be removed. You can remove the objects, calling the "junlink" method manually.

Charactor encoding

The string data is transported to the Java without any modification. The Java side interprets the string data as encoded in UTF-8. So, you need to adjust your string encoding to UTF-8, before you call the Java methods.

Thread

Any thread can call jbridge methods, except finalizer thread. If a jbridge methods is called in the finalizer procedure, a deadlock may occurs.

In the overriding method that is called by Java, all jbridge methods are executed by the Java thread that calls the overriding method by itself. For example, the ActionListener#actionPerformed method is called by the event-dispatch thread. Then, all jbridge calling from the actionPerformed are executed by the event-dispatch thread. The thread mechanism is very important to writing GUI code. Please see the Java document for more detail of thread and GUI.

Threads and Swing
java.sun.com/products/jfc/tsc/articles/threads/threads1.html

When your Ruby program finishes (that is the termination of main thread), the communication bridge is also killed. If you want to wait for the message from the JVM, such as GUI events, you need to stop the main thread. For convenience, you can call "stop_thread" method to hold the bridge communication. Then, you can restart the main thread to call "wakeup_thread".

Exceptions

If an exception occurs in the Java side, jbridge throws a JException to Ruby side. The JException holds the exception class name, message and stacktrace.

If an exception occurs in the overriding method, jbridge throws a jbridge.RemoteRuntimeExcetion to Java side. The exception also holds the exception class name, message and stacktrce.

If an exception occurs in the jbridge communication, IOError or subclass of IOError is thrown.

Speed

In the present implementation, because the arguments and return values are transported by the serial stream, the calling Java method takes longer time than the calling pure Ruby methods.

If you feel your code is too slow, you consider using jlambda or JClassBuilder. Because they are run on the Java, you can reduce the time of the trasportation of values.

In the future, replacing the communication driver by JNI may improve the speed.

jlambda

The utility of jlambda and JClassBuilder works through the javassist. So, your dynamic java code is restricted by the javassist compiler. For example, you can not write any comment, inner class and labeled jump in jlambda code. Please see the javassist documents for more details.

The design of the jlambda utility is not good, because I have little idea. Please give me the advice or codes.

The lower protocol

The YAJB communicates with the Java, using the following protocol that consists of basic remote procedure calls.

Type and Value

  • Z:boolean, B:byte, C:char, S:short, I:int, J:long, F:float, String
  • The other types are managed by the object manager with proxy ID.
  • Explicitly typed array : [(type symbol string), (values as string)…]

Java

  • [proxy ID] = new([class name],[values])
  • [proxy ID] = static([class name])
  • [proxy ID] = extends([class names], [values])
  • impl([proxy ID],[method name], [override flag])
  • [fqcn] = classname([proxy ID])
  • [classinfo] = classinfo([fqcn])
  • [value] = ref([proxy ID],[field name])
  • set([proxy ID],[field name],[value])
  • [value] = call([proxy ID],[method name],[values])
  • [value] = callsuper([proxy ID],[method name],[values])
  • [value] = sessionCall([message],[arguments])
  • import([package names])
  • unlink([proxy ID])
  • exit()
  • dump()

Ruby

  • [value] = call([session ID],[proxy ID],[values])

Methods

Classes and Modules

Module JavaBridge::TransferObjectFilter
Class JavaBridge::AbstractBridgeConnection
Class JavaBridge::BinStreamBridge
Class JavaBridge::JClass
Class JavaBridge::JClassBuilder
Class JavaBridge::JClassInfo
Class JavaBridge::JClassRepository
Class JavaBridge::JCreatedObject
Class JavaBridge::JException
Class JavaBridge::JExtendedClass
Class JavaBridge::JGCManager
Class JavaBridge::JObject
Class JavaBridge::JProxyObject
Class JavaBridge::XMLRPCBridge

Public Instance methods

Sending a shutdown message to the JVM.

return all proxy objects in the object repositry.

Creating an proxy instance that can override public and protected methods of the class and interfaces. Adding the singleton method to the instance, you can override the Java method in Ruby. In the override method, the protected methods can be called. You can use "super" method to invoke the method of super class in Java.

The class name parameter can take zero or one class and arbitrary number of interfaces. Those class names are specified by Symbol(only one) or String(separated by ","). *Ex: "java.awt.event.ActionListener,java.awt.event.WindowAdaptor"

If Ruby class overrides the Java method that has the same, the Java method delegates the operation to the Ruby method. The abstract method delegates Ruby method to execute immediately.

Corresponding notatin: :SomeJavaClassName.jext or :some_package_ClassName.jext

If the block is given, the block is used as a default method implementation.

 set_default_dispatcher {|name,args|
    do somethind...
 }

Where name is the method name and args is the array of argument objects. This notation is useful for the implementation of few methods, such as ActionListener.

jimport is almost similar to "import" statement of Java. Ex:

  • import "java.awt.*"
  • import "java.awt.*,java.awt.event.*"
  • import "java.util.List"

The later entry is given a priority.

Make a proc object in Java.

Creating an instance of the concrete class. The class name can be specified by String and Symbol. Ex: "java.awt.Point" corresponds width :java_awt_Point.

The return value is the proxy object for the instance of the Java.

Corresponding notatin: :SomeJavaClassName.jnew or :some_package_ClassName.jnew

Creating a static reference to the Java class. Through the reference, the any static methods and fields can be called by Ruby.

Corresponding notatin: :SomeJavaClassName.jclass or :some_package_ClassName.jclass

Remove the proxy object in the repositry of Java side. After calling this method, the proxy object can not call any method of the corresponding java object.

This method remove the link between the proxy object and jbridge object repositry so as for the proxy object to be garbage collected. This method never remove the proxy object.

Stopping the main thread so as not to finish the Ruby program. (The GUI programs can wait for messages from the JVM.)

Calling "wakeup_thread", the Ruby program resumes the main thread. (Before calling "exit" to terminate the Ruby program, the main thread should be on the running state.)

Resuming the main thread.

[Validate]