FREE Subscription to Dr. Dobb’s Digest: Same Great Content, New Digital Edition
Site Archive (Complete)
Java
Email
Print
Reprint

add to:
Del.icio.us
Digg
Google
Furl
Slashdot
Y! MyWeb
Blink
TABLE OF CONTENTS
March 08, 2009

JSR 223: Scripting for the Java Platform

(Page 2 of 3)

Calling Scripts From Java

Now that we know that several scripting engines are available, the obvious next thing we would like to do is write a Java program that runs a script written in the language each engine supports. Rather than use the traditional "Hello World", I chose to use the Trabb Pardo-Knuth (TPK) algorithm introduced by Luis Trabb Pardo and Donald Knuth in their 1977 work The Early Development of Programming Languages. The algorithm is as follows:

ask for 11 numbers to be read into a sequence S reverse sequence S for each item in sequence S do an operation if result overflows alert user else print result

TPK.groovy (Listing Three), TPK.py (Listing Four), and TPK.rb (Listing Five) are implementations of the TPK algorithm written in Groovy, Python, and Ruby, respectively.

stdin = new BufferedReader(new InputStreamReader(System.in)) 
stdout = new BufferedWriter(new OutputStreamWriter(System.out))
def numbers = []
println "Enter number at each promnpt:"
for (i in 1..11) {
  println ">"
  x = stdin.readLine()
  numbers.add(Integer.parseInt(x))
}
for (i in numbers.size-1..0) {
try {
println(numbers.get(i)+5*i**3)
}
catch (e) {
println("error")
}
}

Listing Three: Groovy

from math import sqrt
def f(t):
    return sqrt(abs(t))+5*t**3
print "Enter a number at each prompt"
a = [int(raw_input(">")) for i in range(11)]
for i in range(10,-1,-1):
    print "for x = ",i,"y ",
    y = f(a[i])
    if y > 400:
        print "IS TOO LARGE"
    else:
        print "= ", y

Listing Four: Python

nums = Array.new puts "Enter a number at each prompt" for i in 0...11 print ">" nums[i]= gets.to_i end nums.reverse.each do |x| y = Math.sqrt(x.abs) + 5*x**3 puts " for x=#{x} result is #{(y>400) ? ' too large' : y}" end

Listing Five: Ruby

JavaScriptingDemo.java (Listing Six) is a Java program that searches a directory named script for all files whose names are TPK.*; determines if an engine is found for the file's extension and, if an engine is available, invokes the engines eval() method passing as an argument a FileReader that can read a character stream from the file containing the script.

The relevant code is as follows:

String ext = fileName.substring(fileName.lastIndexOf(".")+1); ScriptEngine engine = manager.getEngineByExtension(ext); if (engine != null) { try { ScriptEngineFactory factory = engine.getFactory(); System.out.println("Running " + fileName + " using engine " + factory.getEngineName() + " Version " + factory.getEngineVersion() + " for language " + factory.getLanguageName()); engine.eval(new FileReader(f)); }

/*
 * To change this template, choose Tools | Templates and open the template in the editor.
 */
package ca.tremblett;

import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FilenameFilter; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; import javax.script.ScriptException;

/** * * @author ptremblett */ public class JavaScriptingDemo {

/** * @param args the command line arguments */ public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); File scriptsDir = new File("scripts"); File[] scripts = scriptsDir.listFiles(new TPKFilter()); for (File f : scripts) { String fileName = f.getName(); String ext = fileName.substring(fileName.lastIndexOf(".")+1); ScriptEngine engine = manager.getEngineByExtension(ext); if (engine != null) { try { ScriptEngineFactory factory = engine.getFactory(); System.out.println("Running " + fileName + " using engine " + factory.getEngineName() + " Version " + factory.getEngineVersion() + " for language " + factory.getLanguageName()); engine.eval(new FileReader(f)); } catch (FileNotFoundException ex) { Logger.getLogger(JavaScriptingDemo.class.getName()).log(Level.SEVERE, null, ex); } catch (ScriptException ex) { Logger.getLogger(JavaScriptingDemo.class.getName()).log(Level.SEVERE, null, ex); } finally { System.out.println("============================"); } } else { System.err.println("Could not find scripting engine for " + f.getName()); } } } }

class TPKFilter implements FilenameFilter {

public boolean accept(File dir, String name) { return name.startsWith("TPK."); } }

Listing Six

Running JavaScriptDemo produces the output in Listing Seven. If you run each of the three scripts from the command line, you should see the same output.

Running TPK.groovy using engine groovy Version 1.5.7 for language groovy
Enter number at each promnpt:
>
1
>
2
>
3
>
4
>
5
>
6
>
7
>
8
>
9
>
10
>
11
5011
3655
2569
1723
1087
631
325
139
43
7
1
============================
*sys-package-mgr*: can't create package cache dir, '/Users/ptremblett/NetBeansProjects/JavaScriptingDemo/dist/lib/jython.jar/cachedir/packages'
Running TPK.py using engine jython Version 2.2.1 for language python
Enter a number at each prompt
>1
>2
>3
>4
>5
>6
>7
>8
>9
>10
>11
for x =  10 y  IS TOO LARGE
for x =  9 y  IS TOO LARGE
for x =  8 y  IS TOO LARGE
for x =  7 y  IS TOO LARGE
for x =  6 y  IS TOO LARGE
for x =  5 y  IS TOO LARGE
for x =  4 y  IS TOO LARGE
for x =  3 y  =  322.0
for x =  2 y  =  136.73205080756887
for x =  1 y  =  41.41421356237309
for x =  0 y  =  6.0
============================
Running TPK.rb using engine JRuby Engine Version 1.1.4 for language ruby
Enter a number at each prompt
>1
>2
>3
>4
>5
>6
>7
>8
>9
>10
>11
 for x=11 result is  too large
 for x=10 result is  too large
 for x=9 result is  too large
 for x=8 result is  too large
 for x=7 result is  too large
 for x=6 result is  too large
 for x=5 result is  too large
 for x=4 result is 322.0
 for x=3 result is 136.73205080756887
 for x=2 result is 41.41421356237309
 for x=1 result is 6.0
============================

Listing Seven

Invoking Functions In a Script

Sometimes you don't want to execute an entire script; instead, you want to execute a single function that it includes. The mechanism that provides this capability is the Invocable interface. The specification states that script engines are not required to support this interface. The Mozilla Rhino engine included in Java 6 does support it as does the JRuby engine I am using. Before writing code that invokes functions, you should verify that the engine for the language in which your script is written supports the Invocable interface.

HelloGoodbye.rb (Listing Eight) contains Ruby code that defines three functions: hello, goodbye, and wait.

def hello() puts "Hello world!" end

def goodbye() puts "Goodbye cruel world!" end

def wait(seconds) print "Waiting ",seconds," seconds" sleep(seconds) puts "Finished waiting" end

Listing Eight

InvokeFunctions.java (Listing Nine) is a Java program that executes two of these three functions.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.tremblett;

import java.io.FileNotFoundException; import java.io.FileReader; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; import javax.script.ScriptException;

/** * * @author ptremblett */ public class InvokeFunctions {

/** * @param args the command line arguments */ public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("ruby"); try { engine.eval(new FileReader("scripts/HelloGoodbye.rb")); Invocable inv = (Invocable)engine; try { System.out.println("Invoking hello"); inv.invokeFunction("hello"); System.out.println("Invoking goodbye"); inv.invokeFunction("goodbye"); } catch (NoSuchMethodException ex) { Logger.getLogger(InvokeFunctions.class.getName()).log(Level.SEVERE, null, ex); } } catch (FileNotFoundException ex) { Logger.getLogger(InvokeFunctions.class.getName()).log(Level.SEVERE, null, ex); } catch (ScriptException ex) { Logger.getLogger(InvokeFunctions.class.getName()).log(Level.SEVERE, null, ex); } } }

Listing Nine

The relevant code is as follows:

ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("ruby"); try { engine.eval(new FileReader("scripts/HelloGoodbye.rb")); Invocable inv = (Invocable) engine; try { System.out.println("Invoking hello"); inv.invokeFunction("hello"); System.out.println("Invoking goodbye"); inv.invokeFunction("goodbye"); }

As we did when I ran our three TPK scripts, I invoke engine.eval() but notice that it produces no output. Why not? Because the "execution" consists of simply defining functions. Next, I create an Invocable engine using casting. I then invoke the invocable engines invokeFunction() method passing as a single argument the name of the function we wish to invoke. When I run InvokeFunctions, it displays the following output:

Invoking hello Hello world! Invoking goodbye Goodbye cruel world!

Unlike hello and goodbye, the wait function defined in HelloGoodbye.rb takes an argument. PassArguments.java (Listing Ten) is a program that passes an argument to a function:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.tremblett;

import java.io.FileNotFoundException; import java.io.FileReader; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException;

/** * * @author ptremblett */ public class PassArguments {

/** * @param args the command line arguments */ public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("ruby"); try { engine.eval(new FileReader("scripts/HelloGoodbye.rb")); Invocable inv = (Invocable) engine; try { System.out.println("Invoking hello"); inv.invokeFunction("hello"); System.out.println("invoking wait"); inv.invokeFunction("wait",10); System.out.println("invoking wait again capturing return value"); long waited = ((Long)(inv.invokeFunction("wait", 10))).intValue(); System.out.println("time waited (returned by the function) = " + waited); System.out.println("Invoking goodbye"); inv.invokeFunction("goodbye"); } catch (NoSuchMethodException ex) { Logger.getLogger(InvokeFunctions.class.getName()).log(Level.SEVERE, null, ex); } } catch (FileNotFoundException ex) { Logger.getLogger(InvokeFunctions.class.getName()).log(Level.SEVERE, null, ex); } catch (ScriptException ex) { Logger.getLogger(InvokeFunctions.class.getName()).log(Level.SEVERE, null, ex); } } }

Listing Ten

The only difference between this program and InvokeFunctions.java is the addition of the following code:

System.out.println("invoking wait"); inv.invokeFunction("wait",10); System.out.println("invoking wait again capturing return value"); long waited = ((Long)(inv.invokeFunction("wait", 10))).intValue(); System.out.println("time waited (returned by the function) = " + waited);

Notice that the form of the invokeFunction() method used to call the wait function takes two arguments. The first is the name of the function and the second is the argument to be passed to the function. Also I call the wait function twice. The second call shows that not only can you pass an argument to a function but you can also obtain the value the function returns.

Finally, it is worth reiterating that everything discuss in this section is only possible if the implementation support the Invocable interface.

Previous Page | 1 The Scripting API | 2 Calling Scripts From Java | 3 Sharing Objects Next Page
TOP 5 ARTICLES
No Top Articles.



MICROSITES
FEATURED TOPIC

ADDITIONAL TOPICS

INFO-LINK