001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2005 Danet GmbH (www.danet.de), BU TEL.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: BeanSorter.java,v 1.7 2006/09/29 12:32:08 drmlipp Exp $
021: *
022: * $Log: BeanSorter.java,v $
023: * Revision 1.7 2006/09/29 12:32:08 drmlipp
024: * Consistently using WfMOpen as projct name now.
025: *
026: * Revision 1.6 2005/10/24 14:15:15 drmlipp
027: * Made the sorter serializable.
028: *
029: * Revision 1.5 2005/09/28 20:49:00 mlipp
030: * Fixed problem with comparingTimestamp and Date.
031: *
032: * Revision 1.4 2005/09/28 15:10:16 drmlipp
033: * Made initializable by JSF.
034: *
035: * Revision 1.3 2005/09/09 20:48:11 mlipp
036: * Removed suspicious import.
037: *
038: * Revision 1.2 2005/08/26 13:29:30 drmlipp
039: * Fixed.
040: *
041: * Revision 1.1 2005/06/22 07:43:08 drmlipp
042: * New utility class.
043: *
044: */
045: package de.danet.an.util;
046:
047: import java.io.Serializable;
048: import java.lang.reflect.InvocationTargetException;
049: import java.sql.Timestamp;
050: import java.util.ArrayList;
051: import java.util.Collections;
052: import java.util.Comparator;
053: import java.util.Date;
054: import java.util.Iterator;
055: import java.util.List;
056: import java.util.StringTokenizer;
057:
058: import org.apache.commons.beanutils.PropertyUtils;
059:
060: /**
061: * This class provides a base class for implementing sortable tables.
062: *
063: * @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
064: * @version $Revision: 1.7 $
065: */
066:
067: public class BeanSorter implements Serializable {
068:
069: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
070: .getLog(BeanSorter.class);
071:
072: private boolean modified = false;
073:
074: /**
075: * A single criterion for sorting.
076: */
077: public static class SortCriterion implements Serializable {
078: private String property = null;
079: private boolean isAscending = true;
080:
081: /**
082: * Create a new sort criterion with the attributes set to the
083: * given values.
084: * @param property
085: * @param isAscending
086: */
087: public SortCriterion(String property, boolean isAscending) {
088: this .property = property;
089: this .isAscending = isAscending;
090: }
091:
092: /**
093: * Create a new sort criterion from the given string.
094: * @param criterion as
095: * "<code>property_name [ '/' ( 'ascending' | 'descending' ) ]</code>"
096: */
097: public SortCriterion(String criterion) {
098: StringTokenizer st = new StringTokenizer(criterion, "/");
099: property = st.nextToken();
100: if (st.hasMoreTokens()) {
101: if (st.nextToken().toLowerCase().startsWith("d")) {
102: isAscending = false;
103: }
104: }
105: }
106:
107: /**
108: * @return Returns the property.
109: */
110: public String getProperty() {
111: return property;
112: }
113:
114: /**
115: * @param property The property to set.
116: */
117: public void setProperty(String property) {
118: this .property = property;
119: }
120:
121: /**
122: * @return Returns the isAscending.
123: */
124: public boolean isAscending() {
125: return isAscending;
126: }
127:
128: /**
129: * @param isAscending The isAscending to set.
130: */
131: public void setAscending(boolean isAscending) {
132: this .isAscending = isAscending;
133: }
134:
135: /**
136: * @see java.lang.Object#toString()
137: */
138: public String toString() {
139: return property
140: + (isAscending ? "/ascending" : "/descending");
141: }
142: }
143:
144: private List sortCriteria = null;
145:
146: /**
147: * Creates an instance of <code>BeanSorter</code>
148: * with all attributes initialized to default values.
149: */
150: public BeanSorter() {
151: }
152:
153: /**
154: * Get the value of the highest propritized sort property.
155: * @return value of sort property
156: * @see #setSortColumn
157: */
158: public String getPrimarySortProperty() {
159: return (sortCriteria == null ? null
160: : ((SortCriterion) sortCriteria.get(0)).getProperty());
161: }
162:
163: /**
164: * Set the value of the primary sort property.
165: * @param property value to assign to sort property
166: * @see #getPrimarySortProperty
167: */
168: public void setPrimarySortProperty(String property) {
169: String primProp = getPrimarySortProperty();
170: if (primProp == null || !primProp.equals(property)) {
171: if (sortCriteria == null) {
172: sortCriteria = new ArrayList();
173: } else {
174: for (Iterator i = sortCriteria.iterator(); i.hasNext();) {
175: SortCriterion crit = (SortCriterion) i.next();
176: if (crit.getProperty().equals(property)) {
177: i.remove();
178: }
179: }
180: }
181: sortCriteria.add(0, new SortCriterion(property, true));
182: modified = true;
183: if (logger.isDebugEnabled()) {
184: logger.debug("New sort criteria: " + toString());
185: }
186: }
187: }
188:
189: /**
190: * Get the value of sort ascending of the primary sort property.
191: * @return value of sort ascending
192: * @see #setSortAscending
193: */
194: public boolean isPrimarySortAscending() {
195: return (sortCriteria == null ? true
196: : ((SortCriterion) sortCriteria.get(0)).isAscending());
197: }
198:
199: /**
200: * Set the value of sort ascending for the primary sort property.
201: * @param ascending value to assign to sort ascending
202: * @see #getPrimarySortAscending
203: */
204: public void setPrimarySortAscending(boolean ascending) {
205: if (sortCriteria != null
206: && isPrimarySortAscending() != ascending) {
207: ((SortCriterion) sortCriteria.get(0))
208: .setAscending(ascending);
209: modified = true;
210: if (logger.isDebugEnabled()) {
211: logger.debug("New sort criteria: " + toString());
212: }
213: }
214: }
215:
216: /**
217: * Get all sort critera as a list of strings denoting a criterion as
218: * "<code>property_name [ '/' ( 'ascending' | 'descending' ) ]</code>"
219: * @return the list of sort criteria
220: */
221: public List getSortCriteria() {
222: if (sortCriteria == null) {
223: return null;
224: }
225: return Collections.unmodifiableList(sortCriteria);
226: }
227:
228: /**
229: * Set all sort criteria from the given list of strings.
230: * @param criteria a list of criteria as
231: * "<code>property_name [ '/' ( 'ascending' | 'descending' ) ]</code>"
232: */
233: public void setSortCriteria(List criteria) {
234: if (sortCriteria == null) {
235: sortCriteria = new ArrayList();
236: } else {
237: sortCriteria.clear();
238: }
239: for (Iterator i = criteria.iterator(); i.hasNext();) {
240: sortCriteria.add(new SortCriterion((String) i.next()));
241: }
242: }
243:
244: /**
245: * Return if the sort criteria have been modified since the last invocation
246: * of sort.
247: * @param data
248: * @throws IllegalArgumentException
249: */
250: public boolean isModified() {
251: return modified;
252: }
253:
254: /**
255: * Sort the given argument using the previously specified properties. The
256: * objects in data must have methods <code>get<i>sortColumn</i></code>.
257: * @param data the data to be sorted.
258: * @throws IllegalArgumentException if the data items do not have the
259: * required get method
260: */
261: public void sort(List data) throws IllegalArgumentException {
262: sort(data, sortCriteria);
263: modified = false;
264: }
265:
266: private class DataComparator implements Comparator {
267:
268: private List sortCriteria;
269:
270: /**
271: * Create a new instance with the given sort criteria
272: */
273: public DataComparator(List sortCriteria) {
274: this .sortCriteria = sortCriteria;
275: }
276:
277: /**
278: * Compare the given objects.
279: * @param o1 one object
280: * @param o2 other object
281: */
282: public int compare(Object o1, Object o2) {
283: if (sortCriteria == null) {
284: return 0;
285: }
286: for (Iterator i = sortCriteria.iterator(); i.hasNext();) {
287: SortCriterion criterion = (SortCriterion) i.next();
288: if (criterion.getProperty() == null) {
289: break;
290: }
291: try {
292: Object val1 = PropertyUtils.getProperty(o1,
293: criterion.getProperty());
294: if (!(val1 instanceof Comparable)) {
295: continue;
296: }
297: Object val2 = PropertyUtils.getProperty(o2,
298: criterion.getProperty());
299: if (val2 == null) {
300: return 1;
301: }
302: // Comparing Timestamp with Date raises ClassCastException
303: if ((val1 instanceof Timestamp)
304: && (val2 instanceof Date)) {
305: val1 = new Date(((Timestamp) val1).getTime());
306: }
307: int cmpRes = ((Comparable) val1).compareTo(val2);
308: if (cmpRes == 0) {
309: continue;
310: }
311: if (!criterion.isAscending()) {
312: cmpRes = cmpRes * -1;
313: }
314: return cmpRes;
315: } catch (IllegalAccessException e) {
316: throw new IllegalArgumentException(e.getMessage());
317: } catch (InvocationTargetException e) {
318: throw new IllegalArgumentException(e.getMessage());
319: } catch (NoSuchMethodException e) {
320: throw new IllegalArgumentException(e.getMessage());
321: }
322: }
323: return 0;
324: }
325: }
326:
327: /**
328: * Sort the given argument using the specified parameters. The default
329: * implementation uses reflection to get the values to be compared.
330: * The objects in <code>data</code> must therefore have methods
331: * <code>get<i>sortProp</i></code> for each sort criterion used.
332: * @param data the data to be sorted.
333: * @param sortCriteria an array of tuples { String sortProperty,
334: * Boolean sortAscending } as <code>Object[2]</code>
335: * @throws IllegalArgumentException if the data items do not have the
336: * required get method
337: */
338: protected void sort(List data, List sortCriteria)
339: throws IllegalArgumentException {
340: Collections.sort(data, new DataComparator(sortCriteria));
341: }
342:
343: /**
344: * @see java.lang.Object#toString()
345: */
346: public String toString() {
347: StringBuffer res = new StringBuffer("BeanSorter[");
348: boolean first = true;
349: if (sortCriteria != null) {
350: for (Iterator i = sortCriteria.iterator(); i.hasNext();) {
351: if (first) {
352: first = false;
353: } else {
354: res.append(",");
355: }
356: res.append(i.next().toString());
357: }
358: }
359: res.append("]");
360: return res.toString();
361: }
362: }
|