зеркало из https://github.com/mozilla/pjs.git
495 строки
16 KiB
HTML
495 строки
16 KiB
HTML
<html>
|
||
|
||
<head>
|
||
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
|
||
<title>Scripting Java</title>
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<h1>Scripting Java</h1>
|
||
|
||
<p>This paper shows how to use Rhino to reach beyond JavaScript into
|
||
Java <a href="#ref1">[1]</a>. Scripting Java has many uses. It allows
|
||
us to write powerful scripts quickly by making use of the many Java
|
||
libraries available. We can test Java classes by writing scripts. We
|
||
can also aid our Java development by using scripting for <em>exploratory
|
||
programming</em>. Exploratory programming is the process of
|
||
learning about what a library or API can do by writing quick programs
|
||
that use it. As we will see, scripting makes this process easier.</p>
|
||
|
||
<p>Note that the ECMA standard doesn't cover communication with
|
||
Java (or with any external object system for that matter). All the
|
||
functionality covered in this chapter should thus be considered an
|
||
extension.</p>
|
||
|
||
<h2>Accessing Java packages and classes</h2>
|
||
|
||
<p>Every piece of Java code is part of a class. Every Java
|
||
class is part of a package. In JavaScript, however, scripts exist outside of
|
||
any package hierarchy. How then, do we access classes in Java packages?</p>
|
||
|
||
<p>Rhino defines a top-level variable named <code>Packages</code>. The
|
||
properties of the <code>Packages</code>
|
||
variable are all the top-level Java packages, such as <code>java</code> and
|
||
<code>com</code>. For example,
|
||
we can access the value of the <code>java</code>package:</p>
|
||
|
||
<pre>
|
||
js> Packages.java
|
||
[JavaPackage java]
|
||
</pre>
|
||
|
||
<p>As a handy shortcut, Rhino defines a top-level variable <code>java</code>
|
||
that is equivalent to <code>Packages.java</code>.
|
||
So the previous example could be even shorter:</p>
|
||
|
||
<pre>
|
||
js> java
|
||
[JavaPackage java]
|
||
</pre>
|
||
|
||
<p>We can access Java classes simply by stepping down the
|
||
package hierarchy:</p>
|
||
|
||
<pre>
|
||
js> java.io.File
|
||
[JavaClass java.io.File]
|
||
</pre>
|
||
|
||
<p>If your scripts access a lot of different Java classes it
|
||
can get awkward to use the full package name of the class every time. Rhino
|
||
provides a top-level function <code>importPackage</code> that serves the same
|
||
purpose as Java's <code>import</code> declaration. For example, we could
|
||
import all of the classes in the <code>java.io</code>package and
|
||
access class <code>java.io.File</code>
|
||
using just the name <code>File</code>:</p>
|
||
<pre>
|
||
js> importPackage(java.io)
|
||
js> File
|
||
[JavaClass java.io.File]
|
||
</pre>
|
||
|
||
<p>Here <code>importPackage(java.io)</code> makes all the classes in
|
||
the <code>java.io</code> package<67>(such as <code>File</code>)
|
||
available at the top level. It's equivalent in effect to the Java
|
||
declaration <code>import java.io.*;</code>.</p>
|
||
|
||
<p>It's important to note that Java imports <code>java.lang.*</code>
|
||
implicitly, while Rhino does not. The reason is that JavaScript has
|
||
its own top-level objects <code>Boolean</code>, <code>Math</code>,
|
||
<code>Number</code>, <code>Object</code>, and <code>String</code> that
|
||
are different from the classes by those names defined in the
|
||
<code>java.lang</code> package. Because of this conflict, it's a good
|
||
idea not to use <code>importPackage</code> on the
|
||
<code>java.lang</code> package.</p>
|
||
|
||
<p>One thing to be careful of is Rhino's handling of errors in
|
||
specifying Java package or class names. If <code>java.MyClass</code>
|
||
is accessed, Rhino attempts to load a class named
|
||
<code>java.MyClass</code>. If that load fails, it assumes that
|
||
<code>java.MyClass</code> is a package name, and no error is
|
||
reported:</p>
|
||
|
||
<pre>
|
||
js> java.MyClass
|
||
[JavaPackage java.MyClass]
|
||
</pre>
|
||
|
||
<p>Only if you attempt to use this object as a class will an
|
||
error be reported.</p>
|
||
|
||
<h2>Working with Java objects</h2>
|
||
|
||
<p>Now that we can access Java classes, the next logical step is to
|
||
create an object. This works just as in Java, with the use of the
|
||
<code>new</code> operator:</p>
|
||
<pre>
|
||
js> new java.util.Date()
|
||
Thu Jan 24 16:18:17 EST 2002
|
||
</pre>
|
||
|
||
<p>If we store the new object in a JavaScript variable, we can then
|
||
call methods on it:</p>
|
||
|
||
<pre>
|
||
js> f = new java.io.File("test.txt")
|
||
test.txt
|
||
js> f.exists()
|
||
true
|
||
js> f.getName()
|
||
test.txt
|
||
</pre>
|
||
|
||
<p>Static methods and fields can be accessed from the class object
|
||
itself:</p>
|
||
|
||
<pre>
|
||
js> java.lang.Math.PI
|
||
3.141592653589793
|
||
js> java.lang.Math.cos(0)
|
||
1
|
||
</pre>
|
||
|
||
<p>In JavaScript, unlike Java, the method by itself is an
|
||
object and can be evaluated as well as being called. If we just view the method
|
||
object by itself we can see the various overloaded forms of the method:</p>
|
||
|
||
<pre>
|
||
js> f.listFiles
|
||
function listFiles() {/*
|
||
java.io.File[] listFiles()
|
||
java.io.File[] listFiles(java.io.FilenameFilter)
|
||
java.io.File[] listFiles(java.io.FileFilter)
|
||
*/}
|
||
</pre>
|
||
|
||
<p>This output shows that the <code>File</code> class defines three
|
||
overloaded methods <code>listFiles</code>: one that takes no
|
||
arguments, another with a <code>FilenameFilter</code> argument, and a
|
||
third with a <code>FileFilter</code> argument. All the methods return
|
||
an array of <code>File</code> objects. Being able to view the
|
||
parameters and return type of Java methods is particularly useful in
|
||
exploratory programming where we might be investigating a method and
|
||
are unsure of the parameter or return types. </p>
|
||
|
||
<p>Another useful feature for exploratory programming is the ability
|
||
to see all the methods and fields defined for an object. Using the
|
||
JavaScript <code>for..in</code> construct, we can print out all these
|
||
values:</p>
|
||
|
||
<pre>
|
||
js> for (i in f) { print(i) }
|
||
exists
|
||
parentFile
|
||
mkdir
|
||
toString
|
||
wait
|
||
<em>[44 others]</em>
|
||
</pre>
|
||
|
||
<p>Note that not only the methods of the <code>File</code> class are
|
||
listed, but also the methods inherited from the base class
|
||
<code>java.lang.Object</code> (like <code>wait</code>). This makes it
|
||
easier to work with objects in deeply nested inheritance hierarchies
|
||
since you can see all the methods that are available for that
|
||
object.</p>
|
||
|
||
<p>Rhino provides another convenience by allowing properties of
|
||
JavaBeans to be accessed directly by their property names. A JavaBean
|
||
property <code>foo</code> is defined by the methods
|
||
<code>getFoo</code> and <code>setFoo</code>. Additionally, a boolean
|
||
property of the same name can be defined by an <code>isFoo</code>
|
||
method <a href="#ref2">[2]</a>. For example, the following code
|
||
actually calls the <code>File</code> object's <code>getName</code> and
|
||
<code>isDirectory</code> methods.</p>
|
||
|
||
<pre>
|
||
js> f.name
|
||
test.txt
|
||
js> f.directory
|
||
false
|
||
</pre>
|
||
|
||
<h2>Calling overloaded methods</h2>
|
||
|
||
<p>The process of choosing a method to call based upon the types of
|
||
the arguments is called <em>overload resolution</em>. In Java,
|
||
overload resolution is performed at compile time, while in Rhino it
|
||
occurs at runtime. This difference is inevitable given JavaScript's
|
||
use of dynamic typing as was discussed in Chapter 2: since the type of
|
||
a variable is not known until runtime, only then can overload
|
||
resolution occur.</p>
|
||
|
||
<p>As an example, consider the following Java class that defines a
|
||
number of overloaded methods and calls them.</p>
|
||
|
||
<pre>
|
||
public class Overload {
|
||
|
||
public String f(Object o) { return "f(Object)"; }
|
||
public String f(String s) { return "f(String)"; }
|
||
public String f(int i) { return "f(int)"; }
|
||
|
||
public String g(String s, int i) { return "g(String,int)"; }
|
||
public String g(int i, String s) { return "g(int,String)"; }
|
||
|
||
public static void main(String[] args) {
|
||
Overload o = new Overload();
|
||
Object[] a = new Object[] { new Integer(3), "hi", Overload.class };
|
||
for (int i = 0; i != a.length; ++i)
|
||
System.out.println(o.f(a[i]));
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>When we compile and execute the program, it produces the output</p>
|
||
|
||
<pre>
|
||
f(Object)
|
||
f(Object)
|
||
f(Object)
|
||
</pre>
|
||
|
||
<p>However, if we write a similar script</p>
|
||
|
||
<pre>
|
||
var o = new Packages.Overload();
|
||
var a = [ 3, "hi", Packages.Overload ];
|
||
for (var i = 0; i != a.length; ++i)
|
||
print(o.f(a[i]));
|
||
</pre>
|
||
|
||
<p>and execute it, we get the output</p>
|
||
|
||
<pre>
|
||
f(int)
|
||
f(String)
|
||
f(Object)
|
||
</pre>
|
||
|
||
<p>Because Rhino selects an overloaded method at runtime, it calls the
|
||
more specific type that matches the argument. Meanwhile Java selects
|
||
the overloaded method purely on the type of the argument at compile
|
||
time. </p>
|
||
|
||
<p>Although this has the benefit of selecting a method that may be a
|
||
better match for each call, it does have an impact on performance
|
||
since more work is done at each call. In practice this performance
|
||
cost hasn't been noticeable in real applications.</p>
|
||
|
||
<p>Because overload resolution occurs at runtime, it can fail at
|
||
runtime. For example, if we call <code>Overload</code>'s method
|
||
<code>g</code> with two integers we get an error because neither form
|
||
of the method is closer to the argument types than the other:</p>
|
||
|
||
<pre>
|
||
js> o.g(3,4)
|
||
js:"<stdin>", line 2: The choice of Java method Overload.g
|
||
matching JavaScript argument types (number,number) is ambiguous;
|
||
candidate methods are:
|
||
class java.lang.String g(java.lang.String,int)
|
||
class java.lang.String g(int,java.lang.String)
|
||
</pre>
|
||
|
||
<p>A more precise definition of overloading semantics can be
|
||
found at <a
|
||
href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html</a>.</p>
|
||
|
||
<h2>Implementing Java interfaces</h2>
|
||
|
||
<p>Now that we can access Java classes, create Java objects, and
|
||
access fields, methods, and properties of those objects, we have a
|
||
great deal of power at our fingertips. However, there are a few
|
||
instances where that is not enough: many APIs in Java work by
|
||
providing interfaces that clients must implement. One example of this
|
||
is the <code>Thread</code> class: its constructor takes a
|
||
<code>Runnable</code> that contains a single method <code>run</code>
|
||
that will be called when the new thread is started. </p>
|
||
|
||
<p>To address this need, Rhino provides the ability to create new Java
|
||
objects that implement interfaces. First we must define a JavaScript
|
||
object with function properties whose names match the methods required
|
||
by the Java interface. To implement a <code>Runnable</code>, we need
|
||
only define a single method <code>run</code> with no parameters. If
|
||
you remember from Chapter 3, it is possible to define a JavaScript
|
||
object with the <code>{propertyName: value}</code> notation. We can
|
||
use that syntax here in combination with a function expression to
|
||
define a JavaScript object with a <code>run</code> method:</p>
|
||
|
||
<pre>
|
||
js> obj = { run: function () { print("\nrunning"); } }
|
||
[object Object]
|
||
js> obj.run()
|
||
|
||
running
|
||
</pre>
|
||
|
||
<p>Now we can create an object implementing the <code>Runnable</code> interface
|
||
by constructing a <code>Runnable</code>:</p>
|
||
|
||
<pre>
|
||
js> r = new java.lang.Runnable(obj);
|
||
[object JavaObject]
|
||
</pre>
|
||
|
||
<p>In Java it is not possible to use the <code>new</code> operator on
|
||
an interface because there is no implementation available. Here Rhino
|
||
gets the implementation from the JavaScript object
|
||
<code>obj</code>. Now that we have an object implementing
|
||
<code>Runnable</code>, we can create a <code>Thread</code> and run
|
||
it. The function we defined for <code>run </code>will be called on a
|
||
new thread.</p>
|
||
|
||
<pre>
|
||
js> t = new java.lang.Thread(r)
|
||
Thread[Thread-2,5,main]
|
||
js> t.start()
|
||
js>
|
||
|
||
running
|
||
</pre>
|
||
|
||
<p>The final <code>js</code> prompt and the output from the new thread
|
||
may appear in either order, depending on thread scheduling.</p>
|
||
|
||
|
||
<p>Behind the scenes, Rhino generates the bytecode for a new Java
|
||
class that implements <code>Runnable</code> and forwards all calls to
|
||
its <code>run</code> method over to an associated JavaScript
|
||
object. The object that implements this class is called a <em>Java
|
||
adapter</em>. Because the forwarding to JavaScript occurs at runtime,
|
||
it is possible to delay defining the methods implementing an interface
|
||
until they are called. While omitting a required method is bad
|
||
practice for programming in the large, it's useful for small scripts
|
||
and for exploratory programming.</p>
|
||
|
||
<h2>The <code>JavaAdapter</code> constructor</h2>
|
||
|
||
<p>In the previous section we created Java adapters using the
|
||
<code>new</code> operator with Java interfaces. This approach has its
|
||
limitations: it's not possible to implement multiple interfaces, nor
|
||
can we extend non-abstract classes. For these reasons there is a
|
||
<code>JavaAdapter</code> constructor. </p>
|
||
|
||
<p>The syntax of the <code>JavaAdapter</code> constructor is</p>
|
||
|
||
<pre>
|
||
new JavaAdapter(javaIntfOrClass, [javaIntf, ..., javaIntf,] javascriptObject)
|
||
</pre>
|
||
|
||
<p>Here <code>javaIntfOrClass</code> is an interface to implement or a
|
||
class to extend and <code>javaIntf</code> are aditional interfaces to
|
||
implement. The <code>javascriptObject</code> is the JavaScript object
|
||
containing the methods that will be called from the Java adapter. </p>
|
||
|
||
<p>In practice there's little need to call the
|
||
<code>JavaAdapter</code> constructor directly. Most of the time the
|
||
previous syntaxes using the <code>new</code> operator will be
|
||
sufficient.</p>
|
||
|
||
<h2>JavaScript functions as Java interfaces</h2>
|
||
|
||
<p>Often we need to implement an interface with only one method, like in the
|
||
previous <code>Runnable</code> example or when providing various event
|
||
listener implementations. To facilitate this Rhino allows to pass
|
||
JavaScript function when such interface is expected. The
|
||
function is called as the implementation of interface method.</p>
|
||
|
||
<p>Here is the simplified <code>Runnable</code> example:</p>
|
||
<pre>
|
||
js> t = java.lang.Thread(function () { print("\nrunning"); });
|
||
Thread[Thread-0,5,main]
|
||
js> t.start()
|
||
js>
|
||
running
|
||
</pre>
|
||
|
||
Rhino also allows to use JavaScript function as implementation of Java
|
||
interface with more then method if all the methods has the same
|
||
signature. When calling the function, Rhino passes method's name as
|
||
the additional argument. Function can
|
||
use it to distinguish on behalf of which method it was called:
|
||
|
||
<pre>
|
||
js> var frame = new Packages.javax.swing.JFrame();
|
||
js> frame.addWindowListener(function(event, methodName) {
|
||
if (methodName == "windowClosing") {
|
||
print("Calling System.exit()..."); java.lang.System.exit(0);
|
||
}
|
||
});
|
||
js> frame.setSize(100, 100);
|
||
js> frame.visible = true;
|
||
true
|
||
js> Calling System.exit()...
|
||
</pre>
|
||
|
||
<h2>Creating Java arrays</h2>
|
||
|
||
<p>Rhino provides no special syntax for creating Java arrays. You
|
||
must use the <code>java.lang.reflect.Array</code> class for this
|
||
purpose. To create an array of five Java strings you would make the
|
||
following call:</p>
|
||
|
||
<pre>
|
||
js> a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
|
||
[Ljava.lang.String;@7ffe01
|
||
</pre>
|
||
|
||
<p>To create an array of primitive types, we must use the special TYPE
|
||
field defined in the associated object class in the
|
||
<code>java.lang</code> package. For example, to create an array of
|
||
bytes, we must use the special field
|
||
<code>java.lang.Byte.TYPE</code>:</p>
|
||
|
||
<pre>
|
||
js> a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);
|
||
[C@7a84e4
|
||
</pre>
|
||
|
||
<p>The resulting value can then be used anywhere a Java array
|
||
of that type is allowed.</p>
|
||
|
||
<pre>
|
||
js> a[0] = 104
|
||
104
|
||
js> a[1] = 105
|
||
105
|
||
js> new java.lang.String(a)
|
||
hi
|
||
</pre>
|
||
|
||
<h2>Java strings and JavaScript strings</h2>
|
||
|
||
<p>It's important to keep in mind that Java strings and JavaScript
|
||
strings are <strong>not</strong> the same. Java strings are instances
|
||
of the type <code>java.lang.String</code> and have all the methods
|
||
defined by that class. JavaScript strings have methods defined by
|
||
<code>String.prototype</code>. The most common stumbling block is
|
||
<code>length</code>, which is a method of Java strings and a dynamic
|
||
property of JavaScript strings:</p>
|
||
|
||
<pre>
|
||
js> javaString = new java.lang.String("Java")
|
||
Java
|
||
js> jsString = "JavaScript"
|
||
JavaScript
|
||
js> javaString.length()
|
||
4
|
||
js> jsString.length
|
||
10
|
||
</pre>
|
||
|
||
<p>Rhino provides some help in reducing the differences between the
|
||
two types. First, you can pass a JavaScript string to a Java method
|
||
that requires a Java string and Rhino will perform the conversion. We
|
||
actually saw this feature in action on the call to the
|
||
<code>java.lang.String</code> constructor in the preceding
|
||
example.</p>
|
||
|
||
<p>Rhino also makes the JavaScript methods available to Java strings
|
||
if the java.lang.String class doesn't already define them. For
|
||
example:</p>
|
||
|
||
<pre>
|
||
js> javaString.match(/a.*/)
|
||
ava
|
||
</pre>
|
||
|
||
<hr align=left size=1 width="33%">
|
||
|
||
<p><a name="ref1">[1]</a>
|
||
The ability to call Java from JavaScript was first implemented as part
|
||
of a Netscape browser technology called
|
||
<em>LiveConnect</em>. However, since that technology also
|
||
encompassed communication with browser plugins, and since the way of
|
||
calling JavaScript from Java in Rhino is entirely different, that term
|
||
won't be used in this paper.</p>
|
||
|
||
<p><a name="ref2">[2]</a>
|
||
For more information on JavaBeans, see <em>Developing Java Beans</em>
|
||
by Robert Englander.</p>
|
||
|
||
</body>
|
||
</html>
|