/*
* XXL: The eXtensible and fleXible Library for data processing
*
* Copyright (C) 2000-2013 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.io.converters;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import xxl.core.io.converters.BooleanConverter;
import xxl.core.io.converters.ByteConverter;
import xxl.core.io.converters.DateConverter;
import xxl.core.io.converters.DoubleConverter;
import xxl.core.io.converters.FloatConverter;
import xxl.core.io.converters.IntegerConverter;
import xxl.core.io.converters.LongConverter;
import xxl.core.io.converters.MeasuredConverter;
import xxl.core.io.converters.ShortConverter;
import xxl.core.io.converters.StringConverter;
import xxl.core.io.converters.TimeConverter;
import xxl.core.io.converters.TimestampConverter;
import xxl.core.io.converters.meta.ExtendedResultSetMetaData;
import xxl.core.relational.JavaType;
import xxl.core.relational.RelationalType;
import xxl.core.relational.tuples.ColumnComparableArrayTuple;
import xxl.core.relational.tuples.Tuple;
import xxl.core.util.ConvertUtils;
/**
* This class provides a converter which is able to read and write objects of type <b>Tuple</b>
* which contains a set of individual typed objects. Therefore the MeasuredTupleConverter calls the
* specific built-in XXL converter to read an write the single objects (components) of the tuple.
* First the converter reads or writes the length of the array. Thereafter the objects are read or
* written.<br/>
* <br/>
*
* <b>Note:</b> For various reasons, a tuple contains different typed objects but does not know its
* own column types. Therefore, a MeasuredTupleConverter expects an object that implements
* <b>ResultSetMetaData</b>. This provides the information about which types the tuple uses in its
* columns.<br/>
* <br/>
* <b>Caution:</b> The MeasuredTupleConverter auto matches Java types and relational types, e.g.
* VARCHAR and LONGNVARCHAR to java.lang.String using {@link xxl.core.util.ConvertUtils#toJavaType
* Convert.toJavaTyp()} and vice versa using {@link xxl.core.util.ConvertUtils#toRelationalType
* Convert.toRelationalType()}. Please note that every type is matched currently but not every
* relational type can be written or read at this point. If you use e.g. CLOB (JDBC) type the
* MeasuredTupleConverter will throw an <code>UnsupportedOperationException</code>. <br/>
* <br/>
* Actually you can write and read the following types
* <ul>
* <li>java BOOLEAN and jdbc BOOLEAN</li>
* <li>java BYTE and jdbc TINYINT, BOOLEAN</li>
* <li>java DATE and jdbc DATE</li>
* <li>java DOUBLE and jdbc DOUBLE</li>
* <li>java FLOAT and jdbc REAL</li>
* <li>java INT and jdbc INTEGER</li>
* <li>java LONG and jdbc BIGINT</li>
* <li>java SHORT and jdbc SMALLINT</li>
* <li>java STRING and jdbc CHAR, LONGNVARCHAR, LONGVARCHAR, NCHAR, NVARCHAR, VARCHAR</li>
* <li>java TIME and jdbc TIME</li>
* <li>java TIMESTAMP and jdbc TIMESTAMP</li>
* <ul>
* <br/>
* <br/>
*
*
* <b>Example of MeasuredTupleConverter writing</b><br/>
* <br/>
* The following example shows how to create tuples in a table <code>TABLE_NAME</code> and how to
* store them into a file called <code>dumpFile</code>. Each tuple contains two columns. The first
* is an <code>INTEGER</code> (JDBC type) and the second is a <code>VARCHAR</code> (JDCBC type). The
* example uses <code>ColumnMetaInfo</code> which is designed for quick using <i>RelationalType</i>.
* Instead of this you can also use {@link xxl.core.relational.metaData.ColumnMetaData
* ColumnMetaData}. After adding some tuples with values the example code
* {@link xxl.core.io.converters.MeasuredTupleConverter#write(DataOutput, Tuple) writes} sequentially
* each tuple content to <code>dumpFile</code>.
*
* <code><pre>
* File dumpFile = new File(DUMP_FILE_PATH);
*
* FileOutputStream fileOutputStream = new FileOutputStream(dumpFile);
* DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
* final String TABLE_NAME = "Students";
*
* ColumnMetaInfo columnMetaData[] = new ColumnMetaInfo[] {
* new ColumnMetaInfo(RelationalType.INTEGER, "MatrNr", TABLE_NAME),
* new ColumnMetaInfo(RelationalType.VARCHAR, "Name", TABLE_NAME) };
*
* ColumnMetaDataResultSetMetaData metaData = new ColumnMetaDataResultSetMetaData(columnMetaData);
* MeasuredTupleConverter tc = new MeasuredTupleConverter(metaData);
*
* ArrayTuple tupleArray[] = new ArrayTuple[] {
* new ArrayTuple(new Integer(23), new String("Mustermann")),
* new ArrayTuple(new Integer(24), new String("Musterfrau")),
* new ArrayTuple(new Integer(25), new String("Doe")) };
*
* for (Tuple t : tupleArray)
* tc.write(dataOutputStream, t);
*
* </pre></code> <br/>
* <br/>
*
*
* <b>Example of MeasuredTupleConverter reading</b><br/>
* <br/>
* The following example shows how to restore tuples from a previous session from a file called
* <code>dumpFile</code>. Each tuple contains two columns. The first is an <code>INTEGER</code>
* (JDBC type) and the second is a <code>VARCHAR</code> (JDCBC type). Please make sure that the
* <b>schema</b> for the new tuple is exactly the same as the schema of the written previously (as
* it is in this example code).
*
* The example uses <code>ColumnMetaInfo</code> which is designed for quick using
* <i>RelationalType</i>. Instead of this you can also use
* {@link xxl.core.relational.metaData.ColumnMetaData ColumnMetaData}. After setup this, the
* MeasuredTupleConverter reads the <i>first</i> tuple from <code>dumpFile</code> and restore it
* into a new object <code>tuple</code>. You can load all tuples by calling
* {@link xxl.core.io.converters.MeasuredTupleConverter#read(DataInput) read(DataInput)}
* sequentially.
*
* <code><pre>
* FileInputStream fileInputStream = new FileInputStream(new File("files/tupleDump.tmp"));
* DataInputStream dataInputStream = new DataInputStream(fileInputStream);
* final String TABLE_NAME = "Students";
*
* ColumnMetaInfo columnMetaData[] = new ColumnMetaInfo[] {
* new ColumnMetaInfo(RelationalType.INTEGER, "MatNr", TABLE_NAME),
* new ColumnMetaInfo(RelationalType.VARCHAR, "Name", TABLE_NAME) };
*
* ColumnMetaDataResultSetMetaData metaData = new ColumnMetaDataResultSetMetaData(columnMetaData);
* MeasuredTupleConverter tc = new MeasuredTupleConverter(metaData);
*
* Tuple readedTuple = tc.read(dataInputStream);
* </pre></code>
*
* @see xxl.core.relational.metaData.ColumnMetaData
* @see xxl.core.relational.metaData.ColumnMetaInfo
* @see xxl.core.relational.RelationalType
* @see xxl.core.relational.JavaType
* @see xxl.core.util.ConvertUtils
* @see xxl.core.relational.tuples.Tuple
*
*
* @author Marcus Pinnecke (pinnecke@mathematik.uni-marburg.de)
*/
public final class MeasuredTupleConverter extends MeasuredConverter<Tuple> {
private static final long serialVersionUID = 8652170728856255633L;
/*
* The meta information about the column types in tuple
*/
private ExtendedResultSetMetaData mTupleMetaData;
/**
* Creates a new converter for objects of type Tuple. A tuple includes a lot of different typed
* objects. Since a tuple does not know his own object types a description of the data type of the
* contained objects will be needed here. <br/>
* <b>Note:</b> Please check that meta data and tuples match in their object size (dimension) <br/>
*
* @param tupleMetaData A type description of the contained objects within your tuples. You can
* use objects which implements {@link java.sql.ResultSetMetaData ResultSetMetaData}
*
* @throws NullPointerException if tupleMetaData is null
*/
public MeasuredTupleConverter(ExtendedResultSetMetaData tupleMetaData) {
if (tupleMetaData == null)
throw new NullPointerException(
"MeasuredTupleConverter tupleMetaData is null");
mTupleMetaData = tupleMetaData;
}
/*
* For reason of measuring the length of data types a string value have to in fixed size style.
* This method checks if a given String s of column with the given Index is smaller or equal to
* the max supported fixed size
*/
private void checkStringJustFitsMaxCharacterLength(String s, int columnIndex) {
int maxSupportedLength = mTupleMetaData.getContentLength(columnIndex);
if (s.length() > maxSupportedLength)
throw new UnsupportedOperationException(
"The tuple component value at index \""
+ columnIndex
+ "\" is too long. Maximum supported character count according to the meta data is \""
+ maxSupportedLength + "\".\nThe exception occures for \n\"" + s
+ "\"");
}
/**
* Get the maximum size by the sum of all single sizes of all columns.
*/
@Override
public int getMaxObjectSize() {
int tupleObjectSize = 0;
try {
for (int i = 0; i < mTupleMetaData.getColumnCount(); i++)
tupleObjectSize +=
getSingleObjectSize(mTupleMetaData.getColumnType(i + 1), i);
} catch (SQLException e) {
e.printStackTrace();
}
return tupleObjectSize;
}
/*
* Returns the size in bytes of a column entry. This is needed for I/O with Converters
*/
private int getSingleObjectSize(int columnType, int columnIndex) {
int typeSize = 0;
RelationalType columnRelationalType =
ConvertUtils.toRelationalType(columnType);
JavaType columnJavaType = ConvertUtils.toJavaType(columnRelationalType);
switch (columnJavaType) {
case BOOLEAN:
typeSize = BooleanConverter.SIZE;
break;
case BYTE:
typeSize = ByteConverter.SIZE;
break;
case DATE:
typeSize = DateConverter.SIZE;
break;
case DOUBLE:
typeSize = DoubleConverter.SIZE;
break;
case FLOAT:
typeSize = FloatConverter.SIZE;
break;
case INT:
typeSize = IntegerConverter.SIZE;
break;
case LONG:
typeSize = LongConverter.SIZE;
break;
case SHORT:
typeSize = ShortConverter.SIZE;
break;
case STRING:
typeSize =
FixedSizeStringConverter.calculateSize(mTupleMetaData
.getContentLength(columnIndex + 1));
break;
case TIME:
typeSize = TimeConverter.SIZE;
break;
case TIMESTAMP:
typeSize = TimestampConverter.SIZE;
break;
default:
throw new UnsupportedOperationException("Not implemented yet for \""
+ columnJavaType.toString() + "\"");
}
return typeSize;
}
/**
* Restores a tuple from an input. This read based on the meta data information given in
* constructor. <br/>
* <b>Note:</b> Please check that meta data and tuples match in their object size (dimension) <br/>
*
* @param dataInput the input from which the tuple should be loaded<br/>
* <br/>
* @param _ ignore this argument, it's unused
* @return The restored tuple with the schema you defined in the constructor
* @throws IOException if it fails to read from <code>dataInput</code> RuntimeException if tuple
* dimension and meta data dimension does not match SQLException if a SQLException occurs
* within your <code>ResultSetMetaData</code>
*/
@Override
public Tuple read(DataInput dataInput, Tuple _) throws IOException {
// Read entire tuple object size which is the tuple dimension.
// Example: Tuple(Integer, String) has dimension 2
int tupleDimension = IntegerConverter.DEFAULT_INSTANCE.readInt(dataInput);
// A temporary vector holding the objects for converting this vector
// into Object[] which is used in Tuple constructor. Reads each
// column of singleTuple, find outs the column type and calls the
// specific converter to read the type.
ArrayList<Object> tempArray = new ArrayList<>();
try {
if (mTupleMetaData.getColumnCount() != tupleDimension)
throw new RuntimeException(
"Given tuple dimension and meta data dimension does not match.");
for (int i = 0; i < tupleDimension; i++) {
int columnType = mTupleMetaData.getColumnType(i + 1);
tempArray.add(readWithType(dataInput, columnType, i));
}
} catch (SQLException e) {
throw new RuntimeException(
"MeasuredTupleConverter::read SQLException of ResultSetMetaData tupleMetaData");
}
return new ColumnComparableArrayTuple(
tempArray.toArray(new Comparable[tempArray.size()]));
}
/**
* Reads a date from the <i>dataInput</i>. The metadata about the types of the individual
* components of the tuples is known while constructing this TupleConverter. This method takes a
* relational data type, translates it into a <i>Java language type<i> and reads an object of this
* type from the input. The read object is then returned. <br/>
* <br/>
* For type converting, see <a href="http://db.apache.org/ojb/docu/guides/jdbc-types.html">JDBC
* Types</a>
*
* @param dataInput The input from which a date should be read <br/>
* @param columnType The <i>relational</i> type of the object which should be read <br/>
* @return The (according to the translation between Java language and relational language) typed
* into Object casted date.
*/
public Object readWithType(DataInput dataInput, final int columnType,
int columnIndex) {
Object retval = null;
RelationalType columnRelationalType =
ConvertUtils.toRelationalType(columnType);
JavaType columnJavaType = ConvertUtils.toJavaType(columnRelationalType);
try {
switch (columnJavaType) {
case BOOLEAN:
retval = BooleanConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case BYTE:
retval = ByteConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case DATE:
retval = DateConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case DOUBLE:
retval = DoubleConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case FLOAT:
retval = FloatConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case INT:
retval = IntegerConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case LONG:
retval = LongConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case SHORT:
retval = ShortConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case STRING:
retval = StringConverter.DEFAULT_INSTANCE.read(dataInput);
checkStringJustFitsMaxCharacterLength((String) retval,
columnIndex + 1);
break;
case TIME:
retval = TimeConverter.DEFAULT_INSTANCE.read(dataInput);
break;
case TIMESTAMP:
retval = TimestampConverter.DEFAULT_INSTANCE.read(dataInput);
break;
default:
throw new UnsupportedOperationException("Not implemented yet for \""
+ columnJavaType.toString() + "\"");
}
} catch (IOException e) {
e.printStackTrace();
}
return retval;
}
/**
* Stores a tuple sequentially into <code>dataOutput</code>. This writing operations needs info
* about each column type which you set in constructor of MeasuredTupleConverter.<br/>
* <b>Note:</b> Please check that meta data and tuples match in their object size (dimension) <br/>
*
* @throws NullPointerException if singleTuple is null <b>IOException</b> if it fails to write
* into <code>dataOutput</code> <b>RuntimeException</b> if tuple dimension and meta data
* dimension does not match <b>SQLException</b> if a SQLException occurs within your
* <code>ResultSetMetaData</code>
*/
@Override
public void write(DataOutput dataOutput, Tuple singleTuple)
throws IOException {
if (singleTuple == null)
throw new NullPointerException(
"MeasuredTupleConverter::write singleTuple is null");
// Write entire tuple object size which is the tuple dimension.
// Example: Tuple(Integer, String) has dimension 2
int tupleDimension = singleTuple.getColumnCount();
IntegerConverter.DEFAULT_INSTANCE.writeInt(dataOutput, tupleDimension);
// Iterate through columns and use XXL built in converter to write
// the column with specific type
try {
if (mTupleMetaData.getColumnCount() != singleTuple.getColumnCount())
throw new RuntimeException(
"Given tuple dimension and meta data dimension does not match.");
for (int i = 0; i < tupleDimension; i++) {
writeWithType(dataOutput, singleTuple, i,
mTupleMetaData.getColumnType(i + 1));
}
} catch (SQLException e) {
throw new RuntimeException(
"MeasuredTupleConverter::read SQLException of ResultSetMetaData tupleMetaData");
}
}
/**
* Writes the value of the given column index into dataOutput. For this purpose, the type of the
* column type is determined with the given MetaData and written with the specific XXL converter
* for this type. <br/>
* <br/>
* For type converting, see <a href="http://db.apache.org/ojb/docu/guides/jdbc-types.html">JDBC
* Types</a>
*
* @param dataOutput The output in which the object at <i>columnIndex</i> of <i>tuple</i> should
* be written <br/>
* @param tuple The tuple object <br/>
* @param columnIndex The index of the tuples item which should be written into <i>dataOutput</i>
*/
public void writeWithType(DataOutput dataOutput, final Tuple tuple,
final int columnIndex, final int columnType) {
try {
RelationalType columnRelationalType =
ConvertUtils.toRelationalType(columnType);
JavaType columnJavaType = ConvertUtils.toJavaType(columnRelationalType);
switch (columnJavaType) {
case BOOLEAN:
BooleanConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getBoolean(columnIndex + 1));
break;
case BYTE:
ByteConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getByte(columnIndex + 1));
break;
case DATE:
DateConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getDate(columnIndex + 1));
break;
case DOUBLE:
DoubleConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getDouble(columnIndex + 1));
break;
case FLOAT:
FloatConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getFloat(columnIndex + 1));
break;
case INT:
IntegerConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getInt(columnIndex + 1));
break;
case LONG:
LongConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getLong(columnIndex + 1));
break;
case SHORT:
ShortConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getShort(columnIndex + 1));
break;
case STRING:
String content = tuple.getString(columnIndex + 1);
checkStringJustFitsMaxCharacterLength(content, columnIndex + 1);
StringConverter.DEFAULT_INSTANCE.write(dataOutput, content);
break;
case TIME:
TimeConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getTime(columnIndex + 1));
break;
case TIMESTAMP:
TimestampConverter.DEFAULT_INSTANCE.write(dataOutput,
tuple.getTimestamp(columnIndex + 1));
break;
default:
throw new UnsupportedOperationException("Not implemented yet for \""
+ columnJavaType.toString() + "\"");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}