Columns.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.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Comparator;
  25. import java.util.List;

  26. import org.dbunit.dataset.filter.IColumnFilter;
  27. import org.slf4j.Logger;
  28. import org.slf4j.LoggerFactory;

  29. /**
  30.  * This class exclusively provides static methods that operate on {@link Column} objects.
  31.  *
  32.  * @author gommma
  33.  * @version $Revision$
  34.  * @since 2.3.0
  35.  */
  36. public class Columns
  37. {
  38.     /**
  39.      * Logger for this class
  40.      */
  41.     private static final Logger logger = LoggerFactory.getLogger(Columns.class);

  42.     private static final ColumnComparator COLUMN_COMPARATOR = new ColumnComparator();

  43.     private static final Column[] EMPTY_COLUMNS = new Column[0];

  44.    
  45.     private Columns()
  46.     {
  47.     }
  48.    
  49.    
  50.     /**
  51.      * Search and return the {@link Column}s from the specified column array that
  52.      * match one of the given <code>columnNames</code>.
  53.      * <br>
  54.      * Note that this method has a bad performance compared to {@link #findColumnsByName(String[], ITableMetaData)}
  55.      * because it iterates over all columns.
  56.      *
  57.      * @param columnNames the names of the columns to search.
  58.      * @param columns the array of columns in which the <code>columnNames</code> will be searched.
  59.      * @return the column array which is empty if no column has been found or no
  60.      * column names have been given
  61.      * @see #findColumnsByName(String[], ITableMetaData)
  62.      */
  63.     public static Column[] getColumns(String[] columnNames, Column[] columns) {
  64.         if (logger.isDebugEnabled())
  65.             logger.debug("getColumns(columnNames={}, columns={}) - start",
  66.                     columnNames, columns);

  67.         if (columnNames == null || columnNames.length == 0)
  68.         {
  69.             return EMPTY_COLUMNS;
  70.         }

  71.         List resultList = new ArrayList();
  72.         for (int i = 0; i < columnNames.length; i++)
  73.         {
  74.             Column column = Columns.getColumn(columnNames[i], columns);
  75.             if (column != null)
  76.             {
  77.                 resultList.add(column);
  78.             }
  79.         }

  80.         return (Column[])resultList.toArray(new Column[0]);
  81.     }

  82.     /**
  83.      * Searches for the given <code>columns</code> using only the {@link Column#getColumnName()}
  84.      * in the given <code>tableMetaData</code>
  85.      * @param columnNames The column names that are searched in the given table metadata
  86.      * @param tableMetaData The table metadata in which the columns are searched by name
  87.      * @return The column objects from the given <code>tableMetaData</code>
  88.      * @throws NoSuchColumnException if the given column has not been found
  89.      * @throws DataSetException if something goes wrong when trying to retrieve the columns
  90.      */
  91.     public static Column[] findColumnsByName(String[] columnNames,
  92.             ITableMetaData tableMetaData)
  93.     throws NoSuchColumnException, DataSetException
  94.     {
  95.         logger.debug("findColumnsByName(columnNames={}, tableMetaData={}) - start", columnNames, tableMetaData);

  96.         Column[] resultColumns = new Column[columnNames.length];
  97.         for (int i = 0; i < columnNames.length; i++)
  98.         {
  99.             String sortColumn = columnNames[i];
  100.             int colIndex = tableMetaData.getColumnIndex(sortColumn);
  101.             resultColumns[i] = tableMetaData.getColumns()[colIndex];            
  102.         }
  103.         return resultColumns;
  104.     }

  105.     /**
  106.      * Searches for the given <code>columns</code> using only the {@link Column#getColumnName()}
  107.      * in the given <code>tableMetaData</code>
  108.      * @param columns The columns whose names are searched in the given table metadata
  109.      * @param tableMetaData The table metadata in which the columns are searched by name
  110.      * @return The column objects from the given <code>tableMetaData</code>
  111.      * @throws NoSuchColumnException if the given column has not been found
  112.      * @throws DataSetException if something goes wrong when trying to retrieve the columns
  113.      */
  114.     public static Column[] findColumnsByName(Column[] columns,
  115.             ITableMetaData tableMetaData)
  116.     throws NoSuchColumnException, DataSetException
  117.     {
  118.         logger.debug("findColumnsByName(columns={}, tableMetaData={}) - start", columns, tableMetaData);

  119.         Column[] resultColumns = new Column[columns.length];
  120.         for (int i = 0; i < columns.length; i++)
  121.         {
  122.             Column sortColumn = columns[i];
  123.             int colIndex = tableMetaData.getColumnIndex(sortColumn.getColumnName());
  124.             resultColumns[i] = tableMetaData.getColumns()[colIndex];            
  125.         }
  126.         return resultColumns;
  127.     }

  128.     /**
  129.      * Search and return the specified column from the specified column array.
  130.      * <br>
  131.      * Note that this method has a bad performance compared to {@link ITableMetaData#getColumnIndex(String)}
  132.      * because it iterates over all columns.
  133.      *
  134.      * @param columnName the name of the column to search.
  135.      * @param columns the array of columns in which the <code>columnName</code> will be searched.
  136.      * @return the column or <code>null</code> if the column is not found
  137.      */
  138.     public static Column getColumn(String columnName, Column[] columns)
  139.     {
  140.         logger.debug("getColumn(columnName={}, columns={}) - start", columnName, columns);

  141.         for (int i = 0; i < columns.length; i++)
  142.         {
  143.             Column column = columns[i];
  144.             if (columnName.equalsIgnoreCase(columns[i].getColumnName()))
  145.             {
  146.                 return column;
  147.             }
  148.         }

  149.         return null;
  150.     }

  151.     /**
  152.      * Search and return the specified column from the specified column array.
  153.      *
  154.      * @param columnName the name of the column to search.
  155.      * @param columns the array of columns in which the <code>columnName</code> will be searched.
  156.      * @param tableName The name of the table to which the column array belongs -
  157.      * only needed for the exception message in case of a validation failure
  158.      * @return the valid column
  159.      * @throws NoSuchColumnException If no column exists with the given name
  160.      */
  161.     public static Column getColumnValidated(String columnName, Column[] columns, String tableName)
  162.     throws NoSuchColumnException
  163.     {
  164.         if (logger.isDebugEnabled())
  165.             logger.debug("getColumn(columnName={}, columns={}, tableName={}) - start",
  166.                 columnName, columns, tableName);

  167.         Column column = Columns.getColumn(columnName, columns);
  168.         if(column==null)
  169.         {
  170.             throw new NoSuchColumnException(tableName, columnName);
  171.         }
  172.        
  173.         return column;
  174.     }

  175.     /**
  176.      * Search and return the columns from the specified column array which are
  177.      * accepted by the given {@link IColumnFilter}.
  178.      * @param tableName The name of the table which is needed for the filter invocation
  179.      * @param columns All available columns to which the filter will be applied
  180.      * @param columnFilter The column filter that is applied to the given <code>columns</code>
  181.      * @return The columns that are accepted by the given filter
  182.      */
  183.     public static Column[] getColumns(String tableName, Column[] columns,
  184.             IColumnFilter columnFilter)
  185.     {
  186.         if (logger.isDebugEnabled())
  187.             logger.debug("getColumns(tableName={}, columns={}, columnFilter={}) - start",
  188.                     tableName, columns, columnFilter);

  189.         List resultList = new ArrayList();
  190.         for (int i = 0; i < columns.length; i++)
  191.         {
  192.             Column column = columns[i];
  193.             if (columnFilter.accept(tableName, column))
  194.             {
  195.                 resultList.add(column);
  196.             }
  197.         }

  198.         return (Column[])resultList.toArray(new Column[0]);
  199.     }

  200.     /**
  201.      * Returns a sorted array of column objects
  202.      *
  203.      * @param metaData The metaData needed to get the columns to be sorted
  204.      * @return The columns sorted by their column names, ignoring the case of the column names
  205.      * @throws DataSetException
  206.      */
  207.     public static Column[] getSortedColumns(ITableMetaData metaData)
  208.     throws DataSetException
  209.     {
  210.         logger.debug("getSortedColumns(metaData={}) - start", metaData);

  211.         Column[] columns = metaData.getColumns();
  212.         Column[] sortColumns = new Column[columns.length];
  213.         System.arraycopy(columns, 0, sortColumns, 0, columns.length);
  214.         Arrays.sort(sortColumns, COLUMN_COMPARATOR);
  215.         return sortColumns;
  216.     }

  217.     /**
  218.      * Returns the names of the given column objects as string array
  219.      * @param columns The column objects
  220.      * @return The names of the given column objects
  221.      * @since 2.4
  222.      */
  223.     public static String[] getColumnNames(Column[] columns)
  224.     {
  225.         String[] result = new String[columns.length];
  226.         for (int i = 0; i < columns.length; i++) {
  227.             result[i] = columns[i].getColumnName();
  228.         }
  229.         return result;
  230.     }

  231.     /**
  232.      * Creates a pretty string representation of the given column names
  233.      * @param columns The columns to be formatted
  234.      * @return The string representation of the given column names
  235.      */
  236.     public static String getColumnNamesAsString(Column[] columns)
  237.     {
  238.         logger.debug("getColumnNamesAsString(columns={}) - start", columns);

  239.         String[] names = new String[columns.length];
  240.         for (int i = 0; i < columns.length; i++)
  241.         {
  242.             Column column = columns[i];
  243.             names[i] = column.getColumnName();
  244.         }
  245.         return Arrays.asList(names).toString();
  246.     }

  247.     /**
  248.      * Merges the two arrays of columns so that all of the columns are available in the result array.
  249.      * The first array is considered as master and if a column with a specific name is available in
  250.      * both arrays the one from the first array is used.
  251.      * @param referenceColumns reference columns treated as master columns during the merge
  252.      * @param columnsToMerge potentially new columns to be merged if they do not yet exist in the referenceColumns
  253.      * @return Array of merged columns
  254.      */
  255.     public static Column[] mergeColumnsByName(Column[] referenceColumns, Column[] columnsToMerge) {
  256.         logger.debug("mergeColumnsByName(referenceColumns={}, columnsToMerge={}) - start", referenceColumns, columnsToMerge);

  257.         List resultList = new ArrayList(Arrays.asList(referenceColumns));
  258.         List columnsToMergeNotInRefList = new ArrayList(Arrays.asList(columnsToMerge));
  259.        
  260.         // All columns that exist in the referenceColumns
  261.         for (int i = 0; i < referenceColumns.length; i++) {
  262.             Column refColumn = referenceColumns[i];
  263.             for (int k = 0; k < columnsToMerge.length; k++) {
  264.                 Column columnToMerge = columnsToMerge[k];
  265.                 // Check if this colToMerge exists in the refColumn
  266.                 if(columnToMerge.getColumnName().equals(refColumn.getColumnName())) {
  267.                     // We found the column in the refColumns - so no candidate for adding to the result list
  268.                     columnsToMergeNotInRefList.remove(columnToMerge);
  269.                     break;
  270.                 }
  271.             }
  272.         }
  273.        
  274.         // Add all "columnsToMerge" that have not been found in the referenceColumnList
  275.         resultList.addAll(columnsToMergeNotInRefList);
  276.         return (Column[]) resultList.toArray(new Column[]{});
  277.     }

  278.    
  279.     /**
  280.      * Returns the column difference of the two given {@link ITableMetaData} objects
  281.      * @param expectedMetaData
  282.      * @param actualMetaData
  283.      * @return The columns that differ in the both given {@link ITableMetaData} objects
  284.      * @throws DataSetException
  285.      */
  286.     public static ColumnDiff getColumnDiff(ITableMetaData expectedMetaData,
  287.             ITableMetaData actualMetaData)
  288.     throws DataSetException
  289.     {
  290.         return new ColumnDiff(expectedMetaData, actualMetaData);
  291.     }
  292.    

  293.    
  294.     //  ColumnComparator class
  295.     private static class ColumnComparator implements Comparator
  296.     {
  297.         /**
  298.          * Logger for this class
  299.          */
  300.         private static final Logger logger = LoggerFactory.getLogger(ColumnComparator.class);

  301.         /**
  302.          * Compare columns by name ignoring case
  303.          * @see java.util.Comparator#compare(T, T)
  304.          */
  305.         public int compare(Object o1, Object o2)
  306.         {
  307.             logger.debug("compare(o1={}, o2={}) - start", o1, o2);

  308.             Column column1 = (Column)o1;
  309.             Column column2 = (Column)o2;

  310.             String columnName1 = column1.getColumnName();
  311.             String columnName2 = column2.getColumnName();
  312.             return columnName1.compareToIgnoreCase(columnName2);
  313.         }
  314.     }

  315.     /**
  316.      * Describes the {@link Column}s that are different in two tables.
  317.      * @author gommma
  318.      * @version $Revision$
  319.      * @since 2.3.0
  320.      */
  321.     public static class ColumnDiff
  322.     {
  323.         /**
  324.          * Logger for this class
  325.          */
  326.         private static final Logger logger = LoggerFactory.getLogger(ColumnDiff.class);
  327.         /**
  328.          * String message that is returned when no difference has been found in the compared columns
  329.          */
  330.         private static final String NO_DIFFERENCE = "no difference found";
  331.        
  332.         /**
  333.          * The columns that exist in the expected result but not in the actual
  334.          */
  335.         private Column[] expected;
  336.         /**
  337.          * The columns that exist in the actual result but not in the expected
  338.          */
  339.         private Column[] actual;
  340.         private ITableMetaData expectedMetaData;
  341.         private ITableMetaData actualMetaData;
  342.        
  343.         /**
  344.          * Creates the difference between the two metadata's columns
  345.          * @param expectedMetaData The metadata of the expected results table
  346.          * @param actualMetaData The metadata of the actual results table
  347.          * @throws DataSetException
  348.          */
  349.         public ColumnDiff(ITableMetaData expectedMetaData,
  350.                 ITableMetaData actualMetaData)
  351.         throws DataSetException
  352.         {
  353.             if (expectedMetaData == null) {
  354.                 throw new NullPointerException(
  355.                         "The parameter 'expectedMetaData' must not be null");
  356.             }
  357.             if (actualMetaData == null) {
  358.                 throw new NullPointerException(
  359.                         "The parameter 'actualMetaData' must not be null");
  360.             }
  361.            
  362.             this.expectedMetaData = expectedMetaData;
  363.             this.actualMetaData = actualMetaData;
  364.            
  365.             Column[] allExpectedCols = expectedMetaData.getColumns();
  366.             Column[] allActualCols = actualMetaData.getColumns();
  367.            
  368.             // Get the columns that are missing on the actual side (walk through actual
  369.             // columns and look for them in the expected metadata)
  370.             this.actual = findMissingColumnsIn(expectedMetaData, allActualCols);
  371.             // Get the columns that are missing on the expected side (walk through expected
  372.             // columns and look for them in the actual metadata)
  373.             this.expected = findMissingColumnsIn(actualMetaData, allExpectedCols);
  374.         }
  375.        
  376.         /**
  377.          * Searches and returns all columns that are missing in the given {@link ITableMetaData} object
  378.          * @param metaDataToCheck The {@link ITableMetaData} in which the given columns should be searched
  379.          * @param columnsToSearch The columns to be searched in the given {@link ITableMetaData}
  380.          * @return Those {@link Column}s out of the columnsToSearch that have not been found in metaDataToCheck
  381.          * @throws DataSetException
  382.          */
  383.         private Column[] findMissingColumnsIn(ITableMetaData metaDataToCheck,
  384.                 Column[] columnsToSearch) throws DataSetException
  385.         {
  386.             logger.debug("findMissingColumnsIn(metaDataToCheck={}, columnsToSearch={})", metaDataToCheck, columnsToSearch);
  387.            
  388.             List columnsNotFound = new ArrayList();
  389.             for (int i = 0; i < columnsToSearch.length; i++) {
  390.                 try {
  391.                     metaDataToCheck.getColumnIndex(columnsToSearch[i].getColumnName());
  392.                 }
  393.                 catch(NoSuchColumnException e) {
  394.                     columnsNotFound.add(columnsToSearch[i]);
  395.                 }
  396.             }
  397.            
  398.             Column[] result = (Column[]) columnsNotFound.toArray(new Column[]{});
  399.             return result;
  400.         }

  401.         /**
  402.          * @return <code>true</code> if there is a difference in the columns given in the constructor
  403.          */
  404.         public boolean hasDifference()
  405.         {
  406.             return this.expected.length > 0 || this.actual.length > 0;
  407.         }

  408.         /**
  409.          * @return The columns that exist in the expected result but not in the actual
  410.          */
  411.         public Column[] getExpected() {
  412.             return expected;
  413.         }

  414.         /**
  415.          * @return The columns that exist in the actual result but not in the expected
  416.          */
  417.         public Column[] getActual() {
  418.             return actual;
  419.         }

  420.         /**
  421.          * @return The value of {@link #getExpected()} as formatted string
  422.          * @see #getExpected()
  423.          */
  424.         public String getExpectedAsString() {
  425.             return Columns.getColumnNamesAsString(expected);
  426.         }

  427.         /**
  428.          * @return The value of {@link #getActual()} as formatted string
  429.          * @see #getActual()
  430.          */
  431.         public String getActualAsString() {
  432.             return Columns.getColumnNamesAsString(actual);
  433.         }

  434.         /**
  435.          * @return A pretty formatted message that can be used for user information
  436.          * @throws DataSetException
  437.          */
  438.         public String getMessage() throws DataSetException
  439.         {
  440.             logger.debug("getMessage() - start");

  441.             if(!this.hasDifference())
  442.             {
  443.                 return NO_DIFFERENCE;
  444.             }
  445.             else
  446.             {
  447.                 Column[] allExpectedCols = expectedMetaData.getColumns();
  448.                 Column[] allActualCols = actualMetaData.getColumns();
  449.                 String expectedTableName = expectedMetaData.getTableName();
  450.    
  451.                 String message;
  452.                 if(allExpectedCols.length != allActualCols.length)
  453.                 {
  454.                     message = "column count (table=" + expectedTableName + ", " +
  455.                             "expectedColCount=" + allExpectedCols.length + ", actualColCount=" + allActualCols.length + ")";
  456.                 }
  457.                 else
  458.                 {
  459.                     message = "column mismatch (table=" + expectedTableName + ")";
  460.                 }
  461.                 return message;
  462.             }
  463.         }

  464. //      /**
  465. //       * @return A pretty formatted message that shows up the difference
  466. //       */
  467. //      private String toMessage()
  468. //      {
  469. //          StringBuffer sb = new StringBuffer();
  470. //          sb.append("column-diffs (expected <-> actual): ");
  471. //          if(this.hasDifference())
  472. //          {
  473. //              sb.append(getExpectedAsString());
  474. //              sb.append(" <-> ");
  475. //              sb.append(getActualAsString());
  476. //          }
  477. //          else
  478. //          {
  479. //              sb.append(NO_DIFFERENCE);
  480. //          }
  481. //          return sb.toString();
  482. //      }
  483.        
  484.         public String toString()
  485.         {
  486.             final StringBuilder sb = new StringBuilder();
  487.             sb.append(getClass().getName()).append("[");
  488.             sb.append("expected=").append(Arrays.asList(expected).toString());
  489.             sb.append(", actual=").append(Arrays.asList(actual).toString());
  490.             sb.append("]");
  491.             return sb.toString();
  492.         }

  493.     }


  494. }