Teach ZK a New Language

From Documentation
Teach ZK a New Language

Author
Tom M. Yeh, Potix Corporation
Date
February 15, 2007
Version
Applicable to ZK 2.3 RC and later.


Introduction

Since 2.3, ZK allows developers to write the scripting codes in their favorite scripting language, not just Java. The builtin support includes Java (BeanShell), JavaScript (Rhino), Ruby (JRuby) and Groovy. In this article I'd like to show you how to add a new language, or, more precisely, a new interpreter.

It takes two steps to add a new interpreter:

1. Implement the org.zkoss.zk.scripting.Interpreter interface for the scripting engine you'd like.

2. Register the interpreter.


Implement the Interpreter

Each interpreter must implement the org.zkoss.zk.scripting.Interpreter interface. Instead of implementing this interface from scratch, you can extend from the org.zkoss.zk.scripting.util.GenericInterpreter class.

When extending from GenericInterpreter, you have to implement the following methods.

/** Initializes the interpreter.
 * It is called once when the new instance of interpreter is constructed.
 */
public void init(Page owner, String zslang);
/** Called to cleanup resources, when the interpreter is about to be destroyed.
 * It is optional since most interpreters don't need to cleanup.
 */
public void destroy();

/** Executes the specified script.
 */
protected void exec(String script);
/** Gets the variable from the interpreter.
 * Optional. Implement it if you want to expose variables defined
 * in the interpreter to Java codes.
 */
protected Object get(String name);
/** Sets the variable from the interpreter.
 * Optional. Implement it if you want to allow Java codes to define
 * a variable in the interpreter.
 */
protected void set(String name, Object value);
/** Removes the variable from the interpreter.
 * Optional. Implement it if you want to allow Java codes to undefine
 * a variable from the interpreter.
 */
protected void unset(String name);


The implementation depends on the scripting engine. If the scripting engine supports BSF (Bean Scripting Framework), reading the source codes of the BSF engine is a good starting point to implement the above methods.

Here I'll use Groovy (org.zkoss.zk.scripting.groovy.GroovyInterpreter) as an example.


The init method

    import groovy.lang.Binding;
    import groovy.lang.GroovyShell;
    ...
    public void init(Page owner, String zslang) {
      super.init(owner, zslang);

      _global = new Binding(new Variables());
      _ip = new GroovyShell(_global);
    }

The interpreter initializes the scripting engine in this method. It is straightforward except we have to find a way to let Groovy look for variables we define in the namespaces (org.zkoss.zk.ui.Namespace). In this example, we implement the Variables class to search variables defined in the namespaces -- in addition to global variables.

Different scripting engines use different kinds of maps to store global variables. For example, Groovy uses java.util.HashMap and Rhino org.mozilla.javascript.ImporterTopLevel.

Before going on the implementation of Variables, let me explain the relation between namespaces and interpreters. This is the most difficult part, though it is actually straightforward if you understand the relation.


Namespaces and Interpreters
A namespace is associated with one ID space (org.zkoss.zk.ui.IdSpace). It is hierarchical. On the other hand, the interpret has its own map of global variables. ZK assumes only one map of global variables per interpreter, though it is OK to have hierarchical maps of global variables. Since components are added to the namespaces dynamically, the interpreter has to search the right namespace, in addition to its own map of global variables, when resolving a variable.
For example, if there is a component called win, the interpreter must return a reference to the component when it encounter a (unresolved) variable called win. By unresolved we mean it is not part of the map of global variables.


Implement Variables
Searching variables defined in namespaces can be done easily by invoking the getFromNamespace method of GenericInterpreter. What you need to do is to find what kind of map of global variables is used in the scripting engine you are targeting. For Groovy, it is HashMap.
	//An inner class of GroovyInterpreter
	private class Variables extends HashMap {
	  public Object get(Object key) {
		Object val = super.get(key);
		if (val != null || containsKey(key) || !(key instanceof String))
		  return val;
		return getFromNamespace((String)key); //provided by GenericInterpreter
	  }
	}
Historical Note: Before 2.3, namespaces are tightly coupled with interpreters. However, it is changed, because not all interpreters support hierarchical map of global variables. It is too costly to implement namespaces on top of some interpreters.


The exec method

protected void exec(String script) {
  _ip.evaluate(script);
}

Straightforward, isn't it?


The get method

protected Object get(String name) { //GroovyInterpreter
  return _global.getVariable(name);
}

It is straightforward for Groovy since it uses the same type as Java. For most scripting engines, you usually have to convert the types. For example, the following is JRubyInterpreter's implementation.

protected Object get(String name) { //JRubyInterpreter
  IRubyObject ro = _runtime.getGlobalVariables().get(
	GlobalVariable.variableName(name));
  return rubyToJava(ro);
}

Tip: the get, set and unset methods are optional. Implementing them only if you want Java codes able to access variables defined in the interpreter.

Tip: the source codes of the BSF engine of your targeted scripting engine is the best reference to know how to convert them.


Register the interpreter

There are several ways to register the interpreter.

  • /metainfo/zk/config.xml
Add the following content to /metainfo/zk/config.xml to the jar file containing the interpreter, if you want the interpreter to be registered automatically, when the jar file is added the classpath.
    <config>
      <zscript-config>
        <language-name>Groovy</language-name>
        <interpreter-class>org.zkoss.zk.scripting.groovy.GroovyInterpreter</interpreter-class>
      </zscript-config>
    </config>
  • /WEB-INF/zk.xml
If the interpreter is part of your Web application, you can add the following content to /WEB-INF/zk.xml.
    <zscript-config>
      <language-name>Groovy</language-name>
      <interpreter-class>org.zkoss.zk.scripting.groovy.GroovyInterpreter</interpreter-class>
    </zscript-config>

More Methods to Implement

If you want the application (Java codes) able to access methods defined in the interpreter, you have to implement the getMethod method. Similarly, you have to implement the getClass if you want to expose classes defined in the interpreter.

Note: these are the underlying methods for the getZScriptFunction and getZScriptClass (of Component and Page).




Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.