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.
-
InRule
implements theRule
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 usingjava.util.Collection.contains(E argElement)
method. -
ComparableRule
‘s evaluate method compares the given object with a predefinedjava.lang.Comparable
instance. This supports the common comparison operation such as <, <=, >, >= and = defined by theComparisonOperator
enum. This also provides between comparison operation to check if the given object in in the specified range. -
ComparatorRule
is similar to theComparableRule
but aims at providing the common comparison operations on objects that do not have ajava.lang.Comparable
implementation. This concrete implementation compares the given object with the predefined object using the predefinedjava.util.Comparator
instance. This also supports the between convenience comparison operation. -
NotRule
is the first of four logical operations that simply negates the result of another predefined rule. -
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. -
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. -
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. -
RegexRule
utilizes the Java standardjava.util.regex
package (available in JDK 1.4 or later) to implement theRule
interface. Its evaluate method matches the given object’stoString()
representation with the predefined regular expression patterns specified via its constructor. TheRegexRule
constructors also accept aMatchPolicy
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:
-
Cannot be /secret/Vault.java or /secret/Vault.xml
-
Must have an extension .txt, .java, .html or .xml
-
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.
8 simple rules 404s
8 simple rules rox
What are the Copyrights for it. Can we use this code piece in out propreitary code.
I would go for publishing a series of white papers. This tactic works wonderfully.-acyclovir
acyclovir
The latest edition of Java Blogger Contest :
[URL=http://indicthreads.com/news/200/java_blogger_2_contest.html]Best Java Blogger 2[/URL]