You need to sort a collection of objects that have a preestablished order, such as the days of the week or the order of planets in the solar system.
Use FixedOrderComparator in
Commons Collections. When using FixedOrderComparator, you supply an array of
objects in a sorted order and the Comparator returns comparison results based on
the order of the objects in this array. The following example uses a
fixed string array to compare different Olympic medals:
import java.util.*;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.FixedOrderComparator;
String[] medalOrder = {"tin", "bronze", "silver", "gold", "platinum"};
Comparator medalComparator = new FixedOrderComparator( medalOrder );
Comparator athleteComparator = new BeanComparator( "medal", medalComparator );
Athlete athlete1 = new Athlete( );
Athlete athlete2 = new Athlete( );
int compare = medalComparator.compare( athlete1.getMedal( ),
athlete2.getMedal( ) );In this code, a FixedOrderComparator compares two Athletes by the value of the medal property. The medal property can be "tin," "bronze,"
"silver," "gold," or "platinum," and a FixedOrderComparator uses the order of the
medalOrder array to compare these
values. The medalOrder array
establishes a fixed relationship between the three medal types; "bronze"
is less than "silver," which is less than "gold."
Use FixedOrderComparator when
sorting an array or a collection that contains values that are ordered
in a pre-determined fashion: days of the week, planets in the solar
system, colors in the spectrum, or hands dealt in a poker game. One way
to sort an array containing the days of the week would be to assign a
numerical value to each day—"Monday" is one, "Tuesday" is two,
"Wednesday" is three, etc. Then you could sort the array with a Comparator that takes each day's name, sorting
elements based on the numerical value corresponding to a day's name. An
alternative is the use of FixedOrderComparator, letting the comparator
order objects based on the order of an array of day names.
If a bean contains a property to be sorted according to a
fixed-order array, you can use the BeanComparator in conjunction with FixedOrderComparator. The following example
sorts cards by value and suit using a FixedOrderComparator and a BeanComparator; A PlayingCard object, defined in Example 4-3, is sorted according to the
order of two arrays—one for the face value of the PlayingCard and one for the suit of the
PlayingCard.
Example 4-3. A bean representing a playing card
package org.discursive.jccook.collections.compare;
public class PlayingCard( ) {
public static String JOKER_VALUE = null;
public static String JOKER_SUIT = null;
private String value;
private String suit;
public PlayingCard( ) {}
public PlayingCard(String value, String suit) {
this.value = value;
this.suit = suit;
}
public String getValue( ) { return value; }
public void setValue(String value) { this.value = value; }
public String getSuit( ) { return suit; }
public void setSuit(String suit) { this.suit = suit; }
public String toString( ) {
String cardString = "JOKER";
if( value != null && suit != null ) {
cardString = value + suit;
}
return cardString;
}
}Example 4-4 creates a
ComparatorChain of BeanComparators, which compares the value and suit properties using a FixedOrderComparator. Each card's suit is
compared first, and, if two cards have the same suit, they are compared
by face value. Jokers do not have suits or a face value, and this
example handles jokers with a null-valued suit and value property by wrapping each FixedOrderComparator with a NullComparator.
Example 4-4. Combining FixedOrderComparator with BeanComparator, NullComparator, and ComparatorChain
package com.discursive.jccook.collections.compare;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.NullComparator;
import org.apache.commons.collections.comparators.FixedOrderComparator;
import org.apache.commons.collections.comparators.ComparatorChain;
public class FixedOrderExample {
// Suit order "Spades", "Clubs", "Diamonds", "Hearts"
private String[] suitOrder = { "S", "C", "D", "H" };
private String[] valueOrder = { "2", "3", "4", "5", "6", "7", "8",
"9", "10", "J", "Q", "K", "A" };
public static void main(String[] args) {
FixedOrderExample example = new FixedOrderExample( );
example.start( );
}
public void start( ) {
List cards = new ArrayList( );
cards.add( PlayingCard( "J", "C" ) );
cards.add( PlayingCard( "2", "H" ) );
cards.add( PlayingCard( PlayingCard.JOKER_VALUE, PlayingCard.
JOKER_SUIT));
cards.add( PlayingCard( "2", "S" ) );
cards.add( PlayingCard( "Q", "S" ) );
cards.add( PlayingCard( "4", "C" ) );
cards.add( PlayingCard( "J", "D" ) );
System.out.println( "Before sorting: " + printCards( cards ) );
// Create a null-safe suit order comparator that will compare the
// suit property of two Java beans
Comparator suitComparator = new FixedOrderComparator( suitOrder );
suitComparator = new NullComparator( suitComparator );
suitComparator = new BeanComparator( "suit", suitComparator );
// Create a null-safe value order comparator that will compare the
// value property of two Java beans
Comparator valueComparator = new FixedOrderComparator( valueOrder );
valueComparator = new NullComparator( valueComparator );
valueComparator = new BeanComparator( "value", valueComparator );
// Create a chain of comparators to sort a deck of cards
Comparator cardComparator = new ComparatorChain( );
cardComparator.addComparator( suitComparator );
cardComparator.addComparator( valueComparator );
Collections.sort( cards, cardComparator );
System.out.println( "After sorting: " + printCards( cards ) );
}
private String printCards( List cards ) {
StringBuffer resultBuffer = new StringBuffer( );
Iterator cardIter = cards.iterator( );
while( cardIter.hasNext( ) ) {
PlayingCard card = (PlayingCard) cards.next( );
resultBuffer.append( " " + card.toString( ) );
}
return resultBuffer.toString( );
}
}This example sorts the PlayingCard objects and produces the following
output:
Before sorting: JC 2H JOKER 2S QS 4C JD After sorting: 2S QS 4C JC JD 2H JOKER
The list is sorted such that all the cards of a similar suit are
grouped together—spades are less than clubs, clubs are less than
diamonds, and diamonds are less than hearts. A sorted collection of
cards is grouped by suits, and, within each suit, cards are organized
according to face value—2 is low and aces is high. The order used in the
sorting is captured in two fixed-order arrays, suitOrder and faceOrder. If a shuffled deck were used in the
example, it would end up as a perfectly sorted deck of cards.
Example 4-4 ties a number of
simple Comparators together to
perform a fairly complex sort. A FixedOrderComparator is wrapped in a NullComparator, which is then wrapped with a
BeanComparator. These BeanComparator instances are then combined in
a ComparatorChain. The use of
NullComparator with a BeanComparator is recommended to avoid a
NullPointerException from BeanComparator; BeanComparator is not designed to handle
null-valued bean properties, and it
throws an exception if you ask it to play nice with nulls.
BeanComparator is discussed in
Recipe 3.10. This helpful
utility is indispensable if you are working with systems that need to
sort JavaBeans.
For more information about the ComparatorChain object, see Recipe 4.4. For more information
on the NullComparator, see Recipe 4.5.
