/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
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 3 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, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.relational.metaData;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import xxl.core.collections.MapEntry;
import xxl.core.cursors.Cursor;
import xxl.core.cursors.Cursors;
import xxl.core.cursors.mappers.Mapper;
import xxl.core.cursors.wrappers.IteratorCursor;
import xxl.core.functions.AbstractFunction;
import xxl.core.util.metaData.MetaDataException;
/**
* Appends a number of given ResultSetMetaData objects to each other and
* returns a new unified ResultSetMetaData object such that all column names
* are unique. That is when appending two ResultSetMetaData objects having
* columns with equal names the resulting UnifiedResultSetMetaData object will
* unify these columns and merge them to one column.
*/
public class UnifiedResultSetMetaData extends MergedResultSetMetaData {
/**
* An array representing the columns of this object. Every entry consists
* of a list, holding the occurrences of the affected column in the wrapped
* ResultSetMetaData objects.
*/
protected List<List<int[]>> columns;
/**
* Constructs an UnifiedResultSetMetaData object that wraps the given
* ResultSetMetaData objects. The constructor already performs the
* calculation of the UnifiedResultSetMetaData object's columns by
* determining he multiple occurrance of column names.
*
* @param metaData an array holding the ResultSetMetaData objects to be
* appended to one ResultSetMetaData object.
*/
public UnifiedResultSetMetaData(ResultSetMetaData... metaData) {
super(metaData);
try {
HashMap<String, Entry<ColumnMetaData, List<int[]>>> hashMap = new HashMap<String, Entry<ColumnMetaData, List<int[]>>>();
for (int i = 0; i < this.metaData.length; i++)
for (int j = 1; j <= this.metaData[i].getColumnCount(); j++) {
String columnName = this.metaData[i].getColumnName(j).toUpperCase();
ColumnMetaData columnMetaData = new ResultSetMetaDataColumnMetaData(this.metaData[i], j);
Entry<ColumnMetaData, List<int[]>> list = hashMap.get(columnName);
if (list == null)
hashMap.put(columnName, list = new MapEntry<ColumnMetaData, List<int[]>>(columnMetaData, new ArrayList<int[]>()));
else
if (ColumnMetaDatas.COLUMN_METADATA_COMPARATOR.compare(list.getKey(), columnMetaData) != 0)
throw new MetaDataException("columns named by " + columnName + " cannot be unified because of different types");
list.getValue().add(new int[] {i, j});
}
columns = new ArrayList<List<int[]>>(hashMap.size());
for (Entry<ColumnMetaData, List<int[]>> list : hashMap.values())
columns.add(list.getValue());
Collections.sort(columns, new Comparator<List<int[]>>() {
public int compare(List<int[]> list1, List<int[]> list2) {
int[] intArray1 = list1.get(0), intArray2 = list2.get(0);
int index = intArray1[0] == intArray2[0] ? 1 : 0;
return intArray1[index]-intArray2[index];
}
});
}
catch (SQLException sqle) {
throw new MetaDataException("meta data cannot be constructed due to the following sql exception: " + sqle.getMessage());
}
}
/**
* Constructs an UnifiedResultSetMetaData object that wraps the
* ResultSetMetaData objects contained by the given iteration.
*
* @param metaData an iteration holding the ResultSetMetaData objects to be
* appended to one ResultSetMetaData object.
*/
public UnifiedResultSetMetaData(Iterator<ResultSetMetaData> metaData) {
this(Cursors.toFittingArray(metaData, new ResultSetMetaData[0]));
}
/**
* Returns the number of columns. The number of columns of an
* UnifiedResultSetMetaData object is equal to the number of the unique
* column names of the appended ResultSetMetaData objects.
*
* @return the number of columns.
* @throws SQLException if a database access error occurs.
*/
@Override
public int getColumnCount() throws SQLException {
return columns.size();
}
/**
* Returns an iteration over the indices of the underlying
* ResultSetMetaData objects the given column is originated in.
*
* @param column number of the column: the first column is 1, the second is
* 2, ...
* @return returns an iteration over the indices of the underlying
* ResultSetMetaData objects the given column is originated in.
* @throws SQLException if a database access error occurs.
*/
@Override
public Cursor<Integer> originalMetaDataIndices(int column) throws SQLException {
return new Mapper<int[], Integer>(
new AbstractFunction<int[], Integer>() {
@Override
public Integer invoke(int[] intArray) {
return intArray[0];
}
},
columns.get(column-1).iterator()
);
}
/**
* Determines the original column index from the underlying
* ResultSetMetaData object with the given index, on which the specified
* column of this object is based.
*
* @param originalMetaData the index of the underlying ResultSetMetaData
* object that should be tested for being the origin of the
* specified column.
* @param column number of the column: the first column is 1, the second is
* 2, ... If the given column does not originate in the specified
* ResultSetMetaData object, the return value has to be 0.
* @return the original column number from the underlying ResultSetMetaData
* object with the given index, on which the specified column of
* this object is based.
* @throws SQLException if a database access error occurs.
*/
@Override
public int originalColumnIndex(int originalMetaData, int column) throws SQLException {
for (int[] i : columns.get(column-1))
if (i[0] == originalMetaData)
return i[1];
throw new MetaDataException("column " + column + " is not based on original mata data " + originalMetaData);
}
/**
* Determines the original ResultSetMetaData objects and column indices, on
* which the specified column of this object is based.
*
* @param column number of the column: the first column is 1, the second is
* 2, ... If the given column does not originate in the specified
* ResultSetMetaData object, the return value has to be 0.
* @return the original ResultSetMetaData objects and column indices, on
* which the specified column of this object is based. The returned
* iteration contains two-dimensional <code>int</code>-arrays
* holding the index of the original ResultSetMetaData object and
* the index of the original column.
* @throws SQLException if a database access error occurs.
*/
@Override
public Cursor<int[]> originalColumnIndices(int column) throws SQLException {
return new IteratorCursor<int[]>(columns.get(column-1).iterator());
}
}