Calendar Example
Although it is useful to evaluate scripts that are specified as Strings, you probably have some file-based scripts that you would like to integrate into your Java programs. For example, consider a script for displaying a calendar on a website, with the current date highlighted. Listing 8 presents such a script, which is stored in a caldisp.js file.
Listing 8: caldisp.js
// ---------------- // caldisp.js // // Calendar display // ---------------- // ------------------------------------------------------------- // Function: caldisp // // Display a calendar based on the current year, month, and day. // // Arguments: // // bcMonthYear - background color for month/year // // bcWeekHdr - background color for week header // // bcDay - background color for non-highlighted days // // bcHiliteDay - background color for highlighted day // // bcEmptySquare - background color for empty square // // Return: // // none // ------------------------------------------------------------- function caldisp (bcMonthYear, bcWeekHdr, bcDay, bcHiliteDay, bcEmptySquare) { // Get the current date. var d = new Date (); // Determine the maximum number of days in a month. If the current year is // a leap year, advance the number of days for February from 28 to 29. var daysInMonth = new Array (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); if (isleap (d.getFullYear ())) daysInMonth [1]++; var maxDays = daysInMonth [d.getMonth ()]; // Create an array of month names so we can display the current month's // name in the calendar. var months = new Array ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"); // Start creating the calendar in an HTML table. Display the current month // and year, centered in the table's header. document.writeln ("<table border=1>"); document.writeln ("<th style=\"background-color: " + bcMonthYear + ";\"" + " colspan=7>"); document.writeln ("<center>" + months [d.getMonth ()] + " " + d.getFullYear () + "</center>"); document.writeln ("</th>"); // Determine the weekday on which the first day of the month falls. This is // used to determine the table column for displaying the first month day. var dayOfMonth = d.getDate (); d.setDate (1); var dayOfWeek = d.getDay (); d.setDate (dayOfMonth); // Create the weekday header row. document.writeln ("<tr>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>S</center></b></td>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>M</center></b></td>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>T</center></b></td>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>W</center></b></td>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>T</center></b></td>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>F</center></b></td>"); document.writeln ("<td style=\"background-color: " + bcWeekHdr + ";\"><b><center>S</center></b></td>"); document.writeln ("</tr>"); // Initialize day counter to 1. var day = 1; // Create the HTML table's day rows. Display days in suitable columns, and // highlight the current day using boldface type. for (row = 0; row < 6; row++) { document.writeln ("<tr>"); for (col = 0; col < 7; col++) { if (day == dayOfMonth && !(row == 0 && col < dayOfWeek)) document.writeln ("<td style=\"background-color: " + bcHiliteDay + ";\">"); else if ((row == 0 && col < dayOfWeek) || day > maxDays) document.writeln ("<td style=\"background-color: " + bcEmptySquare + ";\">"); else document.writeln ("<td style=\"background-color: " + bcDay + ";\">"); if ((row == 0 && col < dayOfWeek) || day > maxDays) document.writeln (" "); else { if (day == dayOfMonth) document.write ("<b>"); document.writeln (day++); if (day-1 == dayOfMonth) document.write ("</b>"); document.writeln ("</td>"); } } document.writeln ("</tr>"); } // Stop creating the HTML table. document.writeln ("</table>"); } // ------------------------------------------------ // Function: isleap // // Determine a year's leap-year status. // // Arguments: // // year - the year whose status must be determined // // Return: // // true if the year is a leap year, otherwise false // ------------------------------------------------ function isleap (year) { return (!(year & 3) && year % 100 || !(year % 400)); }
To evaluate caldisp.js and other file-based scripts, you must invoke an eval() method that takes a Reader argument. For example, you could invoke public Object eval(Reader reader), which I mentioned earlier in the article.
Invoking engine.eval (new FileReader ("caldisp.js")); evaluates caldisp.js's contents, which define the caldisp(bcMonthYear, bcWeekHdr, bcDay, bcHiliteDay, bcEmptySquare) and the isleap(year) functions. You can then invoke caldisp() via the invokeFunction() method. All of this activity is demonstrated by the application described in Listing 9.
Listing 9: ScriptDemo8.java
// ScriptDemo8.java import java.io.*; import javax.script.*; public class ScriptDemo8 { public static void main (String [] args) throws Exception { // Create a ScriptEngineManager that discovers all script engine // factories (and their associated script engines) that are visible to // the current thread's classloader. ScriptEngineManager manager = new ScriptEngineManager (); // Obtain a ScriptEngine that supports the JavaScript short name. ScriptEngine engine = manager.getEngineByName ("JavaScript"); // Define the functions specified by caldisp.js. engine.eval (new FileReader ("caldisp.js")); // Invoke caldisp ("#b0e0e6", "#00ff00", "white", "yellow", "#808080");. Invocable inv = (Invocable) engine; inv.invokeFunction ("caldisp", "#b0e0e6", "#00ff00", "white", "yellow", "#808080"); } }
After defining caldisp() and isleap(), ScriptDemo8 executes inv.invokeFunction ("caldisp", "#b0e0e6", "#00ff00", "white", "yellow", "#808080");, which is equivalent to evaluating caldisp ("#b0e0e6", "#00ff00", "white", "yellow", "#808080");. Here is the resulting (reformatted) output:
Exception in thread "main" javax.script.ScriptException: sun.org.mozilla.javascript.internal.EcmaError: ReferenceError: "document" is not defined. (<Unknown source>#57) in <Unknown source> at line number 57 at com.sun.script.javascript.RhinoScriptEngine.invoke(RhinoScriptEngine.java:184) at com.sun.script.javascript.RhinoScriptEngine.invokeFunction(RhinoScriptEngine.java:142) at ScriptDemo8.main(ScriptDemo8.java:29)
Attempting to invoke the caldisp() function results in a thrown ScriptException. This exception is due to the presence of document.writeln() and document.write() member function calls within this function. Unlike a Web browser's document object model, which defines document with these member functions, the Scripting API does not provide a document object.
This problem can be solved by evaluating a script that introduces a document object with its write() and writeln() member functions prior to invoking caldisp(). Because script results are retained between evaluations (at least under Rhino JavaScript), document is available to caldisp()'s invocation, as Listing 10 demonstrates.
Listing 10: ScriptDemo9.java
// ScriptDemo9.java import java.io.*; import javax.script.*; public class ScriptDemo9 { public static void main (String [] args) throws Exception { // Create a ScriptEngineManager that discovers all script engine // factories (and their associated script engines) that are visible to // the current thread's classloader. ScriptEngineManager manager = new ScriptEngineManager (); // Obtain a ScriptEngine that supports the JavaScript short name. ScriptEngine engine = manager.getEngineByName ("JavaScript"); // Define the functions specified by caldisp.js. engine.eval (new FileReader ("caldisp.js")); // Define document within JavaScript. String script = "var document = new Object (); " + "document.writeln = function (s) { println (s); }; " + "document.write = function (s) { print (s); }"; engine.eval (script); // Invoke caldisp ("#b0e0e6", "#00ff00", "white", "yellow", "#808080");. Invocable inv = (Invocable) engine; inv.invokeFunction ("caldisp", "#b0e0e6", "#00ff00", "white", "yellow", "#808080"); } }
ScriptDemo9 sends the calendar display script's output to the standard output device via calls to print() and println(). Unless this HTML output is redirected to a file, the calendar cannot be rendered by a javax.swing.JEditorPane component (which needs to read and then parse the calendar HTML). Listing 11 shows how to redirect the script's output to a file, which is then rendered via JEditorPane.
Listing 11: ScriptDemo10.java
// ScriptDemo10.java import java.awt.*; import java.io.*; import javax.script.*; import javax.swing.*; import javax.swing.event.*; public class ScriptDemo10 { public static void main (String [] args) throws Exception { // Create a ScriptEngineManager that discovers all script engine // factories (and their associated script engines) that are visible to // the current thread's classloader. ScriptEngineManager manager = new ScriptEngineManager (); // Obtain a ScriptEngine that supports the JavaScript short name. ScriptEngine engine = manager.getEngineByName ("JavaScript"); // Evaluate the caldisp.js script. engine.eval (new FileReader ("caldisp.js")); // Have the script send its output to out.html. engine.getContext ().setWriter (new PrintWriter ("out.html")); // Define document within JavaScript. String script = "var document = new Object (); " + "document.writeln = function (s) { println (s); }; " + "document.write = function (s) { print (s); }"; engine.eval (script); // Invoke caldisp ("#b0e0e6", "#00ff00", "white", "yellow", "#808080");. Invocable inv = (Invocable) engine; inv.invokeFunction ("caldisp", "#b0e0e6", "#00ff00", "white", "yellow", "#808080"); // Create a GUI to show the calendar. Runnable r = new Runnable () { public void run () { JFrame frame = new JFrame (); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); String path; path = "file:///" + (new File ("").getAbsolutePath ()) + "/out.html"; try { JEditorPane ep = new JEditorPane (path); frame.setContentPane (new JScrollPane (ep)); } catch (IOException ioe) { } frame.setSize (200, 300); frame.setVisible (true); } }; EventQueue.invokeLater (r); } }
The engine.getContext ().setWriter (new PrintWriter ("out.html")); statement redirects the script's output to out.html. This output is made available to JEditorPane via path = "file:///" + (new File ("").getAbsolutePath ()) + "/out.html"; and JEditorPane's constructor. The rendered calendar appears in Figure 1.
Figure 1 This calendar is just one example of the many interesting hybrid Java/scripting language programs that you can create via the Scripting API.