/* 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.io.converters;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import xxl.core.io.*;
import xxl.core.io.ByteBufferDataOutput;
/**
* This class contains various methods connected to the serialization of
* objects. The <code>toByteArray</code> methods return a serialized object as
* byte array and the <code>sizeOf</code> methods determine the length of a
* serialized object without materializing the bytes into the memory.
*
* <p>The documentation of the methods contained in this class includes a brief
* description of the implementation. Such descriptions should be regarded as
* implementation notes, rather than parts of the specification. Implementors
* should feel free to substitute other algorithms, as long as the
* specification itself is adhered to.</p>
*
* @see ByteArrayOutputStream
* @see DataOutputStream
* @see IOException
* @see OutputStream
*/
public class Converters {
/**
* Represents the different <tt>DataOutput</tt> resp. <tt>DataInput</tt> types
* used for serialization resp. deserialization.
*/
public static enum SerializationMode {
/** Represents a ByteArrayOutputStream */
BYTE_ARRAY,
/** Represents an Unsafe object */
UNSAFE,
/** Represents a ByteBuffer */
BYTE_BUFFER
}
/**
* Returns a converter that is able to read and write objects based on the
* given converter. Internally the returned converter casted the objects
* to be read or written to the type expected by the given converter.
* Therefore the returned converter will cause a
* <code>ClasscastException</code> when its methods are called with object
* that do not have the expected type.
*
* @param <T> the type of the objects which can be read and written by the
* given converter.
* @param converter the converter that is used to read and write the
* objects internally.
* @return a converter that is able to read and write objects based on the
* given comparator.
*/
public static <T> Converter<Object> getObjectConverter(final Converter<T> converter) {
return new Converter<Object>() {
@Override
public Object read(DataInput dataInput, Object object) throws IOException {
return converter.read(dataInput, (T)object);
}
@Override
public void write(DataOutput dataOutput, Object object) throws IOException {
converter.write(dataOutput, (T)object);
}
};
}
/**
* Returns a converter which can convert a java type. Only the name of the
* java type has to be given. If there is no converter available then this
* method returns a ConvertableConverter
* (and hopes that the object to be converted is convertable).
*
* @param javaTypeName String which represents a java classname.
* @return the desired converter
*/
public static Converter<Object> getConverterForJavaType(String javaTypeName) {
if (javaTypeName.equals("java.lang.String"))
return getObjectConverter(StringConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.math.BigDecimal"))
return getObjectConverter(BigDecimalConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Integer"))
return getObjectConverter(IntegerConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Short"))
return getObjectConverter(ShortConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Boolean"))
return getObjectConverter(BooleanConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Byte"))
return getObjectConverter(ByteConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Long"))
return getObjectConverter(LongConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Float"))
return getObjectConverter(FloatConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.lang.Double"))
return getObjectConverter(DoubleConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("byte[]"))
return getObjectConverter(ByteArrayConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.sql.Date"))
return getObjectConverter(DateConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.sql.Time"))
return getObjectConverter(TimeConverter.DEFAULT_INSTANCE);
if(javaTypeName.equals("java.sql.Timestamp"))
return getObjectConverter(TimestampConverter.DEFAULT_INSTANCE);
return getObjectConverter(ConvertableConverter.DEFAULT_INSTANCE);
}
/**
* Creates a newly allocated byte array that contains the serialization of
* the spezified object. Its size is the size of the serialized object as
* returned by <code>sizeOf(converter, object)</code>. If the creation of
* the byte array fails (e.g., an <code>IOException</code> is thrown) this
* method returns <code>null</code>.
*
* <p>This implementation creates a new <code>ByteArrayOutputStream</code>
* that is wrapped by a new <code>DataOutputStream</code>. Thereafter the
* specified object is written to the <code>DataOutputStream</code> using
* its write method of the converter and the result of the
* <code>ByteArrayOutputStream</code>'s
* {@link ByteArrayOutputStream#toByteArray() toByteArray} method is
* returned.</p>
*
* @param <T> the type of the object to be converted.
* @param converter the converter to be used for converting the object.
* @param object the object to be converted.
* @return the serialized state (attributes) of the specified object, as a
* byte array.
*/
public static <T> byte[] toByteArray(Converter<? super T> converter, T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
converter.write(daos, object);
return baos.toByteArray();
}
catch (IOException ie) {
return null;
}
}
/**
* Creates a newly allocated byte array that contains the serialization of
* the spezified object. Its size is the size of the serialized object as
* returned by <code>sizeOf(converter, object)</code>. If the creation of
* the byte array fails (e.g., an <code>IOException</code> is thrown) this
* method returns <code>null</code>.
*
* <p>This implementation creates a new <code>ByteArrayOutputStream</code>
* that is wrapped by a new <code>DataOutputStream</code>. Thereafter the
* specified object is written to the <code>DataOutputStream</code> using
* its write method of the converter and the result of the
* <code>ByteArrayOutputStream</code>'s
* {@link ByteArrayOutputStream#toByteArray() toByteArray} method is
* returned.</p>
*
* @param <T> the type of the object to be converted.
* @param converter the converter to be used for converting the object.
* @param object the object to be converted.
* @param serializationMode the type of <tt>DataOutput</tt> that should be used for serialization
* @param buffer the size of the buffer used during deserialization
* @return the serialized state (attributes) of the specified object, as a
* byte array.
*/
public static <T> byte[] toByteArray(Converter<? super T> converter, T object, SerializationMode serializationMode, int buffer) {
try {
if (serializationMode == SerializationMode.BYTE_BUFFER) {
ByteBufferDataOutput bbdo = new ByteBufferDataOutput(buffer);
converter.write(bbdo, object);
return bbdo.toByteArray();
} else if (serializationMode == SerializationMode.UNSAFE) {
UnsafeDataOutput udo = new UnsafeDataOutput(buffer);
converter.write(udo, object);
return udo.toByteArray();
} else {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
converter.write(daos, object);
return baos.toByteArray();
}
}
catch (IOException ie) {
return null;
}
}
/**
* Creates a newly allocated byte array that contains the serialization of
* the spezified object. Its size is the size of the serialized object as
* returned by <code>sizeOf(converter, object)</code>. If the creation of
* the byte array fails (e.g., an <code>IOException</code> is thrown) this
* method returns <code>null</code>.
*
* <p>This implementation creates a new <code>ByteArrayOutputStream</code>
* that is wrapped by a new <code>DataOutputStream</code>. Thereafter the
* specified object is written to the <code>DataOutputStream</code> using
* its write method of the size converter and the result of the
* <code>ByteArrayOutputStream</code>'s
* {@link ByteArrayOutputStream#toByteArray() toByteArray} method is
* returned.</p>
*
* @param <T> the type of the object to be converted.
* @param converter the size converter to be used for converting the
* object.
* @param object the object to be converted.
* @return the serialized state (attributes) of the specified object, as a
* byte array.
*/
public static <T> byte[] toByteArray(SizeConverter<? super T> converter, T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(converter.getSerializedSize(object));
DataOutputStream daos = new DataOutputStream(baos);
converter.write(daos, object);
return baos.toByteArray();
}
catch (IOException ie) {
return null;
}
}
/**
* Creates a newly allocated byte array that contains the serialization of
* the spezified convertable object. Its size is the size of the serialized
* object as returned by <code>sizeOf(convertable)</code>. If the creation
* of the byte array fails (e.g., an <code>IOException</code> is thrown)
* this method returns <code>null</code>.
*
* <p>This implementation creates a new <code>ByteArrayOutputStream</code>
* that is wrapped by a new <code>DataOutputStream</code>. Thereafter the
* specified object is written to the <code>DataOutputStream</code> using
* its write method and the result of the
* <code>ByteArrayOutputStream</code>'s
* {@link ByteArrayOutputStream#toByteArray() toByteArray} method is
* returned.
*
* @param convertable the convertable object to be converted.
* @return the serialized state (attributes) of the specified object, as a
* byte array.
*/
public static byte[] toByteArray(Convertable convertable) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
convertable.write(daos);
return baos.toByteArray();
}
catch (IOException ie) {
return null;
}
}
/**
* Returns the number of bytes needed to serialize the state (the
* attributes) of the specified object. If the measuring fails this method
* should return <code>-1.</code>
*
* <p>This implementation creates an anonymous local class that implements
* the interface <code>OutputStream</code>. The <code>write</code> method
* of this local class only increases a counter and the
* <code>hashCode</code> method returns it. Thereafter the specified object
* is written to the <code>OutputStream</code> using the <code>write</code>
* method of the converter. If this call succeeds the hashCode of the
* <code>OutputStream</code> (the number of bytes written to the output
* stream) is returned otherwise <code>-1</code> is returned.</p>
*
* @param <T> the type of the object to be converted.
* @param converter the converter to be used for converting the object.
* @param object the object to be converted.
* @return the number of bytes needed to serialize the state (the
* attributes) of the specified object or <code>-1</code> if the
* measuring fails.
*/
public static <T> int sizeOf(Converter<? super T> converter, T object) {
try {
OutputStream output = new OutputStream() {
int counter = 0;
@Override
public void write(int b) {
counter++;
}
@Override
public int hashCode() {
return counter;
}
};
converter.write(new DataOutputStream(output), object);
return output.hashCode();
}
catch (IOException e) {
return -1;
}
}
/**
* Returns the number of bytes needed to serialize the state (the
* attributes) of the specified convertable object. If the measuring fails
* this method should return <code>-1</code>.
*
* <p>This implementation creates an anonymous local class that implements
* the interface <code>OutputStream</code>. The <code>write</code> method
* of this local class only increases a counter and the
* <code>hashCode</code> method returns it. Thereafter the specified object
* is written to the <code>OutputStream</code> using its <code>write</code>
* method. If this call succeeds the hashCode of the
* <code>OutputStream</code> (the number of bytes written to the output
* stream) is returned otherwise <code>-1</code> is returned.</p>
*
* @param convertable the convertable object to be converted.
* @return the number of bytes needed to serialize the state (the
* attributes) of the specified object or <code>-1</code> if the
* measuring fails.
*/
public static int sizeOf(Convertable convertable) {
try {
OutputStream output = new OutputStream() {
int counter = 0;
@Override
public void write(int b) {
counter++;
}
@Override
public int hashCode() {
return counter;
}
};
convertable.write(new DataOutputStream(output));
return output.hashCode();
}
catch (IOException e) {
return -1;
}
}
/**
* Reads a single object from the given file and returns it. The specified
* converter is used for reading the object.
*
* <p>This implementation opens a new data input stream on the given file
* and uses the specified converter to read the desired object. The
* specified object is passed to the read method of the converter.</p>
*
* <p><b>Note</b> that every time this method is called, a new input stream
* is opened and closed on the given file. Therefore this method should not
* be used for continuous reading of objects.<p>
*
* @param <T> the type of the object to be read.
* @param file the file from which to read the object.
* @param object the object to read. If (<code>object==null</code>) the
* converter should create a new object, else the specified object
* is filled with the read data.
* @param converter the converter used for reading the object.
* @return the object which was read.
* @throws IOException includes any I/O exceptions that may occur.
*/
public static <T> T readSingleObject(File file, T object, Converter<T> converter) throws IOException {
DataInputStream din = new DataInputStream(new FileInputStream(file));
object = converter.read(din, object);
din.close();
return object;
}
/**
* Writes a single object to the given file. The specified converter is
* used for writing the object.
*
* <p>This implementation opens a new data output stream on the given file
* and uses the specified converter to write the given object. The
* specified object is passed to the write method of the converter.</p>
*
* <p><b>Note</b> that every time this method is called, a new output
* stream is opened and closed on the given file. Therefore this method
* should not be used for continuous writing of objects.</p>
*
* @param <T> the type of the object to be written.
* @param file the file to which to write the object.
* @param object the object to write.
* @param converter the converter used for writing the object.
* @throws IOException includes any I/O exceptions that may occur.
*/
public static <T> void writeSingleObject(File file, T object, Converter<? super T> converter) throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
converter.write(dos, object);
dos.close();
}
/**
* Reads a single convertable object from the given file and returns it.
*
* <p>This implementation is equivalent to the call of
* <code>readSingleObject(file, object, ConvertableConverter.DEFAULT_INSTANCE)</code>.</p>
*
* <p><b>Note</b> that every time this method is called, a new input stream
* is opened and closed on the given file. Therefore this method should not
* be used for continuous reading of objects.</p>
*
* @param file the file from which to read the object.
* @param object the object to read. If (<code>object==null</code>) the
* converter should create a new object, else the specified object
* is filled with the read data.
* @return the object which was read.
* @throws IOException includes any I/O exceptions that may occur.
*/
public static Convertable readSingleObject(File file, Convertable object) throws IOException {
return readSingleObject(file, object, ConvertableConverter.DEFAULT_INSTANCE);
}
/**
* Writes a single convertable object to the given file.
*
* <p>This implementation is equivalent to the call of
* <code>writeSingleObject(file, object, ConvertableConverter.DEFAULT_INSTANCE)</code>.</p>
*
* <p><b>Note</b> that every time this method is called, a new output
* stream is opened and closed on the given file. Therefore this method
* should not be used for continuous writing of objects.</p>
*
* @param file the file to which to write the object.
* @param object the object to write.
* @throws IOException includes any I/O exceptions that may occur.
*/
public static void writeSingleObject (File file, Convertable object) throws IOException {
writeSingleObject(file, object, ConvertableConverter.DEFAULT_INSTANCE);
}
/**
* This method transforms Converter to MeasuredConverter
*
* MaxObjectSize Parameter will be returned by method getMaxObjectSize() in @see {@link MeasuredConverter}.
*
* @param maxObjectSize getMaxObjectSize()
* @param objectConverter actual converter for object serialization
* @return MeasuredConverter
*/
public static <T> MeasuredConverter<T> createMeasuredConverter(final int maxObjectSize, final Converter<T> objectConverter){
return new MeasuredConverter<T>(){
@Override
public int getMaxObjectSize() {
return maxObjectSize;
}
@Override
public T read(DataInput dataInput, T object) throws IOException {
return objectConverter.read(dataInput, object);
}
@Override
public void write(DataOutput dataOutput, T object)
throws IOException {
objectConverter.write(dataOutput, object);
}
};
}
/**
* This method transforms FixedSizeConverter @see {@link FixedSizeConverter} to MeasuredConverter @see {@link MeasuredConverter}.
* The method uses @see {@link MeasuredFixedSizeConverter} for wrapping the FixedSizeConverter
*
*
* @param objectConverter
* @return
*/
public static <T> MeasuredConverter<T> createMeasuredConverter(final FixedSizeConverter<T> objectConverter){
return new MeasuredFixedSizeConverter<>(objectConverter);
}
/**
* The default constructor has private access in order to ensure
* non-instantiability.
*/
private Converters() {
// private access in order to ensure non-instantiability
}
}