HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Part 4 of a series discussing the many languages that compile and/or run on the Java platform

Do you remember the operating system religious wars? Mac OS versus Windows, Windows NT versus UNIX, OS/2 versus Windows NT. Or how about the text editor wars ­ VI versus Emacs? It may seem silly for programmers to become involved so passionately with the technology they work with, but if you spend more time with your VI text editor than with your family, I guess you do get kind of attached. I'm certain, for example, that we all have our favorite programming language....

Recently, for example, I was explaining to a colleague why I like to prototype things in JPython. He stared at me blankly and asked what at first seemed an innocent enough question: "Why?"

I spent the next five minutes explaining ­ I didn't even take a breath. To which he again responded "why?" before going on to expound on how Java was the perfect language and how you don't need another one. At this point I found it better not to continue the conversation. I try to avoid religious attachment to technology. (See the March issue of JDJ [Vol. 5, issue 3] for more about JPython.)

I've had similar experiences when talking to VB, Perl, Delphi, C++ and Rexx programmers. Perl programmers seem particularly attached to their language. Developers have favorite languages just like developers have their favorite editor. (Mine is Emacs.)

Java is one of my favorite programming languages. However, it's more than just a language, it's a platform, and Java the platform runs many, many programming languages. (See the first article in this series to learn more about programming languages that run in the Java Virtual Machine ["Programming Languages for the JVM," Vol. 5, issue 2].)

Now let's say that you wanted to make available a set of services from a Java-based application via a scripting language, as LotusScript does for Notes or VBA (Visual Basic for Applications) does for Excel. Which scripting language do you use? It's more or less impossible to pick one without leaving someone bent out of shape. Remember, everyone has their favorite. You want your application services to be inclusive, not exclusive.

What if you could support all of the major scripting languages with the same or less effort than it took you to support one? Essentially, with Bean Scripting Framework (BSF) from IBM, you can support Perl, Python, NetRexx, JavaScript and even VBScript. Cool beans!

What's more, BSF is going to add standard ways to debug scripts, a major flaw in some scripting languages. (I know, I know: real programmers don't use visual debuggers. But admit it, you do sometimes ­ and so do I, but don't tell anyone.) BSF brings standard support for many programming languages to the Java platform.

Why Scripting Languages?
The BSF white paper states the following: "As component-oriented software development becomes more and more commonplace, scripting is fast becoming a key development methodology.....Scripting is a natural counterpart to component oriented development ­ components can be written in standard object-oriented languages and then Œglued together' to form applications using scripting languages.....[Scripting languages] are a natural fit in the component oriented development world." (Quote taken from Bean Scripting Framework: A Scripting Architecture for the Java Platform written by Rick Rineholt, Sam Ruby, Matthew J. Duftler and Sanjiva Weerawarana.)

I believe the above echoes my same sentiments from the first article in this series. Scripting languages and components go together like a horse and carriage: having a standard way for scripts to talk to Java components opens up a lot of possibilities. BSF endeavors to provide a standard for scripts to communicate with Java components. (Please refer to the first article for more information on components and scripting languages.)

What Is BSF?
An application that uses BSF can use scripting ­ and become scriptable ­ using any BSF-supported language. Thus, when BSF adds support for additional languages, your application will automatically support the new languages.

The official Java platform from Sun doesn't have a standard scripting architecture, but IBM is submitting BSF to JavaSoft as a Java Specification Request, so BSF may become the basis of the Java platform standard extension for scripting. Even if it doesn't, BSF is likely to be the de facto standard for scripting integration for the Java platform.

Why BSF?
The advantages of having a standard scripting architecture like BSF is as follows:

  • It replaces the ad hoc approach to script integration.
  • It enables applications to support a lot of scripting languages.
  • Its scripts enable "nonprogrammers" to extend your application.
  • Services like debugging can be shared.
What Languages Does BSF Support?
Currently, the BSF supports the following pure Java platform languages:
  • Netscape's Rhino (JavaScript)
  • Jacl (TCL)
  • JPython (Python)
  • NetRexx (Rexx variant)
  • Bean Markup Language (developed by IBM)
  • LotusXSL
  • Pnuts

The scripting language doesn't have to be implemented in Java to be supported by BSF. For example, IBM adds support for Perl and VBScript. Essentially, all active scripting languages including VBScript and JScript are supported via BSF's support for Microsoft Active Scripting Framework (MASF).

BSF is the Java version of MASF, which is weird because some languages that are supported by MASF are also supported by BSF. Thus you can mix classic Python with JPython. This is good for legacy integration. In addition, every language that gets added to MASF automatically gets added to BSF. Pretty tricky, huh?

BSF is to MASF as JDBC is to ODBC. Just as you can have pure database drivers in JDBC or use native ODBC drivers, you can have pure Java scripting languages or use native scripting languages in BSF.

Think of all the people in the world who have done VBScript (or VBA). Now you can allow them to participate in your development efforts. Not only that, but you have another integration point with COM via MASF. With MASF, you can instantiate COM objects. (Don't worry, you 100% Pure zealots, you can still use BSF scripting in a Pure environment as well.)

Distribution
BSF is currently freely available ­ source and all. BSF will be developed under an open-source model in the very near future. If you have a favorite scripting language that's not supported by BSF, you should consider defining your own BSFEngine, which can be plugged into the framework.

Architecture
The BSF architecture consists primarily of two components: BSFManager and BSFEngine. The BSFManager is a common interface to scripting languages: you use it to access scripts from your applications. The BSFEngine interface provides a common interface for BSF to interact with a scripting language. The JDBC is to a JDBC Driver as BSF is to a BSFEngine. Essentially, a BSFEngine is a scripting language driver.

Getting Started
I downloaded the latest version of BSF and tried it out with one of my favorite scripting lanaguges: JPython.

The first step on every journey like this is usually a download or two and this adventure is no different. I went to IBM's alphaWorks Web site, looked up BSF and downloaded the bsf21.zip before extracting it to C:\BSF-2.1.

The zip file contains all the files you need to get started: API documents, a getting started guide, source code and library files (jar files). The ReadMe file has most of the information you need to get started. The getting started guide introduces you to most of the concepts behind BSF with some code example snippets.

Even with all of the above, setting up BSF is not for the averagely motivated person. The BSF guide is good but it needs a little more meat ­ it's not a step-by-step tutorial. If you like to tinker, then BSF is for you, but if you rarely wander from the confines of your favorite Java IDE, BSF may not be for you at this stage.

Eventually I got some sample code that I'd written to work. I had a problem and needed to look at the BSF source to figure out what was happening. It was a little tougher than I thought it would be (see the sidebar for details), but it wasn't impossible.

One of the keys to getting started smoothly is to make sure the jar files for BSF and those for your scripting language are on the CLASSPATH. Two jar files ship with BSF that you need on your classpath: bsf.jar and bsfengines.jar. The bsf.jar has the core BSF files. The bsfengines.jar has the language drivers, i.e., the language engines. You also need the jar files for your language on the classpath, e.g., if you are using JPython you need JPython.jar on your classpath. (JPython.jar ships with the JPython distribution.) For NetRexx you need NetRexxC.jar, NetRexxR.jar and tools.jar on your classpath; for TCL (JACL) you need JACL.jar; and for JavaScript you need js.jar and jstools.jar.

The example I'm going to highlight in this article will focus on integrating JPython. However, with slight modification you can incorporate TCL, JavaScript, NetRexx and so on. I'll leave the modification up to you.

For the code example, we'll map in an instance of a class to the BSFManager, load a script and then execute it. The script will have code that interacts with the class instance we mapped in the BSFManager. The class we'll use should be familiar to you if you've been following this series. Basically, it's a variation of the Statistics class from the last two articles.

The Stats class (see Listing 1) is a simple class that figures out the mean, mode and median for a given set of numbers. The script that we're going to use to manipulate this instance of Stats is very short (see Listing 2) ­ it prints out the mean, mode and median price of a list of houses.

print "The mode of the houses is %2.2f" % houses.mode
print "The mean of the houses is %2.2f" % houses.mean
print "The median of the houses is %2.2f" % houses.median

The houses variable is an instance of the Stats class that gets mapped into the BSFManager.

The steps to use a scripting language in BSF are as follows:

  1. Register the language with the BSF manager.
  2. Load the scripting engine.
  3. Map in your application objects that you want the script to have access to.
  4. Load the script and execute it.
In addition to loading and executing scripts, you can execute arbitrary expression of your scripting language. Listing 3 shows the code that does all of the above steps as well as executes a JPython expression.

The first thing that Listing 3 does is to register JPython with the BSF manager:

BSFManager manager = new BSFManager ();
//Register JPython into the scripting manager.
String[] extensions = {"py"};
manager.registerScriptingEngine ("jpython",
"com.ibm.bsf.engines.jpython.JPythonEngine",
extensions);

The manager has a method called registerScriptingEngine that takes three arguments: the name of the scripting language; the fully qualified class name of the BSF engine corresponding to the scripting language; and an array of string that corresponds to all the possible file extensions for the scripting language.

Remember, the BSF ships with several engines for various languages, so you can easily change the above to work with JavaScript or NetRexx or whatever. Once you've registered the scripting language, you can load its engine and start working with scripts. To load the engine, call the manager's loadScriptingEngine method:

//Load JPython engine
BSFEngine jpythonEngine = manager.loadScriptingEngine ("jpython");

After the scripting engine is loaded, you can start evaluating expressions and executing scripts. A scripting language expression is evaluated by calling the eval method on the manager.

//Execute an expression.
Object result = manager.eval ("jpython", "testString", 0, 0, "2+32");
System.out.println("eval="+result);

The eval method takes five arguments as follows: the name of the scripting language, the name of the file name associated with the expression, the row of the expression, the column of the expression, and ­ last ­ the expression. The above expression is "2+32". When the above code runs, it prints out 34.

Of course, in addition to evaluating expressions, you can execute scripts. Before evaluating scripts you can map objects (or an entire object model) into the BSF manager. All objects that are mapped in the BSF manager are available to the scripts.

The following code instantiates an instance of Stats, and then maps that instance into the BSF manager:

//Map some objects that the script will use.
double[] houses=new double [] {100.0e3, 130.0e3, 140e3, 150e3};
Stats stats = new Stats(houses);
manager.declareBean("houses", stats, Stats.class);

One way to map objects into the BSF manager is to call the declareBean method. The declareBean method takes three arguments: the script variable name of the bean (object), the instance and the class of the instance.

After you've mapped in your application's scriptable objects into the manager, you can load and run a script as follows:

//Load the script and execute it.
String fileName = "TestStat2.py";
String language = manager.getLangFromFilename(fileName);
String script = IOUtils.getStringFromReader(new FileReader(fileName));
manager.exec(language, fileName, 0, 0, script);

The manager's exec takes the same arguments as the eval method, the only difference being that the exec method doesn't return a value, i.e., it returns void. Notice that the scripting language can be decided based on the filename of the script by using the manager's getLangFromFilename method.

I have showed how to register a language, map application scriptable objects and execute a script. You can easily change the above to work with JavaScript, NetRexx and so on.

Parting Shots
The above example just scratches the surface of what BSF currently supports and what it will support in the future. Today IBM uses BSF with WebSphere to provide JSP development that can be done in other languages like Python, TCL and JavaScript. In the near future it will be used with the Apache Tomcat servlet engine to support JSP for other languages, too. If you need to integrate a scripting language in your product, you should use BSF instead of picking a single scripting language.

I hope to see BSF used in a lot of development tools like IDE, editors and modeling tools so that the developers can easily customize their environment.

Java has wonderful features that make creating scripting languages easy. The class reflection and bean introspection APIs are a great basis for integrating scripting languages. Once the scripting language has metadata about a Java's class properties, events and methods, it can use this data to change properties, handle events and invoke methods. BSF complements this feature by providing you with a common way to map Java objects to scripting languages and a common interface for integrating your application with a multitude of scripting languages.

Components (JavaBeans) and distributed components (e.g., CORBA, EJB and RMI) have a symbiotic relationship with high-level languages. For example, Visual Basic did well because of VBX, OCX and ActiveX components and COM/ActiveX/DCOM did well because of tools like Visual Basic, PowerBuilder and Delphi. On the Java platform we have the component models, but we need the glue, i.e., tools for the high-level languages ­ debuggers, IDEs and so on. BSF provides a common way to "glue" components into applications using a multitude of scripting languages. BSF does for JavaBeans what MASF does for ActiveX controls.

Author Bio
Rick Hightower currently works at Buzzeo Corporation (www.buzzeo.com), the maker of ZEOLogix, an EJB application server, rules engine and workflow. In addition to being a principal software engineer working on an EJB container implementation and distributed event management, he is author of a book, Programming the Java APIs with JPython, to be published by Addison Wesley.
[email protected]

	

Listing 1 

package stat; 
import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.Collections; 
import java.util.HashMap; 

public class Stats { 

 public ArrayList nums; 

 public Stats(ArrayList someNums){ 
  nums = new ArrayList(someNums); 
 } 

 public Stats(double[] someNums){ 
  nums = new ArrayList(someNums.length); 

  for (int index=0; index  < someNums.length; index++){ 
    nums.add(new Double(someNums[index])); 
  } 
 } 
  
  

 public double getMean (){ 
  return this.getMean(true); 
 } 

 public double getMean (boolean sample){ 
  // Define mean that finds two types of mean, namely: 
  // population mean and sample mean 
  double sum=0.0; 
  double average=0.0; 
  Iterator iterator = nums.iterator(); 

  while(iterator.hasNext()) 
   sum = sum + ((Double)iterator.next()).doubleValue(); 
  

   // Check to see if this is a sample mean 
  if(sample) 
   average = sum / nums.size()-1; 
  else 
   average = sum / nums.size(); 

  return average; 
 } 

 public ArrayList getRange (){ 
   // Find the range. Returns a tuple with the minimum, 
   // maximum, and range value 
  double min, max; 
  ArrayList ranges; 

  min = ((Double)Collections.min(nums)).doubleValue(); 
  max = ((Double)Collections.max(nums)).doubleValue(); 
  

  ranges = new ArrayList(); 
  ranges.add(new Double (min)); 
  ranges.add(new Double (max)); 
  ranges.add(new Double (max-min)); 

  return ranges; 
 } 

 public double getMedian (){ 
  // Find the Median number 

   // create a duplicate since we are going to modify the 
   // sequence 
  ArrayList seq = new ArrayList(nums); 

   // sort the list of numbers 
  Collections.sort(seq); 

  double median = 0.0; // to hold the median value 

  int length = seq.size(); // to hold the length of the 
                         // sequence 
  int index=0; 

   // Check to see if the length is an even number 
  if ( ( length % 2) == 0){ 
    // since it is an even number 
    // add the two middle number together 
   index = length / 2; 
   double m1 = ((Double)seq.get(index-1)).doubleValue(); 
   double m2 = ((Double)seq.get(index)).doubleValue(); 
   median = (m1 + m2) /2.0; 
  } 
  else{ 
    // since it is an odd number 
    // just grab the middle number 
   index = (length / 2); 
   median = ((Double)seq.get(index)).doubleValue(); 
  } 
  return median; 

 } 

 private int countMode(Object object, ArrayList list){ 
  int index = 0; 
  int count = 0; 
  do { 
   index = Collections.binarySearch(list, object); 
   if(index >=0)list.remove(index); 
   count++; 
  } 
  while (index >=0); 
  return count; 
 } 

 public double getMode (){ 
  // Find the number that repeats the most. 

   // make a duplicate copy of the nums argument 
  ArrayList duplicate = new ArrayList(nums); 

  Collections.sort(duplicate); 
  double highest_count = -100; 
  double mode = -100; 
  

  Iterator iterator = nums.iterator(); 
   // iterate through nums removing each item out of the duplicate 
   // calculate the highest_count and the mode 
  while(iterator.hasNext()){ 
   double count = 0; 
   Object item = iterator.next(); 

    // Count the number of times the item occurs in the list 
    // If Count is 0 go to the next iteration 
   count = countMode(item, duplicate); 
   if (count == 0) continue; 

    // determine the highest count. The highest counted item is the mode. 
   if (count > highest_count){ 
    highest_count = count; 
    mode = ((Double)item).doubleValue(); 
   } 
  } 
  

  return mode; 
 } 

} 

Listing 2 

print "The mode of the houses is %2.2f" %  houses.mode 
print "The mean of the houses is %2.2f" % houses.mean 
print "The median of the houses is %2.2f" % houses.median 

Listing 3 

import com.ibm.cs.util.IOUtils; 
import com.ibm.bsf.BSFManager; 
import com.ibm.bsf.BSFEngine; 
import java.io.FileReader; 
import stat.Stats; 

class Test { 
 public static void main (String [] args){ 
  BSFManager manager = new BSFManager (); 
  

   //Register JPython into the scripting manager. 
  String[] extensions = {"py"}; 
  manager.registerScriptingEngine ("jpython", 
     "com.ibm.bsf.engines.jpython.JPythonEngine", 
     extensions); 

  try{ 
    //Load JPython engine 
   BSFEngine jpythonEngine = manager.loadScriptingEngine ("jpython"); 

    //Execute an expression. 
   Object result = manager.eval ("jpython", "testString", 0, 0, "2+32"); 
   System.out.println("eval="+result); 

    //Map some objects that the script will use. 
   double[] houses=new double [] {100.0e3, 130.0e3, 140e3, 150e3}; 
   Stats stats = new Stats(houses); 
   manager.declareBean("houses", stats, Stats.class); 

    //Load the script and execute it. 
   String fileName = "TestStat2.py"; 
   String language = manager.getLangFromFilename(fileName); 
   String script = IOUtils.getStringFromReader(new FileReader(fileName)); 
   manager.exec(language, fileName, 0, 0, script); 

  }//try 
  catch (Exception e){ 
   System.out.println(""+e); 
  }//catch 

 }//main 
}//Test 



 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.