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   * @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 }