Class GeneralComparator
- All Implemented Interfaces:
Serializable
,Comparator
Skip to bottom of description.
Description Contents:
- Basic Operation
- Ascending/Descending Comparison
- Comparing values
- Multi field sorting
- Thread safety and reuse
- Efficiency
- Examples
Basic Operation
This class works by you specify a number of fields that are then
used to access into each of the objects. The term field is a bit mis-leading
since you can specify either a field name within a class or a zero argument
method name.
When specifying a field you can use dot notation to acess into return types/
field types.
For instance if you have the following class structure:
public class A { public B = new B (); } public class B { public C = new C (); } public class C { String d = ""; }
Then to perform comparison between between 2 objects of type A on
the d field in Class C you would specify a field of: B.C.d.
Notice that you don't specify the current class, all fields are taken relative
to the current type.
Similarly, if class C actually looked like:
public class C { private String d = ""; public String getD () { return d; } }
Then you would could use the same field since if a public field with the
specified name cannot be found then it is converted to a method name instead,
following JavaBeans conventions. So the d is converted to getD
and a public method is looked for (with no arguments) with that name. Finally if a
get* method cannot be found then a method with just the name, in this case d,
is searched for that takes no arguments. (Note: this has been added because there are a
number of cases where the standard java.*.* classes DO NOT follow the JavaBeans conventions
but you would want to use a GeneralComparator).
You can override the method name conversion by using the actual method name
(in case your method doesn't follow the JavaBeans convention), so if C
looked like:
public class C { private d = ""; public String getMyDField () { return d; } }
You would then use a field of: B.C.getMyDField in which case the method getMyDField would be looked for instead of looking for field.
Ascending/Descending Comparison
When you add a field you specify whether you want the comparison to occur in ascending or descending order. Setting to have a descending search just means that the result gained from comparing values is reversed.
Comparing values
If a field value equates to a public Java field in the object then
we call: java.lang.reflect.Field.get(Object)
on each
of the objects passed in and then if the type of the field implements
Comparable
then we call: Comparable.compareTo(Object,Object)
to get the appropriate value. If the type of the field does NOT
implement Comparable
then we call toString on the object
returned and then call String.compareTo(Object,Object)
for
the value to return instead.
Similarly we do the same when the field value equates to a public Java method
of the object. We call java.lang.reflect.Method.invoke(Object,Object[])
with a zero-length argument list and then if the return type of the method
implements Comparable
we call Comparable.compareTo(Object,Object)
with the result of the method calls to get our value. Otherwise we call
toString on the returned values and then call String.compareTo(Object,Object)
to get the value.
The upshot of all this is that you can sort a list of objects on values
returned from method calls or by fields that may be X levels deep within
an Object WITHOUT having to implement a complex version of the Comparable
interface yourself.
Multi field sorting
You can specify as many fields as you want to compare on, when the comparison is made (via the invalid input: '{@' #compare(Object,Object)} method) we only compare on as many fields as we need to, so if the result of the first field comparison indicates that the values are different we just return since there is no point doing further comparing, other fields will have no effect. Only if the values are equal do we move onto "lower" fields.
Thread safety and reuse
This class is NOT Thread safe and never will be (or should be...) since you would not want other threads modifying the fields you compare on. Once configured however the comparator is perfectly reusable since the only data it holds relates to the fields/methods that are accessed. If you do plan to use across multiple Threads then you need some kind of external synchronization, for example:
public synchronized void sortObjects (List objs) { Collections.sort (objs, myGeneralComparator); }
Efficiency
To try and access into the Object structure we use a Getter
which is basically a List of Field and Methods that should be
accessed/invoked when trying to find the value we require. Since we
are reliant on reflection for this the key factor is the cost of
traversing down the accessor chain (so the size of it will be important).
The accessor chain is gained when you call one of the add*** methods
so the finding of the method/field is a one-off.
Warning on Getters
The Getter class supports the [ ] notation for accessing Maps and Lists however in terms of comparisons and sorting this means little and should NOT be used!!! This is not checked because there may be situations where you would want to do it, however you would need to ensure that your Lists/Maps contain heterogeneous Objects.
JDOM Support
This class is capable of initing itself from a JDOM Element
and also to save it's current state into a JDOM element. This is
primarily to support the
invalid reference
ConfigList
invalid reference
ConfigMap
Format
When
invalid reference
#getAsJDOMElement()
invalid reference
#GeneralComparator(Element)
invalid input: '&#'lt;comparator class="[[NAME OF CLASS THAT IS BEING COMPARED]]"> invalid input: '&#'lt;field id="[[ACCESSOR VALUE INTO THE OBJECT]]" type="either ASC or DESC" /> invalid input: '&#'lt;!-- There can be X number of fields, the minimum is 1. --> invalid input: '&#'lt;/>
Examples
Say we wanted to sort a number of
invalid reference
Property
// Create a new GeneralComparator. GeneralComparator g = new GeneralComparator (.class); // Now we want to sort them on type first, then id then description, then // value. g.invalid reference
Property
addField
("type", GeneralComparator.ASC); g.addField
("getID", GeneralComparator.ASC); g.addField
("description", GeneralComparator.ASC); g.addField
("value", GeneralComparator.ASC); // Then (by magic...) get our collection of Properties that can // be sorted. List properties = Helper.getProperties (); // Now sort them using our comparator. Collections.sort (properties, g);
We could always set any of the fields to be a descending sort. Notice
here that we use "getID" since that method name is not using the
correct JavaBeans convention. Also,
invalid reference
Property
Comparable
interface but using the comparator will
override it.
Or if we wanted to sort a slice of messages from a
invalid reference
Logger
GeneralComparator g = new GeneralComparator (Logger.Message.class); // Sort on the time, but this time sort on the day of the Date // this is a deprecated method but it's not a biggie. We want // them in reverse order. g.addField ("time.day", GeneralComparator.DESC); // Get our slice... long time = System.currentTimeMillis (); Date now = new Date (time); Date oneDayAgo = new Date (time - (24*60*60*1000)); int types = Logger.Message.INFORMATION || Logger.Message.ERROR; List messages = myLogger.getMessages (now, oneDayAgo, types); Collections.sort (messages, g); // We could perform another sort but this time, sorting first // on the type, this should be in ascending order, i.e. ERROR first... g.addFieldBefore ("type", GeneralComparator.ASC, "time.day"); Collections.sort (messages, g);
A quite common need is to sort the entries in a Map rather than the keys but still maintain the key/value relationship. To do so use the code below:
// Get all the entries in the Map as a List. List l = new ArrayList (); l.addAll (myMap.entrySet ()); // Create a GeneralComparator. It is also possible to use the specific // implementation of the Map.Entry interface for the specific Map but // this way keeps it generic. GeneralCompartor gc = new GeneralComparator (Map.Entry.class); // Specify the "value" (i.e. getValue method). gc.addField ("value", GeneralComparator.DESC); // Sort. Collections.sort (l, gc);
- See Also:
-
Nested Class Summary
Nested Classes -
Field Summary
Fields -
Constructor Summary
ConstructorsConstructorDescriptionCreate a new GeneralComparator using the data held in the JDOM element. -
Method Summary
Modifier and TypeMethodDescriptionvoid
void
Add a field that we sort on, if you readd the same field then the type is just updated.void
addFieldAfter
(String field, String type, String ref) Add a new field in AFTER the named field, if we don't have the named field then we just calladdField(String,String)
which will add the field in after all the others.void
addFieldAtIndex
(String field, String type, int index) Add a new field in at the specified index.void
addFieldBefore
(String field, String type, String ref) Add a new field in BEFORE the named field, if we don't have the named field then we just calladdField(String,String)
which will add the field in after all the others.int
Implement theinvalid @link
{@link Comparator.compare(Object,Object)
boolean
Implement theinvalid @link
{@link Comparator.equals(Object)
int
getCount()
protected List
Return a List of GeneralComparator.SortField objects, this is used in theequals(Object)
method.void
removeField
(String field) Remove a field that we sort on.Methods inherited from class java.lang.Object
clone, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Methods inherited from interface java.util.Comparator
reversed, thenComparing, thenComparing, thenComparing, thenComparingDouble, thenComparingInt, thenComparingLong
-
Field Details
-
count
public int count -
ASC
Use to indicate that a field should be sorted in ascending order.- See Also:
-
DESC
Use to indicate that a field should be sorted in descending order.- See Also:
-
-
Constructor Details
-
GeneralComparator
Create a new GeneralComparator using the data held in the JDOM element.- Parameters:
root
- The root JDOM element.- Throws:
org.jdom.JDOMException
- If the format is incorrect.ChainException
- If we can't load the class that we need.IllegalArgumentException
- If the field is invalid.
-
-
Method Details
-
getCompareClass
-
addField
- Throws:
IllegalArgumentException
-
addFieldAtIndex
Add a new field in at the specified index. Remember that indices start at 0 and proceed in asceding order. If the index specified is invalid input: '<'0 then we add the field in at 0, moving everything else down by 1. If the index specified is >(fields.length - 1) then we just add to the end of the fields.- Parameters:
field
- The field to add.type
- The type, either GeneralComparator.ASC or GeneralComparator.DESC.index
- The index to add at.- Throws:
IllegalArgumentException
- If we can't find the field in the class/class chain passed into the constructor.
-
addFieldBefore
Add a new field in BEFORE the named field, if we don't have the named field then we just calladdField(String,String)
which will add the field in after all the others.- Parameters:
field
- The field to add.type
- Sort either ascending or descending, should be either GeneralComparator.ASC or GeneralComparator.DESC.ref
- The reference field.- Throws:
IllegalArgumentException
- If we can't find the field in the class/class chain passed into the constructor.
-
addFieldAfter
Add a new field in AFTER the named field, if we don't have the named field then we just calladdField(String,String)
which will add the field in after all the others.- Parameters:
field
- The field to add.type
- Sort either ascending or descending, should be either GeneralComparator.ASC or GeneralComparator.DESC.ref
- The reference field.- Throws:
IllegalArgumentException
- If we can't find the field in the class/class chain passed into the constructor.
-
removeField
Remove a field that we sort on. If we don't have the field then we do nothing.- Parameters:
field
- The field to remove.
-
addField
Add a field that we sort on, if you readd the same field then the type is just updated. The order in which you add the fields provides the order in which the objects are sorted.- Parameters:
field
- The field to sort on.type
- The type either GeneralComparator.ASC or GeneralComparator.DESC.- Throws:
IllegalArgumentException
- If we can't find the field in the class/class chain passed into the constructor.
-
compare
Implement theinvalid @link
{@link Comparator.compare(Object,Object)
- Specified by:
compare
in interfaceComparator
- Parameters:
obj1
- The first object.obj2
- The second object.- Returns:
- A value according to the rules laid out in
invalid @link
{@link Comparator.compare(Object,Object)
null
then we return 0 or we return 0 if either object returned from the accessor chain "get" call is null.
-
equals
Implement theinvalid @link
{@link Comparator.equals(Object)
- Specified by:
equals
in interfaceComparator
- Overrides:
equals
in classObject
- Parameters:
obj
- Another GeneralComparator.- Returns:
true
if all our fields match those in the passed in GeneralComparator AND that they are in the same order AND that they have the same type,false
otherwise.
-
getFields
Return a List of GeneralComparator.SortField objects, this is used in theequals(Object)
method.- Returns:
- A List of GeneralComparator.SortField objects.
-
getCount
public int getCount()
-