/* 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.queues.io; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.List; import xxl.core.collections.queues.AbstractQueue; import xxl.core.collections.queues.LIFOQueue; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Function; import xxl.core.io.converters.ConvertableConverter; import xxl.core.io.converters.Converter; import xxl.core.io.converters.Converters; import xxl.core.io.converters.UniformConverter; import xxl.core.util.WrappingRuntimeException; /** * This class provides an implementation of the Queue interface with a * LIFO (<i>last in, first out</i>) strategy that internally uses a file * for storing its elements. It implements the <tt>peek</tt> method.<p> * * Example usage (1). * <pre> * // create a new file * * File file = new File("file.dat"); * * // create a new file stack queue with ... * * FileStackQueue queue = new FileStackQueue( * // the created file * * file, * * // an integer converter * * IntegerConverter.DEFAULT_INSTANCE * ); * * // open the queue * * queue.open(); * * // create an iteration over the Integer between 0 and 19 * * Cursor cursor = new Enumerator(20); * * // insert all elements of the given iterator * * for (; cursor.hasNext(); queue.enqueue(cursor.next())); * * // print all elements of the queue * * while (!queue.isEmpty()) * System.out.println(queue.dequeue()); * * System.out.println(); * * // close open queue and cursor after use * * queue.close(); * cursor.close(); * * // delete the file * * file.delete(); * </pre> * * @see xxl.core.collections.queues.Queue * @see xxl.core.collections.queues.AbstractQueue * @see xxl.core.collections.queues.LIFOQueue * @see java.io.File * @see java.io.RandomAccessFile */ public class FileStackQueue extends AbstractQueue implements LIFOQueue { /** * A factory method to create a new FileStackQueue (see contract for * {@link xxl.core.collections.queues.Queue#FACTORY_METHOD FACTORY_METHOD} in * interface Queue). In contradiction to the contract in Queue it may * only be invoked with a <i>parameter list</i> (for further * details see Function) of objects. The <i>parameter list</i> * will be used for initializing the file stack queue by calling the * constructor * <code>FileStackQueue((File) list.get(0), (Function) list.get(1))</code>. * * @see Function */ public static final Function<Object,FileStackQueue> FACTORY_METHOD = new AbstractFunction<Object,FileStackQueue>() { public FileStackQueue invoke(List<? extends Object> list) { return new FileStackQueue( (File)list.get(0), (Function)list.get(1) ); } }; /** * The file is internally used for storing the elements of the queue. */ protected File file; /** * A function that implements the functionality of opening the file * for random access. The function is invoked with <tt>file</tt> when * this file stack queue is opened and must implement all the * functionality that is needed to open a file for random access. When * being invoked the function must return a <tt>RandomAccessFile</tt>. * The function determines whether the queue uses objects that are * stored in the file before calling a constructor of FileStackQueue * as initial elements of the queue. E.g. when setting the length of * the <tt>RandomAccessFile</tt> to <tt>0</tt> before returning it, * objects that are stored in the file are removed. */ protected Function openFile; /** * A converter that is used for serializing and de-serializing the * elements of this queue. */ protected Converter converter; /** * A random access file stream for reading from and writing to * <tt>file</tt>. The stream is created by invoking <tt>openFile</tt> * with <tt>file</tt> when the file stack queue is opened. */ protected RandomAccessFile randomAccessFile; /** * Constructs a queue that store its elements in the given file. When * opening the queue, the function <tt>openFile</tt> is invoked with * the file in order to open it for random access. Whether objects * that are stored into the file are used for initializing the queue * or not depends on <tt>openFile</tt>. The specified converter is * used for serializing and de-serializing the elements of the queue. * * @param file the file that is internally used for storing the * elements of the queue. * @param openFile a function that is invoked with the file in order * to open it for random access. * @param converter a converter that is used for serializing and * de-serializing the elements of the queue. */ public FileStackQueue(File file, Function openFile, Converter converter) { this.file = file; this.openFile = openFile; this.converter = converter; } /** * Constructs a queue that stores its elements in the given file. When * opening the queue, the file is opened for random access by calling * the constructor * {@link RandomAccessFile#RandomAccessFile(File, String) * RandomAccessFile(file, "rw")}. Therefore objects that are stored * into the file are used for initializing the queue. The specified * converter is used for serializing and de-serializing the elements * of the queue. * * @param file the file that is internally used for storing the * elements of the queue. * @param converter a converter that is used for serializing and * de-serializing the elements of the queue. */ public FileStackQueue(final File file, Converter converter) { this( file, new AbstractFunction() { public Object invoke(Object object) { try { return new RandomAccessFile(file, "rw"); } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } }, converter ); } /** * Constructs a queue that stores its elements in the given file. When * opening the queue, the function <tt>openFile</tt> is invoked with * the file in order to open it for random access. Whether objects * that are stored into the file are used for initializing the queue * or not depends on <tt>openFile</tt>. The given converter is used * for serializing and de-serializing the elements of the queue and * the specified factory method is used for initializing these * elements before the file is read out.<br> * This constructor is equivalent to the call of * <code>FileStackQueue(file, openFile, new UniformConverter(converter, newObject))</code>. * * @param file the file that is internally used for storing the * elements of the queue. * @param openFile a function that is invoked with the file in order * to open it for random access. * @param converter a converter that is used for serializing and * de-serializing the elements of the queue. * @param newObject a factory method that is used for initializing the * elements of the queue before the file is read out. */ public FileStackQueue(File file, Function openFile, Converter converter, Function newObject) { this(file, openFile, new UniformConverter(converter, newObject)); } /** * Constructs a queue that stores its elements in the given file. When * opening the queue, the file is opened for random access by calling * the constructor * {@link RandomAccessFile#RandomAccessFile(File, String) * RandomAccessFile(file, "rw")}. Therefore objects that are stored * into the file are used for initializing the queue. The given * converter is used for serializing and de-serializing the elements * of the queue and the specified factory method is used for * initializing these elements before the file is read out.<br> * This constructor is equivalent to the call of * <code>FileStackQueue(file, new UniformConverter(converter, newObject))</code>. * * @param file the file that is internally used for storing the * elements of the queue. * @param converter a converter that is used for serializing and * de-serializing the elements of the queue. * @param newObject a factory method that is used for initializing the * elements of the queue before the file is read out. */ public FileStackQueue(final File file, Converter converter, Function newObject) { this(file, new UniformConverter(converter, newObject)); } /** * Constructs a queue that stores its elements in the given file. When * opening the queue, the function <tt>openFile</tt> is invoked with * the the file in order to open it for random access. Whether objects * that are stored into the file are used for initializing the queue * or not depends on <tt>openFile</tt>. A convertable converter is * used for serializing and de-serializing the elements of the queue * and the specified factory method is used for initializing these * elements before the file is read out.<br> * This constructor is equivalent to the call of * <code>FileStackQueue(file, openFile, ConvertableConverter.DEFAULT_INSTANCE, newObject)</code>. * * @param file the file that is internally used for storing the * elements of the queue. * @param openFile a function that is invoked with the file in order * to open it for random access. * @param newObject a factory method that is used for initializing the * elements of the queue before the file is read out. * @see ConvertableConverter#DEFAULT_INSTANCE */ public FileStackQueue(File file, Function openFile, Function newObject) { this(file, openFile, ConvertableConverter.DEFAULT_INSTANCE, newObject); } /** * Constructs a queue that stores its elements in the given file. When * opening the queue, the file is opened for random access by calling * the constructor * {@link RandomAccessFile#RandomAccessFile(File, String) * RandomAccessFile(file, "rw")}. Therefore objects that are stored * into the file are used for initializing the queue. A convertable * converter is used for serializing and de-serializing the elements * of the queue and the specified factory method is used for * initializing these elements before the file is read out.<br> * This constructor is equivalent to the call of * <code>FileStackQueue(file, ConvertableConverter.DEFAULT_INSTANCE, newObject)</code>. * * @param file the file that is internally used for storing the * elements of the queue. * @param newObject a factory method that is used for initializing the * elements of the queue before the file is read out. * @see ConvertableConverter#DEFAULT_INSTANCE */ public FileStackQueue(final File file, Function newObject) { this(file, ConvertableConverter.DEFAULT_INSTANCE, newObject); } /** * Opens the file stack queue for the access to elements of this * queue. This method is called implicitly every time the elements of * the queue are accessed. This implementation checks whether a random * access file stream already exists. When no such stream exists, * <tt>openFile</tt> is invoked with <tt>file</tt> and the elements * that are stored in the file after opening it for random access are * used as initial elements of the queue. */ public void open() { if (isOpened) return; super.open(); try { if (randomAccessFile == null) { randomAccessFile = (RandomAccessFile)openFile.invoke(file); if (randomAccessFile.length() == 0) size = 0; else { randomAccessFile.seek(randomAccessFile.length()-4); size = randomAccessFile.readInt(); randomAccessFile.setLength(randomAccessFile.length()-4); } } } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } /** * Appends the specified element to the <i>end</i> of this queue. <br> * This implementation opens the queue first. Thereafter the object * and the size of its serialization are written to the file. * * @param object element to be appended to the <i>end</i> of this * queue. */ protected void enqueueObject(Object object) { try { randomAccessFile.seek(randomAccessFile.length()); converter.write(randomAccessFile, object); randomAccessFile.writeInt(Converters.sizeOf(converter, object)); } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } /** * Returns the <i>next</i> element in the queue <i>without</i> removing it. * <br> * This implementation opens the queue first. Thereafter the length of * the serialized object and the object itself are read out of the * file and the object is returned. * * @return the <i>next</i> element in the queue. */ protected Object peekObject() { try { randomAccessFile.seek(randomAccessFile.length()-4); randomAccessFile.seek(randomAccessFile.getFilePointer()-randomAccessFile.readInt()); return converter.read(randomAccessFile); } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } /** * Returns the <i>next</i> element in the queue. <br> * This implementation opens the queue first. Thereafter the length of * the serialized object and the object itself are read out of the * file. At last, the file is truncated to its new length (the length * of the file without the serialized object) and the object is * returned. * * @return the <i>next</i> element in the queue. */ protected Object dequeueObject() { try { randomAccessFile.seek(randomAccessFile.length()-4); randomAccessFile.seek(randomAccessFile.getFilePointer()-randomAccessFile.readInt()); long length = randomAccessFile.getFilePointer(); Object object = converter.read(randomAccessFile); randomAccessFile.setLength(length); return object; } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } /** * Removes all elements from this queue. The queue will be * empty after this call returns so that <tt>size() == 0</tt>.<br> * This implementation opens the queue first. Thereafter the length of * the random access file and <tt>size</tt> are set to <tt>0</tt>. */ public void clear() { try { randomAccessFile.setLength(0); size = 0; } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } /** * Closes this queue and releases any system resources associated with * it. This operation is idempotent, i.e., multiple calls of this * method takes the same effect as a single call.<br> * This implementation closes the random access file and deletes the * file, when it is empty. Therefore the file stack queue can be * reopened later and will have the same state as before closing it, * when <tt>openFile</tt> does not affect the elements stored in the * file. */ public void close() { if (isClosed) return; super.close(); try { if (randomAccessFile != null) { if (size == 0) { randomAccessFile.close(); file.delete(); } else { randomAccessFile.seek(randomAccessFile.length()); randomAccessFile.writeInt(size); randomAccessFile.close(); } randomAccessFile = null; } } catch (IOException ie) { throw new WrappingRuntimeException(ie); } } }