/* 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; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Function; import xxl.core.io.converters.FixedSizeConverter; import xxl.core.io.converters.LongConverter; import xxl.core.util.XXLSystem; /** * This class provides an implementation of the Container interface that * internally uses a map to store the (id,*)-elements. Most methods * directly call the methods of the internally used map. * <p> * This class supports a clone mode that means every Object which * is inserted into/retrieved from the map is cloned. This clone * method must use the reflection API because clone is protected inside * the class Object. If you do not clone the objects, then a change of * the inserted Object also changes the Object inside the Container. So * you might have side effects which certainly will cause a different * behaviour when you switch to an external Container (which cannot * have side effects). * <p> * If you want to develop an application which works on external memory, * then test your application with a MapContainer without clone mode first, * then with the clone mode and then switch to the external Container. * <p> * The iterators returned by this class' <tt>ids</tt> and * <tt>objects</tt> methods are fail-fast: if the container is modified at * any time after the iterator is created, in any way except through the * iterator's own remove method, the iterator throws a * ConcurrentModificationException. Thus, in the face of concurrent * modification, the iterator fails quickly and cleanly, rather than * risking arbitrary, non-deterministic behavior at an undetermined time * in the future.<p> * * Usage example (1). * <pre> * // create a new map container (that uses a hash map to store its elements per default) * * MapContainer container = new MapContainer(); * * // create an iteration over the Integer between 0 and 19 * * Iterator iterator = new Enumerator(20); * * // insert all elements of the given iterator and save the returned iterator of the ids * * iterator = container.insertAll(iterator); * * // print all elements of the container * * while (iterator.hasNext()) * System.out.println(container.get(iterator.next())); * * // close the open container after use * * container.close(); * </pre> * * Usage example (2). * <pre> * // create a new map container that uses a tree map to store its elements * * container = new MapContainer(new java.util.TreeMap()); * * // create an iteration over the Integer between 0 and 19 * * iterator = new Enumerator(20); * * // insert all elements of the given iterator * * iterator = container.insertAll(iterator); * * // consume iterator in order to insert elements (lazy evaluation) * * while (iterator.hasNext()) * iterator.next(); * * // generate an iteration over the ids of the container * * iterator = container.ids(); * * // look at every id and ... * * while (iterator.hasNext()) { * Object id = iterator.next(); * int i = ((Integer)container.get(id)).intValue(); * * // remove every Integer that is smaller than 5 * * if (i < 5) { * container.remove(id); * * // uddate the iteration over the ids because the queue has been modified * * iterator = container.ids(); * } * else * * // update every odd Integer by multiplying it with 10 * * if (i%2 != 0) * container.update(id, new Integer(i*10)); * } * * // generate an iteration over the objects of the container * * iterator = container.objects(); * * // print all elements of the container * * while (iterator.hasNext()) * System.out.println(iterator.next()); * * // close the open container after use * * container.close(); * </pre> * * @see xxl.core.io.converters.Converter * @see Function * @see HashMap * @see Iterator * @see LongConverter * @see Map * @see java.util.Map.Entry * @see xxl.core.cursors.mappers.Mapper * @see NoSuchElementException */ public class MapContainer extends AbstractContainer { /** * A factory method to create a new MapContainer. It may be invoked * with a <i>parameter list</i> (for further details see Function) * of maps, an iterator or without any parameters. A * <i>parameter list</i> of maps will be used to initialize the * internally used map with the map at index 0 and an iterator will * be used to insert the contained elements into the new MapContainer. * * @see Function */ public static final Function<Object,MapContainer> FACTORY_METHOD = new AbstractFunction<Object,MapContainer> () { public MapContainer invoke () { return new MapContainer(); } public MapContainer invoke (List<? extends Object> list) { return new MapContainer((Map) list.get(0)); } }; /** * A counter that is used to create unique ids. Everytime an object is * inserted into the container the counter is increased and a * <tt>Long</tt> object with the actual value of the counter is * returned as id. */ protected long counter = 0; /** * The map is internally used to store the (id,*)-elements of the * container. */ protected Map map; /** * Flag which determines if every object is cloned before storing * and before returning it. */ protected boolean cloneObjects; /** * A unique object used to identify mappings where no object has been * assigned to so far. */ protected static final Object empty = new Object(); /** * Constructs a container containing the (id,*)-elements of the map. * The specified map is internally used to store the entries of the * container. * * @param map the map that is used to initialize the internally used * map. * @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 MapContainer(Map map, boolean cloneObjects) { this.map = map; this.cloneObjects = cloneObjects; } /** * Constructs a container containing the (id,*)-elements of the map. * The specified map is internally used to store the entries of the * container. Objects are not cloned before insertion and before retrieval. * So beware of side effects. * * @param map the map that is used to initialize the internally used * map. */ public MapContainer(Map map) { this(map,false); } /** * Constructs an empty container. This container instantiates a new * HashMap in order to store its (id,*)-elements. * @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 MapContainer(boolean cloneObjects){ this(new HashMap(),cloneObjects); } /** * Constructs an empty container. This container instantiates a new * HashMap in order to store its (id,*)-elements. Objects are not * cloned before insertion and before retrieval. So beware of side * effects. */ public MapContainer(){ this(new HashMap()); } /** * 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> * Because this container is returning <tt>Long</tt> objects as ids * the converter <code>LongConverter.DEFAULT_INSTANCE</code> is * returned. * * @return a converter for serializing the identifiers of the * container. * @see LongConverter#DEFAULT_INSTANCE */ public FixedSizeConverter objectIdConverter () { return LongConverter.DEFAULT_INSTANCE; } /** * Returns the size of the ids generated by this container in bytes, * which is 8. * @return 8 */ public int getIdSize() { return LongConverter.SIZE; } /** * Removes all elements from this container. After a call of this * method, <tt>size()</tt> will return 0. */ public void clear () { map.clear(); } /** * Returns <tt>true</tt> if there is an object stored within the container * having the identifier <tt>id</tt>. * * @param id identifier of the object. * @return true if the container contains an object for the specified * identifier. */ public boolean contains (Object id) { return map.containsKey(id) && map.get(id)!=empty; } /** * Returns the object associated to the identifier <tt>id</tt>. An * exception is thrown if there is not object stored with this * <tt>id</tt>. The parameter <tt>unfix</tt> has no function because this * container is unbuffered. * * @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, boolean unfix) throws NoSuchElementException { if (!contains(id)) throw new NoSuchElementException(); if (cloneObjects) return XXLSystem.cloneObject(map.get(id)); else return map.get(id); } /** * Returns an iterator that delivers all the identifiers of * the container that are in use. * * @return an iterator of all identifiers used by this container. */ public Iterator ids () { return map.keySet().iterator(); /* ??? return new Mapper(map.entrySet().iterator(), new AbstractFunction() { public Object invoke (Object object) { return ((Entry)object).getKey(); } } );*/ } /** * Inserts a new object into the container and returns the unique * identifier that the container has been associated to the object. * This container uses a counter to generate an unique id. Everytime * an object is inserted into the container the counter is increased * and a <tt>Long</tt> object with the actual value of the counter is * returned as id. So the identifier will not be reused again when the * object is deleted from the container. The parameter <tt>unfix</tt> * has no function because this container is unbuffered. * * @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) { Object id = new Long(counter++); if (cloneObjects) map.put(id, XXLSystem.cloneObject(object)); else map.put(id, object); 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 map.containsKey(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. * * @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 { if (isUsed(id)) map.remove(id); else throw new NoSuchElementException(); } /** * 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>. * * @param getObject A parameterless function providing the object for * that an id should be reserved. * @return the reserved id. */ public Object reserve (Function getObject) { Object id = new Long(counter++); // do not use insert because cloning of empty must not be done. map.put(id, empty); return id; } /** * Returns the number of elements of the container. * * @return the number of elements. */ public int size () { return map.size(); } /** * 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. * * @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 { if (isUsed(id)) { if (cloneObjects) map.put(id, XXLSystem.cloneObject(object)); else map.put(id, object); } else throw new NoSuchElementException(); } }