JavaBridge (Module)

In: ruby/comm_abstract.rb
ruby/comm_xmlrpc.rb
ruby/jbridge.rb
ruby/comm_bstream.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_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

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".

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])
  • unlink([proxy ID])
  • exit()
  • dump()

Ruby

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

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 implement the Java interfaces or abstract class in Ruby.

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

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]