hengheng123456789

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  297 Posts :: 68 Stories :: 144 Comments :: 0 Trackbacks
 

Rhino Beginning

 

1        General

1.1   Overview

 

Overview of Rhino

Most people who have used JavaScript before have done so by adding scripts to their HTML web pages. However, Rhino is an implementation of the core language only and doesn't contain objects or methods for manipulating HTML documents.

Rhino contains

  • All the features of JavaScript 1.5
  • Allows direct scripting of Java
  • A JavaScript shell for executing JavaScript scripts
  • A JavaScript compiler to transform JavaScript source files into Java class files

Language

The JavaScript language itself is standardized by Standard ECMA-262 ECMAScript: A general purpose, cross-platform programming language. Rhino 1.5 implements JavaScript 1.5, which conforms to Edition 3 of the Standard. The Standard may be downloaded or obtained by mail from

ECMA,
114 Rue du Rhône,
CH1204 Geneva,
Switzerland
.

Rhino 1.6 also implements ECMA-357 ECMAScript for XML (E4X). See the specification for more information on the standard, and Rhino version 1.6R1 for details on the implementation in Rhino.

In addition, Rhino has implemented JavaAdapters, which allows JavaScript to implement any Java interface or extend any Java class with a JavaScript object. See the enum.js example for more information.

Numerous books and tutorials on JavaScript are available.

Internationalization

The messages reported by the JavaScript engine are by default retrieved from the property file org/mozilla/javascript/resources/Messages.properties. If other properties files with extensions corresponding to the current locale exist, they will be used instead.

JavaScript Language Versions

Some behavior in the JavaScript engine is dependent on the language version. In browser embeddings, this language version is selected using the LANGUAGE attribute of the SCRIPT tag with values such as "JavaScript1.2".

Version 1.3 and greater are ECMA conformant.

Operators == and !=
Version 1.2 only uses strict equality for the == and != operators. In version 1.3 and greater, == and != have the same meanings as ECMA. The operators === and !== use strict equality in all versions.

ToBoolean
Boolean(new Boolean(false)) is false for all versions before 1.3. It is true (and thus ECMA conformant) for version 1.3 and greater.

Array.prototype.toString and Object.prototype.toString
Version 1.2 only returns array or object literal notation ("[1,2,3]" or "{a:1, b:2}" for example). In version 1.3 and greater these functions are ECMA conformant.

Array constructor
Array(i) for a number argument i constructs an array with a single element equal to i for version 1.2 only. Otherwise the ECMA conformant version is used (an array is constructed with no elements but with length property equal to i).

String.prototype.substring
For version 1.2 only, the two arguments are not swapped if the first argument is less than the second one. All other versions are ECMA compliant.

String.prototype.split
For version 1.2 only, split performs the Perl4 special case when given a single space character as an argument (skips leading whitespace, and splits on whitespace). All other versions split on the space character proper as specified by ECMA.

Security

The security features in Rhino provide the ability to track the origin of a piece of code (and any pieces of code that it may in turn generate). These features allow for the implementation of a traditional URL-based security policy for JavaScript as in Netscape Navigator. Embeddings that trust the JavaScript code they execute may ignore the security features.

Embeddings that run untrusted JavaScript code must do two things to enable the security features. First, every Context that is created must be supplied an instance of an object that implements the SecuritySupport interface. This will provide Rhino the support functionality it needs to perform security-related tasks.

Second, the value of the property security.requireSecurityDomain should be changed to true in the resource bundle org.mozilla.javascript.resources.Security. The value of this property can be determined at runtime by calling the isSecurityDomainRequired method of Context. Setting this property to true requires that any calls that compile or evaluate JavaScript must supply a security domain object of any object type that will be used to identify JavaScript code. In a typical client embedding, this object might be a string with the URL of the server that supplied the script, or an object that contains a representation of the signers of a piece of code for certificate-based security policies.

When JavaScript code attempts a restricted action, the security domain can be retrieved in the following manner. The class context should be obtained from the security manager (see java.lang.SecurityManager.getClassContext()). Then, the class of the code that called to request the restricted action can be obtained by looking an appropriate index into the class context array. If the caller is JavaScript the class obtained may be one of two types. First, it may be the class of the interpreter if interpretive mode is in effect. Second, it may be a generated class if classfile generation is supported. An embedding can distinguish the two cases by calling isInterpreterClass() in the Context class. If it is the interpreter class, call the getInterpreterSecurityDomain() method of Context to obtain the security domain of the currently executing interpreted script or function. Otherwise, it must be a generated class, and an embedding can call getSecurityDomain() in the class implementing SecuritySupport. When the class was defined and loaded, the appropriate security domain was associated with it, and can be retrieved by calling this method. Once the security domain has been determined, an embedding can perform whatever checks are appropriate to determine whether access should be allowed.


 

1.2   Requirements and Limitations

 

Requirements

Rhino requires version 1.1 or greater of Java.

To use the JavaAdapter feature or an optimization level of 0 or greater, Rhino must be running under a security manager that allows the definition of class loaders.

Limitations

Platforms and JITs

Many platforms and JREs have problems converting decimal numbers to and from strings. These errors are usually boundary case errors and will show up as test failures in section 7.7.3.

Windows versions of the Symantec JIT prior to 3.00.029(i) will report internal errors for some generated class files.

On the Symantec JIT and the AIX JVM, accessing a static field of a class that has not yet loaded may not give the correct value of the field. For example, accessing java.io.File.separatorChar before java.io.File has been loaded will return a value of 0. (This is a bug in the JIT; accessing the field should cause the class to be loaded.)

The AIX Java version "JDK 1.1.6 IBM build a116-19980924 (JIT enabled: jitc)" core dumps running several classes generated by Rhino. It also has errors in java.lang.Math.pow that are reflected as failures in the JavaScript Math object's pow method.

IBM Java for Linux version "JDK 1.1.8 IBM build l118-19991013 (JIT enabled: jitc)" has errors in java.lang.Math.pow that are reflected as test failures in the JavaScript Math object's pow method.

Solaris JDK 1.1.6 has errors in java.lang.Math.atan2 that are reflected as test failures in the JavaScript Math object's atan2 method.

LiveConnect

If a JavaObject's field's name collides with that of a method, the value of that field is retrieved lazily, and can be counter-intuitively affected by later assignments:

javaObj.fieldAndMethod = 5;

var field = javaObj.fieldAndMethod;

javaObj.fieldAndMethod = 7;

// now, field == 7

You can work around this by forcing the field value to be converted to a JavaScript type when you take its value:

javaObj.fieldAndMethod = 5;

var field = javaObj.fieldAndMethod + 0; // force conversion now

javaObj.fieldAndMethod = 7;

// now, field == 5

JSObject

Rhino does NOT support the netscape.javascript.JSObject class.

Date object

The JavaScript Date object depends on time facilities of the underlying Java runtime to determine daylight savings time dates. Earlier JRE versions may report a date for the daylight savings changeover that is a week off. JRE 1.1.6 reports the correct date.

Under the 1.1.6 JRE, evaluating (new Date(1998, 9, 25, 2)).toString() returns:

        Sun Oct 25 02:00:00 GMT-0800 (PST) 1998

Earlier versions may return:

        Sun Oct 25 02:00:00 GMT-0700 (PDT) 1998

(the JRE doesn't report the changeover until Nov. 1.)

The Microsoft SDK 3.1 for Java also exhibits this problem.


 

1.3   FAQ

 

How do I create a Java array from JavaScript?

You must use Java reflection. For instance, to create an array of java.lang.String of length five, do

var stringArray = java.lang.reflect.Array.newInstance(java.lang.String, 5);

Then if you wish to assign the string "hi" to the first element, simply execute stringArray[0] = "hi".

Creating arrays of primitive types is slightly different: you must use the TYPE field. For example, creating an array of seven ints can be done with the code

var intArray = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE, 7);

 

When I try to execute a script I get the exception Required security context missing. What's going on?

You've likely missed placing the Security.properties file in your class path at org.mozilla.javascript.resources.

 

 

 


2        Writing Scripts

2.1   Scripting Java

 

This paper shows how to use Rhino to reach beyond JavaScript into Java [1]. 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 exploratory programming. 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.

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.

Accessing Java packages and classes

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?

Rhino defines a top-level variable named Packages. The properties of the Packages variable are all the top-level Java packages, such as java and com. For example, we can access the value of the javapackage:

js> Packages.java

[JavaPackage java]

As a handy shortcut, Rhino defines a top-level variable java that is equivalent to Packages.java. So the previous example could be even shorter:

js> java

[JavaPackage java]

We can access Java classes simply by stepping down the package hierarchy:

js> java.io.File

[JavaClass java.io.File]

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 importPackage that serves the same purpose as Java's import declaration. For example, we could import all of the classes in the java.io package and access class java.io.File using just the name File:

js> importPackage(java.io)

js> File

[JavaClass java.io.File]

Here importPackage(java.io) makes all the classes in the java.io package (such as File) available at the top level. It's equivalent in effect to the Java declaration import java.io.*;.

It's important to note that Java imports java.lang.* implicitly, while Rhino does not. The reason is that JavaScript has its own top-level objects Boolean, Math, Number, Object, and String that are different from the classes by those names defined in the java.lang package. Because of this conflict, it's a good idea not to use importPackage on the java.lang package.

One thing to be careful of is Rhino's handling of errors in specifying Java package or class names. If java.MyClass is accessed, Rhino attempts to load a class named java.MyClass. If that load fails, it assumes that java.MyClass is a package name, and no error is reported:

js> java.MyClass

[JavaPackage java.MyClass]

Only if you attempt to use this object as a class will an error be reported.

Working with Java objects

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 new operator:

js> new java.util.Date()

Thu Jan 24 16:18:17 EST 2002

If we store the new object in a JavaScript variable, we can then call methods on it:

js> f = new java.io.File("test.txt")

test.txt

js> f.exists()

true

js> f.getName()

test.txt

Static methods and fields can be accessed from the class object itself:

js> java.lang.Math.PI

3.141592653589793

js> java.lang.Math.cos(0)

1

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:

js> f.listFiles

function listFiles() {/*

java.io.File[] listFiles()

java.io.File[] listFiles(java.io.FilenameFilter)

java.io.File[] listFiles(java.io.FileFilter)

*/}

This output shows that the File class defines three overloaded methods listFiles: one that takes no arguments, another with a FilenameFilter argument, and a third with a FileFilter argument. All the methods return an array of File 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.

Another useful feature for exploratory programming is the ability to see all the methods and fields defined for an object. Using the JavaScript for..in construct, we can print out all these values:

js> for (i in f) { print(i) }

exists

parentFile

mkdir

toString

wait

[44 others]

Note that not only the methods of the File class are listed, but also the methods inherited from the base class java.lang.Object (like wait). 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.

Rhino provides another convenience by allowing properties of JavaBeans to be accessed directly by their property names. A JavaBean property foo is defined by the methods getFoo and setFoo. Additionally, a boolean property of the same name can be defined by an isFoo method [2]. For example, the following code actually calls the File object's getName and isDirectory methods.

js> f.name

test.txt

js> f.directory

false

Calling overloaded methods

The process of choosing a method to call based upon the types of the arguments is called overload resolution. 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.

As an example, consider the following Java class that defines a number of overloaded methods and calls them.

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]));

    }

}

When we compile and execute the program, it produces the output

f(Object)

f(Object)

f(Object)

However, if we write a similar script

var o = new Packages.Overload();

var a = [ 3, "hi", Packages.Overload ];

for (var i = 0; i != a.length; ++i)

    print(o.f(a[i]));

and execute it, we get the output

f(int)

f(String)

f(Object)

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.

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.

Because overload resolution occurs at runtime, it can fail at runtime. For example, if we call Overload's method g with two integers we get an error because neither form of the method is closer to the argument types than the other:

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)

A more precise definition of overloading semantics can be found at http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html.

Implementing Java interfaces

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 Thread class: its constructor takes a Runnable that contains a single method run that will be called when the new thread is started.

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 Runnable, we need only define a single method run with no parameters. If you remember from Chapter 3, it is possible to define a JavaScript object with the {propertyName: value} notation. We can use that syntax here in combination with a function expression to define a JavaScript object with a run method:

js> obj = { run: function () { print("\nrunning"); } }

[object Object]

js> obj.run()

 

running

Now we can create an object implementing the Runnable interface by constructing a Runnable:

js> r = new java.lang.Runnable(obj);

[object JavaObject]

In Java it is not possible to use the new operator on an interface because there is no implementation available. Here Rhino gets the implementation from the JavaScript object obj. Now that we have an object implementing Runnable, we can create a Thread and run it. The function we defined for run will be called on a new thread.

js> t = new java.lang.Thread(r)

Thread[Thread-2,5,main]

js> t.start()

js>

 

running

The final js prompt and the output from the new thread may appear in either order, depending on thread scheduling.

Behind the scenes, Rhino generates the bytecode for a new Java class that implements Runnable and forwards all calls to its run method over to an associated JavaScript object. The object that implements this class is called a Java adapter. 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.

The JavaAdapter constructor

In the previous section we created Java adapters using the new 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 JavaAdapter constructor.

The syntax of the JavaAdapter constructor is

new JavaAdapter(javaIntfOrClass, [javaIntf, ..., javaIntf,] javascriptObject)

Here javaIntfOrClass is an interface to implement or a class to extend and javaIntf are aditional interfaces to implement. The javascriptObject is the JavaScript object containing the methods that will be called from the Java adapter.

In practice there's little need to call the JavaAdapter constructor directly. Most of the time the previous syntaxes using the new operator will be sufficient.

JavaScript functions as Java interfaces

Often we need to implement an interface with only one method, like in the previous Runnable 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.

Here is the simplified Runnable example:

js> t = java.lang.Thread(function () { print("\nrunning"); });

Thread[Thread-0,5,main]

js> t.start()

js>

running

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:

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()...

Creating Java arrays

Rhino provides no special syntax for creating Java arrays. You must use the java.lang.reflect.Array class for this purpose. To create an array of five Java strings you would make the following call:

js> a = java.lang.reflect.Array.newInstance(java.lang.String, 5);

[Ljava.lang.String;@7ffe01

To create an array of primitive types, we must use the special TYPE field defined in the associated object class in the java.lang package. For example, to create an array of bytes, we must use the special field java.lang.Byte.TYPE:

js> a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);

[C@7a84e4

The resulting value can then be used anywhere a Java array of that type is allowed.

js> a[0] = 104

104

js> a[1] = 105

105

js> new java.lang.String(a)

hi

Java strings and JavaScript strings

It's important to keep in mind that Java strings and JavaScript strings are not the same. Java strings are instances of the type java.lang.String and have all the methods defined by that class. JavaScript strings have methods defined by String.prototype. The most common stumbling block is length, which is a method of Java strings and a dynamic property of JavaScript strings:

js> javaString = new java.lang.String("Java")

Java

js> jsString = "JavaScript"

JavaScript

js> javaString.length()

4

js> jsString.length

10

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 java.lang.String constructor in the preceding example.

Rhino also makes the JavaScript methods available to Java strings if the java.lang.String class doesn't already define them. For example:

js> javaString.match(/a.*/)

ava


[1] The ability to call Java from JavaScript was first implemented as part of a Netscape browser technology called LiveConnect. 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.

[2] For more information on JavaBeans, see Developing Java Beans by Robert Englander.

 

2.2   Performance Hints

 

var Statements

Use var statements when possible. Not only is it good programming practice, it can speed up your code by allowing the compiler to generate special code to access the variables. For example, you could rewrite

function sum(a) {

    result = 0;

    for (i=0; i < a.length; i++)

        result += a[i];

    return result;

}

as

function sum(a) {

    var result = 0;

    for (var i=0; i < a.length; i++)

        result += a[i];

    return result;

}

This is not equivalent code because the second version does not modify global variables result and i. However, if you don't intend for any other function to access these variables, then storing them globally is probably wrong anyway (what if you called another function that had a loop like the one in sum!).

Arrays

Use the forms of the Array constructor that specify a size or take a list of initial elements. For example, the code

var a = new Array();

for (var i=0; i < n; i++)

    a[i] = i;

could be sped up by changing the constructor call to new Array(n). A constructor call like that indicates to the runtime that a Java array should be used for the first n entries of the array. Similarly,

new Array("a", "b", "c")
or
["a", "b", "c"]

will cause a 3-element Java array to be allocated to hold the contents of the JavaScript array.

eval and new Function

Avoid calling eval when possible. Calls to eval are slow because the script being executed must be compiled. Constructing a new function object can be slow for the same reason, while function expressions are more efficient because the function can be compiled. For example, the code

function MyObject(a) {

    this.s = a;

    this.toString = new Function("return this.s");

}

could be written more efficiently as

function MyObject(a) {

    this.s = a;

    this.toString = function () { return this.s }

}

Beginning with Rhino 1.4 Release 2, code passed to eval and new Function will be interpreted rather than compiled to class files.

with

Using the with statement prevents the compiler from generating code for fast access to local variables. You're probably better off explicitly accessing any properties of the object.


 

 


3        Embedding Rhino

3.1   Embedding tutorial

 

RunScript: A simple embedding

About the simplest embedding of Rhino possible is the RunScript example. All it does it read a script from the command line, execute it, and print a result.

Here's an example use of RunScript from a shell command line:

$ java RunScript "Math.cos(Math.PI)"

-1

$ java RunScript 'function f(x){return x+1} f(7)'

8

Note that you'll have to have both the Rhino classes and the RunScript example class file in the classpath. Let's step through the body of main one line at time.

Entering a Context

The code

Context cx = Context.enter();

Creates and enters a Context. A Context stores information about the execution environment of a script.

Initializing standard objects

The code

Scriptable scope = cx.initStandardObjects();

Initializes the standard objects (Object, Function, etc.). This must be done before scripts can be executed. The null parameter tells initStandardObjects to create and return a scope object that we use in later calls.

Collecting the arguments

This code is standard Java and not specific to Rhino. It just collects all the arguments and concatenates them together.

String s = "";

for (int i=0; i < args.length; i++) {

    s += args[i];

}

Evaluating a script

The code

Object result = cx.evaluateString(scope, s, "<cmd>", 1, null);

uses the Context cx to evaluate a string. Evaluation of the script looks up variables in scope, and errors will be reported with the filename <cmd> and line number 1.

Print the result

The code

System.out.println(cx.toString(result));

prints the result of evaluating the script (contained in the variable result). result could be a string, JavaScript object, or other values. The toString method converts any JavaScript value to a string.

Exit the Context

The code

} finally {

    Context.exit();

}

exits the Context. This removes the association between the Context and the current thread and is an essential cleanup action. There should be a call to exit for every call to enter. To make sure that it is called even if an exception is thrown, it is put into the finally block corresponding to the try block starting after Context.enter().

Expose Java APIs

Use Java APIs

No additional code in the embedding needed! The JavaScript feature called LiveConnect allows JavaScript programs to interact with Java objects:

$ java RunScript 'java.lang.System.out.println(3)'

3.0

undefined

Implementing interfaces

Using Rhino, JavaScript objects can implement arbitrary Java interfaces. There's no Java code to write -- it's part of Rhino's LiveConnect implementation. For example, we can see how to implement java.lang.Runnable in a Rhino shell session:

js> obj = { run: function() { print('hi'); } }

[object Object]

js> obj.run()

hi

js> r = new java.lang.Runnable(obj);

[object Object]

js> t = new java.lang.Thread(r)

Thread[Thread-0,5,main]

js> t.start()

hi

Add Java objects

The next example is RunScript2. This is the same as RunScript, but with the addition of two extra lines of code:

Object wrappedOut = Context.javaToJS(System.out, scope);

ScriptableObject.putProperty(scope, "out", wrappedOut);

These lines add a global variable out that is a JavaScript reflection of the System.out variable:

$ java RunScript2 'out.println(42)'

42.0

undefined

Using JavaScript objects from Java

After evaluating a script it's possible to query the scope for variables and functions, extracting values and calling JavaScript functions. This is illustrated in the RunScript3 example. This example adds the ability to print the value of variable x and the result of calling function f. Both x and f are expected to be defined by the evaluated script. For example,

$ java RunScript3 'x = 7'

x = 7

f is undefined or not a function.

$ java RunScript3 'function f(a) { return a; }'

x is not defined.

f('my args') = my arg

Using JavaScript variables

To print out the value of x, we add the following code.

Object x = scope.get("x", scope);

if (x == Scriptable.NOT_FOUND) {

    System.out.println("x is not defined.");

} else {

    System.out.println("x = " + Context.toString(x));

}

Calling JavaScript functions

To get the function f, call it, and print the result, we add this code:

Object fObj = scope.get("f", scope);

if (!(fObj instanceof Function)) {

    System.out.println("f is undefined or not a function.");

} else {

    Object functionArgs[] = { "my arg" };

    Function f = (Function)fObj;

    Object result = f.call(cx, scope, scope, functionArgs);

    String report = "f('my args') = " + Context.toString(result);

    System.out.println(report);

}

JavaScript host objects

Defining Host Objects

Custom host objects can implement special JavaScript features like dynamic properties.

Counter example

The Counter example is a simple host object. We'll go through it method by method below.

It's easy to try out new host object classes in the shell using its built-in defineClass function. We'll see how to add it to RunScript later. (Note that because the java -jar option preempts the rest of the classpath, we can't use that and access the Counter class.)

$ java -cp 'js.jar;examples' org.mozilla.javascript.tools.shell.Main

js> defineClass("Counter")

js> c = new Counter(7)

[object Counter]

js> c.count

7

js> c.count

8

js> c.count

9

js> c.resetCount()

js> c.count

0

Counter's constructors

The zero-argument constructor is used by Rhino runtime to create instances. For the counter example, no initialization work is needed, so the implementation is empty.

public Counter () { }

The method jsConstructor defines the JavaScript constructor that was called with the expression new Counter(7) in the JavaScript code above.

public void jsConstructor(int a) { count

= a; }

Class name

The class name is defined by the getClassName method. This is used to determine the name of the constructor.

public String getClassName() { return "Counter";

}

Dynamic properties

Dynamic properties are defined by methods beginning with jsGet_ or jsSet_. The method jsGet_count defines the count property.

public int jsGet_count() { return count++;

}

The expression c.count in the JavaScript code above results in a call to this method.

Defining JavaScript "methods"

Methods can be defined using the jsFunction_ prefix. Here we define resetCount for JavaScript.

public void jsFunction_resetCount() { count

= 0; }

The call c.resetCount() above calls this method.

Adding Counter to RunScript

Now take a look at the RunScript4 example. It's the same as RunScript except for two additions. The method ScriptableObject.defineClass uses a Java class to define the Counter "class" in the top-level scope:

ScriptableObject.defineClass(scope, Counter.class);

Now we can reference the Counter object from our script:

$ java RunScript4 'c = new Counter(3); c.count;

c.count;'

It also creates a new instance of the Counter object from within our Java code, constructing it with the value 7, and assigning it to the top-level variable myCounter:

Object[] arg = { new Integer(7) };

Scriptable myCounter = cx.newObject(scope, "Counter", arg);

scope.put("myCounter", scope, myCounter);

Now we can reference the myCounter object from our script:

$ java RunScript3 'RunScript4 'myCounter.count; myCounter.count'

8


 

 

3.2   Examples

posted on 2007-06-28 18:13 哼哼 阅读(1342) 评论(0)  编辑  收藏 所属分类: JAVA-Common

只有注册用户登录后才能发表评论。


网站导航: