Columns.java
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2004, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.dbunit.dataset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.dbunit.dataset.filter.IColumnFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class exclusively provides static methods that operate on {@link Column} objects.
*
* @author gommma
* @version $Revision$
* @since 2.3.0
*/
public class Columns
{
/**
* Logger for this class
*/
private static final Logger logger = LoggerFactory.getLogger(Columns.class);
private static final ColumnComparator COLUMN_COMPARATOR = new ColumnComparator();
private static final Column[] EMPTY_COLUMNS = new Column[0];
private Columns()
{
}
/**
* Search and return the {@link Column}s from the specified column array that
* match one of the given <code>columnNames</code>.
* <br>
* Note that this method has a bad performance compared to {@link #findColumnsByName(String[], ITableMetaData)}
* because it iterates over all columns.
*
* @param columnNames the names of the columns to search.
* @param columns the array of columns in which the <code>columnNames</code> will be searched.
* @return the column array which is empty if no column has been found or no
* column names have been given
* @see #findColumnsByName(String[], ITableMetaData)
*/
public static Column[] getColumns(String[] columnNames, Column[] columns) {
if (logger.isDebugEnabled())
logger.debug("getColumns(columnNames={}, columns={}) - start",
new Object[]{ columnNames, columns });
if (columnNames == null || columnNames.length == 0)
{
return EMPTY_COLUMNS;
}
List resultList = new ArrayList();
for (int i = 0; i < columnNames.length; i++)
{
Column column = Columns.getColumn(columnNames[i], columns);
if (column != null)
{
resultList.add(column);
}
}
return (Column[])resultList.toArray(new Column[0]);
}
/**
* Searches for the given <code>columns</code> using only the {@link Column#getColumnName()}
* in the given <code>tableMetaData</code>
* @param columnNames The column names that are searched in the given table metadata
* @param tableMetaData The table metadata in which the columns are searched by name
* @return The column objects from the given <code>tableMetaData</code>
* @throws NoSuchColumnException if the given column has not been found
* @throws DataSetException if something goes wrong when trying to retrieve the columns
*/
public static Column[] findColumnsByName(String[] columnNames,
ITableMetaData tableMetaData)
throws NoSuchColumnException, DataSetException
{
logger.debug("findColumnsByName(columnNames={}, tableMetaData={}) - start", columnNames, tableMetaData);
Column[] resultColumns = new Column[columnNames.length];
for (int i = 0; i < columnNames.length; i++)
{
String sortColumn = columnNames[i];
int colIndex = tableMetaData.getColumnIndex(sortColumn);
resultColumns[i] = tableMetaData.getColumns()[colIndex];
}
return resultColumns;
}
/**
* Searches for the given <code>columns</code> using only the {@link Column#getColumnName()}
* in the given <code>tableMetaData</code>
* @param columns The columns whose names are searched in the given table metadata
* @param tableMetaData The table metadata in which the columns are searched by name
* @return The column objects from the given <code>tableMetaData</code>
* @throws NoSuchColumnException if the given column has not been found
* @throws DataSetException if something goes wrong when trying to retrieve the columns
*/
public static Column[] findColumnsByName(Column[] columns,
ITableMetaData tableMetaData)
throws NoSuchColumnException, DataSetException
{
logger.debug("findColumnsByName(columns={}, tableMetaData={}) - start", columns, tableMetaData);
Column[] resultColumns = new Column[columns.length];
for (int i = 0; i < columns.length; i++)
{
Column sortColumn = columns[i];
int colIndex = tableMetaData.getColumnIndex(sortColumn.getColumnName());
resultColumns[i] = tableMetaData.getColumns()[colIndex];
}
return resultColumns;
}
/**
* Search and return the specified column from the specified column array.
* <br>
* Note that this method has a bad performance compared to {@link ITableMetaData#getColumnIndex(String)}
* because it iterates over all columns.
*
* @param columnName the name of the column to search.
* @param columns the array of columns in which the <code>columnName</code> will be searched.
* @return the column or <code>null</code> if the column is not found
*/
public static Column getColumn(String columnName, Column[] columns)
{
logger.debug("getColumn(columnName={}, columns={}) - start", columnName, columns);
for (int i = 0; i < columns.length; i++)
{
Column column = columns[i];
if (columnName.equalsIgnoreCase(columns[i].getColumnName()))
{
return column;
}
}
return null;
}
/**
* Search and return the specified column from the specified column array.
*
* @param columnName the name of the column to search.
* @param columns the array of columns in which the <code>columnName</code> will be searched.
* @param tableName The name of the table to which the column array belongs -
* only needed for the exception message in case of a validation failure
* @return the valid column
* @throws NoSuchColumnException If no column exists with the given name
*/
public static Column getColumnValidated(String columnName, Column[] columns, String tableName)
throws NoSuchColumnException
{
if (logger.isDebugEnabled())
logger.debug("getColumn(columnName={}, columns={}, tableName={}) - start",
new Object[] {columnName, columns, tableName } );
Column column = Columns.getColumn(columnName, columns);
if(column==null)
{
throw new NoSuchColumnException(tableName, columnName);
}
return column;
}
/**
* Search and return the columns from the specified column array which are
* accepted by the given {@link IColumnFilter}.
* @param tableName The name of the table which is needed for the filter invocation
* @param columns All available columns to which the filter will be applied
* @param columnFilter The column filter that is applied to the given <code>columns</code>
* @return The columns that are accepted by the given filter
*/
public static Column[] getColumns(String tableName, Column[] columns,
IColumnFilter columnFilter)
{
if (logger.isDebugEnabled())
logger.debug("getColumns(tableName={}, columns={}, columnFilter={}) - start",
new Object[]{ tableName, columns, columnFilter });
List resultList = new ArrayList();
for (int i = 0; i < columns.length; i++)
{
Column column = columns[i];
if (columnFilter.accept(tableName, column))
{
resultList.add(column);
}
}
return (Column[])resultList.toArray(new Column[0]);
}
/**
* Returns a sorted array of column objects
*
* @param metaData The metaData needed to get the columns to be sorted
* @return The columns sorted by their column names, ignoring the case of the column names
* @throws DataSetException
*/
public static Column[] getSortedColumns(ITableMetaData metaData)
throws DataSetException
{
logger.debug("getSortedColumns(metaData={}) - start", metaData);
Column[] columns = metaData.getColumns();
Column[] sortColumns = new Column[columns.length];
System.arraycopy(columns, 0, sortColumns, 0, columns.length);
Arrays.sort(sortColumns, COLUMN_COMPARATOR);
return sortColumns;
}
/**
* Returns the names of the given column objects as string array
* @param columns The column objects
* @return The names of the given column objects
* @since 2.4
*/
public static String[] getColumnNames(Column[] columns)
{
String[] result = new String[columns.length];
for (int i = 0; i < columns.length; i++) {
result[i] = columns[i].getColumnName();
}
return result;
}
/**
* Creates a pretty string representation of the given column names
* @param columns The columns to be formatted
* @return The string representation of the given column names
*/
public static String getColumnNamesAsString(Column[] columns)
{
logger.debug("getColumnNamesAsString(columns={}) - start", columns);
String[] names = new String[columns.length];
for (int i = 0; i < columns.length; i++)
{
Column column = columns[i];
names[i] = column.getColumnName();
}
return Arrays.asList(names).toString();
}
/**
* Merges the two arrays of columns so that all of the columns are available in the result array.
* The first array is considered as master and if a column with a specific name is available in
* both arrays the one from the first array is used.
* @param referenceColumns reference columns treated as master columns during the merge
* @param columnsToMerge potentially new columns to be merged if they do not yet exist in the referenceColumns
* @return Array of merged columns
*/
public static Column[] mergeColumnsByName(Column[] referenceColumns, Column[] columnsToMerge) {
logger.debug("mergeColumnsByName(referenceColumns={}, columnsToMerge={}) - start", referenceColumns, columnsToMerge);
List resultList = new ArrayList(Arrays.asList(referenceColumns));
List columnsToMergeNotInRefList = new ArrayList(Arrays.asList(columnsToMerge));
// All columns that exist in the referenceColumns
for (int i = 0; i < referenceColumns.length; i++) {
Column refColumn = referenceColumns[i];
for (int k = 0; k < columnsToMerge.length; k++) {
Column columnToMerge = columnsToMerge[k];
// Check if this colToMerge exists in the refColumn
if(columnToMerge.getColumnName().equals(refColumn.getColumnName())) {
// We found the column in the refColumns - so no candidate for adding to the result list
columnsToMergeNotInRefList.remove(columnToMerge);
break;
}
}
}
// Add all "columnsToMerge" that have not been found in the referenceColumnList
resultList.addAll(columnsToMergeNotInRefList);
return (Column[]) resultList.toArray(new Column[]{});
}
/**
* Returns the column difference of the two given {@link ITableMetaData} objects
* @param expectedMetaData
* @param actualMetaData
* @return The columns that differ in the both given {@link ITableMetaData} objects
* @throws DataSetException
*/
public static ColumnDiff getColumnDiff(ITableMetaData expectedMetaData,
ITableMetaData actualMetaData)
throws DataSetException
{
return new ColumnDiff(expectedMetaData, actualMetaData);
}
// ColumnComparator class
private static class ColumnComparator implements Comparator
{
/**
* Logger for this class
*/
private static final Logger logger = LoggerFactory.getLogger(ColumnComparator.class);
/**
* Compare columns by name ignoring case
* @see java.util.Comparator#compare(T, T)
*/
public int compare(Object o1, Object o2)
{
logger.debug("compare(o1={}, o2={}) - start", o1, o2);
Column column1 = (Column)o1;
Column column2 = (Column)o2;
String columnName1 = column1.getColumnName();
String columnName2 = column2.getColumnName();
return columnName1.compareToIgnoreCase(columnName2);
}
}
/**
* Describes the {@link Column}s that are different in two tables.
* @author gommma
* @version $Revision$
* @since 2.3.0
*/
public static class ColumnDiff
{
/**
* Logger for this class
*/
private static final Logger logger = LoggerFactory.getLogger(ColumnDiff.class);
/**
* String message that is returned when no difference has been found in the compared columns
*/
private static final String NO_DIFFERENCE = "no difference found";
/**
* The columns that exist in the expected result but not in the actual
*/
private Column[] expected;
/**
* The columns that exist in the actual result but not in the expected
*/
private Column[] actual;
private ITableMetaData expectedMetaData;
private ITableMetaData actualMetaData;
/**
* Creates the difference between the two metadata's columns
* @param expectedMetaData The metadata of the expected results table
* @param actualMetaData The metadata of the actual results table
* @throws DataSetException
*/
public ColumnDiff(ITableMetaData expectedMetaData,
ITableMetaData actualMetaData)
throws DataSetException
{
if (expectedMetaData == null) {
throw new NullPointerException(
"The parameter 'expectedMetaData' must not be null");
}
if (actualMetaData == null) {
throw new NullPointerException(
"The parameter 'actualMetaData' must not be null");
}
this.expectedMetaData = expectedMetaData;
this.actualMetaData = actualMetaData;
Column[] allExpectedCols = expectedMetaData.getColumns();
Column[] allActualCols = actualMetaData.getColumns();
// Get the columns that are missing on the actual side (walk through actual
// columns and look for them in the expected metadata)
this.actual = findMissingColumnsIn(expectedMetaData, allActualCols);
// Get the columns that are missing on the expected side (walk through expected
// columns and look for them in the actual metadata)
this.expected = findMissingColumnsIn(actualMetaData, allExpectedCols);
}
/**
* Searches and returns all columns that are missing in the given {@link ITableMetaData} object
* @param metaDataToCheck The {@link ITableMetaData} in which the given columns should be searched
* @param columnsToSearch The columns to be searched in the given {@link ITableMetaData}
* @return Those {@link Column}s out of the columnsToSearch that have not been found in metaDataToCheck
* @throws DataSetException
*/
private Column[] findMissingColumnsIn(ITableMetaData metaDataToCheck,
Column[] columnsToSearch) throws DataSetException
{
logger.debug("findMissingColumnsIn(metaDataToCheck={}, columnsToSearch={})", metaDataToCheck, columnsToSearch);
List columnsNotFound = new ArrayList();
for (int i = 0; i < columnsToSearch.length; i++) {
try {
metaDataToCheck.getColumnIndex(columnsToSearch[i].getColumnName());
}
catch(NoSuchColumnException e) {
columnsNotFound.add(columnsToSearch[i]);
}
}
Column[] result = (Column[]) columnsNotFound.toArray(new Column[]{});
return result;
}
/**
* @return <code>true</code> if there is a difference in the columns given in the constructor
*/
public boolean hasDifference()
{
return this.expected.length > 0 || this.actual.length > 0;
}
/**
* @return The columns that exist in the expected result but not in the actual
*/
public Column[] getExpected() {
return expected;
}
/**
* @return The columns that exist in the actual result but not in the expected
*/
public Column[] getActual() {
return actual;
}
/**
* @return The value of {@link #getExpected()} as formatted string
* @see #getExpected()
*/
public String getExpectedAsString() {
return Columns.getColumnNamesAsString(expected);
}
/**
* @return The value of {@link #getActual()} as formatted string
* @see #getActual()
*/
public String getActualAsString() {
return Columns.getColumnNamesAsString(actual);
}
/**
* @return A pretty formatted message that can be used for user information
* @throws DataSetException
*/
public String getMessage() throws DataSetException
{
logger.debug("getMessage() - start");
if(!this.hasDifference())
{
return NO_DIFFERENCE;
}
else
{
Column[] allExpectedCols = expectedMetaData.getColumns();
Column[] allActualCols = actualMetaData.getColumns();
String expectedTableName = expectedMetaData.getTableName();
String message;
if(allExpectedCols.length != allActualCols.length)
{
message = "column count (table=" + expectedTableName + ", " +
"expectedColCount=" + allExpectedCols.length + ", actualColCount=" + allActualCols.length + ")";
}
else
{
message = "column mismatch (table=" + expectedTableName + ")";
}
return message;
}
}
// /**
// * @return A pretty formatted message that shows up the difference
// */
// private String toMessage()
// {
// StringBuffer sb = new StringBuffer();
// sb.append("column-diffs (expected <-> actual): ");
// if(this.hasDifference())
// {
// sb.append(getExpectedAsString());
// sb.append(" <-> ");
// sb.append(getActualAsString());
// }
// else
// {
// sb.append(NO_DIFFERENCE);
// }
// return sb.toString();
// }
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName()).append("[");
sb.append("expected=").append(Arrays.asList(expected).toString());
sb.append(", actual=").append(Arrays.asList(actual).toString());
sb.append("]");
return sb.toString();
}
}
}