/* * $Id: Sorter.java,v 1.20 2006/07/05 05:07:09 dmouse Exp $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx.decorator; import java.text.Collator; import java.util.Comparator; import java.util.Locale; /** * Pluggable sorting filter. * * @author Ramesh Gupta */ public abstract class Sorter extends Filter { private boolean ascending = true; // JW: need to be updated if default locale changed private Collator collator; // RG: compute this once private Locale currentLocale; private Comparator comparator; public Sorter() { this(0, true); } public Sorter(int col, boolean ascending) { this(col, ascending, null); } public Sorter(int col, boolean ascending, Comparator comparator) { super(col); this.comparator = comparator; setAscending(ascending); } @Override protected void refresh(boolean reset) { refreshCollator(); super.refresh(reset); } /** * Subclasses must call this before filtering to guarantee the * correct collator! */ protected void refreshCollator() { if (!Locale.getDefault().equals(currentLocale)) { currentLocale = Locale.getDefault(); collator = Collator.getInstance(); } } /** * exposed for testing only! * @return <code>Collator</code> */ protected Collator getCollator() { return collator; } /** * set the Comparator to use when comparing values. * If not null every compare will be delegated to it. * If null the compare will follow the internal compare * (no contract, but implemented here as: * first check if the values are Comparable, if so * delegate, then compare the String representation) * * @param comparator */ public void setComparator(Comparator comparator) { this.comparator = comparator; refresh(); } public Comparator getComparator() { return this.comparator; } /** * Compares and returns the entries in row1 vs row2 and * returns -1, 0, -1 depending on their being <, ==, > in the * current sort direction. * * PRE: getColumnIndex() valid. * * NOTE: this formerly was public ... and without precondition. * * * @param row1 * @param row2 * @return returns -1, 0, -1 depending on row1/row2 being <, ==, > in the current sort direction */ protected int compare(int row1, int row2) { int result = compare(row1, row2, getColumnIndex()); return ascending ? result : -result; } /* Adapted from Phil Milne's TableSorter implementation. This implementation, however, is not coupled to TableModel in any way, and may be used with list models and other types of models easily. */ private int compare(int row1, int row2, int col) { Object o1 = getInputValue(row1, col); Object o2 = getInputValue(row2, col); // If both values are null return 0 if (o1 == null && o2 == null) { return 0; } else if (o1 == null) { // Define null less than everything. return -1; } else if (o2 == null) { return 1; } // JW: have to handle null first of all // Seemingly, Comparators are not required to handle null. Hmm... if (comparator != null) { return comparator.compare(o1, o2); } // make sure we use the collator for string compares if ((o1.getClass() == String.class) && (o2.getClass() == String.class)) { return collator.compare((String)o1, (String) o2); } // patch from Jens Elkner (#189) if ((o1.getClass().isInstance(o2)) && (o1 instanceof Comparable)) { Comparable c1 = (Comparable) o1; return c1.compareTo(o2); } else if (o2.getClass().isInstance(o1) && (o2 instanceof Comparable)) { Comparable c2 = (Comparable) o2; return -c2.compareTo(o1); } return collator.compare(o1.toString(), o2.toString()); } public boolean isAscending() { return ascending; } public void setAscending(boolean ascending) { this.ascending = ascending; refresh(); } public SortOrder getSortOrder() { return isAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING; } /** * Updates itself according to the SortKey's properties. * * @param sortKey * @throws IllegalArgumentException if sortKey = null * @throws IllegalArgumentException if !sortKey.sortOrder().isSorted */ public void setSortKey(SortKey sortKey) { if ((sortKey == null) || (!sortKey.getSortOrder().isSorted())) { throw new IllegalArgumentException("SortKey must not be null with sorted SortOrder"); } boolean forceRefresh = false; if (ascending != sortKey.getSortOrder().isAscending()) { forceRefresh = true; ascending = sortKey.getSortOrder().isAscending(); } if (((comparator != null) && !comparator.equals(sortKey.getComparator())) || ((comparator == null) && (sortKey.getComparator() != null))) { forceRefresh = true; comparator = sortKey.getComparator(); } // JW: take care of notification - refresh only if needed but then guarantee! // problem - if columns are the same, super does nothing if (getColumnIndex() != sortKey.getColumn()) { // super handles event notification forceRefresh = false; setColumnIndex(sortKey.getColumn()); } if (forceRefresh) { refresh(); } } public SortKey getSortKey() { return new SortKey(getSortOrder(), getColumnIndex(), getComparator()); } public void toggle() { ascending = !ascending; refresh(); } }