Mar 11

Embedding Rhino Part I: Parsing Command Line Arguments in JavaScript

Rhino is a JavaScript/ECMAscript implementation in Java. Using the new java1.6 scripting API you can now easily embed JavaScript into your Java applications. I thought I’d try writing yet another implementation of the net_tool application using Rhino. Originally this was going to be a single article, but it turns out to be a two-fer.

The code for this example is available here.

Getting Rhino


Note: Java comes with the Rhino interpreter packaged in so you can skip this bit if you don’t care about getting the latest version

The first step is getting rhino from the Mozilla foundation. The second is to install it to your maven repository with the following command:

 mvn install:install-file -Dfile=build/rhino1_6R7/js.jar \
        -DgroupId=org.mozilla -DartifactId=rhino \
        -Dpackaging=jar -Dversion=1.6

Writing an Interpreter


The second step is to write an interpreter that will run the script and pass along any command line arguments. This is pretty straight forward using the new scripting API for java:

package com.fourthmouse.ecma;
 
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.io.InputStreamReader;
import java.io.IOException;
 
public class Interpreter{
    public static void main(String[] args) throws ScriptException, 
                                                      IOException{
        ScriptEngineManager manager
            = new ScriptEngineManager();
        ScriptEngine engine
            = manager.getEngineByName("js");
        String line;
 
        engine.put("argv", args);
        engine.eval(new InputStreamReader(System.in));
    }
}

Building the Interpreter

The Maven pom is pretty simple for this one:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.4thmouse</groupId>
  <artifactId>js_example</artifactId>
  <packaging>jar</packaging>
  <version>CURRENT</version>
  <name>javascript example</name>
  <url>http://4thmouse.com</url>
  <dependencies>
    <dependency>
      <groupId>org.mozilla</groupId>
      <artifactId>rhino</artifactId>
      <version>1.6</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <!-- support java 1.5 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Running the interpreter


So now to run the interpreter all you have to do is something like

cat program.js | \
java -cp target/js_example-CURRENT-jar-with-dependencies.jar  \
com.fourthmouse.ecma.Interpreter

Writing the Script


So now that we have an interpreter we need to write the script. One of the deficits of JavaScript is that it doesn’t have any command line parsing libraries because most of the time it doesn’t have any command line.

OptionParser


So this is a fun little opportunity to write a command line argument parser in JavaScript. I like Ruby’s OptionParser so I’m going to implement something similar. So the first surprising and annoying issue: IE has a Dictionary object (i.e. hash map) and ECMAscript does not. So first I’m using java hash map to implement a javascript object that looks something like the IE Dictionary:

//Designed to look like the IE dictionary
function Dictionary(){
    var getmap;
    //using closure to provide a private member variable
    (function() {
        var _map = new java.util.HashMap();
        getmap = function(){
            return _map;
        };
    })();
 
    this.add = function(key, value){
        getmap().put(key, value);
    };
    this.get = function(key){
        return getmap().get(key);
    };
    this.exists = function(key){
        return getmap().keySet().contains(key);
    };
    this.remove = function(key){
        getmap().remove(key);
    };
    this.removeall = function(key){
        getmap().clear();
    };
}

Now that I have something resembling a dictionary, I can write the option parser

//represents a single option definition
function Option(shortname, longname, argument, description, action){
    this.shortname = shortname;
    this.longname = longname;
    this.argument = argument;
    this.description = description;
    this.action = action;
    this.requires_argument = function(){
        return this.argument != null;
    };
}
 
//parser for parsing command line options
function OptionParser(help_message){
    var getshorttooptionmap,
        getlongtooptionmap;
 
    this.options = [];
    this.help_message = help_message;
 
    (function() {
        var shorttooptionmap = new Dictionary();
        var longtooptionmap = new Dictionary();
 
        getshorttooptionmap = function(){
            return shorttooptionmap;
        }
 
        getlongtooptionmap = function(){
            return longtooptionmap;
        }
    })();
 
    this.addoption = function(shortname, longname, 
                              argument, description, action){
        option = new Option(shortname, longname, argument, 
                            description, action);
        getshorttooptionmap().add(shortname, option);
        getlongtooptionmap().add(longname, option);
        this.options.push(option);
    }
 
    this.addoptionflag = function(shortname, longname, 
                                  description, action){
        this.addoption(shortname, longname, 
                       null, description, action);
    }
 
 
    this.parse = function(argv){
        var option;
        for(i = 0; i < argv.length; i++){
            if(argv[i].substr(0,2) == "--"){
                option = getlongtooptionmap().get(argv[i].substr(2));
            }else if(argv[i].substr(0,1) == "-"){
                option = getshorttooptionmap().get(argv[i].substr(1));
            }else{
                throw "Was expecting option. Got " + argv[i];
            }
 
            if(option == undefined){
                throw "Undefined option: " + argv[i];
            }
 
            if(option.requires_argument()){
                if(i == argv.length - 1){
                    throw "option " + argv[i] + " requires argument";
                }
                arg = argv[i + 1];
                i++;
                option.action(arg);
            }else{
                option.action();
            }
        }
    }
 
    this.help = function(){
        ret = "";
        ret = ret + help_message + "\n";
        for(i=0; i< this.options.length; i++){
            ret = ret + "\t-" + this.options[i].shortname;
            ret = ret + " --" + this.options[i].longname;
            if(this.options[i].argument != null){
                ret = ret + " [" + this.options[i].argument + "]";
            }
            ret = ret + " " + this.options[i].description + "\n";
        }
        return ret;
    }
}

Parsing the Command Line Options


Using our new library parsing the command line options is now a piece of cake:

optionparser = new OptionParser("net_tool.js [options]");
var options = {};
 
optionparser.addoptionflag("c", "connect", 
                           "connect to the host", function(){
    options.connect = true;
});
optionparser.addoptionflag("l", "listen", 
                           "listen for remote host", function(){
    options.connect = false;
});
optionparser.addoption("p", "port", "PORT", 
                       "TCPIP connection port", function(arg){
    options.port = arg;
});
optionparser.addoption("r", "remote-host", 
                       "HOSTNAME", "Hostname to connect to", function(arg){
    options.hostname = arg;
});
optionparser.addoptionflag("h", "help", "print this message", function(){
    print(optionparser.help());
    java.lang.System.exit(0);
});
 
try{
    optionparser.parse(argv);
}catch(e){
    print(e);
    print("\n");
    print(optionparser.help());
    java.lang.System.exit(1);
}
 
if(options.connect == undefined){
    print("Must specify connect or listen\n");
    print(optionparser.help());
    java.lang.System.exit(1);
}
 
if(options.port == undefined){
    print("Must specify a port\n");
    print(optionparser.help());
    java.lang.System.exit(1);
}
 
if(options.connect && options.hostname == undefined){
    print("In connect mode you must specify a hostname\n");
    print(optionparser.help());
    java.lang.System.exit(1);
}

First Impressions


I have to admit being less impressed with ECMAScript now that I was last time I played with it. There are a combination of things: The anonymous function object to define “private” methods and variables, the mandatory use of the this keyword all over the place. Although this didn’t come up here, javascript copies the C/C++ switch statement which in my opinion is a cardinal sin in any new programming language. C# does it much better and any other language that simply copies the stupid C style switch statements out of inertia is suspect in my book.


Anyway as a result of all of this, if I’m going to embed a language it will probably be Ruby instead.

The Author

Michael Smit is a software engineer in Seattle, Washington who works for amazon

Comments are off for this post

Comments are closed.