View Javadoc
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   *          $
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 }