Using Java 6 Scripting


Beginning in Java 6, the Java language has standard support for scripting. This has been utilize in SwiftVis to give you greater flexibility without having to write components in Java. In Java 6 the JavaScript language (officially called ECMAScript) is included by default when you install Java. This is supported through the Rhino implementation from Mozilla. Follow this link for a tutorial on JavaScript if you don't know it yet, but would be interested in trying it. There are also a number of other scripting languages that users can set up to work with as well. This tutorial begins by showing you how to add support for other scripting languages before going through examples of the different elements you can use scripting with. It is worth noting that doing things through scripting is likely to be slower and to use more memory than would be the case with standard SwiftVis components, but for some tasks this tradeoff can be well worth it.

New scripting languages can be added to SwiftVis through the options by adding to the class path. Normally this is done by adding JAR files. The Options dialog box has a tab for the Class Path as is shown in the image below. The user can click the add button and select a directory or one or more JAR files to add into the path the Java uses when it is looking for classes to load. These are saved in a separate file in the SwiftVis options directory. The figure below shows a setup in which JAR files have been added for Jython (a Python implementation), Jaskell (a Haskell implementation), and JRuby. Information and downloads on scripting languages for Java 6 can be found here. Users need to add at least two JAR files for each scripting language from this page. The download that that page has one JAR file for each language that provides the boilerplate so that the language can be used in the standard Java scripting framework. There is also a link to the homepage for each language where users will have to download the actual implementation of the scripting language.

Making a tool that can work with any language has some challenges to it. Most significant among these is getting data from SwiftVis into the script and pulling data produced by the script back into SwiftVis. SwiftVis includes a source, a filter, and a plotting style that use the general scripting capabilities. Each of these registers a number of different variables with the scripting language that the user can reference in the script. They vary by the type of the graph element and will be described in detail below. The greater challenge is actually pulling the data back out of the script environment. To make this happen, users simply have to store the values they want into the proper variable names. Generally the variables p and v should be set up as something like a 2-D array representing all of the parameters and values. The Java code has been written to handle the arrays in JavaScript and any structures that are Iterable. The normal arrays in Jython are Iterable. If you write code for a filter or a source in a scripting language other JavaScript or Jython and run into problems getting your data back into SwiftVis, please contact us so we can try to find a workaround if the arrays aren't Iterable.

The tab in the options dialog for scripts will allow you to define functions or classes that you might find helpful. Code written there for each language will be called any time that a script is run in the given language. The code written here is also saved in the SwiftVis directory and each engine gets a separate file. These can be edited with any normal text editor when you aren't running SwiftVis and the resulting scripts will be loaded the next time you run SwiftVis.

There are three SwiftVis elements that utilize the general scripting capabilities. The ScriptSource allows you to write code that reads in from a file. The GUI interface is shown in the figure below. At the top it has a drop box that allows you to pick the language you want to use with a large text box under this where you can type in your script. The bottom part of this element allows you to specify multiple files. These are passed into the script code in the variable files which is of the Java type ArrayList<File>. This same information cane be reached through filesArray which is an array of File. You can use the normal file handling capabilities of the scripting language to open, read, and close the files.

This figure shows a sample script that doesn't actually read in from a file, but instead simply sets up data in a JavaScript script. This illustrates how data is returned to the main environment. The script source will look for and try to interpret four variables that the script can set up. These are v, p, valueNames, and paramNames. Both p and v can be 2-D arrays of numbers. As you see here, p is given a value of a 1-D array. This is intepreted such that the integers are used as p[0] of the various elements. In the case of the values, v is 2-D so the first item in it is taken to contain the array of values for that element. The arrays valueNames and paramNames are 1-D arrays of strings that give the names that should be attached to the different parameters and values from this source. The figure below shows the output that is produced by this sample code. If neither p nor v is defined in the script engine when execution finishes, SwiftVis will try to interpret the return value of the script as v. The return value is the value of the last statement executed in the script. In some scripting languages the variables might not be set up nicely after the script finishes and this could be the only way to get values back into SwiftVis.

As was mentioned above, SwiftVis is set to work with any Iterable type. As it happens, JavaScript arrays aren't Iterable but special code has been implemented to wrap them in an Iterable wrapper so they can be interpreted. The JavaScript code can also create Java objects as can most of the other scripting languages being included in Java. If one wanted, one could create a 2-D array of the type java.lang.Float for the values or use various implementations of the List interface. However, it is easiest to work with the native array types provided by the language so we have provided that functionality. Also it should be noted that print statements in the script will be printed to the console in which SwiftVis was run. This can be handy for users debugging their scripts.

Because JavaScript does not have native, cross-platform file access function, we have provided a helper class that allows you to get hold of a java.util.Scanner that can read from one of the user selected files. This class has been provided in a variable called fileAccess. Currently the only method in this object is getScanner(int i) where i is the index of the input you want to read from (beginning with 0 for the first user specified file). The link earlier in this paragraph shows the methods of the Scanner class. The most useful methods will likely be the one of the form hasNext* and next*. The hasNext* methods check to see if a specific type of data is avaliable next in the file. Simply calling hasNext() checks if there is another "word" in the file. (A word is a sequence of non-white-space characters separated from other characters by white-space.) The next() method returns a java.lang.String with the next word. You can use this to skip over anything you don't want to read. Methods like nextInt() and nextDouble() will read and return numeric values. For whole lines use nextLine(). The following script shows a script that reads from a file that has a format of three columns per line where we want to ignore the first column (perhaps it is a text element we want to throw away). This code utilizes the fact that JavaScript arrays automatically grow in size as elements are added beyond the end of the current array size.

Functionality has also been added to assist in working with binary files, especially those written in Fortran. The fileAcess object also has a method called getBinaryInput(int i) that works just like getScanner(int i), but it returns a BinaryInput object that gives you methods for reading from binary files. If you go to the class page using the link in the previous sentence you can look at the readFortranRecord method. The figure below shows this being used with code that reads in a Swift bin.dat file. Using the Binary Position source is much more efficient for this particular purpose (in fact, this code is extremely slow for anything but small bin.dat file), but this illustrates reading Fortran binary files with a script. The way this code works is that it goes into an infinite loop reading a header, followed by massive body data, followed by test particle data. It throws an exception and gives you a message when it runs out of file to read. All the read values will be in the output.

The second element that was created to work with scripting is the ScriptFilter. Like all filters, this filter takes one or more inputs and produces an output. The interface for the filter is simpler than for the source. It allows you to select a language, write a script, and process the script. There are two variables set up in the script engine for the user to get information from: params and values. Each is a 3-D array containing the parameters and values from the input sources. They are 3-D because the filter can take input from multiple sources. So p[i] and v[i] come from d[i] for i in the range of 0 to n-1 for n input sources. The second subscript is the element index and the third is which value or parameter to access from that element. Like the source, the script can create variables named p, v, paramNames, and valueNames and they will be interpreted in the same way.

The following screen shot shows a script using JavaScript that does something that no single SwiftVis filter can do. It finds the element in the data set with the maximal value of d[0].v[1] and does a parabolic fit to it and the points on either side of it using d[0].v[0] as the x value and d[0].v[1] as the y value. It also finds the minimum values to either side of the maximum and does fits to those. The output has three elements where v[0]=A, v[1]=B, and v[2]=C for the fit y=Ax^2+Bx+C. This can be done without scripting in SwiftVis, but it produces a fairly complex graph and uses some of the more advanced SwiftVis techniques.

The last feature that was added that uses scripting is the Script Style for plotting. This allows the user to write scripts that draw to the plots. There are three variables added for the plot style. The params and values variables are 3-D arrays again with the same format. A variable called drawer is also passed in that is bound to an edu.swri.swiftvis.scripting.ScriptDrawer object. This object has the following methods, which will draw to the plot area.

The first two methods set the style of the drawing while the others actually draw. The polygon functions need to be passed the eqivalent of 2-D arrays where the first index represents a point and the second index indicates x or y. The other methods can be called pretty much directly using whatever mechanism the scripting language in question has for calling back to method in Java. This is straightforward in the object-oriented scripting languages such as JavaScript and Jython. The figure below shows an example using this method. The source is a simple sequence of numbers ranging from 0 to 1. The plotting commands in drawer will automatically find the bounds of the points plotted and use that as the default bounds for the axes if they are set to auto-scale.