001: package org.apache.velocity.tools.generic;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.Comparator;
025: import java.util.Collection;
026: import java.util.Collections;
027: import java.util.List;
028: import java.util.Map;
029: import org.apache.commons.beanutils.PropertyUtils;
030:
031: /**
032: * SortTool allows a user to sort a collection (or array, iterator, etc)
033: * on any arbitary set of properties exposed by the objects contained
034: * within the collection.
035: *
036: * <p>The sort tool is specifically designed to use within a #foreach
037: * but you may find other uses for it.</p>
038: *
039: * <p>The sort tool can handle all of the collection types supported by
040: * #foreach and the same constraints apply as well as the following.
041: * Every object in the collection must support the set of properties
042: * selected to sort on. Each property which is to be sorted on must
043: * return one of the follow:
044: * <ul>
045: * <li>Primitive type: e.g. int, char, long etc</li>
046: * <li>Standard Object: e.g. String, Integer, Long etc</li>
047: * <li>Object which implements the Comparable interface.</li>
048: * </ul>
049: * </p>
050: *
051: * <p>During the sort operation all properties are compared by calling
052: * compareTo() with the exception of Strings for which
053: * compareToIgnoreCase() is called.</p>
054: *
055: * <p>The sort is performed by calling Collections.sort() after
056: * marshalling the collection to sort into an appropriate collection type.
057: * The original collection will not be re-ordered; a new list containing
058: * the sorted elements will always be returned.</p>
059: *
060: * <p>The tool is used as follows:
061: * <pre>
062: * Single Property Sort
063: * #foreach($obj in $sorter.sort($objects, "name"))
064: * $obj.name Ordinal= $obj.ordinal
065: * #end
066: * End
067: *
068: * Multiple Property Sort
069: * #foreach($obj in $sorter.sort($objects, ["name", "ordinal"]))
070: * $obj.name, $obj.ordinal
071: * #end
072: * End
073: * </pre>
074: *
075: * The sort method takes two parameters a collection and a property name
076: * or an array of property names. The property names and corresponding
077: * methods must conform to java bean standards since commons-beanutils
078: * is used to extract the property values.</p>
079: *
080: * <p>By default the sort tool sorts ascending, you can override this by
081: * adding a sort type suffix to any property name.</p>
082: *
083: * <p>The supported suffixes are:
084: * <pre>
085: * For ascending
086: * :asc
087: * For descending
088: * :desc
089: *
090: * Example
091: * #foreach($obj in $sorter.sort($objects, ["name:asc", "ordinal:desc"]))
092: * $obj.name, $obj.ordinal
093: * #end
094: * </pre><p>
095: *
096: * <p>This will sort first by Name in ascending order and then by Ordinal
097: * in descending order, of course you could have left the :asc off of the
098: * 'Name' property as ascending is always the default.</p>
099: *
100: * @author S. Brett Sutton
101: * @author Nathan Bubna
102: * @since VelocityTools 1.2
103: * @version $Id: SortTool.java 477914 2006-11-21 21:52:11Z henning $
104: */
105: public class SortTool {
106:
107: public Collection sort(Collection collection) {
108: return sort(collection, (List) null);
109: }
110:
111: public Collection sort(Object[] array) {
112: return sort(array, (List) null);
113: }
114:
115: public Collection sort(Map map) {
116: return sort(map, (List) null);
117: }
118:
119: /**
120: * Sorts the collection on a single property.
121: *
122: * @param object the collection to be sorted.
123: * @param property the property to sort on.
124: */
125: public Collection sort(Object object, String property) {
126: List properties = new ArrayList(1);
127: properties.add(property);
128:
129: if (object instanceof Collection) {
130: return sort((Collection) object, properties);
131: } else if (object instanceof Object[]) {
132: return sort((Object[]) object, properties);
133: } else if (object instanceof Map) {
134: return sort((Map) object, properties);
135: }
136: // the object type is not supported
137: return null;
138: }
139:
140: public Collection sort(Collection collection, List properties) {
141: List list = new ArrayList(collection.size());
142: list.addAll(collection);
143: return internalSort(list, properties);
144: }
145:
146: public Collection sort(Map map, List properties) {
147: return sort(map.values(), properties);
148: }
149:
150: public Collection sort(Object[] array, List properties) {
151: return internalSort(Arrays.asList(array), properties);
152: }
153:
154: protected Collection internalSort(List list, List properties) {
155: try {
156: if (properties == null) {
157: Collections.sort(list);
158: } else {
159: Collections.sort(list, new PropertiesComparator(
160: properties));
161: }
162: return list;
163: } catch (Exception e) {
164: //TODO: log this
165: return null;
166: }
167: }
168:
169: /**
170: * Does all of the comparisons
171: */
172: public class PropertiesComparator implements Comparator {
173: private static final int TYPE_ASCENDING = 1;
174: private static final int TYPE_DESCENDING = -1;
175:
176: public static final String TYPE_ASCENDING_SHORT = "asc";
177: public static final String TYPE_DESCENDING_SHORT = "desc";
178:
179: List properties;
180: int[] sortTypes;
181:
182: public PropertiesComparator(List properties) {
183: this .properties = properties;
184:
185: // determine ascending/descending
186: sortTypes = new int[properties.size()];
187:
188: for (int i = 0; i < properties.size(); i++) {
189: if (properties.get(i) == null) {
190: throw new IllegalArgumentException(
191: "Property "
192: + i
193: + "is null, sort properties may not be null.");
194: }
195:
196: // determine if the property contains a sort type
197: // e.g "Name:asc" means sort by property Name ascending
198: String prop = properties.get(i).toString();
199: int colonIndex = prop.indexOf(':');
200: if (colonIndex != -1) {
201: String sortType = prop.substring(colonIndex + 1);
202: properties.set(i, prop.substring(0, colonIndex));
203:
204: if (TYPE_ASCENDING_SHORT.equalsIgnoreCase(sortType)) {
205: sortTypes[i] = TYPE_ASCENDING;
206: } else if (TYPE_DESCENDING_SHORT
207: .equalsIgnoreCase(sortType)) {
208: sortTypes[i] = TYPE_DESCENDING;
209: } else {
210: //FIXME: log this
211: // invalide property sort type. use default instead.
212: sortTypes[i] = TYPE_ASCENDING;
213: }
214: } else {
215: // default sort type is ascending.
216: sortTypes[i] = TYPE_ASCENDING;
217: }
218: }
219: }
220:
221: public int compare(Object lhs, Object rhs) {
222: for (int i = 0; i < properties.size(); i++) {
223: int comparison = 0;
224: String property = (String) properties.get(i);
225:
226: // properties must be comparable
227: Comparable left = getComparable(lhs, property);
228: Comparable right = getComparable(rhs, property);
229:
230: if (left == null && right != null) {
231: // find out how right feels about left being null
232: comparison = right.compareTo(left);
233: // and reverse that (if it works)
234: comparison *= -1;
235: } else if (left instanceof String) {
236: //TODO: make it optional whether or not case is ignored
237: comparison = ((String) left)
238: .compareToIgnoreCase((String) right);
239: } else {
240: comparison = left.compareTo(right);
241: }
242:
243: // return the first difference we find
244: if (comparison != 0) {
245: // multiplied by the sort direction, of course
246: return comparison * sortTypes[i];
247: }
248: }
249: return 0;
250: }
251: }
252:
253: /**
254: * Safely retrieves the comparable value for the specified property
255: * from the specified object. Subclasses that wish to perform more
256: * advanced, efficient, or just different property retrieval methods
257: * should override this method to do so.
258: */
259: protected static Comparable getComparable(Object object,
260: String property) {
261: try {
262: return (Comparable) PropertyUtils.getProperty(object,
263: property);
264: } catch (Exception e) {
265: throw new IllegalArgumentException(
266: "Could not retrieve comparable value for '"
267: + property + "' from " + object + ": " + e);
268: }
269: }
270:
271: }
|