/* 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.util.Iterator; 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.Constant; import xxl.core.functions.Function; import xxl.core.io.Buffer; import xxl.core.util.XXLSystem; /** * This class wraps a container by buffering its elements in a given * buffer. There are only little differences between the buffered * container and the wrapped container:<br> * every time elements of the buffered container are accessed, the buffer * is check first, whether it contains the desired elements. When the * desired elements are not buffered, the elements are taken from the * wrapped container.<p> * * Example usage (1). * <pre> * // create a new buffered container with ... * * BufferedContainer container = new BufferedContainer( * * // a map container that stores the elements * * new MapContainer(), * * // a LRU buffer with 5 slots * * new LRUBuffer(5) * ); * * // 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(); * } * * // fix 5 elements in the buffer * * for (int i = 0; i < 5 && iterator.hasNext(); i++) * container.get(iterator.next(), false); * * // try to access another element (the buffer will overrun) * * try { * container.get(iterator.next()); * } * catch (Exception e) { * System.out.println(e); * } * * // flushes the whole buffer * * container.flush(); * * // get the ids of the 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 Constant * @see Container * @see ConstrainedDecoratorContainer * @see Function * @see Iterator * @see NoSuchElementException */ public class BufferedContainer extends ConstrainedDecoratorContainer { /** * The buffer that is used for buffering the elements of the container. */ protected Buffer buffer; /** * A flag that determines whether the buffered elements should be * written back to the container, when they are flushed (true) or if * they already should be written back after each update operation * (false). */ protected boolean writeBack; /** * Flag which determines if every object is cloned before storing * and before returning it. */ protected boolean cloneObjects; /** * Constructs a new buffered container that uses the specified buffer * for buffering the elements of the given container. The flag * <tt>writeBack</tt> determines whether the buffered elements should be * written back to the container, when they are flushed (true) or if * they already should be written back after each update operation * (false). * * @param container the container to be buffered. * @param buffer the buffer used for buffering the specified * container. * @param writeBack Signals that the buffered elements should be * written back to the container, when they are flushed (true) or if * they already should be written back after each update operation * (false). <b>Use writeBack with care.</b> The inserted/updated object * must stay unchanged until the buffer writes back the object to * the underlying container. * @param cloneObjects determines if every object is cloned before storing * and before returning it. The clone mode is a lot slower, because * reflection is needed to call the protected clone Method. */ public BufferedContainer (Container container, Buffer buffer, boolean writeBack, boolean cloneObjects) { super(container); this.buffer = buffer; this.writeBack = writeBack; this.cloneObjects = cloneObjects; } /** * Constructs a new buffered container that uses the specified buffer * for buffering the elements of the given container. The flag * <tt>writeBack</tt> determines whether the buffered elements should be * written back to the container, when they are flushed (true) or if * they already should be written back after each update operation * (false). * * @param container the container to be buffered. * @param buffer the buffer used for buffering the specified * container. * @param writeBack Signals that the buffered elements should be * written back to the container, when they are flushed (true) or if * they already should be written back after each update operation * (false). <b>Use writeBack with care.</b> The inserted/updated object * must stay unchanged until the buffer writes back the object to * the underlying container. */ public BufferedContainer (Container container, Buffer buffer, boolean writeBack) { this(container, buffer, writeBack, false); } /** * Constructs a new buffered container that uses the specified buffer * for buffering the elements of the given container. When flushing a * buffered element, it will be written back to the container. This * constructor is equivalent to the call of * <code>BufferedContainer(container, buffer, true)</code>. * Look at the remarks for writeBack! * * @param container the container to be buffered. * @param buffer the buffer used for buffering the specified * container. */ public BufferedContainer (Container container, Buffer buffer) { this(container, buffer, true); } /** * Removes all elements from the container. After a call of this * method, <tt>size()</tt> will return <tt>0</tt>.<br> * This implementation removes all buffered elements out of the buffer * and clears the wrapped container thereafter. */ public void clear () { buffer.removeAll(this); super.clear(); } /** * Closes the Container and releases all sources. For external * containers, this method closes the files immediately. MOREOVER, all * iterators operating on the container can be in illegal states. * Close can be called a second time without any impact.<br> * This implementation flushes all buffered elements and removes them * out of the buffer. Thereafter the wrapped container is closed. */ public void close () { buffer.flushAll(this); buffer.removeAll(this); super.close(); } /** * Returns <tt>true</tt> if the container contains an object for the * identifier <tt>id</tt>. <br> * First, this implementation checks the buffer, if it contains an * object for id. Thereafter the wrapped container is checked. * * @param id identifier of the object. * @return <tt>true</tt> if the container contains an object for the * specified identifier. */ public boolean contains (Object id) { return buffer.contains(this, id) || super.contains(id); } /** * Flushes all modified elements from the buffer into the container. * After this call the buffer and the container are synchronized.<br> * This implementation flushes all buffered elements first. Thereafter * the elements of the wrapped container are flushed (in order to * support a second buffer etc.). */ public void flush () { buffer.flushAll(this); super.flush(); } /** * Flushes the object with identifier <tt>id</tt> from the buffer into * the container. <br> * This implementation flushes the buffered element with the given id * first. Thereafter the element of the wrapped container is flushed * (in order to support a second buffer etc.). * * @param id identifier of the object that should be written back. */ public void flush (Object id) { buffer.flush(this, id); super.flush(id); } /** * Returns the object associated to the identifier <tt>id</tt>. An * exception is thrown when the desired object is not found. If <tt>unfix</tt>, * the object can be removed from the underlying buffer. Otherwise * (<tt>!unfix</tt>), the object has to be kept in the buffer.<br> * This implementation calls the get method of the buffer with a * function that returns the desired object of the container, when it * is invoked. * * @param id identifier of the object. * @param unfix signals 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, final boolean unfix) throws NoSuchElementException { Object object = buffer.get(this, id, new AbstractFunction () { public Object invoke (Object id) { return BufferedContainer.super.get(id, unfix); } }, unfix ); if (cloneObjects) return XXLSystem.cloneObject(object); else return object; } /** * Returns an iterator that delivers the identifiers of all objects of * the container. * * @return an iterator of object identifiers. */ public Iterator ids () { return new Iterator () { Iterator ids = BufferedContainer.super.ids(); Object id; public boolean hasNext () { return ids.hasNext(); } public Object next () throws NoSuchElementException { return id = ids.next(); } public void remove () throws IllegalStateException { ids.remove(); buffer.remove(BufferedContainer.this, id); } }; } /** * Inserts a new object into the container and returns the unique * identifier that the container has been associated to the object. * If unfixed, the object can be removed from the buffer. Otherwise, * it has to be kept in the buffer until an <tt>unfix()</tt> is * called.<br> * After an insertion all the iterators operating on the container can * be in an invalid state.<br> * This implementation inserts the object into the wrapped container. * Thereafter it is inserted into the buffer by calling the buffer's * <tt>get</tt> method. * * @param object is the new object. * @param unfix signals whether the object can be removed from the * underlying buffer. * @return the identifier of the object. */ public Object insert (Object object, boolean unfix) { if (cloneObjects) object = XXLSystem.cloneObject(object); Object id = reserve(new Constant(object)); update(id, object, unfix); return id; } /** * Checks whether the <tt>id</tt> has been returned previously by a * call to insert or reserve and hasn't been removed so far. * * @param id the id to be checked. * @return true exactly if the <tt>id</tt> is still in use. */ public boolean isUsed (Object id) { return super.isUsed(id); } /** * Removes the object with identifier <tt>id</tt>. An exception is * thrown when an object with an identifier <tt>id</tt> is not in the * container.<br> * After a call of <tt>remove()</tt> all the iterators (and cursors) * can be in an invalid state.<br> * First, this implementation removes the object from the buffer. * Thereafter it is removed from the wrapped container. * * @param id an identifier of an object. * @throws NoSuchElementException if an object with an identifier * <tt>id</tt> is not in the container. */ public void remove (Object id) throws NoSuchElementException { buffer.remove(this, id); super.remove(id); } /** * Unfixes the object with identifier <tt>id</tt>. This method throws * an exception when no element with an identifier <tt>id</tt> is in * the container. After one call of unfix the buffer is allowed to * remove the object (although the objects have been fixed more than * once).<br> * This implementation unfixes the buffered object first. Thereafter * the object of the wrapped container is unfixed (in order to support * a second buffer etc.). * * @param id identifier of an object that should be unfixed in the * buffer. * @throws NoSuchElementException if no element with an identifier * <tt>id</tt> is in the container. */ public void unfix (Object id) throws NoSuchElementException { buffer.unfix(this, id); super.unfix(id); } /** * 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.<br> * This implementation calls update method of the buffer. When the * buffered elements should be written back to the buffer, the update * method is called with a flush function, that updates the element of * the wrapped container with the buffered element. Otherwise, the * element of the wrapped container is updated once after calling the * buffer's update method. * * @param id identifier of the element. * @param object the new object that should be associated to * <tt>id</tt>. * @param unfix signals 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 { if (cloneObjects) object = XXLSystem.cloneObject(object); buffer.update(this, id, object, !writeBack ? null : new AbstractFunction () { public Object invoke (Object id, Object object) { BufferedContainer.super.update(id, object, !buffer.isFixed(BufferedContainer.this, id)); return null; } }, unfix ); // no cloning needed here, because the called container is responsible for that. if (!writeBack) super.update(id, object, unfix); } /** * Returns the number of fixed elements in the container's buffer. */ public int fixedElements() { return buffer.fixedSlots(); } }