Channels ▼
RSS

JVM Languages

JSR 223: Scripting for the Java Platform


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.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video