BPP: The Beanshell Preprocessor

By Warren MacEvoy

 

 


What is the Beanshell Preprocessor?                                

The Beanshell preprocessor, or BPP for short, is intended for Java developers as a convenient and powerful preprocessing tool. It is convenient because the preprocessor is based on Beanshell, which is essentially interpreted Java. This means that Java or Beanshell programmers can quickly use all of BPP's features. It is powerful for the same reasons: all the power of the Java SDK in the convenience of the Beanshell scripting language is available as double payment; once as a development language and once as a preprocessing language.

In the current world of sexy words for new concepts in software development, a new preprocessor is sure to draw a yawn. But for Java developers, this preprocessor can revolutionize core software development.   The reason is because it isn’t actually just a new processor, but an entire different kind of preprocessor: a symmetric preprocessor.  A language with a symmetric preprocessor gives the full power (and syntax) of the language as a preprocessor, including the provision for a preprocessor, and so on.

Getting BPP and Beanshell

BPP is available as an executable jar file from http://bpp.sourceforge.net. Beanshell is available from http://beanshell.org. BPP uses Beanshell under the hood, and so must find its jar file in the class path. Putting the bsh-2.0b1.jar in your classpath ensures it will be found.   Once this has been set up correctly, use the command

java bsh.Console

 

from a command shell to start up the beanshell desktop. A windowed Beanshell desktop should appear. In the window, type:

 
you="Jay R. EE";
print("Welcome, " + you);
 

After seeing the expected message, add a sticky note to your monitor with the words: “Learn Beanshell now; save time later.”  For now, just close the Beanshell desktop.  BPP does not use the beanshell desktop, but instead uses the bsh.Interpreter class internally as a lightweight Java interpreter.

 

<<Insert figure: desktop.png>>

Screenshot of the beanshell deskop.

 

 

You is what?

Beanshell is a get-to-the-point Java. Typing

you="Jay R. EE";
print("Welcome, " + you);
 

in Beanshell is equivalent to compiling and executing the following Java code:

 
public class SomeClass
{
  public static void main(String[] args)
  {
    String you="Jay R. EE";
    System.out.println("Welcome, " + you);
  }
}

Which one would you rather type? See the www.beanshell.org web site for many useful resources on Beanshell. For now, here is what I claim to be the shortest tutorial of a production programming language in history:

Beanshell is an interpreted version of Java with optional types.

 

Types are great for safety, but they can turn the quick and dirty into the slow and tedious. Beanshell adheres to types you specify, but also allows unspecified types, so you can choose your safety level. As of version 2.0, Beanshell has seamless integration with JDK 1.3 and above. Thus Beanshell is a flexible superset of Java I encourage any Java developer to get to know.

Show me how useful BPP is!

BPP allows you to write Java code that writes Java code in a convenient way.  The preprocessor lines begin with a # sign, and are executed at preprocess time, while normal lines are undecorated.  $id and $(expr) on otherwise normal lines are translated in a natural way.  For example, the following BPP source file will create the traditional “Hello World” java program, but with the message appearing in four different languages.

 
#
# greetings=new String[] {
#  "hola mundo",
#  "ciao mondo",
#  "hello world",
#  "\u043F\u0440\u0438\u0432\u0435\u0442 \u043C\u0438\u0440",
# };
#
public class Xample1 {
  public static void main(String[] args) {
#for (i=0;i<greetings.length; ++i) {
    System.out.println("$(greetings[i])");
#}
  }
}

If you save the above text in a file named Xample1.java.bp, then running BPP with the line

 
java -jar bp.jar Xample1.java.bp
 

will produce the following text in Xample1.java:

 
public class Xample1 {
  public static void main(String[] args) {
    System.out.println("hola mundo");
    System.out.println("ciao mondo");
    System.out.println("hello world");
    System.out.println("привет мир");
  }
}
 

The last line is "hello world" in Russian, and may appear strangely on systems that don't understand UTF-8 encoded unicode. Point is, the #'ed lines are executed at "preprocess time" by BPP. This results in a Java source file with, in compiler optimization parlance, an "unwound loop."

 

Admittedly, unwinding the above loop at preprocess time will give no particular advantage over executing the loop at run time. There are places where such unwinding could make a great deal of difference. Similar preprocessor code could write a substantial “boiler plate” code which you might otherwise use a more traditional “copy/paste/edit” approach on, but we will leave that to the reader’s imagination (and online tutorials on the BPP web site).  The next example takes on a loftier software engineering goal: compile time vs. run time safety.

Safe Sets

It is a basic principle of software engineering that the earlier you find an error, the less expensive it is correct. The collections framework in Java is a good example of something that is run-time safe (because you can't put in an Apple and treat it like an Orange without a ClassCastException), but it isn't compile-time safe, since you can add any Object to any Collection, even if you only really wanted Oranges in it.

BPP can quickly create compile-time type-safe wrapper classes for collections (or wherever else you need them). Here is a snippet of the type-safe template for Collection:

 
#void makeTypedCollection(String className,String element) {
public class $className {
  public static class $(className)Iterator { 
    java.util.Iterator iterator;
    public Iterator(java.util.Iterator _iterator) { iterator=_iterator; }
    public boolean hasNext() { return iterator.hasNext(); }
    public $element next() { return ($element)iterator.next(); }
    public void remove() { iterator.remove(); }
  }
  protected java.util.Collection collection;
  public $className(Collection _collection) { collection=_collection; } 
  public boolean add($element arg1) { return collection.add(arg1); }
  public boolean contains($element arg1) { return collection.contains(arg1); }
  public boolean remove($element arg1) { return collection.remove(arg1);
  public $(className)Iterator iterator() { 
   return new $(className)Iterator(collection.iterator()); 
  }
}
#} // makeTypedCollection
 

To use this, save this in a file called "makeTypedCollection.bp", and generate the equivalent Beanshell script with

 
java -jar bp.jar -b makeTypedCollection.bp
 

The -b option tells BPP to create the script, but not execute it. The script is written in this case to makeTypedCollection.bsh. With this handy "template" around, creating a compile-time type safe collection is a snap. For example a string collection would be the following smidgen of lines in StringCollection.java.bp:

 
#source("makeTypedCollection.bsh");
#makeTypedCollection("StringCollection","java.lang.String");
 

Notice that I source the Beanshell script generated by BPP, not the BPP script itself.

There is a philosophical point here: judicious use of BPP can move many checks from "run-time" to "compile-time". This can make an order-of-magnitude difference in how long takes (and expensive it is) to find and correct mistakes.  Now let’s look at how BPP does its magic.

Dr. Piet Jonas in his Type Safe Collections article in JavaWorld also addresses type safety, but in another manner.

Jonas' idea is to verify the types for collections at insertion time using run-time type information. This causes an exception to be thrown early (at insertion time) rather than late (at extraction time). However, in either case, the exception is thrown at run time.

The type-safe wrappers we suggest using here allow for compile-time safety.

 

 

Ok, so how does it work?

BPP is similar in nature to servelets and php (and a very similar perl-based preprocessor, perlpp). It works by translating the BPP code into Beanshell code under the following rules:

·         Lines with a # (pound) in column 1 have the remainder of the line copied exactly to the Beanshell script.

For example,

#n=10;
#for(i=0; i<n; ++i) {

becomes

n=10;
for(i=0; i<n; ++i) {

in the Beanshell script.

·         Lines with a " (double quote) in column 1 have the remainder of the line "quoted magically." This means it becomes a print statement with $IDENTIFIER and $(EXPRESSION) patterns concatenated in.

Two compromises were made in the magic translation:

    • $ is a legal start and part of a Java identifier. If you need to have such a value in a BP script use $(my$strange$id).
    • When $ is not succeeded by an identifier, a left parentheses, or another $, it simply represents a single $. $$ on a magically quoted line represents a single $.

For example,

#static import pp.Format.*;
"Dear $title $lastName;
"You owe $$$(N(blnc,"#,###.00")).

becomes

static import pp.Format.*;
print("Dear "+title+" "+lastName+";");
print("You owe $"+(N(blnc,"#,###.00"))+".");

in the Beanshell script. For those new to Beanshell, print() is equivalent to System.out.println(). The N(Number n,String f) method is a static member of the pp.Format class as a convenience for formatting numbers.

  • Lines with a ' (single quote) in column 1 have the remainder of the line 'quoted exactly'. This generates a print statement in the Beanshell script that will faithfully reproduce the line of text.

For example,

'#static import pp.Format.*;
'Dear $title $lastName;
'You owe $(N(blnc,"$#,###.00")).

becomes

print("#static import pp.Format.*;");
print("Dear $title $lastName;");
print("You owe $(N(blnc,\"$#,###.00\")).");

in the Beanshell script.

  • Lines with none of the above "translation codes" have the default translation applied to them. This is "quoted magically" unless the -q ('quote exactly by default') option is passed to BPP.
Putting these together for the first example, the first BPP source file, Xample1.java.bp, produces the Beanshell script          
 
 
 greetings=new String[] {
  "hola mundo",
  "ciao mondo",
  "hello world",
  "\u043F\u0440\u0438\u0432\u0435\u0442 \u043C\u0438\u0440",
 };
 
print("");
print("public class Xample1 {");
print("  public static void main(String[] args) {");
for (i=0;i<greetings.length; ++i) {
print("    System.out.println(\""+(greetings[i])+"\");");
}
print("  }");
print("}");
 

Executing this script with beanshell generates the promised pure-java source file shown above as Xample1.java.

In Conclusion...

BPP facilitates: versioning, templates, macros, optimizations, and compile-time type safety. These things are a normal expectation of preprocessors. However, because the preprocessor is essentially the same full-featured language as the target language, including the fact that it has a preprocessor, these features are much more accessible than say, C++ templates are to C programmers. This gives big productivity benefits.

Here are a few other gains:

  • Have fun! Writing code over and over that's just a little different from the last time is boring. After the third time, you usually see the part that's staying the same and what is changing. With BPP you can codefy that and stick to the fun (new) stuff.
  • Big tools to make little ones. You can use BPP in development and take advantage of the latest and greatest JDK's in your developement environment to produce solutions in any target language/architecture. BPP is used in this way to support a multi-language environment called FRAMES in this way.
  • Protecting IP. The Java Bean model is to reduce an uber-model to one that solves a particular problem. Giving such a bean to someone else allows them to reverse-engineer your IP. BPP can generate a specific solution to a problem without revealing any general techniques on how that specific solution was constructed. Karl Castleton is writing a BPP-based tool to generate 80% of the boiler plate code for a Java Servelet/MySQL web site based on an XML description of the database architecture. This includes the SQL initialization, basic form pages, and compile-time-safe SQL access classes.
  • Simplification. In both the above cases, the code that BPP writes is as readable as what a specific programmer would have written to solve the same problem. For example, the above SQL framework generates code that a Java programmer that knows nothing about BP, XML, or even SQL can easily use. In the FRAMES application, BPP writes specific documentation appropriate for each supported target language based on a single document root.

Enjoy BPP!

References

Beanshell: http://www.beanshell.org

BPP: http://bpp.sourceforge.net

FRAMES: http://mepas.pnl.gov/earth. Click on the "FRAMES" link.

BPPSql: http://home.mesastate.edu/~kcastlet/BPPSql.html

perlpp: http://tldp.org/LDP/LG/issue44/macevoy/macevoy.html

Bio

Dr. Warren MacEvoy has been editor of corporate java training manuals and lead Java instructor for Sun java programmer certification programs. He has been a programmer and educator since time immemorial (or a least since 1995). He is the Rocky Mountain Regional director of the ACM intercollegiate programming contest, which is his main contribution to fun for young software engineers. Away from a CPU, he enjoys hiking and canoeing in Western Colorado.


Contact Info

Dr. Warren MacEvoy

Email:

wmacevoy@mesastate.edu

Mesa State College

Office:

Wubben 186

CSMS Department

Phone:

(970) 248-1070

1100 North Ave.

FAX:

(970) 248-1324

Grand Junction, CO 81501

Web:

http://www.mesastate.edu/~wmacevoy