|
Listing 11.1 hashtable.judo |
---|
a = new java::java.util.Hashtable; a.put('date', Date(2001,1,1)); a.put('integer', 1); a.put('double', 10.01); a.put('string', 'Hello, World!'); a.put('Dimension', new java::java.awt.Dimension(10,20)); for k in a.keys() { // for-in statement handles Enumeration/Iterator println k:>10, ' = ', a.get(k); } |
When calling Java methods and constructors, generally just pass Judo variables as parameters, Judo tries to pick the right method. This should work most of the time, but is not always possible when there are overloaded methods with different numbers of parameters. (Overloaded methods are those in a same class with the same name but different parameters.)
Casting values and instanceof test
Let's see two Java classes, Foo
and Foo1
; both have methods with the same name, bar()
, and one parameter of various types.
Listing 11.2 Foo.java |
---|
public class Foo { public String bar(byte x) { return "byte"; } public String bar(char x) { return "char"; } public String bar(Integer x) { return "Integer"; } public String bar(java.util.Date x) { return "Date"; } } |
Listing 11.3 Foo1.java |
---|
public class Foo1 extends Foo { public String bar(int x) { return "int"; } public String bar(long x) { return "long"; } public String bar(double x) { return "double"; } public String bar(String x) { return "String"; } public String bar(java.sql.Date x) { return "SQLDate"; } } |
The following Judo program conducts three sets of tests: calling Foo
, calling Foo1
and calling Foo1
with precise Java types.
Listing 11.4 casting.judo |
---|
foo = new java::Foo; foo1 = new java::Foo1; sqldate = new java::java.sql.Date(1000); sqldate_ = sqld.cast(java::java.util.Date); println foo.bar('a') ; // char println foo.bar('abc') ; // char println foo.bar(1) ; // Integer println foo.bar(1.01) ; // Integer println foo.bar(Date()) ; // Date println foo.bar(sqldate) ; // Date println foo1.bar('a') ; // String println foo1.bar('abc') ; // String println foo1.bar(1) ; // long println foo1.bar(1.01) ; // double println foo1.bar(Date()) ; // Date println foo1.bar(sqldate) ; // SQLDate println foo1.bar(sqldate_) ; // Date println foo1.bar(char('1')); // char println foo1.bar(byte(1)) ; // byte println foo1.bar(char(1)) ; // char println foo1.bar(short(1)) ; // int println foo1.bar(int(1)) ; // int println foo1.bar(long(1)) ; // long println foo1.bar(float(1)) ; // double println foo1.bar(double(1)); // double |
Look closely at the output of all test cases and refer to each Java method invoked. As you see, Judo guesses reasonably well even in confusing situations. By casting Judo primitive values to specific Java types, you can invoke a particular method. The casting operators for Java primitive types look like functions, such as char(1)
; this syntax is consistent with JavaScript's. Java objects are cast with the cast()
method.
All Java objects in Judo have two built-in methods: cast()
and instanceof()
; this usage is different from Java but serves the same purposes. Generally object casting is not needed in method calls, since the object type is precise and there is no ambiguity. Java object casting is needed to use public members of an instance of a non-public class that implements public interfaces or extends other classes with public methods. To access the publicly accessible methods and fields, you would have to cast that object to the Java interface or parent class which are public. You will see this in examples of EJBs (Enterprise Java Beans).
The instanceof()
is used to check the class of an Java object. It can take a string representing the fully qualified Java class name or a Java class object, which is discussed later.
Listing 11.5 instof.judo |
---|
date1 = new java::java.sql.Date(1000); if date1.instanceof('java.util.Date') { println 'Yes, this is a java.util.Date.'; } else { println 'No, this is NOT a java.util.Date.'; } if date1.instanceof(java::java.sql.Date) { println 'Yes, this is a java.sql.Date.'; } else { println 'No, this is NOT a java.sql.Date.'; } |
Fully qualified Java class names include a number of package names and the class name itself, separated by dots; they are usually quite long. Judo uses the same Java import
directive to allow programs use class names without package prefixes. In Java, the import
directives must appear at the beginning in the source code; in Judo, however, import
directives can appear anywhere a statement is allowed, and they only affect Java class resolutions thereafter.
Pre-imported standard Java packages
Following explicit import directives, these three Java packages are pre-imported: java.lang.*
, java.io.*
and java.util.*
. Thus, you can directly use classes like System
, HashMap
, InputStream
, etc.
Listing 11.6 import.judo |
---|
x = new java::Date; println x.getClass().getName(); // java.util.Date import java.awt.*; import java.sql.Date; x = new java::Dimension(100,200); println x.getClass().getName(); // java.awt.Dimension x = new java::Date(10000); println x.getClass().getName(); // java.sql.Date |
In the above example, the first x
is java.util.Date
because of the rule of pre-imported java.util.*
package. The second x
is resolved by matching a class in the imported java.awt.*
package, and the latest x
is an exact match for the imported java.sql.Date
class, not java.util.Date
.
Inner class names
Java classes can contain other class definitions; the contained classes are called inner classes. The inner class name is separated from its containing class with a dollar sign ($).
Listing 11.7 Foo2.java |
---|
public class Foo2 { public static class Bar { } } |
Listing 11.8 innercls.judo |
---|
x = new java::Foo2$Bar; println x.getClass().getName(); // prints: Foo2$Bar |
In Java, using inner classes is more of a style than necessity. Only public static
inner classes can be created in Judo; the name of the enclosing class serves the role of a namespace in this case. This topic of inner class itself is beyond the scope of Judo.
Judo primitive values can be passed directly into Java methods like we have seen earlier. This includes integers, floats, strings and Date
s. Other native Judo data structures including arrays generally can not be passed into Java methods. Judo array objects have a number of built-in methods that return various Java arrays for the values in the array.
Array.toBooleanArray() // boolean[] Array.toBooleanObjectArray() // Boolean[] Array.toByteArray() // byte[] Array.toByteObjectArray() // Byte[] Array.toCharArray() // char[] Array.toCharObjectArray() // Character[] Array.toShortArray() // short[] Array.toShortObjectArray() // Short[] Array.toIntArray() // int[] Array.toIntObjectArray() // Integer[] Array.toLongArray() // long[] Array.toLongObjectArray() // Long[] Array.toFloatArray() // float[] Array.toFloatObjectArray() // Float[] Array.toDoubleArray() // double[] Array.toDoubleObjectArray() // Double[] Array.toObjectArray() // Object[] Array.toStringArray() // String[]
Otherwise, you need to explicitly convert the Judo data structure into a particular Java class instance.
Java classes are special objects within JVMs. In Judo, the java::
operator returns Java class objects. They can be stored in variables or assigned to constants, and the variables and constants can be used to instantiate classes or arrays.
Listing 11.9 javacls.judo |
---|
const #Dimension = java::java.awt.Dimension; CDim = java::java.awt.Dimension; println new #Dimension(100,200); println new :CDim(100,200); |
Once the class object is in a constant or variable, you can use the new
without "java" namespace prefix, because the information about the class object is already there. In the new
operator, the variable name must be prefixed with :
; otherwise, Judo would take the variable name as a (native) class name and try to instantiate an object of that name.
One of the main reasons to use Java class objects is to use Java class static members. Java classes' static methods and fields are class-wide members and can be accessed without creating individual instances. In Judo, you need to use the Java class objects to use their static members:
println (java::System).currentTimeMillis();
This works, but the syntax is a little cumbersome. In fact, Judo treats Java class names as namespaces, so that Java class static members can be accessed directly like this:
println System::currentTimeMillis();
System::out.println('abc');
mode = java.awt.Frame::MAXIMIZED_BOTH;
The following example prints out the values of java.sql.Types
's static fields.
Listing 11.10 sqltypes.judo |
---|
const #Types = java::java.sql.Types; fields = [ 'BIT', 'TINYINT', 'SMALLINT', 'INTEGER', 'BIGINT', 'FLOAT', 'REAL', 'DOUBLE', 'NUMERIC', 'DECIMAL', 'CHAR', 'VARCHAR', 'LONGVARCHAR', 'DATE', 'TIME', 'TIMESTAMP', 'BINARY', 'VARBINARY', 'LONGVARBINARY', 'NULL', 'OTHER', 'JAVA_OBJECT', 'DISTINCT', 'STRUCT', 'ARRAY', 'BLOB', 'CLOB', 'REF' ]; println 'JDBC types in Java ', sysProperty('java.version'), ':'; for fld in fields { println fld:<13, ': ', #Types.(fld); } |
In the loop, we used the .()
to access the static fields. The result is:
JDBC types in Java 1.4.1_01:
BIT : -7
TINYINT : -6
SMALLINT : 5
INTEGER : 4
BIGINT : -5
FLOAT : 6
REAL : 7
DOUBLE : 8
NUMERIC : 2
DECIMAL : 3
CHAR : 1
VARCHAR : 12
LONGVARCHAR : -1
DATE : 91
TIME : 92
TIMESTAMP : 93
BINARY : -2
VARBINARY : -3
LONGVARBINARY: -4
NULL : 0
OTHER : 1111
JAVA_OBJECT : 2000
DISTINCT : 2001
STRUCT : 2002
ARRAY : 2003
BLOB : 2004
CLOB : 2005
REF : 2006
Alias Java Static Methods as Functions
You can create function aliases for static Java methods; these function aliases behave like regular Judo functions. This is a great way to build Judo/Java libraries: you can choose to implement a function as a Java static method and alias it. In fact, many of Judo's own system functions are implemented this way. The syntax is:
Listing 11.11 func_alias.judo |
---|
function prop for java::System.getProperty(); function now for java::System.currentTimeMillis(); function rt for java::Runtime.getRuntime(); println now(), nl; println prop('java.class.path'), nl; rt().gc(); |
Note that function aliases don't care about the parameters of the Java static method. If a function alias is to take variable number of parameters, the Java class must have the name-sake static methods with parameters of all permutations. For instance, the Judo system function initialContext()
is an alias to get the initial JNDI context:
function initialContext for java::com.judoscript.bio.SysFunLib.initCtxt;
That function may take no parameters, or a factory name and optionally url, user, password and auth as parameters. Hence, the class com.judoscript.bio.SysFunLib
has these methods for that function alias:
public class SysFunLib { static InitialContext initCtxt(); static InitialContext initCtxt(String factory); static InitialContext initCtxt(String factory, String url); static InitialContext initCtxt(String factory, String url, String user); static InitialContext initCtxt(String factory, String url, String user, String pwd); static InitialContext initCtxt(String factory, String url, String user, String pwd, String auth); }
Another system function, startServer()
, is also an alias. Among methods of various parameter combinations, two of them take two parameters but of different types. That equates to two static methods as well.
public class SysFunLib
{
public static ServerSocket startServer(int port, String addr) throws IOException { ... }
public static ServerSocket startServer(int port, InetAddress addr) throws IOException { ... }
}
Like in Java, in Judo you can create Java arrays of primitive types and objects, either by size or by initialization:
// Create array by size a = new java::int[3]; a[0] = 1; a[1] = 2; a[2] = 4; for x in a { println x; } // Create array by initialization a = new java::String[] { 'abcd', 'efg', 8, 9 }; for x in a { println x; }
The for in
statement is useful for iterating through Java and Judo arrays. Java arrays can be passed to Java methods:
a = new java::String[] { 'abcd', 'efg', 8, "ab" }; Arrays::sort(a); for x in a { println x; }
Multi-dimensional arrays are created and used similarly:
Listing 11.12 java_array.judo |
---|
ia = new java::int[][][] { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 7, 8, 9 }, { 10 } } }; Fa = new java::Float[][][] { { { 1.5, 2.5, 3.5 }, { 4.5, 5.5, 6.5 } }, { { 7.5, 8.5, 9.5 }, { 10.5 } } }; ba = new java::boolean[][][] { { { true, false, true }, { false, true, true } }, { { true, true, true }, { true, true } } }; Ba = new java::Boolean[][][] { { { true, false, true }, { false, true, true } }, { { true, true, true }, { true, true } } }; ca = new java::char[][][] { { { 'A', 'B', 'C' }, { 'D', 'E', 'F' } }, { { 'G', 'H', 'I' }, { 'J', 'K' } } }; Oa = new java::Object[][][] { { { 'a', 'b', 'c' }, { 'd', 'e', 'f' } }, { { 'g', 'h', 'i' }, { 'j', 'k' } } }; print3d ia; print3d Fa; print3d ba; print3d Ba; print3d ca; print3d Oa; function print3d a { println '----- print 3D: ', a; for i from 0 to a.length-1 { for j from 0 to a[i].length-1 { for k from 0 to a[i][j].length-1 { println i, ' ', j, ' ', k, ' => ', a[i][j][k]; } } } } |
When a value is assigned to an array element, Judo tries to convert it to the array element type.
Judo has special treatment of common Java data structures including arrays, some of which we have already seen. This is the topic of the next section.
Java data structures are treated almost identical to their Judo counterparts.
The java.util.Map
interface is treated almost the same as Judo's own Object
. That is, java.util.Map
instance can be initialized with name-value pairs, and the member-access operator, .()
can be used to get and set values instead of Map
's get()
and put()
methods. In addition, if the key value is a string, you can access its value by directly using that string as an "attribute" name, seen in the following example.
ht = new java::Hashtable; // is a Map. ht.Hello = 'Hello, World!'; ht.'Hi, World' = 'Hello, World!'; println ht.Hello; // prints: Hello, World! println ht.'Hi, World'; // prints: Hello, World! println ht.DontExist; // prints nothing.
The most commonly used classes that implement the Map
interfaces are HashMap
, Hashtable
and Properties
. The following example creates a Properties
instance with initial values:
Listing 11.13 props.judo |
---|
props = new java::Properties( a.b.c.d.e.f.g = 'a thru g', h.i.j.k.l.m.n = 'h thru n' ); println props; |
Iterate Java Data Structures and Objects
Earlier, we have seen that the for in
statement can be used to iterate through Java arrays. In fact, for in
can be used to iterate these Java data structures:
java.util.Iterator
instancesjava.util.Enumeration
instancesjava.util.List
instancesjava.util.Collection
instancesjava.util.Map
instances (through their keys)iterator()
method that returns a java.util.Iterator
java.util.Iterator
or java.util.Enumeration
The rules should be obvious, except perhaps the last two. The following Java source file contains three classes; they are all ordinary classes but have those "signature" methods.
Listing 11.14 IterationDemo.java |
---|
import java.util.*; // A class with one iterator() method public class IterationDemo { int i = 0; public Iterator iterator() { i = 0; return new Iterator() { public boolean hasNext() { return i++<3; } public Object next() { return "iterator()-" + i; } public void remove() {} }; } // A class with one method that returns // an Iterator and takes no parameters public static class WithOneIterator { int i = 0; public Iterator foo() { i = 0; return new Iterator() { public boolean hasNext() { return i++<3; } public Object next() { return "foo()-" + i; } public void remove() {} }; } } // A class with one method that returns // an Enumeration and takes no parameters public static class WithOneEnumeration { int i = 0; public Enumeration bar() { i = 0; return new Enumeration() { public boolean hasMoreElements() { return i++<3; } public Object nextElement() { return "bar()-" + i; } }; } } } |
The following Judo program uses the for in
statement to iterate through each of them.
Listing 11.15 for_in_iter.judo |
---|
o = new java::IterationDemo; for x in o { println x; } println; o = new java::IterationDemo$WithOneIterator; for x in o { println x; } println; o = new java::IterationDemo$WithOneEnumeration; for x in o { println x; } |
Access elements in java.util.List
Objects of java.util.List
are treated very much like Judo arrays. For instance, they can be iterated forward and backward, and elements can be accessed via the array element access operator []
, as shown the following example.
Listing 11.16 javalists.judo |
---|
testList new java::java.util.Vector(); testList new java::java.util.ArrayList(); testList new java::java.util.LinkedList(); function testList lst { println nl, '=== ', lst.getClass().getName(), ' ==='; lst.add( Date(2001,1,1) ); lst.add( 1 ); lst.add( 10.01 ); lst.add( 'Hello, World!' ); println 'lst[0] = ', lst[0]; println 'lst[1] = ', lst[1]; println 'lst[2] = ', lst[2]; println 'lst[3] = ', lst[3]; println 'lst[4] = ', lst[4]; lst[9] = 'XYZ'; println 'lst[9] = ', lst[9]; } |
For the first testList()
call, for instance, the output is:
=== java.util.Vector ===
--- size: 4 ---
lst[0] = 1/1/01 12:00 AM
lst[1] = 1
lst[2] = 10.01
lst[3] = Hello, World!
lst[4] =
lst[9] = XYZ
So far we have covered all the basic Java scripting topics in Judo. Other than the special "java" namespace prefix, the operations are natural to both Judo and Java. Next, we are going to look at extending Java classes, including implementing Java interfaces.
The capability for a Java scripting language to extend Java classes and implement Java interfaces is important, because sometimes Java methods expect parameters of interfaces and require the client program to provide implementation classes. A classical use case is event handling. For instance, in a GUI program, you construct the GUI by assembling various components; based on user interaction, GUI components fire events by calling the controller object. This requires that the controller class implement certain interfaces, so the components "know" how to communicate with it. Such a call-back mechanism is not unique to GUI programs. A Java scripting language incapable of extending Java classes and implementing interfaces has serious limitations in terms of full-scale Java scripting.
Judo itself is an object-oriented language, and can extend/implement Java classes and interfaces; such classes are called Java extension classes in Judo. They are defined with the presence of the extends java::
clause. These are the rules governing Java extension classes:
extends
zero or one Java classes and zero or more interfaces, separated by comma. The resultant class is a Java class with that class name without any package names. This user-defined class are created like a normal Judo class via the new
operator without "java" namespace prefix.
super
decorator. You can also invoke own methods, and access this and parent's data fields, public or protected.
super()
, which is one of the parent class's constructors. This call must precede data member initializations and invocations of any other methods.
The most important things to remember are, the class itself is defined very much like a Java class, except the method bodies are written in Judo; everything is public; and constructor is a special issue. Let's look at examples now.
Listing 11.17 extend_class.judo |
---|
class MyHashtable extends java::Hashtable { // Test overriding an existing method int hashCode() { return super.hashCode() ^ 1; } // A new method. String[] getKeys() { arr = []; for x in this { arr.add(x); } return arr.toStringArray(); } // A new method. Object[] getValues() { arr = []; for x in values() { arr.add(x); } return arr.toObjectArray(); } } mht = new MyHashtable; mht.put('adate', Date(2004, 6, 6)); mht.put('anumber', 2); mht.put('astring', 'string'); println ' keys: ', mht.getKeys(); println ' values: ', mht.getValues(); println 'hashCode: ', mht.hashCode(); |
The hashCode()
method overrides the existing method in the parent class, and it invokes the parent's name-sake method. The two new methods, getKeys()
and getValues()
, use Judo arrays to collect keys and values, and eventually return them as Object[]
's.
Java interfaces can be extended (implemented) exactly the same way. The following example defines a Java extension class that implements java.util.Iterator
without implementing any of the methods, because any unimplemented abstract methods in a Java extension class are given empty bodies, so the new class we define is still a concrete (Java) class.
Listing 11.18 extend_itf.judo |
---|
class MyIterator extends java::Iterator { } println o = new MyIterator; for x in o { println x; } |
The following program extends a Java class and a Java interface. You can implement any number of Java interfaces, separated by comma(s); but you can extend no more than one Java class as you would expect.
Listing 11.19 extend_class_itf.judo |
---|
class MySetIterator extends java::HashSet, Iterator { Iterator iter; constructor a, b, c { super(); iter = null; if c != null { add(c); } if b != null { add(b); } if a != null { add(a); } } // Iterator methods boolean hasNext() { if iter == null { iter = iterator(); // of HashSet. } return iter.hasNext(); } Object next() { return (iter==null) ? null : iter.next(); } } o = new MySetIterator('Hi!', 9); o.add('abc'); o.add(Date(2003,7,4)); for x in o { println x; } |
All these examples are to demonstrate Judo's Java extension classes; they may not make much practical sense. This time, we declared a data member, iter
. They can only be initialized in the constructor. The constructor takes up to three parameters; in the testing, we passed two parameters. The program's output is:
Hi! 9 abc 7/4/03 12:00 AM
So, we have covered all the topics of Java scripting in Judo, from the very basics to advanced topics and everything in between. Java scripting is used extensively in building Java GUIs of AWT or Swing, as you will find out a lot in chapter 19. Java GUI Scripting. Next, we present some practical J2EE topics as further examples of Java scripting applications.
Java interface adapters are nothing but anonymous Java objects that implement some Java interfaces. Java interface adapters happen most frequently in event handling, notably in Java GUI programs:
import java.awt.Frame; import java.awt.event.*; Frame f = new Frame(); f.addWindowListener( new WindowListener() { public void windowClosing(WindowEvent e) { System.exit(0); } public void windowClosed(WindowEvent e) {} public void windowOpened(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} } ); f.setVisible(true);
In Judo, this is done via the Java extension class mechanism like this:
Listing 11.20 adapter.judo |
---|
import java.awt.Frame; import java.awt.event.*; f = new java::Frame; f.addWindowListener( new java::WindowListener { void windowClosing(WindowEvent e) { exit 0; } } ); f.setVisible(true); |
As discussed earlier, the unimplemented methods in the interface are given default (empty) implementations.
In Java, an interface adapter can implement just one interface; in Judo, you can implement as many interfaces as you want. In fact, you can even extend a class that has a default constructor. The following example demonstrates:
Listing 11.21 fromPaMa.judo |
---|
a = new java::Papa, Mama, Foo { void playBall() { println 'Make a few dents!'; } void singSong() { println 'Utter some sound.'; } }; a.playBall(); a.singSong(); println "Height: " + a.height(); |
In this example, Papa
and Mama
are two interfaces, and Foo
is a class with a default constructor:
public interface Papa { void playBall(); int height(); } public interface Mama { void singSong(); int height(); } public class Foo { }
Both Papa
and Mama
have a height()
method. What if they have same parameter types but different return types? The result may be unpredictable; it's the user's responsibility to prevent this from happening. The output of the example is:
Make a few dents! Utter some sound. Height: 0
Creating EJB clients in Judo is natural. It is easier to write than in Java because the ubiquitous casting is not needed at all. Coupled with Judo's JDBC capabilities, this constitutes a perfect testing platform for EJBs. With the built-in scheduling support, practical uses of EJBs are just as easy.
Listing 11.22 ejb_client |
---|
1: //ctx = getInitialContext('weblogic.jndi.WLInitialContextFactory', 't3://server'); 2: ctx = getWeblogicContext('t3://server'); // shortcut for Weblogic. 3: home = ctx.lookup(OrderHome); 4: key = new java::OrderPK('00234'); 5: order = home.findByPrimaryKey(key); 6: // do something with order. |
This is a hypothetical case where an order entity bean is retrieved from the server for some operations. Line 1, which is commented out, is the system function getInitialContext()
. Judo has a number of convenience functions to get initial contexts for popular J2EE application servers, including WebLogic, WebSphere, JBoss, Oracle 9iAS and IONA; see line 2. On line 3, OrderHome holds the unique registered home interface name, which is typically the same as its class name. The rest is just using Java classes.
Judo internal classes, i.e., classes in packages com.judoscript.*
, etc., are prohibited from being used. Classes in com.judoscript.util.*
, however, are allowed, because these classes have nothing to do with the Judo language engine.