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
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Comparator;
26 import java.util.List;
27
28 import org.dbunit.dataset.filter.IColumnFilter;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33 * This class exclusively provides static methods that operate on {@link Column} objects.
34 *
35 * @author gommma
36 * @version $Revision$
37 * @since 2.3.0
38 */
39 public class Columns
40 {
41 /**
42 * Logger for this class
43 */
44 private static final Logger logger = LoggerFactory.getLogger(Columns.class);
45
46 private static final ColumnComparator COLUMN_COMPARATOR = new ColumnComparator();
47
48 private static final Column[] EMPTY_COLUMNS = new Column[0];
49
50
51 private Columns()
52 {
53 }
54
55
56 /**
57 * Search and return the {@link Column}s from the specified column array that
58 * match one of the given <code>columnNames</code>.
59 * <br>
60 * Note that this method has a bad performance compared to {@link #findColumnsByName(String[], ITableMetaData)}
61 * because it iterates over all columns.
62 *
63 * @param columnNames the names of the columns to search.
64 * @param columns the array of columns in which the <code>columnNames</code> will be searched.
65 * @return the column array which is empty if no column has been found or no
66 * column names have been given
67 * @see #findColumnsByName(String[], ITableMetaData)
68 */
69 public static Column[] getColumns(String[] columnNames, Column[] columns) {
70 if (logger.isDebugEnabled())
71 logger.debug("getColumns(columnNames={}, columns={}) - start",
72 columnNames, columns);
73
74 if (columnNames == null || columnNames.length == 0)
75 {
76 return EMPTY_COLUMNS;
77 }
78
79 List resultList = new ArrayList();
80 for (int i = 0; i < columnNames.length; i++)
81 {
82 Column column = Columns.getColumn(columnNames[i], columns);
83 if (column != null)
84 {
85 resultList.add(column);
86 }
87 }
88
89 return (Column[])resultList.toArray(new Column[0]);
90 }
91
92 /**
93 * Searches for the given <code>columns</code> using only the {@link Column#getColumnName()}
94 * in the given <code>tableMetaData</code>
95 * @param columnNames The column names that are searched in the given table metadata
96 * @param tableMetaData The table metadata in which the columns are searched by name
97 * @return The column objects from the given <code>tableMetaData</code>
98 * @throws NoSuchColumnException if the given column has not been found
99 * @throws DataSetException if something goes wrong when trying to retrieve the columns
100 */
101 public static Column[] findColumnsByName(String[] columnNames,
102 ITableMetaData tableMetaData)
103 throws NoSuchColumnException, DataSetException
104 {
105 logger.debug("findColumnsByName(columnNames={}, tableMetaData={}) - start", columnNames, tableMetaData);
106
107 Column[] resultColumns = new Column[columnNames.length];
108 for (int i = 0; i < columnNames.length; i++)
109 {
110 String sortColumn = columnNames[i];
111 int colIndex = tableMetaData.getColumnIndex(sortColumn);
112 resultColumns[i] = tableMetaData.getColumns()[colIndex];
113 }
114 return resultColumns;
115 }
116
117 /**
118 * Searches for the given <code>columns</code> using only the {@link Column#getColumnName()}
119 * in the given <code>tableMetaData</code>
120 * @param columns The columns whose names are searched in the given table metadata
121 * @param tableMetaData The table metadata in which the columns are searched by name
122 * @return The column objects from the given <code>tableMetaData</code>
123 * @throws NoSuchColumnException if the given column has not been found
124 * @throws DataSetException if something goes wrong when trying to retrieve the columns
125 */
126 public static Column[] findColumnsByName(Column[] columns,
127 ITableMetaData tableMetaData)
128 throws NoSuchColumnException, DataSetException
129 {
130 logger.debug("findColumnsByName(columns={}, tableMetaData={}) - start", columns, tableMetaData);
131
132 Column[] resultColumns = new Column[columns.length];
133 for (int i = 0; i < columns.length; i++)
134 {
135 Column sortColumn = columns[i];
136 int colIndex = tableMetaData.getColumnIndex(sortColumn.getColumnName());
137 resultColumns[i] = tableMetaData.getColumns()[colIndex];
138 }
139 return resultColumns;
140 }
141
142 /**
143 * Search and return the specified column from the specified column array.
144 * <br>
145 * Note that this method has a bad performance compared to {@link ITableMetaData#getColumnIndex(String)}
146 * because it iterates over all columns.
147 *
148 * @param columnName the name of the column to search.
149 * @param columns the array of columns in which the <code>columnName</code> will be searched.
150 * @return the column or <code>null</code> if the column is not found
151 */
152 public static Column getColumn(String columnName, Column[] columns)
153 {
154 logger.debug("getColumn(columnName={}, columns={}) - start", columnName, columns);
155
156 for (int i = 0; i < columns.length; i++)
157 {
158 Column column = columns[i];
159 if (columnName.equalsIgnoreCase(columns[i].getColumnName()))
160 {
161 return column;
162 }
163 }
164
165 return null;
166 }
167
168 /**
169 * Search and return the specified column from the specified column array.
170 *
171 * @param columnName the name of the column to search.
172 * @param columns the array of columns in which the <code>columnName</code> will be searched.
173 * @param tableName The name of the table to which the column array belongs -
174 * only needed for the exception message in case of a validation failure
175 * @return the valid column
176 * @throws NoSuchColumnException If no column exists with the given name
177 */
178 public static Column getColumnValidated(String columnName, Column[] columns, String tableName)
179 throws NoSuchColumnException
180 {
181 if (logger.isDebugEnabled())
182 logger.debug("getColumn(columnName={}, columns={}, tableName={}) - start",
183 columnName, columns, tableName);
184
185 Column column = Columns.getColumn(columnName, columns);
186 if(column==null)
187 {
188 throw new NoSuchColumnException(tableName, columnName);
189 }
190
191 return column;
192 }
193
194 /**
195 * Search and return the columns from the specified column array which are
196 * accepted by the given {@link IColumnFilter}.
197 * @param tableName The name of the table which is needed for the filter invocation
198 * @param columns All available columns to which the filter will be applied
199 * @param columnFilter The column filter that is applied to the given <code>columns</code>
200 * @return The columns that are accepted by the given filter
201 */
202 public static Column[] getColumns(String tableName, Column[] columns,
203 IColumnFilter columnFilter)
204 {
205 if (logger.isDebugEnabled())
206 logger.debug("getColumns(tableName={}, columns={}, columnFilter={}) - start",
207 tableName, columns, columnFilter);
208
209 List resultList = new ArrayList();
210 for (int i = 0; i < columns.length; i++)
211 {
212 Column column = columns[i];
213 if (columnFilter.accept(tableName, column))
214 {
215 resultList.add(column);
216 }
217 }
218
219 return (Column[])resultList.toArray(new Column[0]);
220 }
221
222 /**
223 * Returns a sorted array of column objects
224 *
225 * @param metaData The metaData needed to get the columns to be sorted
226 * @return The columns sorted by their column names, ignoring the case of the column names
227 * @throws DataSetException
228 */
229 public static Column[] getSortedColumns(ITableMetaData metaData)
230 throws DataSetException
231 {
232 logger.debug("getSortedColumns(metaData={}) - start", metaData);
233
234 Column[] columns = metaData.getColumns();
235 Column[] sortColumns = new Column[columns.length];
236 System.arraycopy(columns, 0, sortColumns, 0, columns.length);
237 Arrays.sort(sortColumns, COLUMN_COMPARATOR);
238 return sortColumns;
239 }
240
241 /**
242 * Returns the names of the given column objects as string array
243 * @param columns The column objects
244 * @return The names of the given column objects
245 * @since 2.4
246 */
247 public static String[] getColumnNames(Column[] columns)
248 {
249 String[] result = new String[columns.length];
250 for (int i = 0; i < columns.length; i++) {
251 result[i] = columns[i].getColumnName();
252 }
253 return result;
254 }
255
256 /**
257 * Creates a pretty string representation of the given column names
258 * @param columns The columns to be formatted
259 * @return The string representation of the given column names
260 */
261 public static String getColumnNamesAsString(Column[] columns)
262 {
263 logger.debug("getColumnNamesAsString(columns={}) - start", columns);
264
265 String[] names = new String[columns.length];
266 for (int i = 0; i < columns.length; i++)
267 {
268 Column column = columns[i];
269 names[i] = column.getColumnName();
270 }
271 return Arrays.asList(names).toString();
272 }
273
274 /**
275 * Merges the two arrays of columns so that all of the columns are available in the result array.
276 * The first array is considered as master and if a column with a specific name is available in
277 * both arrays the one from the first array is used.
278 * @param referenceColumns reference columns treated as master columns during the merge
279 * @param columnsToMerge potentially new columns to be merged if they do not yet exist in the referenceColumns
280 * @return Array of merged columns
281 */
282 public static Column[] mergeColumnsByName(Column[] referenceColumns, Column[] columnsToMerge) {
283 logger.debug("mergeColumnsByName(referenceColumns={}, columnsToMerge={}) - start", referenceColumns, columnsToMerge);
284
285 List resultList = new ArrayList(Arrays.asList(referenceColumns));
286 List columnsToMergeNotInRefList = new ArrayList(Arrays.asList(columnsToMerge));
287
288 // All columns that exist in the referenceColumns
289 for (int i = 0; i < referenceColumns.length; i++) {
290 Column refColumn = referenceColumns[i];
291 for (int k = 0; k < columnsToMerge.length; k++) {
292 Column columnToMerge = columnsToMerge[k];
293 // Check if this colToMerge exists in the refColumn
294 if(columnToMerge.getColumnName().equals(refColumn.getColumnName())) {
295 // We found the column in the refColumns - so no candidate for adding to the result list
296 columnsToMergeNotInRefList.remove(columnToMerge);
297 break;
298 }
299 }
300 }
301
302 // Add all "columnsToMerge" that have not been found in the referenceColumnList
303 resultList.addAll(columnsToMergeNotInRefList);
304 return (Column[]) resultList.toArray(new Column[]{});
305 }
306
307
308 /**
309 * Returns the column difference of the two given {@link ITableMetaData} objects
310 * @param expectedMetaData
311 * @param actualMetaData
312 * @return The columns that differ in the both given {@link ITableMetaData} objects
313 * @throws DataSetException
314 */
315 public static ColumnDiff getColumnDiff(ITableMetaData expectedMetaData,
316 ITableMetaData actualMetaData)
317 throws DataSetException
318 {
319 return new ColumnDiff(expectedMetaData, actualMetaData);
320 }
321
322
323
324 // ColumnComparator class
325 private static class ColumnComparator implements Comparator
326 {
327 /**
328 * Logger for this class
329 */
330 private static final Logger logger = LoggerFactory.getLogger(ColumnComparator.class);
331
332 /**
333 * Compare columns by name ignoring case
334 * @see java.util.Comparator#compare(T, T)
335 */
336 public int compare(Object o1, Object o2)
337 {
338 logger.debug("compare(o1={}, o2={}) - start", o1, o2);
339
340 Column column1 = (Column)o1;
341 Column column2 = (Column)o2;
342
343 String columnName1 = column1.getColumnName();
344 String columnName2 = column2.getColumnName();
345 return columnName1.compareToIgnoreCase(columnName2);
346 }
347 }
348
349 /**
350 * Describes the {@link Column}s that are different in two tables.
351 * @author gommma
352 * @version $Revision$
353 * @since 2.3.0
354 */
355 public static class ColumnDiff
356 {
357 /**
358 * Logger for this class
359 */
360 private static final Logger logger = LoggerFactory.getLogger(ColumnDiff.class);
361 /**
362 * String message that is returned when no difference has been found in the compared columns
363 */
364 private static final String NO_DIFFERENCE = "no difference found";
365
366 /**
367 * The columns that exist in the expected result but not in the actual
368 */
369 private Column[] expected;
370 /**
371 * The columns that exist in the actual result but not in the expected
372 */
373 private Column[] actual;
374 private ITableMetaData expectedMetaData;
375 private ITableMetaData actualMetaData;
376
377 /**
378 * Creates the difference between the two metadata's columns
379 * @param expectedMetaData The metadata of the expected results table
380 * @param actualMetaData The metadata of the actual results table
381 * @throws DataSetException
382 */
383 public ColumnDiff(ITableMetaData expectedMetaData,
384 ITableMetaData actualMetaData)
385 throws DataSetException
386 {
387 if (expectedMetaData == null) {
388 throw new NullPointerException(
389 "The parameter 'expectedMetaData' must not be null");
390 }
391 if (actualMetaData == null) {
392 throw new NullPointerException(
393 "The parameter 'actualMetaData' must not be null");
394 }
395
396 this.expectedMetaData = expectedMetaData;
397 this.actualMetaData = actualMetaData;
398
399 Column[] allExpectedCols = expectedMetaData.getColumns();
400 Column[] allActualCols = actualMetaData.getColumns();
401
402 // Get the columns that are missing on the actual side (walk through actual
403 // columns and look for them in the expected metadata)
404 this.actual = findMissingColumnsIn(expectedMetaData, allActualCols);
405 // Get the columns that are missing on the expected side (walk through expected
406 // columns and look for them in the actual metadata)
407 this.expected = findMissingColumnsIn(actualMetaData, allExpectedCols);
408 }
409
410 /**
411 * Searches and returns all columns that are missing in the given {@link ITableMetaData} object
412 * @param metaDataToCheck The {@link ITableMetaData} in which the given columns should be searched
413 * @param columnsToSearch The columns to be searched in the given {@link ITableMetaData}
414 * @return Those {@link Column}s out of the columnsToSearch that have not been found in metaDataToCheck
415 * @throws DataSetException
416 */
417 private Column[] findMissingColumnsIn(ITableMetaData metaDataToCheck,
418 Column[] columnsToSearch) throws DataSetException
419 {
420 logger.debug("findMissingColumnsIn(metaDataToCheck={}, columnsToSearch={})", metaDataToCheck, columnsToSearch);
421
422 List columnsNotFound = new ArrayList();
423 for (int i = 0; i < columnsToSearch.length; i++) {
424 try {
425 metaDataToCheck.getColumnIndex(columnsToSearch[i].getColumnName());
426 }
427 catch(NoSuchColumnException e) {
428 columnsNotFound.add(columnsToSearch[i]);
429 }
430 }
431
432 Column[] result = (Column[]) columnsNotFound.toArray(new Column[]{});
433 return result;
434 }
435
436 /**
437 * @return <code>true</code> if there is a difference in the columns given in the constructor
438 */
439 public boolean hasDifference()
440 {
441 return this.expected.length > 0 || this.actual.length > 0;
442 }
443
444 /**
445 * @return The columns that exist in the expected result but not in the actual
446 */
447 public Column[] getExpected() {
448 return expected;
449 }
450
451 /**
452 * @return The columns that exist in the actual result but not in the expected
453 */
454 public Column[] getActual() {
455 return actual;
456 }
457
458 /**
459 * @return The value of {@link #getExpected()} as formatted string
460 * @see #getExpected()
461 */
462 public String getExpectedAsString() {
463 return Columns.getColumnNamesAsString(expected);
464 }
465
466 /**
467 * @return The value of {@link #getActual()} as formatted string
468 * @see #getActual()
469 */
470 public String getActualAsString() {
471 return Columns.getColumnNamesAsString(actual);
472 }
473
474 /**
475 * @return A pretty formatted message that can be used for user information
476 * @throws DataSetException
477 */
478 public String getMessage() throws DataSetException
479 {
480 logger.debug("getMessage() - start");
481
482 if(!this.hasDifference())
483 {
484 return NO_DIFFERENCE;
485 }
486 else
487 {
488 Column[] allExpectedCols = expectedMetaData.getColumns();
489 Column[] allActualCols = actualMetaData.getColumns();
490 String expectedTableName = expectedMetaData.getTableName();
491
492 String message;
493 if(allExpectedCols.length != allActualCols.length)
494 {
495 message = "column count (table=" + expectedTableName + ", " +
496 "expectedColCount=" + allExpectedCols.length + ", actualColCount=" + allActualCols.length + ")";
497 }
498 else
499 {
500 message = "column mismatch (table=" + expectedTableName + ")";
501 }
502 return message;
503 }
504 }
505
506 // /**
507 // * @return A pretty formatted message that shows up the difference
508 // */
509 // private String toMessage()
510 // {
511 // StringBuffer sb = new StringBuffer();
512 // sb.append("column-diffs (expected <-> actual): ");
513 // if(this.hasDifference())
514 // {
515 // sb.append(getExpectedAsString());
516 // sb.append(" <-> ");
517 // sb.append(getActualAsString());
518 // }
519 // else
520 // {
521 // sb.append(NO_DIFFERENCE);
522 // }
523 // return sb.toString();
524 // }
525
526 public String toString()
527 {
528 final StringBuilder sb = new StringBuilder();
529 sb.append(getClass().getName()).append("[");
530 sb.append("expected=").append(Arrays.asList(expected).toString());
531 sb.append(", actual=").append(Arrays.asList(actual).toString());
532 sb.append("]");
533 return sb.toString();
534 }
535
536 }
537
538
539 }