/**
*
*/
package prefuse.data.util;
import java.util.Arrays;
import java.util.Comparator;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.expression.Predicate;
import prefuse.data.tuple.TupleSet;
import prefuse.util.collections.CompositeComparator;
import prefuse.util.collections.NullComparator;
/**
* <p>Utility class representing sorting criteria, this can be given as
* input to the {@link TupleSet#tuples(Predicate, Sort)} method to
* get a sorted iteration of tuples.</p>
*
* <p>Sort criteria consists of an ordered list of data field names to
* sort by, along with an indication to sort tuples in either ascending
* or descending order. These criteria can be passed in to the
* constructor or added incrementally using the
* {@link #add(String, boolean)} method.</p>
*
* <p>Alternatively, one can also specify the sorting criteria using a
* single string, which is parsed using the {@link #parse(String)} method.
* This string should consist
* of a comma-delimited list of field names, which optional "ASC" or
* "DESC" modifiers to specify ascending or descending sorts. If no
* modifier is given, ascending order is assumed. Field
* names which include spaces or other non-standard characters should
* be written in brackets ([]), just as is done in
* {@link prefuse.data.expression.parser.ExpressionParser expression
* language statements}. For example, the
* following string</p>
*
* <pre>"Profit DESC, [Product Type]"</pre>
*
* <p>sorts first by the data field "Profit" in descending order,
* additionally sorting in ascending order by the data field
* "Product Type" for tuples which have identical values in the
* "Profit" field.</p>
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class Sort {
private static final String ASC = " ASC";
private static final String DESC = " DESC";
private static final String asc = ASC.toLowerCase();
private static final String desc = DESC.toLowerCase();
private String[] m_fields;
private boolean[] m_ascend;
/**
* Creates a new, empty Sort specification.
*/
public Sort() {
this(new String[0], new boolean[0]);
}
/**
* Creates a new Sort specification that sorts on the
* given fields, all in ascending order.
* @param fields the fields to sort on, in order of precedence
*/
public Sort(String[] fields) {
this(fields, new boolean[fields.length]);
Arrays.fill(m_ascend, true);
}
/**
* Creates a new Sort specification that sorts on the
* given fields in the given orders.
* @param fields the fields to sort on, in order of precedence
* @param ascend for each field, indicates if the field should
* be sorted in ascending (true) or descending (false) order
*/
public Sort(String[] fields, boolean[] ascend) {
m_fields = fields;
m_ascend = ascend;
}
/**
* Adds a new field to this Sort specification.
* @param field the additional field to sort on
* @param ascend indicates if the field should
* be sorted in ascending (true) or descending (false) order
*/
public void add(String field, boolean ascend) {
String[] f = new String[m_fields.length+1];
System.arraycopy(m_fields, 0, f, 0, m_fields.length);
f[m_fields.length] = field;
m_fields = f;
boolean[] b = new boolean[m_fields.length+1];
System.arraycopy(m_ascend, 0, b, 0, m_ascend.length);
b[m_ascend.length] = ascend;
m_ascend = b;
}
/**
* Returns the number of fields in this Sort specification.
* @return the number of fields to sort on
*/
public int size() {
return m_fields.length;
}
/**
* Returns the sort field at the given index.
* @param i the index to look up
* @return the sort field at the given index
*/
public String getField(int i) {
return m_fields[i];
}
/**
* Returns the ascending modifier as the given index.
* @param i the index to look up
* @return true if the field at the given index is to be sorted
* in ascending order, false for descending order
*/
public boolean isAscending(int i) {
return m_ascend[i];
}
/**
* Generates a Comparator to be used for sorting tuples drawn from
* the given tuple set.
* @param ts the TupleSet whose Tuples are to be sorted
* @return a Comparator instance for sorting tuples from the given
* set using the sorting criteria given in this specification
*/
public Comparator getComparator(TupleSet ts) {
// get the schema, so we can lookup column value types
Schema s = null;
if ( ts instanceof Table ) {
// for Tables, we can get this directly
s = ((Table)ts).getSchema();
} else {
// if non-table tuple set is empty, we punt
if ( ts.getTupleCount() == 0 )
return new NullComparator();
// otherwise, use the schema of the first tuple in the set
s = ((Tuple)ts.tuples().next()).getSchema();
}
// create the comparator
CompositeComparator cc = new CompositeComparator(m_fields.length);
for ( int i=0; i<m_fields.length; ++i ) {
cc.add(new TupleComparator(m_fields[i],
s.getColumnType(m_fields[i]), m_ascend[i]));
}
return cc;
}
// ------------------------------------------------------------------------
private static void subparse(String s, Object[] res) {
s = s.trim();
// extract ascending modifier first
res[1] = Boolean.TRUE;
if ( s.endsWith(DESC) || s.endsWith(desc) ) {
res[1] = Boolean.FALSE;
s = s.substring(0, s.length()-DESC.length()).trim();
} else if ( s.endsWith(ASC) || s.endsWith(asc) ) {
s = s.substring(0, s.length()-ASC.length()).trim();
}
if ( s.startsWith("[") ) {
if ( s.lastIndexOf("[") == 0 &&
s.endsWith("]") && s.indexOf("]") == s.length() ) {
res[0] = s.substring(1, s.length()-1);
} else {
throw new RuntimeException();
}
} else {
if ( s.indexOf(" ") < 0 && s.indexOf("\t") < 0 ) {
res[0] = s;
} else {
throw new RuntimeException();
}
}
}
/**
* Parse a comma-delimited String of data fields to sort on, along
* with optional ASC or DESC modifiers, to generate a new Sort
* specification. This string should consist
* of a comma-delimited list of field names, which optional "ASC" or
* "DESC" modifiers to specify ascending or descending sorts. If no
* modifier is given, ascending order is assumed. Field
* names which include spaces or other non-standard characters should
* be written in brackets ([]), just as is done in
* {@link prefuse.data.expression.parser.ExpressionParser expression
* language statements}. For example, the
* following string</p>
*
* <pre>"Profit DESC, [Product Type]"</pre>
*
* <p>sorts first by the data field "Profit" in descending order,
* additionally sorting in ascending order by the data field
* "Product Type" for tuples which have identical values in the
* "Profit" field.</p>
* @param s the sort specification String
* @return a new Sort specification
*/
public static Sort parse(String s) {
Sort sort = new Sort();
Object[] res = new Object[2];
int idx = 0, len = s.length();
int comma = s.indexOf(',');
int quote = s.indexOf('[');
while ( idx < len ) {
if ( comma < 0 ) {
subparse(s.substring(idx), res);
sort.add((String)res[0], ((Boolean)res[1]).booleanValue());
break;
} else if ( quote < 0 || comma < quote ) {
subparse(s.substring(idx, comma), res);
sort.add((String)res[0], ((Boolean)res[1]).booleanValue());
idx = comma + 1;
comma = s.indexOf(idx, ',');
} else {
int q2 = s.indexOf(quote, ']');
if ( q2 < 0 ) {
throw new RuntimeException();
} else {
comma = s.indexOf(q2, ',');
subparse(s.substring(idx, comma), res);
sort.add((String)res[0], ((Boolean)res[1]).booleanValue());
idx = comma + 1;
comma = s.indexOf(idx, ',');
}
}
}
return sort;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer sbuf = new StringBuffer();
for ( int i=0; i<m_fields.length; ++i ) {
if ( i > 0 ) sbuf.append(", ");
sbuf.append('[').append(m_fields[i]).append(']');
sbuf.append((m_ascend[i]) ? ASC : DESC);
}
return sbuf.toString();
}
} // end of class Sort