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: jeffjensen $
40 * @version $Revision: 1176 $ $Date: 2009-05-01 02:56:07 -0500 (Fri, 01 May 2009)
41 * $
42 * @since Feb 19, 2003
43 */
44 public class SortedTable extends AbstractTable {
45
46 /**
47 * Logger for this class
48 */
49 private static final Logger logger =
50 LoggerFactory.getLogger(SortedTable.class);
51
52 private final ITable _table;
53 private final Column[] _columns;
54 private Integer[] _indexes;
55
56 /**
57 * The row comparator which is used for sorting
58 */
59 private Comparator rowComparator;
60
61 /**
62 * Sort the decorated table by specified columns order.
63 *
64 * @param table
65 * decorated table
66 * @param columns
67 * columns to be used for sorting
68 * @throws DataSetException
69 */
70 public SortedTable(ITable table, Column[] columns) throws DataSetException {
71 _table = table;
72 _columns = validateAndResolveColumns(columns);
73 initialize();
74 }
75
76 /**
77 * Sort the decorated table by specified columns order.
78 *
79 * @param table
80 * decorated table
81 * @param columnNames
82 * names of columns to be used for sorting
83 * @throws DataSetException
84 */
85 public SortedTable(ITable table, String[] columnNames)
86 throws DataSetException {
87 _table = table;
88 _columns = validateAndResolveColumns(columnNames);
89 initialize();
90 }
91
92 /**
93 * Sort the decorated table by specified metadata columns order. All
94 * metadata columns will be used.
95 *
96 * @param table
97 * The decorated table
98 * @param metaData
99 * The metadata used to retrieve all columns which in turn are
100 * used for sorting the table
101 * @throws DataSetException
102 */
103 public SortedTable(ITable table, ITableMetaData metaData)
104 throws DataSetException {
105 this(table, metaData.getColumns());
106 }
107
108 /**
109 * Sort the decorated table by its own columns order which is defined by
110 * {@link ITable#getTableMetaData()}. All table columns will be used.
111 *
112 * @param table
113 * The decorated table
114 * @throws DataSetException
115 */
116 public SortedTable(ITable table) throws DataSetException {
117 this(table, table.getTableMetaData());
118 }
119
120 /**
121 * Verifies that all given columns really exist in the current table and
122 * returns the physical {@link Column} objects from the table.
123 *
124 * @param columns
125 * @return
126 * @throws DataSetException
127 */
128 private Column[] validateAndResolveColumns(Column[] columns)
129 throws DataSetException {
130 ITableMetaData tableMetaData = _table.getTableMetaData();
131 Column[] resultColumns =
132 Columns.findColumnsByName(columns, tableMetaData);
133 return resultColumns;
134 }
135
136 /**
137 * Verifies that all given columns really exist in the current table and
138 * returns the physical {@link Column} objects from the table.
139 *
140 * @param columnNames
141 * @return
142 * @throws DataSetException
143 */
144 private Column[] validateAndResolveColumns(String[] columnNames)
145 throws DataSetException {
146 ITableMetaData tableMetaData = _table.getTableMetaData();
147 Column[] resultColumns =
148 Columns.findColumnsByName(columnNames, tableMetaData);
149 return resultColumns;
150 }
151
152 private void initialize() {
153 logger.debug("initialize() - start");
154
155 // The default comparator is the one that sorts by string - for
156 // backwards compatibility
157 this.rowComparator =
158 new RowComparatorByString(this._table, this._columns);
159 }
160
161 /**
162 * @return The columns that are used for sorting the table
163 */
164 public Column[] getSortColumns() {
165 return this._columns;
166 }
167
168 private int getOriginalRowIndex(int row) throws DataSetException {
169 if (logger.isDebugEnabled()) {
170 logger.debug("getOriginalRowIndex(row={}) - start", Integer
171 .toString(row));
172 }
173
174 if (_indexes == null) {
175 Integer[] indexes = new Integer[getRowCount()];
176 for (int i = 0; i < indexes.length; i++) {
177 indexes[i] = new Integer(i);
178 }
179
180 try {
181 Arrays.sort(indexes, rowComparator);
182 } catch (DatabaseUnitRuntimeException e) {
183 throw (DataSetException) e.getCause();
184 }
185
186 _indexes = indexes;
187 }
188
189 return _indexes[row].intValue();
190 }
191
192 /**
193 * Whether or not the comparable interface should be used of the compared
194 * columns instead of the plain strings Default value is <code>false</code>
195 * for backwards compatibility Set whether or not to use the Comparable
196 * implementation of the corresponding column DataType for comparing values
197 * or not. Default value is <code>false</code> which means that the old
198 * string comparison is used. <br>
199 *
200 * @param useComparable
201 * @since 2.3.0
202 */
203 public void setUseComparable(boolean useComparable) {
204 if (logger.isDebugEnabled()) {
205 logger.debug("setUseComparable(useComparable={}) - start", Boolean
206 .valueOf(useComparable));
207 }
208
209 if (useComparable) {
210 setRowComparator(new RowComparator(this._table, this._columns));
211 } else {
212 setRowComparator(new RowComparatorByString(this._table,
213 this._columns));
214 }
215 }
216
217 /**
218 * Sets the comparator to be used for sorting the table rows.
219 *
220 * @param comparator
221 * that sorts the table rows
222 * @since 2.4.2
223 */
224 public void setRowComparator(Comparator comparator) {
225 if (logger.isDebugEnabled()) {
226 logger.debug("setRowComparator(comparator={}) - start", comparator);
227 }
228
229 if (_indexes != null) {
230 // TODO this is an ugly design to avoid increasing the number of
231 // constructors from 4 to 8. To be discussed how to implement it the
232 // best way.
233 throw new IllegalStateException(
234 "Do not use this method after the table has been used (i.e. #getValue() has been called). "
235 + "Please invoke this method immediately after the intialization of this object.");
236 }
237
238 this.rowComparator = comparator;
239 }
240
241 // //////////////////////////////////////////////////////////////////////////
242 // ITable interface
243
244 public ITableMetaData getTableMetaData() {
245 logger.debug("getTableMetaData() - start");
246
247 return _table.getTableMetaData();
248 }
249
250 public int getRowCount() {
251 logger.debug("getRowCount() - start");
252
253 return _table.getRowCount();
254 }
255
256 public Object getValue(int row, String columnName) throws DataSetException {
257 if (logger.isDebugEnabled()) {
258 logger.debug("getValue(row={}, columnName={}) - start", Integer
259 .toString(row), columnName);
260 }
261
262 assertValidRowIndex(row);
263
264 return _table.getValue(getOriginalRowIndex(row), columnName);
265 }
266
267 // //////////////////////////////////////////////////////////////////////////
268 // Comparator interface
269
270 /**
271 * Abstract class for sorting the table rows of a given table in a specific
272 * order
273 */
274 public static abstract class AbstractRowComparator implements Comparator {
275 /**
276 * Logger for this class
277 */
278 private final Logger logger =
279 LoggerFactory.getLogger(AbstractRowComparator.class);
280 private final ITable _table;
281 private final Column[] _sortColumns;
282
283 /**
284 * @param table
285 * The wrapped table to be sorted
286 * @param sortColumns
287 * The columns to be used for sorting in the given order
288 */
289 public AbstractRowComparator(ITable table, Column[] sortColumns) {
290 this._table = table;
291 this._sortColumns = sortColumns;
292 }
293
294 public int compare(Object o1, Object o2) {
295 logger.debug("compare(o1={}, o2={}) - start", o1, o2);
296
297 Integer i1 = (Integer) o1;
298 Integer i2 = (Integer) o2;
299
300 try {
301 for (int i = 0; i < _sortColumns.length; i++) {
302 String columnName = _sortColumns[i].getColumnName();
303
304 Object value1 = _table.getValue(i1.intValue(), columnName);
305 Object value2 = _table.getValue(i2.intValue(), columnName);
306
307 if (value1 == null && value2 == null) {
308 continue;
309 }
310
311 if (value1 == null && value2 != null) {
312 return -1;
313 }
314
315 if (value1 != null && value2 == null) {
316 return 1;
317 }
318
319 // Compare the two values with each other for sorting
320 int result = compare(_sortColumns[i], value1, value2);
321
322 if (result != 0) {
323 return result;
324 }
325 }
326 } catch (DataSetException e) {
327 throw new DatabaseUnitRuntimeException(e);
328 }
329
330 return 0;
331 }
332
333 /**
334 * @param column
335 * The column to be compared
336 * @param value1
337 * The first value of the given column
338 * @param value2
339 * The second value of the given column
340 * @return 0 if both values are considered equal.
341 * @throws TypeCastException
342 */
343 protected abstract int compare(Column column, Object value1,
344 Object value2) throws TypeCastException;
345
346 }
347
348 /**
349 * Compares the rows with each other in order to sort them in the correct
350 * order using the data type and the Comparable implementation the current
351 * column has.
352 */
353 protected static class RowComparator extends AbstractRowComparator {
354 /**
355 * Logger for this class
356 */
357 private final Logger logger =
358 LoggerFactory.getLogger(RowComparator.class);
359
360 public RowComparator(ITable table, Column[] sortColumns) {
361 super(table, sortColumns);
362 }
363
364 protected int compare(Column column, Object value1, Object value2)
365 throws TypeCastException {
366 if (logger.isDebugEnabled()) {
367 logger.debug(
368 "compare(column={}, value1={}, value2={}) - start",
369 new Object[] {column, value1, value2});
370 }
371
372 DataType dataType = column.getDataType();
373 int result = dataType.compare(value1, value2);
374 return result;
375 }
376
377 }
378
379 /**
380 * Compares the rows with each other in order to sort them in the correct
381 * order using the string value of both values for the comparison.
382 */
383 protected static class RowComparatorByString extends AbstractRowComparator {
384 /**
385 * Logger for this class
386 */
387 private final Logger logger =
388 LoggerFactory.getLogger(RowComparatorByString.class);
389
390 public RowComparatorByString(ITable table, Column[] sortColumns) {
391 super(table, sortColumns);
392 }
393
394 protected int compare(Column column, Object value1, Object value2)
395 throws TypeCastException {
396 if (logger.isDebugEnabled()) {
397 logger.debug(
398 "compare(column={}, value1={}, value2={}) - start",
399 new Object[] {column, value1, value2});
400 }
401
402 // Default behavior since ever
403 String stringValue1 = DataType.asString(value1);
404 String stringValue2 = DataType.asString(value2);
405 int result = stringValue1.compareTo(stringValue2);
406 return result;
407 }
408 }
409
410 /**
411 * {@inheritDoc}
412 */
413 public String toString() {
414 StringBuilder sb = new StringBuilder(2000);
415
416 sb.append(getClass().getName()).append("[");
417 sb.append("_columns=[").append(Arrays.toString(_columns)).append("], ");
418 sb.append("_indexes=[").append(_indexes).append("], ");
419 sb.append("_table=[").append(_table).append("]");
420 sb.append("]");
421
422 return sb.toString();
423 }
424 }