by Benedict Chng

Tcl your Java apps

news
Mar 23, 200119 mins

Enhance the customizability and flexibility of your enterprise Java apps

Any viable company has to react quickly to gain or regain an edge over its competitors. For companies that rely on their Websites to generate revenue, this means frequent changes to those Websites. Some of the changes are more than cosmetic and require many code changes on the server side. Changes might be required, for example, in order to give holiday discounts, reward frequent customers with loyalty points, or send personalized marketing emails. If you use EJB technology to create your Web-based enterprise application, you know that you have to look through the Java source code, figure out which parts require changing, change the actual code, compile, and then retest your application.

All is well if you are the developer and know the system inside and out. Unfortunately, if someone else takes over, that person will experience a learning curve — so by the time the change is rolled out, your business may have lost many customers.

The solution I propose is to isolate parts of the Java code that are likely to change and implement them in a scripting language — Tcl, in this case — that is easy to read, understand, and modify. If properly done, I believe even your manager could perform certain changes without paging you in the middle of the night.

Tcl’s background

Tcl was developed by UC Berkeley professor John Ousterhout as a cross-platform scripting language that is easy to read and understand, and is easily extensible and embeddable. You can extend it by writing your own extensions and commands. This article will show you how, if want your application to interface with a Tcl script, you can easily embed a Tcl interpreter to process your script.

Since its creation, Tcl has empowered hundreds of thousands of users in a wide range of applications such as rapid prototyping, unit testing, and the task of tying together applications that were never meant to work together.

Not too long ago, considerable efforts had been spent integrating Java with Tcl (previously only extensible and embeddable in C). The TclBlend project, led by Moses Dejong, is an extension to the existing Tcl interpreter written in C, which allows a Tcl script to instantiate and call methods in Java objects, on top of the existing extensions written in C/C++. You can also create new commands or extensions in Java. The other project, Jacl (Java Command Language), is a total rewrite of the existing Tcl script interpreter in Java that will make it easy to embed into a Java application. I will explain in this article how, by embedding Jacl into your existing application, you will make your app more dynamic and configurable.

Caveat emptor

This article does not teach you the Tcl language or walk you through downloading and installing the necessary software. It is meant only as an introduction to the possibility of addressing a situation that you might frequently encounter. Fortunately, Tcl scripting is easy to read and understand, so you can explore it further to see whether it meets your enterprise needs. I have provided some links in

Resources

that discuss the principles and the development of the Tcl scripting language and its integration with Java.

Software download

From the

Resources

section, you can download all the source code for this article. In addition, you will need to install JDK 1.1 or above and a version of the Jacl interpreter; you will find links to both of those in

Resources

. I have tested my code with Jacl 1.2.6.

Scenario

Let’s take a typical scenario in which you’re developing a business-to-consumer Website. To attract customers during a certain period, you sometimes need to offer perks, such as holiday discounts. During one such promotional period, the company might want to give first-time customers a discount on any purchase above 0.

Solution 1

The typical way to go about managing this discount would be to hard code the logic into your Java code as follows:

// The result of the code will calculate the miscellaneous
// deduction to be to the purchase price and the discount
// percent to award to the customer
double miscDeduction=0.0;
double discountPercent=0.0;
// if this is a new customer, give a  discount
// if he/she spents above 0
if (customer.isFirstTimeCustomer()) {
    if (shoppingCart.getValue()>=10.0) {
        miscDeduction=5;
    }
}
// get the amount of money this customer
// already spent on this site
double dollarSpent=customer.getShoppingHistory().getDollarSpent();
// decides the percentage discount to award to the customer
// based on his/her past spendings
if (dollarSpent>100.0) {
    discountPercent=5.0;
} else if (dollarSpent>500) {
    discountPercent=7.0;
}
System.out.println("Discount awarded: "+discountPercent+"%");
System.out.println("Additional discount: $"+miscDeduction);

The code first initializes miscDeduction and discountPercent to zero. The variable miscDeduction will hold the amount of deduction to the purchase price and the variable discountPercent is the discount to be awarded to the customer.

The code then checks whether the customer is a first-timer by calling the method isFirstTimeCustomer() from the customer object. Then the purchase price of the customer’s shopping cart is obtained by calling the method getvalue() from the shoppingCart object. The creation of the shoppingCart and customer objects is not shown because they are generic enough and not important for our discussion here.

Next the dollarSpent variable is initialized with the purchase amount the customer has made since registration. Finally, the discount awarded to the customer will be determined by the dollarSpent variable.

Problems with solution 1

All would be well if your boss had not made some last-minute decision, such as increasing the discount to 8 percent for customers who spend more than 00, to compete with a competitor doing the same thing.

Solution 2

Another way to handle this would be to parameterize some of the values that are likely to change and store them into an external XML file, database, or even from a network connection, so that you can change the values without impacting the code being written, like this:

// DiscountSetting is an interface that allows you to query an external
// XML file or database for the amount of discounts to award to
// a customer
DiscountSettings discountSettings=new MyDiscountSettings();
// The result of the code will calculate the miscellaneous
// deduction to be to the purchase price and the discount
// percent to award to the customer
double miscDeduction=0.0;
double discountPercent=0.0;
// if this is a new customer, give a  discount
// if he/she spents above 0
if (customer.isFirstTimeCustomer()) {
    if (shoppingCart.value()>=discountSettings.get("First time purchase")) {
        miscDeduction=discountSettings.get("First time deduction");
    }
}
// get the amount of money this customer
// already spent on this site
double dollarSpent=customer.getShoppingHistory().getDollarSpent();
// decides the percentage discount to award to the customer
// based on his/her past spendings
if (dollarSpent>discountSettings.get("First level spending")) {
    discountPercent=discountSettings.get("First level discount percent");
} else if (dollarSpent>discountSettings.get("Second level spending")) {
    discountPercent=discountSettings.get("Second level discount percent");
}
System.out.println("Discount awarded: "+discountPercent+"%");
System.out.println("Additional discount: $"+miscDeduction);

The code first creates a discountSettings object. This is an interface that exposes a single method to obtain a double value based on a String input:

public interface DiscountSettings {
  public double get(String valueToRetrieve);
}

Implementation of the DiscountSettings interface will be responsible for retrieving the actual information from a data source, such as an XML data file or a database table.

As in the previous example, the code will award a discount to first-time customers whose purchase values exceed 0. Instead of hard coding the cash value, the code now uses the discountSettings object to look up the value based on fixed string constants, "First time purchase" and "First time deduction".

The code then goes on to award additional discounts to customers who have spent a certain amount of money. As in the above case, the discountSettings object now finds out the percentage of discount to be awarded based on how much each customer has spent.

Problems with solution 2

Solution 2 solves our problem when the reward structure is the same and the only values that change are the discount amounts. Unfortunately, we have somehow hard coded our logic and structure here. If management decides to terminate the first customer rewards, or introduce customer coupons, we would have to go back to our code once again.

Solution 3

Solution 3 to the problem above moves the reward code into an easily understood and modified script. We will make use of Jacl to parse our Tcl script. The Java code and the Tcl script is as follows:

// Creates the Tcl interpreter object
Interp interp=new Interp();
// The result of the code will calculate the miscellaneous
// deduction to be to the purchase price and the discount
// percent to award to the customer
double miscDeduction=0.0;
double discountPercent=0.0;
try {        
    // the file name of the script
    String scriptFileName="discount_decision.tcl";
    // get the amount of money this customer
    // already spent on this site
    double dollarSpent=customer.getShoppingHistory().getDollarSpent();
    double shoppingCartValue=shoppingCart.getValue();
    // set the dollarspent variable in the Tcl script
    interp.setVar(
        "dollarspent",
        TclDouble.newInstance(dollarSpent),
        0);
    // set the firsttimecustomer variable in the Tcl script
    interp.setVar(
        "firsttimecustomer",
        TclBoolean.newInstance(customer.isFirstTimeCustomer()),
        0);
    // set the shoppingcartvalue variable in the Tcl script
    interp.setVar(
        "shoppingcartvalue",
        TclDouble.newInstance(shoppingCartValue),
        0);
    // execute the script within the script file
    interp.evalFile(scriptFileName);
    // retrieve the values that are evaluated in the script
    String tclMiscDeduction=
      interp.getVar("miscdeduction",0).toString();
    String tclDiscountPercent=
      interp.getVar("discount",0).toString();
    miscDeduction=new Double(tclMiscDeduction).doubleValue();
    discountPercent=new Double(tclDiscountPercent).doubleValue();
    // free up resources used by the interpreter
    interp.dispose();
    // output the results to console
    System.out.println("Discount awarded: "+discountPercent+"%");
    System.out.println("Additional discount: $"+miscDeduction);
} catch (TclException e) {
    e.printStackTrace();
}

The following Tcl script evaluates the result:

# first time customer gets  off for all purchases > 0
set miscdeduction 0
if {$firsttimecustomer} {
  if {$shoppingcartvalue>10} {
    set miscdeduction 5
  }
}
# determines the amount of discount to award to the customer
# based on spending history
set discount 0
if {$dollarspent > 100} {
  set discount 5
} elseif {$dollarspent > 500} {
  set discount 7
}

Let’s first take a look at the Java code. We create a Tcl interpreter object called interp to parse our Tcl script and then, as usual, set up the miscDeduction and discountPercent variables. Next, we initialize shoppingCartValue and dollarSpent.

From this point onward, things start to get interesting. We create three Tcl variables for our interp object — dollarspent, shoppingcartvalue, and firsttimecustomer — by calling setVar of the interpreter object, and initialize them to appropriate values. These variables will then be accessed from our Tcl script.

The method signature for setVar of our interp object is:

public void setVar(String name, TclObject value, int flags) throws TclException

The first argument is the name of the variable that we want to create.

The second argument is a TclObject that contains the initial value of the variable. To create this TclObject to contain our value, we call a factory method newInstance() of a Tcl data class corresponding to the type we want to create. For example, to create a TclObject representation of type double with a value of 2.0, we call TclDouble.newInstance(2.0). Similarly, to create a TclObject representation of type boolean with a value of true, we call TclBoolean.newInstance(true). The API also provides us with TclString and TclInteger to generate a TclObject representation of strings and integers.

The third argument is a flag that defines the namespace in which we want our variable to be created. A namespace is how the Tcl language creates, accesses, and destroys separate context for variables and procedures. The idea is similar to the way Java uses packages to encapsulate method names and member variables, but in the case of Tcl, a namespace can be destroyed. We use a zero value to create our variables in the current default namespace.

After we set up our variables, we execute our script contained in a script file by calling evalFile from our interp object.

Now take a look at the Tcl script, repeated here for convenience:

# first time customer gets s  off for all purchases > 0
set miscdeduction 0
if {$firsttimecustomer} {
  if {$shoppingcartvalue>10} {
    set miscdeduction 5
  }
}
# determines the amount of discount to award to the customer
# based on spending history
set discount 0
if {$dollarspent > 100} {
  set discount 5
} elseif {$dollarspent > 500} {
  set discount 7
}

The first line is a comment. All comments start with a pound character (#). The second line creates a miscdeduction variable and sets it to zero by using the Tcl set command. You can use the set command to create a variable or to reinitialize it to a different value. The format for creating and initializing a variable in Tcl is:

set variable_name variable_value

For example, to create a variable called miscdeduction and initialize it to zero, we would write:

set miscdeduction 0

To access the value of our variable later, we will write the variable name preceded by a dollar sign ($). For example, to access the variable miscdeduction, we will write $miscdeduction.

The rest of the block will award first-time customers with a discount if their purchase exceeds 0, using the if-then-else block. The syntax for the if-then-else conditional statement in Tcl is as follows:

if {expr1} {statement1}
if {expr1} {statement1}
elseif {expr2}
  {statement2}

You can see from the Tcl script how we access the firsttimecustomer and shoppingcartvalue variables, initialized in our Java code.

The second block will create a discount variable and initialize it based on the amount of money spent by a customer.

So in our script, we created two Tcl variables discount and miscdeductions. When the evalFile() method returns, those two variables are initialized with our results. We need to be able to access them from our Java code, so we call getVar from our interp object. The signature of the method is as follows:

public TclObject getVar(String name, int flags) throws TclException

An object of type TclObject is returned and we call its toString() method to get the String representation of our value. We then convert it to “double” to get our result.

Before the end of our code, we clean up our interpreter object by calling dispose to free up its resources; that is, when we are sure we don’t want to use it anymore.

Problems with Solution 3

Solution 3 has definitely solved most of our problems. Now we can easily change our script to accommodate new business requirements. Currently, we anticipate that our script will use only three variables from the Java code and we set up the three variables by calling the method setVar() of the interpreter object. If we had 10 variables to set up, then we would need 10 calls to the interpreter object. But what if we don’t know in advance the number of variables that the script will need?

Solution 4

To solve the problems with Solution 3, we pass the object reference of our

customer

and

shoppingCart

objects into the

interpreter

. To wrap up, I’ll also show you how you can create Java objects within Tcl script to do some useful things. First, we change the code as follows:

// Creates the Tcl interpreter object
Interp interp=new Interp();
// The result of the code will calculate the miscellaneous
// deduction to be to the purchase price and the discount
// percent to award to the customer
double miscDeduction=0.0;
double discountPercent=0.0;
try {
    // the file name of the script
    String scriptFileName="discount_decision.tcl";
    // set the customer variable in the Tcl script that
    // points to the customer object
    interp.setVar(
        "customer",
        ReflectObject.newInstance(interp,Customer.class,customer),
        0);
    // set the shopping cart variable in the Tcl script that
    // points to the shoppingCart object
    interp.setVar(
        "shoppingcart",
        ReflectObject.newInstance(interp,ShoppingCart.class,shoppingCart),
        0);
    // execute the script within the script file
    interp.evalFile(scriptFileName);
    // retrieve the values that are evaluated in the script
    String tclMiscDeduction=
      interp.getVar("miscdeduction",0).toString();
    String tclDiscountPercent=
      interp.getVar("discount",0).toString();
    miscDeduction=new Double(tclMiscDeduction).doubleValue();
    discountPercent=new Double(tclDiscountPercent).doubleValue();
    // free up resources used by the interpreter
    interp.dispose();
    // output the results to console
    System.out.println("Discount awarded: "+discountPercent+"%");
    System.out.println("Additional discount: $"+miscDeduction);
} catch (TclException e) {
    e.printStackTrace();
}

And the new and improved Tcl script looks like this:

# first time customer gets  off for all purchases > 0
set miscdeduction 0
if {[$customer isFirstTimeCustomer]} {
  if {[$shoppingcart getValue] > 10} {
    set miscdeduction 5
  }
}
# determines the amount of discount to award to the customer
# based on spending history
set discount 0
set shoppinghistory [$customer getShoppingHistory]
set dollarspent [$shoppinghistory getDollarSpent]
if {$dollarspent > 100} {
  set discount 5
} elseif {$dollarspent > 500} {
  set discount 7
}
# Load the package necessary to run the new commands.
package require java
# Import the Calendar and GregorianCalendar
# from the java.util package
java::import -package java.util GregorianCalendar Calendar
# if customer buys a product on Sunday,
# give an additional 3% discount
set cal [java::new GregorianCalendar]
set dayofweekconstant [java::field Calendar DAY_OF_WEEK]
set sundayconstant [java::field Calendar SUNDAY]
if {[$cal get $dayofweekconstant]==$sundayconstant} {
  set discount [expr $discount + 3]
}

Let’s compare the Java code from Solution 4 and from Solution 3. In Solution 3, we created and initialized two variables — dollarSpent and shoppingCartValue — from the customer and the shoppingCart objects. In Solution 4, these variables are not created and initialized. Instead, the customer and shoppingCart objects are passed into the interp object. The idea is to let the script manipulate these objects.

    // set the customer variable in the Tcl script that
    // points to the customer object
    interp.setVar(
        "customer",
        ReflectObject.newInstance(interp,Customer.class,customer),
        0
    );

Remember in Solution 3, we created a TclObject instance to represent a double value of a variable we wished to create by calling TclDouble.newInstance. To do the same for an object reference, we call the newInstance() method of the ReflectObject class. The signature of the newInstance() method in ReflectObject is as follows:

public static TclObject newInstance(Interp interp, Class class, Object javaObj)

The first argument is the interpreter object itself. The second argument is the class of the object that we want to pass in. The last argument is the object itself to be passed into the Tcl script.

Let’s now take a look at the new Tcl script. Instead of referencing the firsttimecustomer variable, it calls the isFirstTimeCustomer() method from the customer object directly. The format for calling a method from an object instance in Tcl is simply as follows:

$varName methodName arg1 arg2 ...

varName is the name of the object reference, and methodName is the name of the method we want to call. The rest of the line represents arguments we wish to pass to the method. Therefore, to call the isFirstTimeCustomer() method from the customer object, we do it in Tcl as follows:

$customer isFirstTimeCustomer

You’ll probably notice that we use a square bracket to enclose our expression. We do that to tell the parser to evaluate the expression.

Most of the time, the parser can determine which method to call based on the method’s signature. The potential for a problem exists here if an object has two methods with the same name but one of them accepts a primitive integer and the other accepts an Integer. To make the matter worse, all literals in Tcl are represented as Strings internally and are converted to the appropriate types based on the context in which they are accessed (when you perform mathematical operations on a variable created in Tcl, it will be converted to a number). Imagine we have an object called foo that has two set() methods, one accepting a primitive integer (int) and another accepting a String. Therefore, to explicitly execute a method that accepts a primitive integer, we code our script as follows:

$foo {set int} 1

You should be able to go through the next few lines yourself, as it is pretty straightforward.

Java objects within Tcl

To end this discussion, let me show you how to create Java objects within the Tcl script. Let’s suppose that you want to give your customer an additional 3 percent discount for shopping on Sunday. Since Sun Microsystems has provided a very useful GregorianCalendar class that allows you to check what day of the week it is given the system time, it would be better to use that than to replicate the logic in our script. Here’s how you do it:

First, you need to tell the interpreter to provide an interface to create and manipulate Java objects from the script:

package require java

Next you import two classes (Calendar and GregorianCalendar) from the java.util package by calling:

java::import -package java.util GregorianCalendar Calendar

There is currently no way to import a whole Java package. Not a bad thing, considering it will create an ambiguous situation where you import two classes of the same name from different packages. If you find yourself importing too many classes, you might want to rethink your strategy; you don’t want to put too much code into your script, as you are not rewriting your whole application in Tcl.

Next, you create a GregorianCalendar object named cal by calling the java::new Tcl command. After that, you create two constants to refer to the public static variable of the Calendar class using the java::field command. Finally, you use the Calendar‘s get() method to find out whether today is Sunday. If it is, you add 3 percent to your discount variable. You use the Tcl command expr to evaluate the addition.

For more information about these and other commands, refer to the Resources section.

Conclusion

I have shown you how embedding the Tcl scripting language into your Java application can make your code more maintainable and dynamic. Now you know how easy it is to learn the Tcl scripting language. The next step for you will be to find out which part of your application will be most suitable for converting into a Tcl script. I am sure you’ll be able to come up with many cases wherein this technique proves invaluable.

Benedict Chng is currently consulting in the Boston, Mass. area. He hails from sunny and tropical Singapore. Benedict received his Java certification several years ago and has been working in the software development field for close to four years. His current interests include writing applications for the Palm Pilot and sightseeing in New England.