/* 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.Iterator; import xxl.core.cursors.Cursor; import xxl.core.cursors.Cursors; import xxl.core.cursors.mappers.Mapper; import xxl.core.functions.AbstractFunction; import xxl.core.util.WrappingRuntimeException; import xxl.core.util.metaData.MetaDataException; /** * This abstract class is a ResultSetMetaData skeleton that wraps a number of * given ResultSetMetaData objects to a single one. * * This is useful especially for Joins. There are three abstract methods: * <ul> * <li><code>int <B>getColumnCount</B>()</code></li> * <li><code>Cursor <B>originalMetaDataIndices</B>(int column)</code></li> * <li><code>int <B>originalColumnIndex</B>(int originalMetaData, int column)</code></li> * </ul> * * These functions define a mapping between the original metadata objects and * an object of this class. * * For each call to <code>getXXX(int column)</code> it is first called * <code>originalMetaDataIndices(column)</code> to determine the indices of the * ResultSetMetaData objects thie column belongs to. * * Thereafter the <code>getXXX(int column)</code>-call is redirected to * <code>getXXX(originalColumnIndex(originalMetaData, column))</code> where * <code>originalMetaData</code> is the index obtained from the iteration * returned by the <code>originalMetaDataIndices(column)</code> method.<br /> * For merged columns the behaviour of a method depends on the kind of * underlying column. For example <code>columnDisplaySize</code> is the maximum * of the <code>columnDisplaySizes</code> of the corresponding columns from all * underlying MetaData objects. */ public abstract class MergedResultSetMetaData implements ResultSetMetaData { /** * An array holding the ResultSetMetaData objects to be merged. */ protected ResultSetMetaData[] metaData; /** * Constructs a MergedResultSetMetaData object that wraps the given * ResultSetMetaData objects. * * @param metaData an array holding the ResultSetMetaData objects to be * merged. */ public MergedResultSetMetaData(ResultSetMetaData... metaData) { this.metaData = metaData; } /** * Constructs a MergedResultSetMetaData object that wraps the * ResultSetMetaData objects contained by the given iteration. * * @param metaData an iteration holding the ResultSetMetaData objects to be * merged. */ public MergedResultSetMetaData(Iterator<? extends ResultSetMetaData> metaData) { this(Cursors.toFittingArray(metaData, new ResultSetMetaData[0])); } /** * Returns the number of columns of this ResultSetMetaData object. * * @return the number of columns of this ResultSetMetaData object * @throws SQLException if a database access error occurs. */ public abstract int getColumnCount() throws SQLException; /** * Returns an iteration over the indices of the underlying * ResultSetMetaData objects the given column is originated in. If you * derive a class from this class, this method defines your mapping from * the columns of the ResultSetMetaData objects to the * MergedResultSetMetaData object. * * @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. */ public abstract Cursor<Integer> originalMetaDataIndices(int column) throws SQLException; /** * 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. */ public abstract int originalColumnIndex(int originalMetaData, int column) throws SQLException; /** * 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. */ public Cursor<int[]> originalColumnIndices(final int column) throws SQLException { return new Mapper<Integer, int[]>( new AbstractFunction<Integer, int[]>() { @Override public int[] invoke(Integer metaDataIndex) { try { return new int[] { metaDataIndex, originalColumnIndex( metaDataIndex, column ) }; } catch (SQLException sqle) { throw new WrappingRuntimeException(sqle); } } }, originalMetaDataIndices(column) ); } /** * Returns the name the catalog. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the name of the catalog. * @throws SQLException if a database access error occurs. */ public String getCatalogName(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getCatalogName(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the name the Java class that is associated with the column * type. The call is redirected to the ResultSetMetaData object the given * column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the name of the column class. * @throws SQLException if a database access error occurs. */ public String getColumnClassName(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getColumnClassName(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the display size of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in and the * maximum of all column display sizes is returned. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the display size of the column. * @throws SQLException if a database access error occurs. */ public int getColumnDisplaySize(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int columnDiplaySize = Integer.MIN_VALUE; while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); columnDiplaySize = Math.max(columnDiplaySize, metaData[originalColumnIndex[0]].getColumnDisplaySize(originalColumnIndex[1])); } return columnDiplaySize; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the column label. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the name of the column label. * @throws SQLException if a database access error occurs. */ public String getColumnLabel(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getColumnLabel(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the name of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the name of the column. * @throws SQLException if a database access error occurs. */ public String getColumnName(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getColumnName(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the type of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the type of the column. * @throws SQLException if a database access error occurs. */ public int getColumnType(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getColumnType(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the type name of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the type name of the column. * @throws SQLException if a database access error occurs. */ public String getColumnTypeName(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getColumnTypeName(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the precision of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the precision of the column. * @throws SQLException if a database access error occurs. */ public int getPrecision(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getPrecision(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the scale of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the scale of the column. * @throws SQLException if a database access error occurs. */ public int getScale(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getScale(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the schema name of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the schema name of the column. * @throws SQLException if a database access error occurs. */ public String getSchemaName(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getSchemaName(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the table name of the column. The call is redirected to the * ResultSetMetaData object the given column is originated in. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return the table name of the column. * @throws SQLException if a database access error occurs. */ public String getTableName(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return metaData[originalColumnIndex[0]].getTableName(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is an auto increment column. The call is * redirected to the ResultSetMetaData object the given column is * originated in. If the column is originated in more than one * ResultSetMetaData object it cannot be an auto increment column. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column is an auto increment column. * @throws SQLException if a database access error occurs. */ public boolean isAutoIncrement(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); return !originalColumnIndices.hasNext() && metaData[originalColumnIndex[0]].isAutoIncrement(originalColumnIndex[1]); } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is case sensitive. The call is redirected to the * ResultSetMetaData object the given column is originated in. If the * column is originated in more than one metadata object, it is case * sensitive, if one of the original columns is case sensitive. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column is case sensitive. * @throws SQLException if a database access error occurs. */ public boolean isCaseSensitive(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (metaData[originalColumnIndex[0]].isCaseSensitive(originalColumnIndex[1])) return true; } return false; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column contains a currency value. The call is redirected * to the ResultSetMetaData object the given column is originated in. If * the column is originated in more than one metadata object, it is a * currency value if all original values are currency values. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column contains a currency value. * @throws SQLException if a database access error occurs. */ public boolean isCurrency(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (!metaData[originalColumnIndex[0]].isCurrency(originalColumnIndex[1])) return false; } return true; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is definitively writable. The call is redirected * to the ResultSetMetaData object the given column is originated in. If * the column is originated in more than one metadata object, it is * definitively writable if all original columns are definitively writable. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column is definitively writable. * @throws SQLException if a database access error occurs. */ public boolean isDefinitelyWritable(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (!metaData[originalColumnIndex[0]].isDefinitelyWritable(originalColumnIndex[1])) return false; } return true; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is nullable. The call is redirected to the * ResultSetMetaData object the given column is originated in. If the * column is originated in more than one metadata object, the column is * nullabe if one of the underlying columns is nullable. If all underlying * columns do not allow null values (<code>columnNoNulls</code>), the * column also does not allow null values. In any other case, the return * value is <code>columnNullableUnknown</code>. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return one of the constants defined in * {@link java.sql.ResultSetMetaData}: <code>columnNoNulls</code>, * <code>columnNullable</code> or * <code>columnNullableUnknown</code>. * @throws SQLException if a database access error occurs. */ public int isNullable(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { int isNullable = ResultSetMetaData.columnNullableUnknown; while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); int isNextNullable = metaData[originalColumnIndex[0]].isNullable(originalColumnIndex[1]); if (isNextNullable == ResultSetMetaData.columnNullable) return ResultSetMetaData.columnNullable; isNullable = (isNullable == ResultSetMetaData.columnNoNulls && isNextNullable == ResultSetMetaData.columnNoNulls) ? ResultSetMetaData.columnNoNulls : ResultSetMetaData.columnNullableUnknown; } return isNullable; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is read only. The call is redirected to the * ResultSetMetaData object the given column is originated in. If the * column is originated in more than one metadata object, it is read only, * if one of the original columns is read only. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column is read only. * @throws SQLException if a database access error occurs. */ public boolean isReadOnly(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (metaData[originalColumnIndex[0]].isReadOnly(originalColumnIndex[1])) return true; } return false; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is searchable. The call is redirected to the * ResultSetMetaData object the given column is originated in. If the * column is originated in more than one metadata objects, it is searchable * if all original columns are searchable. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column is searchable. * @throws SQLException if a database access error occurs. */ public boolean isSearchable(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (!metaData[originalColumnIndex[0]].isSearchable(originalColumnIndex[1])) return false; } return true; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is signed. The call is redirected to the * ResultSetMetaData object the given column is originated in. If the * column is originated in more than one metadata object, it is signed, if * one of the original columns is signed. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column signed. * @throws SQLException if a database access error occurs. */ public boolean isSigned(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (metaData[originalColumnIndex[0]].isSigned(originalColumnIndex[1])) return true; } return false; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns if the column is writable. The call is redirected to the * ResultSetMetaData object the given column is originated in. If the * column is originated in more than one metadata object, it is writable if * all original columns are writable. * * @param column number of the column: the first column is 1, the second is * 2, ... * @return true if the column is writable. * @throws SQLException if a database access error occurs. */ public boolean isWritable(int column) throws SQLException { Cursor<int[]> originalColumnIndices = originalColumnIndices(column); if (originalColumnIndices.hasNext()) { while (originalColumnIndices.hasNext()) { int[] originalColumnIndex = originalColumnIndices.next(); if (!metaData[originalColumnIndex[0]].isWritable(originalColumnIndex[1])) return false; } return true; } throw new SQLException("the specified column " + column + " cannot be identified"); } /** * Returns the metadata set that is specified by the given index. * * @param input the number of the metadata set: the first metadata set is * 1, the second is 2, ... * @return the metadata object. */ protected ResultSetMetaData getMetaData(int input) { return metaData[input]; } /** * Returns a string representation of this relational metadata. * * @return a string representation of this relational metadata. */ @Override public String toString() { try { if (getColumnCount() == 0) return "[]"; StringBuffer string = new StringBuffer().append('[').append(getColumnTypeName(1)).append(' ').append(getColumnName(1)); for (int column = 2; column <= getColumnCount(); column++) string.append(", ").append(getColumnTypeName(column)).append(' ').append(getColumnName(column)); return string.append(']').toString(); } catch (SQLException sqle) { throw new MetaDataException("sql exception occured during string construction: \'" + sqle.getMessage() + "\'"); } } /** * Indicates whether some other object is "equal to" this relational * metadata. * * <p>The <code>equals</code> method implements an equivalence relation: * <ul> * <li> * It is <i>reflexive</i>: for any reference value <code>x</code>, * <code>x.equals(x)</code> should return <code>true</code>. * </li> * <li> * It is <i>symmetric</i>: for any reference values <code>x</code> * and <code>y</code>, <code>x.equals(y)</code> should return * <code>true</code> if and only if <code>y.equals(x)</code> * returns <code>true</code>. * </li> * <li> * It is <i>transitive</i>: for any reference values * <code>x</code>, <code>y</code>, and <code>z</code>, if * <code>x.equals(y)</code> returns <code>true</code> and * <code>y.equals(z)</code> returns <code>true</code>, then * <code>x.equals(z)</code> should return <code>true</code>. * </li> * <li> * It is <i>consistent</i>: for any reference values <code>x</code> * and <code>y</code>, multiple invocations of * <code>x.equals(y)</code> consistently return <code>true</code> * or consistently return <code>false</code>, provided no * information used in <code>equals</code> comparisons on the * object is modified. * </li> * <li> * For any non-<code>null</code> reference value <code>x</code>, * <code>x.equals(null)</code> should return <code>false</code>. * </li> * </ul></p> * * <p>The current <code>equals</code> method returns true if and only if * the given object: * <ul> * <li> * is this relational metadata or * </li> * <li> * is an instance of the type <code>ResultSetMetaData</code> and a * {@link ResultSetMetaDatas#RESULTSET_METADATA_COMPARATOR comparator} * for relational metadata returns 0. * </li> * </ul></p> * * @param object the reference object with which to compare. * @return <code>true</code> if this object is the same as the specified * object; <code>false</code> otherwise. * @see #hashCode() */ @Override public boolean equals(Object object) { if (object == null) return false; if (this == object) return true; return object instanceof ResultSetMetaData && ResultSetMetaDatas.RESULTSET_METADATA_COMPARATOR.compare(this, (ResultSetMetaData)object) == 0; } /** * Returns the hash code value for this relational metadata using a * {@link ResultSetMetaDatas#RESULTSET_METADATA_HASH_FUNCTION hash function} * for relational metadata. * * @return the hash code value for this relational metadata. * @see Object#hashCode() * @see #equals(Object) */ @Override public int hashCode() { return ResultSetMetaDatas.RESULTSET_METADATA_HASH_FUNCTION.invoke(this); } /** * Returns an object that implements the given interface to allow access to * non-standard methods, or standard methods not exposed by the proxy. The * result may be either the object found to implement the interface or a * proxy for that object. If the receiver implements the interface then * that is the object. If the receiver is a wrapper and the wrapped object * implements the interface then that is the object. Otherwise the object * is the result of calling <code>unwrap</code> recursively on the wrapped * object. If the receiver is not a wrapper and does not implement the * interface, then an <code>SQLException</code> is thrown. * * @param iface a class defining an interface that the result must * implement. * @return an object that implements the interface. May be a proxy for the * actual implementing object. * @throws SQLException if no object found that implements the interface. */ public <T> T unwrap(Class<T> iface) throws SQLException { throw new UnsupportedOperationException("this method is not implemented yet."); } /** * Returns true if this either implements the interface argument or is * directly or indirectly a wrapper for an object that does. Returns false * otherwise. If this implements the interface then return true, else if * this is a wrapper then return the result of recursively calling * <code>isWrapperFor</code> on the wrapped object. If this does not * implement the interface and is not a wrapper, return false. This method * should be implemented as a low-cost operation compared to * <code>unwrap</code> so that callers can use this method to avoid * expensive <code>unwrap</code> calls that may fail. If this method * returns true then calling <code>unwrap</code> with the same argument * should succeed. * * @param iface a class defining an interface. * @return true if this implements the interface or directly or indirectly * wraps an object that does. * @throws SQLException if an error occurs while determining whether this * is a wrapper for an object with the given interface. */ public boolean isWrapperFor(Class<?> iface) throws SQLException { throw new UnsupportedOperationException("this method is not implemented yet."); } }