You need to perform complex conditional logic using multiple Predicate
objects, and you need to combine and
expose multiple criteria as one Predicate
.
To combine several Predicate
instances, create a Predicate
to
capture each portion of a compound condition, and combine each condition
with AndPredicate
, OrPredicate
, AllPredicate
, OnePredicate
, AnyPredicate
, or NonePredicate
. All of these predicate
implementations are used to combine the results of multiple
predicates—creating a compound predicate. The following code
demonstrates the use of the AndPredicate
, OrPredicate
, AllPredicate
, and OnePredicate
:
import org.apache.commons.collections.Predicate; import org.apache.commons.collections.functors.*; // Create Base Predicates Predicate isTim = new EqualsPredicate("Tim"); Predicate isDouble = new InstanceOfPredicate( Double.class ); Predicate isNotNull = NotNullPredicate.INSTANCE; Predicate[] predicates = new Predicate[] { isTim, isDouble, isNotNull }; // Create 2 argument logical predicate composites Predicate andPredicate = new AndPredicate( isTim, isNotNull ); Predicate orPredicate = new OrPredicate( isTim, isNotNull ); // Create n-argument logical predicate composites Predicate allPredicate = new AllPredicate( predicates ); Predicate onePredicate = new OnePredicate( predicates ); System.out.println( "'Tim' and not null?: " + andPredicate. evalute( "Tim" ) ); System.out.println( "'Tim' or not null?: " + andPredicate. evalute(new Long(3))); System.out.println( "'Tim', not null, and Double?: " + allPredicate.evaluate( "Impossible" ) ); System.out.println( "XOR ('Tim', not null, or Double?): " + allPredicate.evaluate( "Impossible" ) );
This example creates the following output:
'Tim' and not null?: true 'Tim' or not null?: true 'Tim', not null, and Double?: false XOR('Tim', not null, or Double?): true
An AndPredicate
returns
true
if both predicates supplied to
its constructor return true
, and an
OrPredicate
returns true
if at least one of the two predicates
passed to its constructor returns true
. An AllPredicate
takes an array of predicates,
only returning true
if every
predicate evaluates to true
. The
OnePredicate
also takes an array of
predicates, only returning true
if
exactly one predicate evaluates to true
.
In the code sample, the use of the second to last predicate,
AllPredicate
, is impossible to
satisfy; an object can never be a String
and a Double
at the same time. This example fails to
demonstrate AnyPredicate
and NonePredicate
—both take an array of
predicates. AnyPredicate
returns
true
if any of the predicates
evaluate to true
, and NonePredicate
returns true
only if none of the predicates evaluate
to true
. The behavior of these
objects is easily inferred from the names: And, Or, All, One, Any, or
None.
Any logical expression can be modeled by connecting Predicate
objects together— similar to the way
that simple logic gates are connected to create complex digital logic.
Logical inputs (1 and 0) are routed to logic gates (AND, OR, NOR, NAND,
XOR, etc.), and the outputs of a logic circuit are a result of stages
that perform the same function as the Predicate
objects introduced in this recipe.
In the next example, a logic circuit will be used to demonstrate a
complex hierarchy of Predicate
objects; a circuit diagram is drawn, and a series of predicates are
developed to model this circuit. Figure
4-1 contains a logical expression that is implemented with
digital logic and Predicate
objects.
Assuming that every letter corresponds to a boolean
variable, this expression corresponds
to the circuit diagram in Figure
4-2. Each gate can be modeled as a composite Predicate
, and from Figure 4-2 it is clear that this example
will include two AndPredicates
, an
OrPredicate
, and a NotPredicate
. The "AND" gate is modeled with
an AndPredicate
, and an "OR" gate
with an OrPredicate
. The "NAND" gate
is transformed into a three-input "AND" gate followed by an inverter
that is modeled with an AllPredicate
wrapped in a NotPredicate
.
The system has five inputs, which will be stored in a Map
with five keys: A, B, C, D, and E. A
simple InputPredicate
is developed to
handle the inputs to the system—a map of Boolean
input objects is passed to the
top-level Predicate
. An InputPredicate
is configured to evaluate the
input Map
and return the boolean
value of one of the inputs; in other
words, an InputPredicate
selects a boolean
value from a Map
, always returning
the value of that input from the Map
it evaluates. (See Example
4-7.)
Example 4-7. InputPredicate: a predicate that selects an input from a Map
package com.discursive.jccook.collections.predicate; import org.apache.commons.collections.Predicate; public class InputPredicate implements Predicate { private String inputKey; public BooleanPredicate(String inputKey) { this.inputKey = inputKey; } public boolean evaluate(Object object) { boolean satisfies = false; Map inputMap = (Map) object; Boolean input = (Boolean) inputMap.get( inputKey ); if( input != null ) { satisfies = input.booleanValue( ); } return satisfies; } }
The entire circuit is modeled by one top-level Predicate
and a Map
of Boolean
input signals is passed down a
hierarchy of predicates as needed. Unlike a real circuit, where inputs
would cause gates to fire sequentially, the predicate hierarchy is
evaluated from the final stage backward—the example evaluates the
Predicate
variable circuit
. The input map is passed to the
top-most Predicate
, which, in turn,
passes this same map to the Predicate
that precedes it in the circuit. Example
4-8 ties everything together, and the logic to create our
circuit-modeling predicate has been confined to the createPredicate()
method.
Example 4-8. Implementing a multilevel composite Predicate
package com.discursive.jccook.collections.predicate; import org.apache.commons.collections.Predicate; import org.apache.commons.collections.functors.*; public class CompoundPredicateExample { public static void main(String[] args) { CompoundPredicateExample example = new CompoundPredicateExample( ); example.start( ); } public void start( ) { Predicate circuit = createPredicate( ); Object[] inputsArray = new Object[][] { {"A", Boolean.TRUE}, {"B", Boolean.FALSE}, {"C", Boolean.TRUE}, {"D", Boolean.FALSE}, {"E", Boolean.FALSE} }; Map inputs = ArrayUtils.toMap( inputsArray ); boolean result = circuit.evaluate( inputs ); System.out.println( "The circuit fired?: " + result ); } public Predicate createPredicate( ) { Predicate aPredicate = new InputPredicate("A"); Predicate bPredicate = new InputPredicate("B"); Predicate cPredicate = new InputPredicate("C"); Predicate dPredicate = new InputPredicate("D"); Predicate ePredicate = new InputPredicate("E"); Predicate expression1 = new AndPredicate( aPredicate, bPredicate ); Predicate expression2 = new OrPredicate( cPredicate, dPredicate ); Predicate[] secondLevel = new Predicate( ) { expression1, expression2, ePredicate }; Predicate topLevel = new NotPredicate( secondLevel ); return topLevel; } }
This code prints The
circuit
fired?
: true
. This complex example has demonstrated
the process of modeling composite, multistage logic with a hierarchy of
predicates. A Predicate
is the most
basic functor and when combined with other Predicate
instances, there is no limit to the
level of complexity that can be achieved. Logic circuits were used in
this example because a logic gate is a great analogy for a
Predicate
. Think of a Predicate
as a component—a gate in a logic
circuit.