1 /*
2 *
3 * The DbUnit Database Testing Framework
4 * Copyright (C)2002-2004, DbUnit.org
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21
22 package org.dbunit.dataset;
23
24 import java.util.Arrays;
25 import java.util.Comparator;
26
27 import org.dbunit.DatabaseUnitRuntimeException;
28 import org.dbunit.dataset.datatype.DataType;
29 import org.dbunit.dataset.datatype.TypeCastException;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34 * This is a ITable decorator that provide a sorted view of the decorated table.
35 * This implementation does not keep a separate copy of the decorated table
36 * data.
37 *
38 * @author Manuel Laflamme
39 * @author Last changed by: $Author$
40 * @version $Revision$ $Date: 2009-05-01 02:56:07 -0500 (Fri, 01 May 2009) $
41 * @since Feb 19, 2003
42 */
43 public class SortedTable extends AbstractTable
44 {
45 private static final Logger logger =
46 LoggerFactory.getLogger(SortedTable.class);
47
48 private final ITable _table;
49 private final Column[] _columns;
50 private Integer[] _indexes;
51
52 /**
53 * The row comparator which is used for sorting
54 */
55 private Comparator rowComparator;
56
57 /**
58 * Sort the decorated table by specified columns order. Resulting table uses
59 * column definitions from the specified table's metadata, not the specified
60 * columns.
61 *
62 * @param table
63 * decorated table
64 * @param columns
65 * columns to be used for sorting
66 * @throws DataSetException
67 */
68 public SortedTable(final ITable table, final Column[] columns)
69 throws DataSetException
70 {
71 this(table, columns, false);
72 }
73
74 /**
75 * Sort the decorated table by specified columns order. Resulting table uses
76 * column definitions from the specified columns, not the ones from the
77 * specified table's metadata.
78 *
79 * @param table
80 * decorated table
81 * @param columns
82 * columns to be used for sorting
83 * @param useSpecifiedColumns
84 * true to use the column definitions specified by the columns
85 * parameter, false to use the column definitions from the
86 * specified table's metadata.
87 * @throws DataSetException
88 */
89 public SortedTable(final ITable table, final Column[] columns,
90 final boolean useSpecifiedColumns) throws DataSetException
91 {
92 _table = table;
93
94 final Column[] validatedColumns = validateAndResolveColumns(columns);
95 if (useSpecifiedColumns)
96 {
97 _columns = columns;
98 } else
99 {
100 _columns = validatedColumns;
101 }
102
103 initialize();
104 }
105
106 /**
107 * Sort the decorated table by specified columns order. Resulting table uses
108 * column definitions from the specified table's metadata.
109 *
110 * @param table
111 * decorated table
112 * @param columnNames
113 * names of columns to be used for sorting
114 * @throws DataSetException
115 */
116 public SortedTable(final ITable table, final String[] columnNames)
117 throws DataSetException
118 {
119 _table = table;
120 _columns = validateAndResolveColumns(columnNames);
121 initialize();
122 }
123
124 /**
125 * Sort the decorated table by specified metadata columns order. All
126 * metadata columns will be used.
127 *
128 * @param table
129 * The decorated table
130 * @param metaData
131 * The metadata used to retrieve all columns which in turn are
132 * used for sorting the table
133 * @throws DataSetException
134 */
135 public SortedTable(final ITable table, final ITableMetaData metaData)
136 throws DataSetException
137 {
138 this(table, metaData.getColumns());
139 }
140
141 /**
142 * Sort the decorated table by its own columns order which is defined by
143 * {@link ITable#getTableMetaData()}. All table columns will be used.
144 *
145 * @param table
146 * The decorated table
147 * @throws DataSetException
148 */
149 public SortedTable(final ITable table) throws DataSetException
150 {
151 this(table, table.getTableMetaData());
152 }
153
154 /**
155 * Verifies that all given columns really exist in the current table and
156 * returns the physical {@link Column} objects from the table.
157 *
158 * @param columns
159 * @return
160 * @throws DataSetException
161 */
162 private Column[] validateAndResolveColumns(final Column[] columns)
163 throws DataSetException
164 {
165 final ITableMetaData tableMetaData = _table.getTableMetaData();
166 final Column[] resultColumns =
167 Columns.findColumnsByName(columns, tableMetaData);
168 return resultColumns;
169 }
170
171 /**
172 * Verifies that all given columns really exist in the current table and
173 * returns the physical {@link Column} objects from the table.
174 *
175 * @param columnNames
176 * @return
177 * @throws DataSetException
178 */
179 private Column[] validateAndResolveColumns(final String[] columnNames)
180 throws DataSetException
181 {
182 final ITableMetaData tableMetaData = _table.getTableMetaData();
183 final Column[] resultColumns =
184 Columns.findColumnsByName(columnNames, tableMetaData);
185 return resultColumns;
186 }
187
188 private void initialize()
189 {
190 logger.debug("initialize() - start");
191
192 // The default comparator is the one that sorts by string - for
193 // backwards compatibility
194 this.rowComparator =
195 new RowComparatorByString(this._table, this._columns);
196 }
197
198 /**
199 * @return The columns that are used for sorting the table
200 */
201 public Column[] getSortColumns()
202 {
203 return this._columns;
204 }
205
206 private int getOriginalRowIndex(final int row) throws DataSetException
207 {
208 if (logger.isDebugEnabled())
209 {
210 logger.debug("getOriginalRowIndex(row={}) - start",
211 Integer.toString(row));
212 }
213
214 if (_indexes == null)
215 {
216 final Integer[] indexes = new Integer[getRowCount()];
217 for (int i = 0; i < indexes.length; i++)
218 {
219 indexes[i] = i;
220 }
221
222 try
223 {
224 Arrays.sort(indexes, rowComparator);
225 } catch (final DatabaseUnitRuntimeException e)
226 {
227 throw (DataSetException) e.getCause();
228 }
229
230 _indexes = indexes;
231 }
232
233 return _indexes[row].intValue();
234 }
235
236 /**
237 * Whether or not the comparable interface should be used of the compared
238 * columns instead of the plain strings Default value is <code>false</code>
239 * for backwards compatibility Set whether or not to use the Comparable
240 * implementation of the corresponding column DataType for comparing values
241 * or not. Default value is <code>false</code> which means that the old
242 * string comparison is used. <br>
243 *
244 * @param useComparable
245 * @since 2.3.0
246 */
247 public void setUseComparable(final boolean useComparable)
248 {
249 if (logger.isDebugEnabled())
250 {
251 logger.debug("setUseComparable(useComparable={}) - start",
252 Boolean.valueOf(useComparable));
253 }
254
255 if (useComparable)
256 {
257 setRowComparator(new RowComparator(this._table, this._columns));
258 } else
259 {
260 setRowComparator(
261 new RowComparatorByString(this._table, this._columns));
262 }
263 }
264
265 /**
266 * Sets the comparator to be used for sorting the table rows.
267 *
268 * @param comparator
269 * that sorts the table rows
270 * @since 2.4.2
271 */
272 public void setRowComparator(final Comparator comparator)
273 {
274 if (logger.isDebugEnabled())
275 {
276 logger.debug("setRowComparator(comparator={}) - start", comparator);
277 }
278
279 if (_indexes != null)
280 {
281 // TODO this is an ugly design to avoid increasing the number of
282 // constructors from 4 to 8. To be discussed how to implement it the
283 // best way.
284 throw new IllegalStateException(
285 "Do not use this method after the table has been used (i.e. #getValue() has been called). "
286 + "Please invoke this method immediately after the intialization of this object.");
287 }
288
289 this.rowComparator = comparator;
290 }
291
292 // //////////////////////////////////////////////////////////////////////////
293 // ITable interface
294
295 @Override
296 public ITableMetaData getTableMetaData()
297 {
298 logger.debug("getTableMetaData() - start");
299
300 return _table.getTableMetaData();
301 }
302
303 @Override
304 public int getRowCount()
305 {
306 logger.debug("getRowCount() - start");
307
308 return _table.getRowCount();
309 }
310
311 @Override
312 public Object getValue(final int row, final String columnName)
313 throws DataSetException
314 {
315 if (logger.isDebugEnabled())
316 {
317 logger.debug("getValue(row={}, columnName={}) - start",
318 Integer.toString(row), columnName);
319 }
320
321 assertValidRowIndex(row);
322
323 return _table.getValue(getOriginalRowIndex(row), columnName);
324 }
325
326 // //////////////////////////////////////////////////////////////////////////
327 // Comparator interface
328
329 /**
330 * Abstract class for sorting the table rows of a given table in a specific
331 * order
332 */
333 public static abstract class AbstractRowComparator implements Comparator
334 {
335 /**
336 * Logger for this class
337 */
338 private final Logger logger =
339 LoggerFactory.getLogger(AbstractRowComparator.class);
340 private final ITable _table;
341 private final Column[] _sortColumns;
342
343 /**
344 * @param table
345 * The wrapped table to be sorted
346 * @param sortColumns
347 * The columns to be used for sorting in the given order
348 */
349 public AbstractRowComparator(final ITable table,
350 final Column[] sortColumns)
351 {
352 this._table = table;
353 this._sortColumns = sortColumns;
354 }
355
356 @Override
357 public int compare(final Object o1, final Object o2)
358 {
359 logger.debug("compare(o1={}, o2={}) - start", o1, o2);
360
361 final Integer i1 = (Integer) o1;
362 final Integer i2 = (Integer) o2;
363
364 try
365 {
366 for (int i = 0; i < _sortColumns.length; i++)
367 {
368 final String columnName = _sortColumns[i].getColumnName();
369
370 final Object value1 =
371 _table.getValue(i1.intValue(), columnName);
372 final Object value2 =
373 _table.getValue(i2.intValue(), columnName);
374
375 if (value1 == null && value2 == null)
376 {
377 continue;
378 }
379
380 if (value1 == null && value2 != null)
381 {
382 return -1;
383 }
384
385 if (value1 != null && value2 == null)
386 {
387 return 1;
388 }
389
390 // Compare the two values with each other for sorting
391 final int result = compare(_sortColumns[i], value1, value2);
392
393 if (result != 0)
394 {
395 return result;
396 }
397 }
398 } catch (final DataSetException e)
399 {
400 throw new DatabaseUnitRuntimeException(e);
401 }
402
403 return 0;
404 }
405
406 /**
407 * @param column
408 * The column to be compared
409 * @param value1
410 * The first value of the given column
411 * @param value2
412 * The second value of the given column
413 * @return 0 if both values are considered equal.
414 * @throws TypeCastException
415 */
416 protected abstract int compare(Column column, Object value1,
417 Object value2) throws TypeCastException;
418
419 }
420
421 /**
422 * Compares the rows with each other in order to sort them in the correct
423 * order using the data type and the Comparable implementation the current
424 * column has.
425 */
426 protected static class RowComparator extends AbstractRowComparator
427 {
428 /**
429 * Logger for this class
430 */
431 private final Logger logger =
432 LoggerFactory.getLogger(RowComparator.class);
433
434 public RowComparator(final ITable table, final Column[] sortColumns)
435 {
436 super(table, sortColumns);
437 }
438
439 @Override
440 protected int compare(final Column column, final Object value1,
441 final Object value2) throws TypeCastException
442 {
443 if (logger.isDebugEnabled())
444 {
445 logger.debug("compare(column={}, value1={}, value2={}) - start",
446 new Object[] {column, value1, value2});
447 }
448
449 final DataType dataType = column.getDataType();
450 final int result = dataType.compare(value1, value2);
451 return result;
452 }
453
454 }
455
456 /**
457 * Compares the rows with each other in order to sort them in the correct
458 * order using the string value of both values for the comparison.
459 */
460 protected static class RowComparatorByString extends AbstractRowComparator
461 {
462 /**
463 * Logger for this class
464 */
465 private final Logger logger =
466 LoggerFactory.getLogger(RowComparatorByString.class);
467
468 public RowComparatorByString(final ITable table,
469 final Column[] sortColumns)
470 {
471 super(table, sortColumns);
472 }
473
474 @Override
475 protected int compare(final Column column, final Object value1,
476 final Object value2) throws TypeCastException
477 {
478 if (logger.isDebugEnabled())
479 {
480 logger.debug("compare(column={}, value1={}, value2={}) - start",
481 new Object[] {column, value1, value2});
482 }
483
484 // Default behavior since ever
485 final String stringValue1 = DataType.asString(value1);
486 final String stringValue2 = DataType.asString(value2);
487 final int result = stringValue1.compareTo(stringValue2);
488 return result;
489 }
490 }
491
492 /**
493 * {@inheritDoc}
494 */
495 @Override
496 public String toString()
497 {
498 final StringBuilder sb = new StringBuilder(2000);
499
500 sb.append(getClass().getName()).append("[");
501 sb.append("_columns=[").append(Arrays.toString(_columns)).append("], ");
502 sb.append("_indexes=[").append(_indexes).append("], ");
503 sb.append("_table=[").append(_table).append("]");
504 sb.append("]");
505
506 return sb.toString();
507 }
508 }