/* Copyright 2005-2006 Tim Fennell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sourceforge.stripes.util.bean;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
/**
* <p>A comparator which compares objects based on one or more bean properties. Nested properties
* are fully supported. If a property is non-String and implements {@link Comparable} then the
* {@code compareTo()} method is delegated to. Otherwise the property is converted to a String
* and a {@link Locale} aware {@link Collator} is used to to compare property values.</p>
*
* @author Tim Fennell
* @since Stripes 1.5
*/
public class BeanComparator implements Comparator<Object> {
private Locale locale;
private PropertyExpression[] expressions;
/**
* Constructs a BeanComparator for comparing beans based on the supplied set of properties,
* using the default system Locale to collate Strings.
*
* @param properties one or more property names to be used, in order, to sort beans
*/
public BeanComparator(String... properties) {
this(Locale.getDefault(), properties);
}
/**
* Constructs a BeanComparator for comparing beans based on the supplied set of properties,
* using the supplied Locale to collate Strings.
*
* @param locale the Locale to be used for collating Strings
* @param properties one or more property names to be used, in order, to sort beans
*/
public BeanComparator(Locale locale, String... properties) {
this.locale = locale;
this.expressions = new PropertyExpression[properties.length];
for (int i=0; i<properties.length; ++i) {
this.expressions[i] = PropertyExpression.getExpression(properties[i]);
}
}
/**
* <p>Compares two JavaBeans for order. Returns a negative integer, zero, or a positive
* integer as the first argument sorts earlier, equal to, or later than the second.</p>
*
* <p>Iterates through the properties supplied in the constructor comparing the values of
* each property for the two beans. As soon as a property is found that supplied a non-equal
* ordering, the ordering is returned. If all properties are equal, will return 0.</p>
*
* @param o1 the first object to be compared, must not be null.
* @param o2 the second object to be compared, must not be null.
* @return a negative integer, zero, or a positive integer as the first argument is less than,
* equal to, or greater than the second.
* @throws ClassCastException if the arguments' types, or the types of the properties,
* prevent them from being compared by this Comparator.
*/
@SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
int retval = 0;
Collator collator = Collator.getInstance(this.locale);
for (PropertyExpression expression : this.expressions) {
PropertyExpressionEvaluation e1 = new PropertyExpressionEvaluation(expression, o1);
PropertyExpressionEvaluation e2 = new PropertyExpressionEvaluation(expression, o2);
Object prop1 = e1.getValue();
Object prop2 = e2.getValue();
if (prop1 == null && prop2 == null) {
retval = 0;
}
else if (prop1 == null) {
retval = 1;
}
else if (prop2 == null) {
retval = -1;
}
else if ( !(prop1 instanceof String) && prop1 instanceof Comparable) {
retval = ((Comparable) prop1).compareTo(prop2);
}
else {
String string1 = prop1.toString();
String string2 = prop2.toString();
retval = collator.compare(string1, string2);
}
if (retval != 0) break;
}
return retval;
}
}