8 Simple Rules: Java Generics

No. This article is not about John Ritter’s "8 Simple Rules for Dating
My Teenage Daughter." Rather this is about 8 concrete classes that
implement an interface called Rule. These classes use JDK 5 Generics
and couple of GOF patterns to provide a simple but flexible means to
build applications using functors. Some developers admire the
notion of functors but some think they are pure evil (may be because of
over engineered implementations such as JGA library that uses 10 patterns in 40 lines of code).

** Winner of the Best Java Blogger Contest (Feb – Mar 2005) **


Even though there are very similar APIs out there, I have chosen to re-implement these Rules because I needed to regain my "Generic Programming" aptitude and also to show how a simple design would influence the way you deal with XML, Collections or even Business Rule validations. Why talk about it when we can get to the code?

The Rule Interface

Rule is a parameterized (generic) interface. It defines an evaluate method that takes an argument argInput of type T. The method evaluates the rule and returns a boolean value indicating the result – true if the rule is satisfied and false otherwise. This is a simplistic view of any rule: A rule is either satisfied or not satisfied by someone or something! At this level, we don’t really care to whom this rule applies and for what purpose it exists. That is expressed by the generic type parameter T in the following interface definition:

package com.cyslab.functors;
public interface Rule<T> {
 public boolean evaluate(T argInput);
}

The 8 Simple Rules

Now, lets take a look at the 8 concrete implementations of Rule interface that address the common evaluation needs such as comparision, look ups, regular expression matching and a combination of two or more of those. These classes are defined in the com.cyslab.simplerules package.

  1. InRule implements the Rule interface’s evaluate method to check if the given object is present in the predefined collection of objects. The implementation of this method is as simple as looking up an element of a collection using java.util.Collection.contains(E argElement) method.

  2. ComparableRule‘s evaluate method compares the given object with a predefined java.lang.Comparable instance. This supports the common comparison operation such as <, <=, >, >= and = defined by the ComparisonOperator enum. This also provides between comparison operation to check if the given object in in the specified range.

  3. ComparatorRule is similar to the ComparableRule but aims at providing the common comparison operations on objects that do not have a java.lang.Comparable implementation. This concrete implementation compares the given object with the predefined object using the predefined java.util.Comparator instance. This also supports the between convenience comparison operation.

  4. NotRule is the first of four logical operations that simply negates the result of another predefined rule.

  5. AndRule is the composite rule that logically ANDs the result of one or more predefined rules. Meaning, the evaluate method returns true if and only if the given object satisfies all the rules.

  6. OrRule is another composite rule that logically ORs the result of one or more predefined rules. The evaluate method returns true if the given object satisfies any of the given rule.

  7. XorRule implements the logical XOR operation on the predefined rules. XOR is not common in business applications but could come in handy to express some complex business rules such as select all employees that are either married and have kids or that are not married and do not have kids. Logical XOR operation returns true if and only if all the the rules are satisfied or if all of the rules are not satisfied.

  8. RegexRule utilizes the Java standard java.util.regex package (available in JDK 1.4 or later) to implement the Rule interface. Its evaluate method matches the given object’s toString() representation with the predefined regular expression patterns specified via its constructor. The RegexRule constructors also accept a MatchPolicy enum to decide if NONE, ALL, or ANY of the specified regular expression patterns need to be matched while evaluating the rule.

The RuleBuilder Class

In addition to these 8 simple rules, a RuleBuilder class is provided to minimize the burden of using these Rules. The RuleBuilder is similar to the StringBuffer or more appropriately the StringBuilder (of JDK 5) in that each of its method returns a reference to self, thus enabling the "chaining" of rules. As you can see the implementation of RuleBuilder is nothing more than a bunch of methods that provide most common combinations of Logical, Comparison and list operations.

Putting the Rules to Work

Imagine that you are developing an application that spiders through the file system and archives some files. The files are selected based on certain criteria. In other words, we need to archive all the files that satisfy a Rule. In this case, let’s assume the rule is the file:

  1. Cannot be /secret/Vault.java or /secret/Vault.xml

  2. Must have an extension .txt, .java, .html or .xml

  3. Must not be located under /proc, /usr/bin or /sbin folders of one of their subdirectories.

Assume that we have a FileArchiver class (see below) that uses a Rule<String> object to select files and archive them. The implementation details of FileArchiver are avoided for brevity.

public class FileArchiver {
 // Creates a file archiver that spiders the file system
 // and archives the files that satisfy the specified Rule.
 public FileArchiver(Rule<String> argFileSelectionRule){}
 // Archives the qualified files and returns the File object 
 // that represents the archive.
 public File archive() throws IOException{}    
}

All we got to do now is to create a Rule that translates our file selection rules into the concrete Rule interface implemenations. The code fragment below is self explanatory and does the needful.

// Rule 1: Uses InRule 
InRule<String> inRule = new InRule<String>("/secret/Vault.java""/secret/Vault.xml");
NotRule<String> notInRule = new NotRule<String>(inRule);
// Rule 2: Uses RegexRule to match ANY of the specified patterns
RegexRule<String> extRule = RegexRule.<String>matchAny("*.txt""*.java""*.html""*.xml");
// Rule 3: Uses RegexRule to match NONE of the specified patterns
RegexRule<String> dirRule = RegexRule.<String>matchNone("/proc/*""/usr/bin/*""/sbin/*");
// A composite rule that logically ANDs all the three rules.
Rule<String> fileSelectionRule = new AndRule<String>(notInRule, extRule, dirRule);

Or, if you prefer a shorthand notation, use the RuleBuilder as shown below. The RuleBulder‘s default constructor behaves as if it contains a "NOOP" rule that does nothing.

Rule<String> fileSelectionRule = 
 new RuleBuilder<String>()
 .andNotIn("/secret/Vault.java""/secret/Vault.xml")
 .andMatchAny("*.txt""*.java""*.html""*.xml")
 .andMatchNone("/proc/*""/usr/bin/*""/sbin/*")
 .toRule();

Once you have the fileSelectionRule ready, it’s a matter of couple of lines to spider the file system and archive the selected files using the FileArchiver.

FileArchiver archiver = new FileArchiver(fileSelectionRule);
File archivedFile = archiver.archive();

In the next installment of "8 Simple Rules" will look at more applications of Rules and Java Generics.


Side Bar: JDK 5 Generics Syntax Cheat Sheet

private final Collection<? extends T> values;

The ? is known as the wild card and ? extends T is known as a bounded wild card (with an upper bound) and it means – any class that implements T (if T were an interface) or extends T (if T were a class). Therefore, this collection can hold any object of type T or any subtype of T (either class or interface).

private final Comparable<? super T> value;.

This ? super T is known as a bounded wild card with lower bound. and it means that value could be any instance of T or any of its supertype.

private final Collection<? extends Rule<? super T>> rules;

This collection can hold any subtype of Rule that operates on any supertype of T or T itself.

public AndRule(Rule<? super T>... argRules) { }

In JDK 5, Varargs are denoted by ellipses, the three dots. JDK 5 presents the Varargs as one dimensional array of the specified type. In this case the AndRule constructor can accept any Rule implemenation that operates on the type T or any of its super type.

RegexRule.<String>matchAny("*.txt""*.java""*.html""*.xml");

The above line shows how a static parameterized (Generic/Template) method is invoked. Note the position of the period (.) and the <String>. The Java Generics notation at times is cryptic. It will take few days to get used to it.

The following two tabs change content below.
Content Team

Content Team

The IndicThreads Content Team posts news about the latest and greatest in software development as well as content from IndicThreads' conferences and events. Track us social media @IndicThreads. Stay tuned!
Content Team

Content Team

The IndicThreads Content Team posts news about the latest and greatest in software development as well as content from IndicThreads' conferences and events. Track us social media @IndicThreads. Stay tuned!

  • GUEST

    8 simple rules 404s

  • GUEST

    8 simple rules rox

  • ‘Guest’

    What are the Copyrights for it. Can we use this code piece in out propreitary code.

  • Noname

    I would go for publishing a series of white papers. This tactic works wonderfully.-acyclovir
    acyclovir

  • Noname

    The latest edition of Java Blogger Contest :
    [URL=http://indicthreads.com/news/200/java_blogger_2_contest.html]Best Java Blogger 2[/URL]