SortedTable.java

  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. package org.dbunit.dataset;

  22. import java.util.Arrays;
  23. import java.util.Comparator;

  24. import org.dbunit.DatabaseUnitRuntimeException;
  25. import org.dbunit.dataset.datatype.DataType;
  26. import org.dbunit.dataset.datatype.TypeCastException;
  27. import org.slf4j.Logger;
  28. import org.slf4j.LoggerFactory;

  29. /**
  30.  * This is a ITable decorator that provide a sorted view of the decorated table.
  31.  * This implementation does not keep a separate copy of the decorated table
  32.  * data.
  33.  *
  34.  * @author Manuel Laflamme
  35.  * @author Last changed by: $Author$
  36.  * @version $Revision$ $Date: 2009-05-01 02:56:07 -0500 (Fri, 01 May 2009) $
  37.  * @since Feb 19, 2003
  38.  */
  39. public class SortedTable extends AbstractTable
  40. {
  41.     private static final Logger logger =
  42.             LoggerFactory.getLogger(SortedTable.class);

  43.     private final ITable _table;
  44.     private final Column[] _columns;
  45.     private Integer[] _indexes;

  46.     /**
  47.      * The row comparator which is used for sorting
  48.      */
  49.     private Comparator rowComparator;

  50.     /**
  51.      * Sort the decorated table by specified columns order. Resulting table uses
  52.      * column definitions from the specified table's metadata, not the specified
  53.      * columns.
  54.      *
  55.      * @param table
  56.      *            decorated table
  57.      * @param columns
  58.      *            columns to be used for sorting
  59.      * @throws DataSetException
  60.      */
  61.     public SortedTable(final ITable table, final Column[] columns)
  62.             throws DataSetException
  63.     {
  64.         this(table, columns, false);
  65.     }

  66.     /**
  67.      * Sort the decorated table by specified columns order. Resulting table uses
  68.      * column definitions from the specified columns, not the ones from the
  69.      * specified table's metadata.
  70.      *
  71.      * @param table
  72.      *            decorated table
  73.      * @param columns
  74.      *            columns to be used for sorting
  75.      * @param useSpecifiedColumns
  76.      *            true to use the column definitions specified by the columns
  77.      *            parameter, false to use the column definitions from the
  78.      *            specified table's metadata.
  79.      * @throws DataSetException
  80.      */
  81.     public SortedTable(final ITable table, final Column[] columns,
  82.             final boolean useSpecifiedColumns) throws DataSetException
  83.     {
  84.         _table = table;

  85.         final Column[] validatedColumns = validateAndResolveColumns(columns);
  86.         if (useSpecifiedColumns)
  87.         {
  88.             _columns = columns;
  89.         } else
  90.         {
  91.             _columns = validatedColumns;
  92.         }

  93.         initialize();
  94.     }

  95.     /**
  96.      * Sort the decorated table by specified columns order. Resulting table uses
  97.      * column definitions from the specified table's metadata.
  98.      *
  99.      * @param table
  100.      *            decorated table
  101.      * @param columnNames
  102.      *            names of columns to be used for sorting
  103.      * @throws DataSetException
  104.      */
  105.     public SortedTable(final ITable table, final String[] columnNames)
  106.             throws DataSetException
  107.     {
  108.         _table = table;
  109.         _columns = validateAndResolveColumns(columnNames);
  110.         initialize();
  111.     }

  112.     /**
  113.      * Sort the decorated table by specified metadata columns order. All
  114.      * metadata columns will be used.
  115.      *
  116.      * @param table
  117.      *            The decorated table
  118.      * @param metaData
  119.      *            The metadata used to retrieve all columns which in turn are
  120.      *            used for sorting the table
  121.      * @throws DataSetException
  122.      */
  123.     public SortedTable(final ITable table, final ITableMetaData metaData)
  124.             throws DataSetException
  125.     {
  126.         this(table, metaData.getColumns());
  127.     }

  128.     /**
  129.      * Sort the decorated table by its own columns order which is defined by
  130.      * {@link ITable#getTableMetaData()}. All table columns will be used.
  131.      *
  132.      * @param table
  133.      *            The decorated table
  134.      * @throws DataSetException
  135.      */
  136.     public SortedTable(final ITable table) throws DataSetException
  137.     {
  138.         this(table, table.getTableMetaData());
  139.     }

  140.     /**
  141.      * Verifies that all given columns really exist in the current table and
  142.      * returns the physical {@link Column} objects from the table.
  143.      *
  144.      * @param columns
  145.      * @return
  146.      * @throws DataSetException
  147.      */
  148.     private Column[] validateAndResolveColumns(final Column[] columns)
  149.             throws DataSetException
  150.     {
  151.         final ITableMetaData tableMetaData = _table.getTableMetaData();
  152.         final Column[] resultColumns =
  153.                 Columns.findColumnsByName(columns, tableMetaData);
  154.         return resultColumns;
  155.     }

  156.     /**
  157.      * Verifies that all given columns really exist in the current table and
  158.      * returns the physical {@link Column} objects from the table.
  159.      *
  160.      * @param columnNames
  161.      * @return
  162.      * @throws DataSetException
  163.      */
  164.     private Column[] validateAndResolveColumns(final String[] columnNames)
  165.             throws DataSetException
  166.     {
  167.         final ITableMetaData tableMetaData = _table.getTableMetaData();
  168.         final Column[] resultColumns =
  169.                 Columns.findColumnsByName(columnNames, tableMetaData);
  170.         return resultColumns;
  171.     }

  172.     private void initialize()
  173.     {
  174.         logger.debug("initialize() - start");

  175.         // The default comparator is the one that sorts by string - for
  176.         // backwards compatibility
  177.         this.rowComparator =
  178.                 new RowComparatorByString(this._table, this._columns);
  179.     }

  180.     /**
  181.      * @return The columns that are used for sorting the table
  182.      */
  183.     public Column[] getSortColumns()
  184.     {
  185.         return this._columns;
  186.     }

  187.     private int getOriginalRowIndex(final int row) throws DataSetException
  188.     {
  189.         if (logger.isDebugEnabled())
  190.         {
  191.             logger.debug("getOriginalRowIndex(row={}) - start",
  192.                     Integer.toString(row));
  193.         }

  194.         if (_indexes == null)
  195.         {
  196.             final Integer[] indexes = new Integer[getRowCount()];
  197.             for (int i = 0; i < indexes.length; i++)
  198.             {
  199.                 indexes[i] = i;
  200.             }

  201.             try
  202.             {
  203.                 Arrays.sort(indexes, rowComparator);
  204.             } catch (final DatabaseUnitRuntimeException e)
  205.             {
  206.                 throw (DataSetException) e.getCause();
  207.             }

  208.             _indexes = indexes;
  209.         }

  210.         return _indexes[row].intValue();
  211.     }

  212.     /**
  213.      * Whether or not the comparable interface should be used of the compared
  214.      * columns instead of the plain strings Default value is <code>false</code>
  215.      * for backwards compatibility Set whether or not to use the Comparable
  216.      * implementation of the corresponding column DataType for comparing values
  217.      * or not. Default value is <code>false</code> which means that the old
  218.      * string comparison is used. <br>
  219.      *
  220.      * @param useComparable
  221.      * @since 2.3.0
  222.      */
  223.     public void setUseComparable(final boolean useComparable)
  224.     {
  225.         if (logger.isDebugEnabled())
  226.         {
  227.             logger.debug("setUseComparable(useComparable={}) - start",
  228.                     Boolean.valueOf(useComparable));
  229.         }

  230.         if (useComparable)
  231.         {
  232.             setRowComparator(new RowComparator(this._table, this._columns));
  233.         } else
  234.         {
  235.             setRowComparator(
  236.                     new RowComparatorByString(this._table, this._columns));
  237.         }
  238.     }

  239.     /**
  240.      * Sets the comparator to be used for sorting the table rows.
  241.      *
  242.      * @param comparator
  243.      *            that sorts the table rows
  244.      * @since 2.4.2
  245.      */
  246.     public void setRowComparator(final Comparator comparator)
  247.     {
  248.         if (logger.isDebugEnabled())
  249.         {
  250.             logger.debug("setRowComparator(comparator={}) - start", comparator);
  251.         }

  252.         if (_indexes != null)
  253.         {
  254.             // TODO this is an ugly design to avoid increasing the number of
  255.             // constructors from 4 to 8. To be discussed how to implement it the
  256.             // best way.
  257.             throw new IllegalStateException(
  258.                     "Do not use this method after the table has been used (i.e. #getValue() has been called). "
  259.                             + "Please invoke this method immediately after the intialization of this object.");
  260.         }

  261.         this.rowComparator = comparator;
  262.     }

  263.     // //////////////////////////////////////////////////////////////////////////
  264.     // ITable interface

  265.     @Override
  266.     public ITableMetaData getTableMetaData()
  267.     {
  268.         logger.debug("getTableMetaData() - start");

  269.         return _table.getTableMetaData();
  270.     }

  271.     @Override
  272.     public int getRowCount()
  273.     {
  274.         logger.debug("getRowCount() - start");

  275.         return _table.getRowCount();
  276.     }

  277.     @Override
  278.     public Object getValue(final int row, final String columnName)
  279.             throws DataSetException
  280.     {
  281.         if (logger.isDebugEnabled())
  282.         {
  283.             logger.debug("getValue(row={}, columnName={}) - start",
  284.                     Integer.toString(row), columnName);
  285.         }

  286.         assertValidRowIndex(row);

  287.         return _table.getValue(getOriginalRowIndex(row), columnName);
  288.     }

  289.     // //////////////////////////////////////////////////////////////////////////
  290.     // Comparator interface

  291.     /**
  292.      * Abstract class for sorting the table rows of a given table in a specific
  293.      * order
  294.      */
  295.     public static abstract class AbstractRowComparator implements Comparator
  296.     {
  297.         /**
  298.          * Logger for this class
  299.          */
  300.         private final Logger logger =
  301.                 LoggerFactory.getLogger(AbstractRowComparator.class);
  302.         private final ITable _table;
  303.         private final Column[] _sortColumns;

  304.         /**
  305.          * @param table
  306.          *            The wrapped table to be sorted
  307.          * @param sortColumns
  308.          *            The columns to be used for sorting in the given order
  309.          */
  310.         public AbstractRowComparator(final ITable table,
  311.                 final Column[] sortColumns)
  312.         {
  313.             this._table = table;
  314.             this._sortColumns = sortColumns;
  315.         }

  316.         @Override
  317.         public int compare(final Object o1, final Object o2)
  318.         {
  319.             logger.debug("compare(o1={}, o2={}) - start", o1, o2);

  320.             final Integer i1 = (Integer) o1;
  321.             final Integer i2 = (Integer) o2;

  322.             try
  323.             {
  324.                 for (int i = 0; i < _sortColumns.length; i++)
  325.                 {
  326.                     final String columnName = _sortColumns[i].getColumnName();

  327.                     final Object value1 =
  328.                             _table.getValue(i1.intValue(), columnName);
  329.                     final Object value2 =
  330.                             _table.getValue(i2.intValue(), columnName);

  331.                     if (value1 == null && value2 == null)
  332.                     {
  333.                         continue;
  334.                     }

  335.                     if (value1 == null && value2 != null)
  336.                     {
  337.                         return -1;
  338.                     }

  339.                     if (value1 != null && value2 == null)
  340.                     {
  341.                         return 1;
  342.                     }

  343.                     // Compare the two values with each other for sorting
  344.                     final int result = compare(_sortColumns[i], value1, value2);

  345.                     if (result != 0)
  346.                     {
  347.                         return result;
  348.                     }
  349.                 }
  350.             } catch (final DataSetException e)
  351.             {
  352.                 throw new DatabaseUnitRuntimeException(e);
  353.             }

  354.             return 0;
  355.         }

  356.         /**
  357.          * @param column
  358.          *            The column to be compared
  359.          * @param value1
  360.          *            The first value of the given column
  361.          * @param value2
  362.          *            The second value of the given column
  363.          * @return 0 if both values are considered equal.
  364.          * @throws TypeCastException
  365.          */
  366.         protected abstract int compare(Column column, Object value1,
  367.                 Object value2) throws TypeCastException;

  368.     }

  369.     /**
  370.      * Compares the rows with each other in order to sort them in the correct
  371.      * order using the data type and the Comparable implementation the current
  372.      * column has.
  373.      */
  374.     protected static class RowComparator extends AbstractRowComparator
  375.     {
  376.         /**
  377.          * Logger for this class
  378.          */
  379.         private final Logger logger =
  380.                 LoggerFactory.getLogger(RowComparator.class);

  381.         public RowComparator(final ITable table, final Column[] sortColumns)
  382.         {
  383.             super(table, sortColumns);
  384.         }

  385.         @Override
  386.         protected int compare(final Column column, final Object value1,
  387.                 final Object value2) throws TypeCastException
  388.         {
  389.             if (logger.isDebugEnabled())
  390.             {
  391.                 logger.debug("compare(column={}, value1={}, value2={}) - start",
  392.                         new Object[] {column, value1, value2});
  393.             }

  394.             final DataType dataType = column.getDataType();
  395.             final int result = dataType.compare(value1, value2);
  396.             return result;
  397.         }

  398.     }

  399.     /**
  400.      * Compares the rows with each other in order to sort them in the correct
  401.      * order using the string value of both values for the comparison.
  402.      */
  403.     protected static class RowComparatorByString extends AbstractRowComparator
  404.     {
  405.         /**
  406.          * Logger for this class
  407.          */
  408.         private final Logger logger =
  409.                 LoggerFactory.getLogger(RowComparatorByString.class);

  410.         public RowComparatorByString(final ITable table,
  411.                 final Column[] sortColumns)
  412.         {
  413.             super(table, sortColumns);
  414.         }

  415.         @Override
  416.         protected int compare(final Column column, final Object value1,
  417.                 final Object value2) throws TypeCastException
  418.         {
  419.             if (logger.isDebugEnabled())
  420.             {
  421.                 logger.debug("compare(column={}, value1={}, value2={}) - start",
  422.                         new Object[] {column, value1, value2});
  423.             }

  424.             // Default behavior since ever
  425.             final String stringValue1 = DataType.asString(value1);
  426.             final String stringValue2 = DataType.asString(value2);
  427.             final int result = stringValue1.compareTo(stringValue2);
  428.             return result;
  429.         }
  430.     }

  431.     /**
  432.      * {@inheritDoc}
  433.      */
  434.     @Override
  435.     public String toString()
  436.     {
  437.         final StringBuilder sb = new StringBuilder(2000);

  438.         sb.append(getClass().getName()).append("[");
  439.         sb.append("_columns=[").append(Arrays.toString(_columns)).append("], ");
  440.         sb.append("_indexes=[").append(_indexes).append("], ");
  441.         sb.append("_table=[").append(_table).append("]");
  442.         sb.append("]");

  443.         return sb.toString();
  444.     }
  445. }