/* 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.collections.containers.io;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.NoSuchElementException;
import xxl.core.collections.containers.ConstrainedDecoratorContainer;
import xxl.core.collections.containers.Container;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Function;
import xxl.core.io.Block;
import xxl.core.io.ByteBufferDataInput;
import xxl.core.io.UnsafeDataInput;
import xxl.core.io.converters.Converter;
import xxl.core.io.converters.Converters;
import xxl.core.io.converters.FixedSizeConverter;
import xxl.core.util.WrappingRuntimeException;
/**
* This class provides a container that stores a set of converted
* elements. The container decorates an underlying container that must be
* able to store byte arrays (as blocks). <p>
*
* When storing an element in this converter it is converted and the
* resulting byte array is wrapped by a block. Thereafter this block is
* stored in the decorated container.<p>
*
* By using a ConverterContainer it is possible to decorate a container
* that is based on extenal memory, because conversions between objects
* and byte arrays will be internally done.<p>
*
* Example usage (1).
* <pre>
* // create a new coverter container with ...
*
* ConverterContainer container = new ConverterContainer(
*
* // a map container that stores the elements
*
* new MapContainer(),
*
* // an integer converter for converting the elements
*
* IntegerConverter.DEFAULT_INSTANCE
* );
*
* // create an iteration over 20 random Integers (between 0 and 100)
*
* Iterator iterator = new Enumerator(20);
*
* // insert all elements of the given iterator
*
* iterator = container.insertAll(iterator);
*
* // print all elements of the queue
*
* while (iterator.hasNext())
* System.out.println(container.get(iterator.next()));
*
* // get the ids of the elements of the container
*
* iterator = container.ids();
*
* // remove 5 elements
*
* for (int i = 0; i < 5 && iterator.hasNext(); i++) {
* container.remove(iterator.next());
*
* // refresh the iterator (cause it can be in an invalid state)
*
* iterator = container.ids();
* }
*
* // update 5 elements
*
* for (int i = 0; i < 5 && iterator.hasNext(); i++) {
* Object id = iterator.next();
* container.update(id, new Integer(((Integer)container.get(id)).intValue()+100));
* }
*
* // get the ids of all elements of the container
*
* iterator = container.ids();
*
* // print all elements of the queue
*
* while (iterator.hasNext())
* System.out.println(container.get(iterator.next()));
*
* // close the open queue after use
*
* container.close();
* </pre>
*
* @see ByteArrayInputStream
* @see ConstrainedDecoratorContainer
* @see Container
* @see DataInputStream
* @see Function
* @see IOException
* @see NoSuchElementException
* @see WrappingRuntimeException
*/
public class ConverterContainer extends ConstrainedDecoratorContainer {
/**
* The converter that is used for converting the elements of the
* underlying container.
*/
protected Converter converter;
/**
* Specified the type of <tt>DataOutput</tt> used
* for serialization resp. deserialization
*/
protected Converters.SerializationMode serializationMode;
/**
* The size of the buffer used for serialization resp. deserialization.
* This value is only relevant for serialization modes <tt>BYTE_BUFFER</tt>
* and <tt>UNSAFE</tt>.
*/
protected int bufferSize;
/**
* Constructs a new ConverterContainer that decorates the specified
* container and uses the specified converter for converting its
* elements.
*
* @param container the underlying container that is used for storing
* the converted elements.
* @param converter the converter that is used for converting the
* elements of this container.
*/
public ConverterContainer (Container container, Converter converter) {
this(container, converter, Converters.SerializationMode.BYTE_ARRAY, 0);
}
/**
* Constructs a new ConverterContainer that decorates the specified
* container and uses the specified converter for converting its
* elements.
*
* @param container the underlying container that is used for storing
* the converted elements.
* @param converter the converter that is used for converting the
* elements of this container.
*/
public ConverterContainer (Container container, Converter converter, Converters.SerializationMode serializationMode, int bufferSize) {
super(container);
this.converter = converter;
this.serializationMode = serializationMode;
this.bufferSize = bufferSize;
}
/**
* Returns the object associated to the identifier <tt>id</tt>. An
* exception is thrown when the desired object is not found via contains.<br>
* This implementation gets the block associated to the <tt>id</tt>
* from the underlying container and tries to convert the wrapped byte
* array. The parameter <tt>unfix</tt> is passed to the underlying container.
*
* @param id identifier of the object.
* @param unfix signals a buffered container whether the object can
* be removed from the underlying buffer.
* @return the object associated to the specified identifier.
* @throws NoSuchElementException if the desired object is not found.
*/
public Object get (Object id, boolean unfix) throws NoSuchElementException {
try {
Block block = null;
block = (Block)super.get(id, unfix);
if (serializationMode == Converters.SerializationMode.BYTE_BUFFER)
return converter.read(new ByteBufferDataInput(new ByteArrayInputStream(block.array, block.offset, block.size)));
else if (serializationMode == Converters.SerializationMode.UNSAFE)
return converter.read(new UnsafeDataInput(new ByteArrayInputStream(block.array, block.offset, block.size)));
else
return converter.read(new DataInputStream(new ByteArrayInputStream(block.array, block.offset, block.size)));
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
/**
* Inserts a new object into the container and returns the unique
* identifier that the container has been associated to the object.
* <br>
* This implementation tries to convert the object and inserts a block
* wrapping the resulting byte array into the underlying container.
* The parameter <tt>unfix</tt> is passed to the decorated container.
* Thereafter the identifier that has been associated to the block by
* the underlying container is returned.
*
* @param object is the new object.
* @param unfix signals a buffered container whether the object can
* be removed from the underlying buffer.
* @return the identifier of the object.
*/
public Object insert (Object object, boolean unfix) {
byte [] array = Converters.toByteArray(converter, object, serializationMode, bufferSize);
return super.insert(new Block(array, 0, array.length), unfix);
}
/**
* Returns a converter for the ids generated by this container. A
* converter transforms an object to its byte representation and vice
* versa - also known as serialization in Java.<br>
* Since the identifier may have an arbitrary type (which has to be
* known in the container), the container has to provide such a method
* when the data is not stored in main memory.
*
* @return a converter for serializing the identifiers of the
* container.
*/
public FixedSizeConverter objectIdConverter () {
return container.objectIdConverter();
}
/**
* Returns the size of the ids generated by this container in bytes.
* This call is forwarded to the underlying Container.
* @return the size in bytes of each id.
*/
public int getIdSize() {
return container.getIdSize();
}
/**
* Reserves an id for subsequent use. The container may or may not
* need an object to be able to reserve an id, depending on the
* implementation. If so, it will call the parameterless function
* provided by the parameter <tt>getObject</tt>.
* This implementation wraps the function getObject by converting
* the object when invoking the function.
*
* @param getObject A parameterless function providing the object for
* that an id should be reserved.
* @return the reserved id.
*/
public Object reserve (final Function getObject) {
return super.reserve(
new AbstractFunction () {
public Object invoke () {
return new Block(Converters.toByteArray(converter, getObject.invoke(), serializationMode, bufferSize));
}
}
);
}
/**
* Overwrites an existing (id,*)-element by (id, object). This method
* throws an exception if an object with an identifier <tt>id</tt>
* does not exist in the container (checked via isUsed).<br>
* This implementation tries to convert the object and updates the
* existing element of the underlying container by a block wrapping
* the resulting byte array. The parameter <tt>unfix</tt> is passed to the
* decorated container.
*
* @param id identifier of the element.
* @param object the new object that should be associated to
* <tt>id</tt>.
* @param unfix signals a buffered container whether the object can
* be removed from the underlying buffer.
* @throws NoSuchElementException if an object with an identifier
* <tt>id</tt> does not exist in the container.
*/
public void update (Object id, Object object, boolean unfix) throws NoSuchElementException {
byte [] array = Converters.toByteArray(converter, object, serializationMode, bufferSize);
super.update(id, new Block(array, 0, array.length), unfix);
}
}