package com.onaro.util.jfc.grouping; import java.util.Comparator; import com.netapp.oci.server.annotation.data.AnnotationValue; import com.onaro.client.annotations.ui.AnnotationsUtils; import com.onaro.commons.lang.InterruptedRuntimeException; /** * Manages the sorting of a grouping table. It keeps the current sorting configuration * as a list of column number and sorting order. It also provides the comparators * for performing the sorting. *

* Node, all column numbers in this class refer to a model containing the navigation * column. */ public class GroupingSorter { private static final int FULL_SORT_THRESHOLD = 5000; /** * Tells the order that columns are evaluated and the direction of each column * (ascending or descending). This array is initialized only when needed. * Indexed by the precedence of the column in the comparison order, so 0 will * be the first compared column. */ private Column[] sortingColumns; /** * Maps column numbers to the index in {@link GroupingSorter#sortingColumns}. */ private int[] columnPosition; /** * When sorting is changed by appending columns (as opposed as replacing the * most significant one), this variable keeps track on the position of the * last appended column. It is reset to "1" whenever sorting changes in a * not-appending mode. */ private int nextAppendColumnPosition = 1; /** * A comparator that compares all the columns of two rows. The order of the * columns is determined by the {@link GroupingSorter#sortingColumns} array. */ private final Comparator nodeComparator = new NodeComparator(); private int rowCount = 0; private boolean cancelSorting = false; /** * Default constructor of the grouping sorter */ public GroupingSorter() { } /** * Create a new GroupingSorter with the state matching the cloneSource sorter. * @param cloneSource the source sorter */ public GroupingSorter(GroupingSorter cloneSource) { // Copy the columns Column[] sourceColumns = cloneSource.sortingColumns; if (sourceColumns == null) { this.sortingColumns = null; } else { this.sortingColumns = new Column[sourceColumns.length]; for (int i = 0; i < sourceColumns.length; i++) { Column sourceColumn = sourceColumns[i]; this.sortingColumns[i] = new Column(sourceColumn.number, sourceColumn.ascending); } } int[] sourcePosition = cloneSource.columnPosition; if (sourcePosition == null) { this.columnPosition = null; } else { this.columnPosition = new int[sourcePosition.length]; System.arraycopy(sourcePosition, 0, this.columnPosition, 0, sourcePosition.length); } this.nextAppendColumnPosition = cloneSource.nextAppendColumnPosition; this.rowCount = cloneSource.rowCount; } /** * A comparator that allows to compare values even when the values are of different * classes. */ public final static Comparator VALUE_COMPARATOR = new Comparator() { @SuppressWarnings("unchecked") public int compare(Object o1, Object o2) { if (o1 == o2) return 0; if (o1 == null) return -1; if (o2 == null) return 1; if (o1 instanceof String && o2 instanceof String) { return ((String) o1).compareToIgnoreCase((String) o2); } if (o1.getClass().equals(o2.getClass()) && o1 instanceof Comparable) { return ((Comparable)o1).compareTo(o2); } if (o1 instanceof AnnotationValue && o2 instanceof AnnotationValue) { return AnnotationsUtils.ANNOTATION_VALUE_COMPARATOR.compare((AnnotationValue)o1, (AnnotationValue)o2); } if (o1 instanceof Comparable && o1.getClass().isAssignableFrom(o2.getClass())) { return ((Comparable) o1).compareTo(o2); } if (o2 instanceof Comparable && o2.getClass().isAssignableFrom(o1.getClass())) { return -((Comparable) o2).compareTo(o1); } return o1.toString().compareToIgnoreCase(o2.toString()); } }; /** * Initialize according to the number of columns in the sorted table. In order * to keep the user's sorting preferences, this call is ignored if the number * of columns didn't change since the previous invocation. * * @param columnCount the number of column in the table (including the navigation column). */ public void setColumnCount(int columnCount) { int sortingColumnsCount = columnCount == 0 ? 0 : columnCount - 1; if (sortingColumns == null || sortingColumns.length != sortingColumnsCount) { sortingColumns = new Column[sortingColumnsCount]; for (int i = 0; i < sortingColumnsCount; ++i) { sortingColumns[i] = new Column(i + 1, true); } columnPosition = new int[columnCount]; for (int col = 0; col < columnCount; ++col) { columnPosition[col] = col - 1; } } } public int getColumnCount() { return columnPosition == null ? 0 : columnPosition.length; } /** * Gets the node comparator. * * @return the node comparator for comparing two rows */ public Comparator getNodeComparator() { return nodeComparator; } /** * Tells the current sorting order of the given column. * * @param column the column to get its sorting order * @return true if the column is sorted in ascending order */ public boolean isAscending(int column) { assert column > 0 : "Column 0 isn't sortable"; //$NON-NLS-1$ assert column < columnPosition.length : "Column " + column + " is out of bounds (" + columnPosition.length + " columns)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return sortingColumns[columnPosition[column]].ascending; } /** * Gets the number of the last column that was selected for sorting. * * @return column number */ public int getSortingColumn() { if (sortingColumns != null && sortingColumns.length > 0) { return sortingColumns[0].number; } return -1; } /** * Checks to see whether the given column is sorted. * @param column the column index * @return true if the column is sorted, false otherwise. */ public boolean isSorted(int column) { return getSortingPrecedence(column) != -1; } /** * Finds the precedence (order) of the specified column among the columns used for * sorting. * @param column to column to get it's precedence * @return the precedence of the column or -1 if it's not used for sorting */ public int getSortingPrecedence(int column) { if (column == 0) return -1; assert column < columnPosition.length : "Column " + column + " is out of bounds (" + columnPosition.length + " columns)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ if (sortingColumns != null && sortingColumns.length > 0) { return (columnPosition[column] < nextAppendColumnPosition) ? columnPosition[column] : -1; } return -1; } /** * Gets the precedence/order of the next column that will be appended to the list * of sorting column. * @return value of 1 signifies that only one column is used for sorting */ public int getNextAppendColumnPosition() { return nextAppendColumnPosition; } /** * Set the ascending order of a column and making it the first column that * is compared when comparing nodes. * * @param column the sorting column * @param ascending true if the sorting order should be ascending */ public void setAscending(int column, boolean ascending) { assert column > 0 : "Column 0 isn't sortable"; //$NON-NLS-1$ assert column < columnPosition.length : "Column " + column + " is out of bounds (" + columnPosition.length + " columns)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ int currColPos = columnPosition[column]; Column col = sortingColumns[currColPos]; col.ascending = ascending; if (currColPos > 0) { nextAppendColumnPosition = 0; setSorting(column); } nextAppendColumnPosition = 1; } /** * Set the ascending order of a column and append it the last sorting column that * is compared when comparing nodes. * * @param column the sorting column * @param ascending true if the sorting order should be ascending */ public void appendAscending(int column, boolean ascending) { assert column > 0 : "Column 0 isn't sortable"; //$NON-NLS-1$ assert column < columnPosition.length : "Column " + column + " is out of bounds (" + columnPosition.length + " columns)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ int currColPos = columnPosition[column]; Column col = sortingColumns[currColPos]; col.ascending = ascending; if (currColPos >= nextAppendColumnPosition) { setSorting(column); } } /** * Updates the sorting criterion, resetting the order of columns and the direction * according to the user's request. *

By default, the specified column will become * the most significant column. If "append" was specified, the specified column * will be moved after the last, previously appended column. *

The direction of sorting by the specified column will be toggled if order * the column are compared wasn't modified. * @param column the column to update * @param append false if the column should become the most significant column, * true if the last appended column */ public void updateSorting(int column, boolean append) { assert column > 0 : "Column 0 isn't sortable"; //$NON-NLS-1$ assert column < columnPosition.length : "Column " + column + " is out of bounds (" + columnPosition.length + " columns)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ int currColPos = columnPosition[column]; Column col = sortingColumns[currColPos]; /** * When not in "append" mode, if the column is already the most significant * column, just toggle it's direction. Otherwise, promote the column but * keep it's direction. */ if (!append) { if (currColPos == 0) { col.ascending = !col.ascending; } else { setAscending(column, col.ascending); } nextAppendColumnPosition = 1; } /** * When in append mode, if the column was already selected for sorting, then * only toggle it's direction. Otherwise, promote the column and keep * the direction. */ else { if (currColPos < nextAppendColumnPosition) { col.ascending = !col.ascending; } else { setSorting(column); } } } private void setSorting (int column) { int currColPos = columnPosition[column]; Column col = sortingColumns[currColPos]; for (int from = currColPos - 1; from >= nextAppendColumnPosition; --from) { int to = from + 1; sortingColumns[to] = sortingColumns[from]; columnPosition[sortingColumns[to].number] = to; } sortingColumns[nextAppendColumnPosition] = col; columnPosition[column] = nextAppendColumnPosition; ++nextAppendColumnPosition; } void setRowCount(int rowCount) { this.rowCount = rowCount; } void setSortingCanceled(boolean cancelSorting) { this.cancelSorting = cancelSorting; } /** * Compare two nodes by comparing all their values according to the order * of columns in {@link GroupingSorter#sortingColumns}. */ class NodeComparator implements Comparator { public int compare(Node node1, Node node2) { if (cancelSorting) { throw new InterruptedRuntimeException(); } // If the number of rows to be sorted is small, consider all columns for // comparing nodes. If the rows is large, only consider the sorting columns // the user selected. int loopMax = (rowCount < FULL_SORT_THRESHOLD) ? sortingColumns.length : nextAppendColumnPosition; for (int i = 0; i < loopMax; ++i) { int col = sortingColumns[i].number; Object val1 = node1.getValueAt(col); Object val2 = node2.getValueAt(col); int comp = VALUE_COMPARATOR.compare(val1, val2); if (comp != 0) { if (sortingColumns[i].ascending) return comp; else return -comp; } } return 0; } } /** * Holds the number and sorting order of a column. */ static class Column { /** * The column number. */ final int number; /** * If true, than this column is sorted in ascending order. */ boolean ascending; /** * Sets the column number and ascending. * * @param number the column number. * @param ascending if true, than this column is sorted in ascending order */ public Column(int number, boolean ascending) { this.number = number; this.ascending = ascending; } } }