001: /**
002: *
003: */package prefuse.data.util;
004:
005: import java.util.Arrays;
006: import java.util.Comparator;
007:
008: import prefuse.data.Schema;
009: import prefuse.data.Table;
010: import prefuse.data.Tuple;
011: import prefuse.data.expression.Predicate;
012: import prefuse.data.tuple.TupleSet;
013: import prefuse.util.collections.CompositeComparator;
014: import prefuse.util.collections.NullComparator;
015:
016: /**
017: * <p>Utility class representing sorting criteria, this can be given as
018: * input to the {@link TupleSet#tuples(Predicate, Sort)} method to
019: * get a sorted iteration of tuples.</p>
020: *
021: * <p>Sort criteria consists of an ordered list of data field names to
022: * sort by, along with an indication to sort tuples in either ascending
023: * or descending order. These criteria can be passed in to the
024: * constructor or added incrementally using the
025: * {@link #add(String, boolean)} method.</p>
026: *
027: * <p>Alternatively, one can also specify the sorting criteria using a
028: * single string, which is parsed using the {@link #parse(String)} method.
029: * This string should consist
030: * of a comma-delimited list of field names, which optional "ASC" or
031: * "DESC" modifiers to specify ascending or descending sorts. If no
032: * modifier is given, ascending order is assumed. Field
033: * names which include spaces or other non-standard characters should
034: * be written in brackets ([]), just as is done in
035: * {@link prefuse.data.expression.parser.ExpressionParser expression
036: * language statements}. For example, the
037: * following string</p>
038: *
039: * <pre>"Profit DESC, [Product Type]"</pre>
040: *
041: * <p>sorts first by the data field "Profit" in descending order,
042: * additionally sorting in ascending order by the data field
043: * "Product Type" for tuples which have identical values in the
044: * "Profit" field.</p>
045: *
046: * @author <a href="http://jheer.org">jeffrey heer</a>
047: */
048: public class Sort {
049:
050: private static final String ASC = " ASC";
051: private static final String DESC = " DESC";
052: private static final String asc = ASC.toLowerCase();
053: private static final String desc = DESC.toLowerCase();
054:
055: private String[] m_fields;
056: private boolean[] m_ascend;
057:
058: /**
059: * Creates a new, empty Sort specification.
060: */
061: public Sort() {
062: this (new String[0], new boolean[0]);
063: }
064:
065: /**
066: * Creates a new Sort specification that sorts on the
067: * given fields, all in ascending order.
068: * @param fields the fields to sort on, in order of precedence
069: */
070: public Sort(String[] fields) {
071: this (fields, new boolean[fields.length]);
072: Arrays.fill(m_ascend, true);
073: }
074:
075: /**
076: * Creates a new Sort specification that sorts on the
077: * given fields in the given orders.
078: * @param fields the fields to sort on, in order of precedence
079: * @param ascend for each field, indicates if the field should
080: * be sorted in ascending (true) or descending (false) order
081: */
082: public Sort(String[] fields, boolean[] ascend) {
083: m_fields = fields;
084: m_ascend = ascend;
085: }
086:
087: /**
088: * Adds a new field to this Sort specification.
089: * @param field the additional field to sort on
090: * @param ascend indicates if the field should
091: * be sorted in ascending (true) or descending (false) order
092: */
093: public void add(String field, boolean ascend) {
094: String[] f = new String[m_fields.length + 1];
095: System.arraycopy(m_fields, 0, f, 0, m_fields.length);
096: f[m_fields.length] = field;
097: m_fields = f;
098:
099: boolean[] b = new boolean[m_fields.length + 1];
100: System.arraycopy(m_ascend, 0, b, 0, m_ascend.length);
101: b[m_ascend.length] = ascend;
102: m_ascend = b;
103: }
104:
105: /**
106: * Returns the number of fields in this Sort specification.
107: * @return the number of fields to sort on
108: */
109: public int size() {
110: return m_fields.length;
111: }
112:
113: /**
114: * Returns the sort field at the given index.
115: * @param i the index to look up
116: * @return the sort field at the given index
117: */
118: public String getField(int i) {
119: return m_fields[i];
120: }
121:
122: /**
123: * Returns the ascending modifier as the given index.
124: * @param i the index to look up
125: * @return true if the field at the given index is to be sorted
126: * in ascending order, false for descending order
127: */
128: public boolean isAscending(int i) {
129: return m_ascend[i];
130: }
131:
132: /**
133: * Generates a Comparator to be used for sorting tuples drawn from
134: * the given tuple set.
135: * @param ts the TupleSet whose Tuples are to be sorted
136: * @return a Comparator instance for sorting tuples from the given
137: * set using the sorting criteria given in this specification
138: */
139: public Comparator getComparator(TupleSet ts) {
140: // get the schema, so we can lookup column value types
141: Schema s = null;
142: if (ts instanceof Table) {
143: // for Tables, we can get this directly
144: s = ((Table) ts).getSchema();
145: } else {
146: // if non-table tuple set is empty, we punt
147: if (ts.getTupleCount() == 0)
148: return new NullComparator();
149: // otherwise, use the schema of the first tuple in the set
150: s = ((Tuple) ts.tuples().next()).getSchema();
151: }
152: // create the comparator
153: CompositeComparator cc = new CompositeComparator(
154: m_fields.length);
155: for (int i = 0; i < m_fields.length; ++i) {
156: cc.add(new TupleComparator(m_fields[i], s
157: .getColumnType(m_fields[i]), m_ascend[i]));
158: }
159: return cc;
160: }
161:
162: // ------------------------------------------------------------------------
163:
164: private static void subparse(String s, Object[] res) {
165: s = s.trim();
166:
167: // extract ascending modifier first
168: res[1] = Boolean.TRUE;
169: if (s.endsWith(DESC) || s.endsWith(desc)) {
170: res[1] = Boolean.FALSE;
171: s = s.substring(0, s.length() - DESC.length()).trim();
172: } else if (s.endsWith(ASC) || s.endsWith(asc)) {
173: s = s.substring(0, s.length() - ASC.length()).trim();
174: }
175:
176: if (s.startsWith("[")) {
177: if (s.lastIndexOf("[") == 0 && s.endsWith("]")
178: && s.indexOf("]") == s.length()) {
179: res[0] = s.substring(1, s.length() - 1);
180: } else {
181: throw new RuntimeException();
182: }
183: } else {
184: if (s.indexOf(" ") < 0 && s.indexOf("\t") < 0) {
185: res[0] = s;
186: } else {
187: throw new RuntimeException();
188: }
189: }
190: }
191:
192: /**
193: * Parse a comma-delimited String of data fields to sort on, along
194: * with optional ASC or DESC modifiers, to generate a new Sort
195: * specification. This string should consist
196: * of a comma-delimited list of field names, which optional "ASC" or
197: * "DESC" modifiers to specify ascending or descending sorts. If no
198: * modifier is given, ascending order is assumed. Field
199: * names which include spaces or other non-standard characters should
200: * be written in brackets ([]), just as is done in
201: * {@link prefuse.data.expression.parser.ExpressionParser expression
202: * language statements}. For example, the
203: * following string</p>
204: *
205: * <pre>"Profit DESC, [Product Type]"</pre>
206: *
207: * <p>sorts first by the data field "Profit" in descending order,
208: * additionally sorting in ascending order by the data field
209: * "Product Type" for tuples which have identical values in the
210: * "Profit" field.</p>
211: * @param s the sort specification String
212: * @return a new Sort specification
213: */
214: public static Sort parse(String s) {
215: Sort sort = new Sort();
216: Object[] res = new Object[2];
217: int idx = 0, len = s.length();
218: int comma = s.indexOf(',');
219: int quote = s.indexOf('[');
220: while (idx < len) {
221: if (comma < 0) {
222: subparse(s.substring(idx), res);
223: sort.add((String) res[0], ((Boolean) res[1])
224: .booleanValue());
225: break;
226: } else if (quote < 0 || comma < quote) {
227: subparse(s.substring(idx, comma), res);
228: sort.add((String) res[0], ((Boolean) res[1])
229: .booleanValue());
230: idx = comma + 1;
231: comma = s.indexOf(idx, ',');
232: } else {
233: int q2 = s.indexOf(quote, ']');
234: if (q2 < 0) {
235: throw new RuntimeException();
236: } else {
237: comma = s.indexOf(q2, ',');
238: subparse(s.substring(idx, comma), res);
239: sort.add((String) res[0], ((Boolean) res[1])
240: .booleanValue());
241: idx = comma + 1;
242: comma = s.indexOf(idx, ',');
243: }
244: }
245: }
246: return sort;
247: }
248:
249: /**
250: * @see java.lang.Object#toString()
251: */
252: public String toString() {
253: StringBuffer sbuf = new StringBuffer();
254: for (int i = 0; i < m_fields.length; ++i) {
255: if (i > 0)
256: sbuf.append(", ");
257: sbuf.append('[').append(m_fields[i]).append(']');
258: sbuf.append((m_ascend[i]) ? ASC : DESC);
259: }
260: return sbuf.toString();
261: }
262:
263: } // end of class Sort
|