More action with Struts 2
In a recent review of Struts 2 in Action, JW Blogger Oleg Mikheev notes that Struts 2 is "just a collection of extensions built upon WebWork, which is ultimately the right thing to learn before starting a Struts 2 project." While Struts 2 has some architectural flaws, Oleg calls WebWork well-designed, well-tested, and reliable. What are your experiences using Struts 2 and WebWork?

Also see "Hello World the WebWork way," a JavaWorld excerpt from WebWork in Action, by Patrick Lightbody and Jason Carreira.

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Using the if-then-else framework, Part 3

Enhance the framework to support large-scale projects

The if-then-else framework is a tool for coding complex branching logic in a maintainable way. In the first two parts of this series, I described how to use the framework in a simple but typical case. The purpose of this final installment is to study the problems that arise when you place more significant demands on the framework. I will show how its performance starts to bog down as you increase the number of conditions, rules, and actions. I will then identify the performance bottlenecks, introduce a sequencing mechanism that will automate bookkeeping, and introduce an automated consistency checker that will throw an exception if inconsistent rules have been loaded. These modifications will not alter the fundamental steps for using the framework, already described in Parts 1 and 2. The end result will be a tool for handling branching logic that is industry ready and much more efficient.

TEXTBOX:

TEXTBOX_HEAD: Using the if-then-else framework: Read the whole series!



:END_TEXTBOX

For convenience, Listing 1 below displays the implementation of the if-then-else framework for the example used in Parts 1 and 2. I suggest you review the use of Conditions, Actions, Rules, the Updateable interface, and the Invoker subclass before diving into the rest of the discussion. (You may also wish to compare my approach to coding branching logic with the Hashed Adapter Objects design pattern, discussed in Mark Grand's book on design patterns -- see Resources for more information.)

Listing 1. The URLProcessor_good class with user-defined inner classes

class URLProcessor_good extends URLProcessor implements logic.Updateable {
    static final String URL_STR = "urlString";
    URLProcessor_good(){
        super();
    }
    void decideUrl() {
        try {
            Invoker inv = new ConcreteInvoker((Updateable)this);
            inv.execute();
        }
        catch(NestingTooDeepException ntde) {}
        catch(IllegalExpressionException iee) {}
        catch(RuleNotFoundException rnfe) {}
        catch(DataNotFoundException dnfe) {}
    }
/** Updateable interface implementation */
    public void doUpdate(Hashtable ht) {
       if(ht == null) return;
       Object ob = ht.get(Action.MAIN_KEY);
       if(ob != null && ob instanceof String) {
           setUrl((String)ob);
       }
    }
    static class OtherCondition extends Condition {
        public static final String KEY = "key";
        public OtherCondition(Hashtable ht) {
            super(ht);
        }
        public Boolean evaluate() throws DataNotFoundException {
            Object ob = null;
            if(getData() == null || (ob = getData().get(KEY)) == null) {
                throw new DataNotFoundException();
            }
            if(!(ob instanceof DataBank.Region)) {
                return Boolean.FALSE;
            }
            DataBank.Region region = (DataBank.Region)ob;
            boolean result = !region.equals(DataBank.WEST_REGION) &&
                             !region.equals(DataBank.EAST_REGION);
            return new Boolean(result);
        }
    }
    static class MemberCondition extends Condition {
        public static final String KEY = "id";
        public static final String TABLE = "table";
        public MemberCondition(Hashtable ht) {
            super(ht);
        }
        public Boolean evaluate() throws DataNotFoundException {
            Object id = null, table = null;
            if(getData() == null || 
               ((id = getData().get(KEY)) == null) || 
                ((table = getData().get(TABLE))==null) ||
                 !(table instanceof Hashtable)) {
                throw new DataNotFoundException();
            }                                
            Hashtable members = (Hashtable)table;
            return new Boolean(members.containsKey(id));
        }
    }
    public class ConcreteInvoker extends Invoker {
        public ConcreteInvoker(Updateable ud) throws NestingTooDeepException {
            super(ud);
        }
        public void loadRules() throws NestingTooDeepException {
            rules = new Rules();
            try {
                //             Conditions:                              Actions:
                //        East|West|Other|Lim|Member    
                rules.addRule("TFFT*",               new Action(ud,EAST_PRIVILEGED));
                rules.addRule("TFFF*",                    new Action(ud,EAST_NOT_PRIVILEGED));
                rules.addRule("FTFTT",               new Action(ud,WEST_MEMBER_PRIVILEGED));
                rules.addRule("FTFTF",               new Action(ud,WEST_NONMEMBER_PRIVILEGED)); 
                rules.addRule("FTFFT",               new Action(ud,WEST_MEMBER_NOT_PRIVILEGED));
                rules.addRule("FTFFF",               new Action(ud,WEST_NONMEMBER_NOT_PRIVILEGED)); 
                rules.addRule("FFT**",               new Action(ud,OTHER_REGION));
             }
             catch(NestingTooDeepException e) {
                 throw e;
             }
        }
                
        public void loadConditions() throws IllegalExpressionException {
            try {  
                conditions = new Vector(5);
                conditions.addElement(new Condition(db.getRegion(),DataBank.EAST_REGION));
                conditions.addElement(new Condition(db.getRegion(),DataBank.WEST_REGION));
                Hashtable other = new Hashtable();
                other.put(OtherCondition.KEY,db.getRegion());
                conditions.addElement(new OtherCondition(other));
                conditions.addElement(new Condition(db.LIMIT_THRESHOLD, Condition.LESS,db.getLimit() ));                         
                Hashtable mem = new Hashtable();
                mem.put(MemberCondition.TABLE, db.getWestMembers());
                mem.put(MemberCondition.KEY, db.getUserId());
                conditions.addElement(new MemberCondition(mem));
            }
            catch(IllegalExpressionException e) {
                throw e;
            }
        }
    }
}


Main issues

The following issues, which I outlined at the end of Part 2, will provide a structure for this discussion:

  1. Order of evaluation. As you can see in Listing 1, the comments in the loadRules() method spell out the intended order of evaluation. I always consider conditions in the following order, whether I am loading conditions or creating a sequence of Booleans: East, West, Other, Limit, Member.

    But what happens when the number of conditions grows to 15 or 20? You can no longer expect to rely on a difficult-to-read set of notes in your documentation as a reliable means to guarantee adherence to a particular order of evaluation. Clearly, this part of the framework begs for an automated solution that can eliminate human error.

    Resources
    • The source code files used for this series contain both the if-then-else framework (the logic package) and two sample code packages, sampleCode2 and sampleProfile. The sampleCode2 package shows how to use sequencers in URLProcessor_good; the package name for this GUI code has changed to sampleCode2, and so the HTML file that runs the applet had to be changed to iteGUI2.html. The sampleProfile package contains the Profiler class and the Main class, with which you can test performance. The files were compiled with JDK 1.1.7, and the GUI was written in Swing. If you attempt to recompile, be sure to include the swingall.jar file for Swing 1.0.3 in your classpath. Download the zip file
      http://www.javaworld.com/jw-06-2000/framework/CodePart3.zip
    • You can also download the source code and javadocs from Paul's Website
      http://www.kdsi.net/~pcorazza/papers/IfThenElseArticle/codeAndDocsPart3_053100.zip
    • You can view the javadocs and the sample code packages related to Part 3 at Paul's Website
      http://www.kdsi.net/~pcorazza/papers/IfThenElseArticle/javaDocs3/packages.html
    • Data Structures and Algorithms in Java, Mitchell Waite and Robert Lafore (Waite Group Press, 1998) is an excellent introduction to data structures in Java, and gives an especially good treatment of Hashtables in Chapter 11
      http://www.amazon.com/exec/obidos/ASIN/1571690956/o/qid=961111354/sr=2-1/104-8147619-4924763
    • Data Structures and Algorithm Analysis in Java, Mark Weiss (Addison-Wesley, 1998) -- p. 158 briefly discusses Java's slow hashing of strings
      http://www1.fatbrain.com/asp/bookinfo/bookinfo.asp?theisbn=0201357542
    • Design PatternsElements of Reusable Object Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1995)
      http://shop.barnesandnoble.com/booksearch/isbnInquiry.asp?userid=4DMTVYCOPZ&mscssid=&srefer=&isbn=0201633612
    • The Hashed Adapter Objects design pattern was formulated by Mark Grand in Patterns in Java, Volume 2. Kyle Davis pointed out to me a connection between the if-then-else framework and this design pattern. Grand's design pattern was not developed to handle the same breadth of scenarios of branching logic as the if-then-else framework (he explains in his section on context that he is simply looking to improve design and performance for typical if-chain implementations involving string comparisons), but the two do share a common themereplacing repeated occurrences of "ifs" and "else ifs" with Hashtable lookups. Some obvious differences between Grand's pattern and my framework are:

    • The if-then-else framework is not a design pattern but a bona fide framework -- most of the work involved in implementing branching logic has already been done for you. By contrast, a design pattern suggests a common solution to a broad range of problems, but the pattern user is responsible for all the coding in applications. The reason for creating the if-then-else framework was to save the user the large number of repetitive tasks involved in implementing the designs that underlie an object-oriented solution to coding branching logic.


    • Grand's pattern does not discuss nested-ifs for more than one level of nesting, does not handle conditions other than "equals" comparisons, and handles actions in a less uniform way.


    If one level of nesting is sufficient and "equals" comparisons are enough, since Grand's pattern requires less training to use, it could be a better choice for users who have not mastered the if-then-else framework. Patterns in Java, Volume 2, Mark Grand (John Wiley, 1999)
    http://www.amazon.com/exec/obidos/ASIN/0471258415/qid=961111885/sr=1-5/104-8147619-4924763