There is one utility API need to be specifically
illustrated in this chapter, which is the
Luban imports all Java object as an external type java::javaobj. The construction of Java object is the same as constructing other Luban data types. Below are examples of Java object constructions.
javapie = java::javaobj(“java.lang.Double”, 3.1416);
// build a java double number
javahello = java::javaobj(“java.lang.String”, “Hello, World!”); // java string
javanow = java::javaobj(“java.util.Date”); // obtain current date/time from java
From above you can see the way to construct Java object is actually very simple, all you need to do is to always put the Java class name as the first argument and put the arguments the class constructor needs afterwards.
Calling Java object member function using LJB is exactly the same as calling any object member function in Luban or Java. The below are examples.
jmap = java::javaobj("java.util.HashMap");
jmap.put("one",1);
one = jmap.get(“one”);
std::println(obj=one);
The above code constructs a Java hash map object and calls the “put” member function to add one key value pair into the map. Then the following code then reads out the value by looking up by the same keys with “get” member function. The last line print out the look up result to show.
You can see it is extremely simple to call Java object member function in Luban, just construct the object and call as you would do in Java itself.
To call a Java static member function is also simple, like below code shows.
jdumy = java::javaobj();
connection = jdumy.java_callStaticFunction(
"java.sql.DriverManager","getConnection",URL,
user, pswd);
The way to call Java static function is to go through a special member function of Luban java::javaobj type. The Luban java::javaobj member function name is “java_callStaticFuncion” and its arguments are Java class name, static function name followed by actual arguments for that Java static function. Because the calling Java static function does not require any specific Java object instance, we can use a Java null object as a gateway to call any Java static functions.
In the above example, we first construct a Java null object using the default constructor of java::javaobj type. The we use the “java_call StaticFunction” to call the Java static function “java.sql.DriverManager.getConnection(URL, user, pswd)”. This Java static function is to obtain a connection to a SQL server.
Below is another example of calling Java static function. This example is to split a string using Java regular expression utilities.
jnull = java::javaobj();
pattern = jnull.java_callStaticFunction("java.util.regex.Pattern", "compile",
"[,\\s]+");
jresult = pattern.split("one,two,
three, four, five"); // jresult
is java String[]
The above code calls Java static function “java.util.regex.Pattern.complile()” to construct a Java regular expression pattern, then uses the pattern to split a string into an array of Java strings.
The way to access Java static data fields is similar to call its static member function, you need to use two special member function on the java::javaobj object, which can be Java null since the field is static. The two functions are “java_getStaticField” for reading and “java_setStaticField” for writing. Take a look at below code example.
dumy = java::javaobj();
hourfield = dumy.java_getStaticField("java.util.Calendar",
"SUNDAY");
std::println(obj=hourfield);
status = dumy.java_setStaticField("java.util.Calendar", "SUNDAY", 2); // try to
set
std::println(obj=status); // should print error message can’t set final
field
Above code first
gets the value of a Java static field “java.util.Calendar.SUNDAY”
that is of value integer 1. Then it tries to set the same field value to
integer 2, which is going to fail because the field is final. The code will
print out the value of the field and the error status message for the field
value set operation.
To access Java instance data field, you need to use a valid java::javaobj instance that has that data field accessible. It can not be a Java null object. You still use special member functions to access, and the member function names are “java_getField” for reading and “java_setField” for writing. Take a look at below example code.
jdim = java::javaobj("java.awt.Dimension", 200,200);
height = jdim.java_getField("height"); //
read height field
std::println(obj=height);
jdim.java_setField("width",100); // set width to 100
std::println(obj=jdim);
The above code constructs a Java object of Java type “java.awt.Dimension” that has two accessible fields “height” and “width” that are initialized to be both of value 200. Then the code reads the value of “height” field and prints it out. The following code then sets the “width” field to value 100 and print out the whole object to show the effect.
You can easily do “instanceof”
checking on a Java object in Java. The similar thing you can do in Luban is “isa” operation. Though for Luban all Java objects are of
type “java::javaobj” and you can not check the
object’s Java type using Luban
”isa” operator. The way to check the actual type of a
Java object in Luban is to call another special member function “java_instanceof”. Below is one code example.
x=java::javaobj("java.lang.Double",3.1416);
xisajavadouble = x.java_instanceof("java.lang.Double");
std::println(obj=xisajavadouble);
numberclass = x.java_getClassForName("java.lang.Number");
xisajavanumber = x.java_instanceof(numberclass);
std::println(obj=xisajavanumber);
In the above example, we first construct a Java object that is of Java type “java.lang.Double”. Then we call the “java_instanceof” function to check if the object is of type “java.lang.Double”. The function call should return a Java Boolean value true and we print it out. In the following code we do the similar type checking, yet using a different argument to call the “java_instanceof” function. This time we use a Java class object as the argument for checking. And the Java class object for Java class “java.lang.Number” is obtained from another Luban java::javaobj member function “java_getClassForName”.
“java_getClassForName” is a function to load and return any Java class object using its class name. When class object loading is a frequent operation, this function becomes handly.
I have explained how to construct Java object, call its static or instance member function, access its static or instance data field plus how to do Java’s “instanceof” type checking in Luban. Everything seems smooth so far.
Though there is one important thing I haven’t explained. In Java all objects are object references, while in Luban all objects are values. How do we bridge the difference?
The solution is technical. In
So in another word, Luban has no choice but to honor Java’s reference semantics because that’s the only thing we can get from Java. The below code shows the impact of Java object reference in Luban code.
jvec1 = java::javaobj(“java.util.Vector”);
jvec2 = jvec1;
jvec1.add(1);
std::println(obj=jvec1);
std::println(obj=jvec2);
std::println(obj= jvec2==jvec2 );
Run above code will print out two identical Java vector both contain element 1, and print out the result of testing “jvec1==jvec2” as Boolean value true. Since java::javaobj type contains only Java object reference as its value, so the assignment to a new variable jvec2 does NOT actually generate a new copy of the Java Vector object. The two variables contain the reference to the same actual Java object. So the print statements print out the same vector and the equality checking ends with value true.
In order to get a new copy, you need to call the clone() member function (if available), like what you would do in Java itself. Below modified code operates on two different instances after calling the clone() function.
jvec1 = java::javaobj(“java.util.Vector”);
jvec2 = jvec1.clone();
jvec1.add(1);
std::println(obj=jvec1);
std::println(obj=jvec2);
std::println(obj= jvec2==jvec2 );
Actually the object reference kinds of data types already exist in Luban before Java object. The Luban types like std::console, std::file and net::socket are all using reference in their implementations. So the assignment of file, console and/or socket to a new variable does not end up with new copy, the reference gets copied instead of the real object.
One of the most interesting uses of
frm = java::javaobj("javax.swing.JFrame", "Hello Swinger");
jpnl = java::javaobj("javax.swing.JPanel");
jbut = java::javaobj("javax.swing.JButton", "Push me");
jpnl.add(jbut);
frm.setContentPane(jpnl);
frm.setSize(java::javaobj("java.awt.Dimension",
300,100));
queue = java::javaobj("luban.InvocationQueue");
// new invocation queue, line 7
listener1 = queue.newProxy("java.awt.event.ActionListener"); // line 8
jbut.addActionListener(listener1); // line 9
listener2 = queue.newProxy("java.awt.event.WindowListener");
//line 10
frm.addWindowListener(listener2); // line 11
// Run below code between brackets in a different thread
{
for(i=0; ; ++i)
{
//below gets the next event in the queue, block
if queue empty
event
= queue.getInvocation();
std::println(obj=event);
if
( string(event.getMethod().getName())
== "windowClosing" ) break;
jbut.setText("Push Me
"+string(i));
}
}&
frm.setVisible(true);
Run the above code and you will see a Java Swing window with a button inside labeled “Push Me”. When you click the button the label of the button will change to “Push Me X” with X to be the number of times the button has been pushed. And in the console, when button is pushed the event message will be printed.
Now let’s go through the details of this program. The first five lines of the program do the simple widget construction. It constructs a JFrame, a JPanel and a JButton. It then put the JButton into the JPanel, JPanel into the JFrame and set the size of the JFrame to 300 by 100. All these are very much like what you would do in Java, just in a slightly different syntax.
The code afterward takes care of the GUI event call back. Here we need to introduce the Java class Luban.InvocationQueue. This class is used here as the gateway to pass Java GUI event into Luban. The two most important member functions of this class are “newProxy” and “getInvocation”. Luban.InvocationQueue.newProxy() member function takes the name of a Java Interface and generates a Java proxy project to mimic the Java Interface. The generated Java proxy object can be used any Java function that takes that Java Interface. When the member function of that Java Interface gets invoked, the proxy object handles it in a very simple manner, it puts the member function method object and its arguments into a invocation queue that resides inside the original Luban.InvocationQueue.
The luban.InvocationQueue.getInvocation() function simply fetch one invocation from the invocation queue. The function blocks and waits when the queue is empty. The next new invocation will then wake up the function and it will get the new invocation and continue.
Now we can get back to how Luban code uses the Luban.InvocationQueue class to handle the GUI events. In our example, line 7 creates the InvocationQueue object. Then line 8 calls the “newProxy” member function to create a Java proxy object with the Java interface “java.awt.event.ActionListener”. Line 9 uses that Java proxy object as the call back listener for the JButton object we created before. According to my explanation above, every time the button get pressed, the member function “actionPerformed” of the Java Interface “java.awt.event.ActionListener” will be called and the Java proxy object will put the “actionPerformed” method object and the argument into the invocation queue. Line 10 creates another proxy object with Java interface “java.awt.event.WindowListener”. Line 11 uses that proxy object as the listener of the JFrame object to listen to window events like window closing. Please note you can create multiple proxy objects on the same queue. That means all the invocation upon all the different proxy objects will end up in the same queue.
The following Luban code handling the GUI event is dispatched into a separate thread. Within the thread, it is an infinite loop. In the loop, the code first takes one event out of the invocation queue. If the queue is empty it will block and wait until next invocation comes. Then the code prints out the invocation event details. Afterwards the code check if the event is a window closing event, and break out of the loop and finish the thread if it is. Remember we have the windows event and JButton event in the same queue. Otherwise it changes the label of the JButton to include the number of loops and continue.
It is a good practice to put the event handling of one invocation queue into a separated thread so the main thread can continue when it is waiting for the next invocation event. The next example uses two queues and two threads to do pretty much the same thing as above example. Below is the code.
frm = java::javaobj("javax.swing.JFrame", "Hello Swinger");
jpnl = java::javaobj("javax.swing.JPanel");
jbut = java::javaobj("javax.swing.JButton", "Push me");
jpnl.add(jbut);
frm.setContentPane(jpnl);
frm.setSize(java::javaobj("java.awt.Dimension",
300,100));
queue = java::javaobj("luban.InvocationQueue");
q1=queue;
listener = queue.newProxy("java.awt.event.ActionListener");
jbut.addActionListener(listener);
queue2 = java::javaobj("luban.InvocationQueue");
listener2 = queue2.newProxy("java.awt.event.WindowListener");
frm.addWindowListener(listener2);
{
for(i=1; ; ++i)
// thread to handle the button click event
{
event = queue.getInvocation();
if ( event == null ) break;
std::println(obj=event);
if ( string(event.getMethod().getName()) == "windowClosing" ) break;
jbut.setText("Push
Me "+string(i));
}
}&
// a different thread to handle window
events
{
while(true)
{
event2
= queue2.getInvocation();
std::println(obj=event2);
if ( string(event2.getMethod().getName())
== "windowClosing" )
{
q1.close(); // close
another queue
break;
}
}
}&
frm.setVisible(true);
When the above example is run, it does the same thing as previous example does. The difference between this example and previous example is that previous example use one invocation queue to handle the events from both the JButton and the JFrame widgets, while this example use two different queues for JButton and JFrame. And this example uses two different threads to obtain and handle the events from that two different event queues. In this example, when one thread detects that window is closing, it closes the other queue that handles the button events, so the other thread can return from “getInvocation” call with null and finish.
One trick, when you call the InvocationQueue.close() function of another queue, you need to use different variable that contains the reference to the queue used in another thread, like in above example we use variable “q1” instead of the “queue1” variable. The reason is that Luban has a thread safe feature that only allows one function call to one variable at any time. So while the “queue1” variable blocks at “getInvocation” function call, the “queue1.close()” can not get through and that will prevent both thread from finishing. So we assign “q1=queue1” first, and later we use q1 to close the queue. Because all Java objects are actually object references, this trick works.
The luban.InvocationQueue and associated luban.InvocationEvent are included in luban.jar file that is part of the Luban source and binary package.
Talking to a database is commonly required for applications. Here I give one example for using JDBC interface to talk to a MySQL database server.
javadumy = java::javaobj();
drvclass = javadumy.java_getClassForName("com.mysql.jdbc.Driver");
std::println(obj="load jdbc
driver:\t"+string(drvclass));
URL = "jdbc:mysql://mypc:3306/mydb";
user = "root";
pswd = "rootpassword";
conn = javadumy.java_callStaticFunction(
"java.sql.DriverManager", "getConnection",URL, user, pswd);
std::println(obj="get connection:\t"+string(conn));
st = conn.createStatement();
rs = st.executeQuery
("select * from stocks");
std::println(obj="Content of stocks table:");
while ( bool(rs.next())
)
{
stockname = rs.getString(1);
stockprice=rs.getDouble(2);
std::println ( obj=string(stockname)+"\t"+string(stockprice));
}
rs.close();
st.close();
conn.close();
The above example is actually very similar to any Java JDBC example code. They go through the same routine. First it loads the MySQL jdbc driver class by calling the Luban java::javaobj utility member function “getClassForName()” on a dummy java::javaobj instance. Then it calls the Java static function java.sql.DriverManager.getConnection to obtain a connection to the MySQL server with specified URL, user name and password. Then it calls the “createStatement()” member function on the connection object, then execute a query. The query is to get the content of a table named “stocks”. After it gets the results for the query, it then iterates through the results and print out contents. The program nicely closes everything at the end.
Regular expression is another commonly used tool for text processing. Below example shows how to use Java regular expression tools in Luban through LJB.
// EXAMPLE 1, string split
jnull = java::javaobj();
pattern = jnull.java_callStaticFunction("java.util.regex.Pattern", "compile",
"[,\\s]+");
strtosplit = "one,two,
three,, four, five";
jresult = pattern.split(strtosplit); // jresult is java
String[]
result=vector(jresult); // convert String[] to luban vector
std::println(obj=strtosplit);
std::println(obj=result);
// EXAMPLE 2, find and replace
pattern2 = jnull.java_callStaticFunction("java.util.regex.Pattern",
"compile", "cat");
original = "one cat, two cats in the yard";
matcher = pattern2.matcher(original);
match = bool(matcher.find());
jbuffer = java::javaobj("java.lang.StringBuffer");
while(match)
{
matcher.appendReplacement(jbuffer, "dog");
match
= bool(matcher.find());
}
matcher.appendTail(jbuffer);
result2 = string(jbuffer);
std::println(obj=original);
std::println(obj=result2);
The above Luban code example uses Java regular expression
classes to do two things, one is to split a string another is to find all “cat”
words in a string and replace them with word “dog”. In the first example, it constructs a Java regular expression pattern
by calling a Java static function. Then it calls the “split” member function of
the pattern to split a string. Finally it converts the Java string array object
to a Luban vector and prints the vector out. In the second example, it also
constructs a Java regular expression pattern. Then it builds a matcher for the string
to be processed with the pattern. It then goes looping to find all the
occurrences of the pattern “cat” and replace it with “dog” and put the result
into a Java string buffer. After the loop is done, it converts the Java string
buffer to a Luban string and prints it out.
There are two
kinds of conversions we will discuss below, Luban to Java and vice versa.
The Luban to
Java conversion is always implicit. It happens when Luban data type is used to
construct Java object and call Java function. For example, when below Luban
code runs:
Jdouble = java::javaobj(“java.lang.Double”, 3.1416);
The constant
3.1416 is a Luban data type, it will be converted to a
Java Double type and used as one argument to construct a Java object that
happens to be also a Java Double type. The conversion is implicit because the
code does not state to do that conversion explicitly.
The rules for
Luban to Java type conversion is listed below.
Luban Type Java Type
double java.lang.Double
int java.lang.Integer
bool java.lang.Boolean
char java.lang.Character
string java.lang.String
map java.util.HashMap
vector java.util.Vector
set java.util.HashSet
java::javaobj NO
CONVERSION NEEDED
One more note,
the Luban to Java conversion is always a “deep” conversion. It means that if a
Luban container type is converted, not only the container will be converted, every element inside the container will be
converted recursively.
Differing from
Luban to Java conversion, Java to Luban conversion has to be explicit, or it
will not happen. What I mean here is that any the Java object that Luban
obtained from JVM with Java constructor or function calls, it will be always
wrapped as a special Luban data type “java::javaobj”
you can use this object just like any other Luban object without conversion.
There are
situations you want to cast Luban Java object into some other Luban type. Then
you need to use Luban cast expression like below:
jvec = java::javaobj(“java.util.Vector”);
jvec.add(1.0);
// use it directly
lubanvec = vector(jvec) // cast to Luban vector;
foreach( x
in lubanvec )
std::println(obj=x);
The above code converts a Java vector object into a Luban vector using a cast expression, then use foreach statement on the converted Luban vector. If no conversion happens you can still use the Java object inside variable “jvec” as it is. The above code casts the Java vector into a Luban vector only to iterate through it.
All Java to Luban conversions have to be explicit with a cast expression. Below list all Luban types that be used as cast operators and the Java types applicable to them.
Java Types Luban Type
All array types
+ java.util.List vector
java.util.Map map
java.util.Set set
java.lang.Boolean bool
java.lang.Number
+ java.lang.Character char
java.lang.Number double
java.lang.Number int
Also different from Luban to Java conversion, Java to Luban conversion is always shallow, meaning when converting a Java container type to another Luban container type, only the container itself get converted, while all the elements inside container kept untouched.
To build and run LJB, there are some configurations need to be done. The below are the details.
· set up environment variable JAVA_HOME before build and install
For example, you have your JDK at C:\jdk1.5.0_02 you need to specify this in POSIX format on cygwin:
JAVA_HOME=/cygdrive/c/jdk1.5.0_02
On linux, you need to do the same thing.
If you don’t have JAVA_HOME env variable setup, the build script will ask you for the value of JAVA_HOME at the beginning.
On Windows with Cygwin, you also need to make sure you have the write permission to JAVA_HOME/lib directory. Luban build script will copy one file jvm.dll.a file into that directory to enable gcc to link against jvm.dll
· Make sure jvm.dll accessible at run time
On Linux, for example you have Java installed on /usr/lib/java, you need to have the environment variable LD_LIBRARY_PATH setup like below:
LD_LIBRARY_PATH=
/usr/lib/java/jre/lib/i386/client:/usr/lib/java/jre/lib/i386
On Windows, for example your Java installed at C:\jdk1.5.0, you need to put this into PATH environment variable:
PATH=C:\jdk1.5.0\jre\bin\client
Luban uses two environment variables to pass parameters it needs to start the JVM, including the place to find the jar files.
The first environment variable is “CLASSPATH”. This is the variable to contain the path to all the jar files that contains the classes that’s not a part of the JVM standard distribution. For example if you have a MySQL JDBC driver package plus the Luban InvocationQueue class to load, you can put the jar file location into CLASSPATH like below:
CLASSPATH=
D:\mysqljdbc\mysql-connector-java-3.1.10\mysql-connector-java-3.1.10-bin.jar;
D:\luban\javacode\luban.jar
If you have multiple jar files and/or multiple class directories, you can list them all in CLASSPATH, separate them with semicolon.
The other environment variable is “LUBAN_JVMOPTIONS”, you can put arbitrary JVM options there as far as you separate them with space. For example, you can specify JVM maximum and minimum heap size like below:
LUBAN_JVMOPTIONS=”-Xms64m
–Xmx256m”