You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sc...@apache.org on 2003/11/03 00:41:46 UTC

cvs commit: jakarta-commons/collections/src/java/org/apache/commons/collections Flat3Map.java

scolebourne    2003/11/02 15:41:46

  Added:       collections/src/test/org/apache/commons/collections
                        TestFlat3Map.java
               collections/src/java/org/apache/commons/collections
                        Flat3Map.java
  Log:
  Add Flat3Map, a new map design for small Maps that is faster than HashMap
  
  Revision  Changes    Path
  1.1                  jakarta-commons/collections/src/test/org/apache/commons/collections/TestFlat3Map.java
  
  Index: TestFlat3Map.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/collections/src/test/org/apache/commons/collections/TestFlat3Map.java,v 1.1 2003/11/02 23:41:46 scolebourne Exp $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowledgement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgement may appear in the software itself,
   *    if and wherever such third-party acknowledgements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  package org.apache.commons.collections;
  
  import java.util.Map;
  
  import junit.framework.Test;
  import junit.textui.TestRunner;
  
  import org.apache.commons.collections.iterators.AbstractTestMapIterator;
  import org.apache.commons.collections.iterators.MapIterator;
  
  /**
   * JUnit tests.
   * 
   * @version $Revision: 1.1 $ $Date: 2003/11/02 23:41:46 $
   * 
   * @author Stephen Colebourne
   */
  public class TestFlat3Map extends AbstractTestMap {
  
      public TestFlat3Map(String testName) {
          super(testName);
      }
  
      public static void main(String[] args) {
          TestRunner.run(suite());
      }
      
      public static Test suite() {
          return BulkTest.makeSuite(TestFlat3Map.class);
      }
  
      protected Map makeEmptyMap() {
          return new Flat3Map();
      }
  
      //-----------------------------------------------------------------------
      public BulkTest bulkTestMapIterator() {
          return new TestFlatMapIterator();
      }
      
      public class TestFlatMapIterator extends AbstractTestMapIterator {
          public TestFlatMapIterator() {
              super("TestFlatMapIterator");
          }
          
          protected Object addSetValue() {
              return TestFlat3Map.this.getNewSampleValues()[0];
          }
          
          protected boolean supportsRemove() {
              return TestFlat3Map.this.isRemoveSupported();
          }
  
          protected boolean supportsSetValue() {
              return TestFlat3Map.this.isSetValueSupported();
          }
  
          protected MapIterator makeEmptyMapIterator() {
              resetEmpty();
              return ((Flat3Map) TestFlat3Map.this.map).mapIterator();
          }
  
          protected MapIterator makeFullMapIterator() {
              resetFull();
              return ((Flat3Map) TestFlat3Map.this.map).mapIterator();
          }
          
          protected Map getMap() {
              // assumes makeFullMapIterator() called first
              return TestFlat3Map.this.map;
          }
          
          protected void verify() {
              super.verify();
              // cannot cross verify as we don't know what superclass did
          }
      }
      
      //-----------------------------------------------------------------------
      public void testMapIteratorRemove() {
          resetFull();
          Flat3Map testMap = (Flat3Map) map;
          MapIterator it = testMap.mapIterator();
          assertEquals(true, it.hasNext());
          Object key = it.next();
          
          if (isRemoveSupported() == false) {
              try {
                  it.remove();
                  fail();
              } catch (UnsupportedOperationException ex) {
              }
              return;
          }
          
          it.remove();
          confirmed.remove(key);
          assertEquals(false, testMap.containsKey(key));
          verify();
          
          try {
              it.remove();  // second remove fails
          } catch (IllegalStateException ex) {
          }
          verify();
      }
  
      //-----------------------------------------------------------------------
      public void testMapIteratorSet() {
          Object newValue1 = getOtherValues()[0];
          Object newValue2 = getOtherValues()[1];
          
          resetFull();
          Flat3Map testMap = (Flat3Map) map;
          MapIterator it = testMap.mapIterator();
          assertEquals(true, it.hasNext());
          Object key1 = it.next();
          
          if (isSetValueSupported() == false) {
              try {
                  it.setValue(newValue1);
                  fail();
              } catch (UnsupportedOperationException ex) {
              }
              return;
          }
          
          it.setValue(newValue1);
          confirmed.put(key1, newValue1);
          assertSame(key1, it.getKey());
          assertSame(newValue1, it.getValue());
          assertEquals(true, testMap.containsKey(key1));
          assertEquals(true, testMap.containsValue(newValue1));
          assertEquals(newValue1, testMap.get(key1));
          verify();
          
          it.setValue(newValue1);  // same value - should be OK
          confirmed.put(key1, newValue1);
          assertSame(key1, it.getKey());
          assertSame(newValue1, it.getValue());
          assertEquals(true, testMap.containsKey(key1));
          assertEquals(true, testMap.containsValue(newValue1));
          assertEquals(newValue1, testMap.get(key1));
          verify();
          
          Object key2 = it.next();
          it.setValue(newValue2);
          confirmed.put(key2, newValue2);
          assertSame(key2, it.getKey());
          assertSame(newValue2, it.getValue());
          assertEquals(true, testMap.containsKey(key2));
          assertEquals(true, testMap.containsValue(newValue2));
          assertEquals(newValue2, testMap.get(key2));
          verify();
      }
  
  }
  
  
  
  1.1                  jakarta-commons/collections/src/java/org/apache/commons/collections/Flat3Map.java
  
  Index: Flat3Map.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/Flat3Map.java,v 1.1 2003/11/02 23:41:46 scolebourne Exp $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowledgement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgement may appear in the software itself,
   *    if and wherever such third-party acknowledgements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  package org.apache.commons.collections;
  
  import java.util.AbstractCollection;
  import java.util.AbstractSet;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Set;
  
  import org.apache.commons.collections.iterators.DefaultMapIterator;
  import org.apache.commons.collections.iterators.MapIterator;
  import org.apache.commons.collections.iterators.ResetableMapIterator;
  import org.apache.commons.collections.pairs.AbstractMapEntry;
  
  /**
   * A <code>Map</code> implementation that stores data in simple fields until
   * the size is greater than 3.
   * <p>
   * This map is designed for performance and can outstrip HashMap.
   * It also has good garbage collection characteristics.
   * <ul>
   * <li>Optimised for operation at size 3 or less.
   * <li>Still works well once size 3 exceeded.
   * <li>Gets at size 3 or less are about 0-10% faster than HashMap,
   * <li>Puts at size 3 or less are over 4 times faster than HashMap.
   * <li>Performance 5% slower than HashMap once size 3 exceeded once.
   * </ul>
   * The design uses two distinct modes of operation - flat and delegate.
   * While the map is size 3 or less, operations map straight onto fields using
   * switch statements. Once size 4 is reached, the map switches to delegate mode
   * and never switches back. In delegate mode, all operations are forwarded 
   * straight to a HashMap resulting in the 5% performance loss.
   * <p>
   * The performance gains on puts are due to not needing to create a Map Entry
   * object. This is a large saving not only in performance but in garbage collection.
   * <p>
   * Whilst in flat mode this map is also easy for the garbage collector to dispatch.
   * This is because it contains no complex objects or arrays which slow the progress.
   * (Note that the impact of this has not actually been tested!)
   * 
   * @since Commons Collections 3.0
   * @version $Revision: 1.1 $ $Date: 2003/11/02 23:41:46 $
   *
   * @author Stephen Colebourne
   */
  public class Flat3Map implements Map {
      
      /** The size of the map, used while in flat mode */
      private int iSize;
      /** Hash, used while in flat mode */
      private int iHash1;
      /** Hash, used while in flat mode */
      private int iHash2;
      /** Hash, used while in flat mode */
      private int iHash3;
      /** Key, used while in flat mode */
      private Object iKey1;
      /** Key, used while in flat mode */
      private Object iKey2;
      /** Key, used while in flat mode */
      private Object iKey3;
      /** Value, used while in flat mode */
      private Object iValue1;
      /** Value, used while in flat mode */
      private Object iValue2;
      /** Value, used while in flat mode */
      private Object iValue3;
      /** Map, used while in delegate mode */
      private HashMap iMap;
  
      /**
       * Constructor.
       */
      public Flat3Map() {
          super();
      }
  
      /**
       * Constructor copying elements from another map.
       *
       * @param map  the map to copy
       */
      public Flat3Map(Map map) {
          super();
          putAll(map);
      }
  
      //-----------------------------------------------------------------------
      /**
       * Gets the value mapped to the key specified.
       * 
       * @param key  the key
       * @return the mapped value, null if no match
       */
      public Object get(Object key) {
          if (iMap != null) {
              return iMap.get(key);
          }
          if (key == null) {
              switch (iSize) {
                  // drop through
                  case 3:
                      if (iKey3 == null) return iValue3;
                  case 2:
                      if (iKey2 == null) return iValue2;
                  case 1:
                      if (iKey1 == null) return iValue1;
              }
          } else {
              if (iSize > 0) {
                  int hashCode = key.hashCode();
                  switch (iSize) {
                      // drop through
                      case 3:
                          if (iHash3 == hashCode && key.equals(iKey3)) return iValue3;
                      case 2:
                          if (iHash2 == hashCode && key.equals(iKey2)) return iValue2;
                      case 1:
                          if (iHash1 == hashCode && key.equals(iKey1)) return iValue1;
                  }
              }
          }
          return null;
      }
  
      /**
       * Gets the size of the map.
       * 
       * @return the size
       */
      public int size() {
          if (iMap != null) {
              return iMap.size();
          }
          return iSize;
      }
  
      /**
       * Checks whether the map is currently empty.
       * 
       * @return true if the map is currently size zero
       */
      public boolean isEmpty() {
          return (size() == 0);
      }
  
      //-----------------------------------------------------------------------
      /**
       * Checks whether the map contains the specified key.
       * 
       * @param key  the key to search for
       * @return true if the map contains the key
       */
      public boolean containsKey(Object key) {
          if (iMap != null) {
              return iMap.containsKey(key);
          }
          if (key == null) {
              switch (iSize) {  // drop through
                  case 3:
                      if (iKey3 == null) return true;
                  case 2:
                      if (iKey2 == null) return true;
                  case 1:
                      if (iKey1 == null) return true;
              }
          } else {
              if (iSize > 0) {
                  int hashCode = key.hashCode();
                  switch (iSize) {  // drop through
                      case 3:
                          if (iHash3 == hashCode && key.equals(iKey3)) return true;
                      case 2:
                          if (iHash2 == hashCode && key.equals(iKey2)) return true;
                      case 1:
                          if (iHash1 == hashCode && key.equals(iKey1)) return true;
                  }
              }
          }
          return false;
      }
  
      /**
       * Checks whether the map contains the specified value.
       * 
       * @param value  the value to search for
       * @return true if the map contains the key
       */
      public boolean containsValue(Object value) {
          if (iMap != null) {
              return iMap.containsValue(value);
          }
          if (value == null) {  // drop through
              switch (iSize) {
                  case 3:
                      if (iValue3 == null) return true;
                  case 2:
                      if (iValue2 == null) return true;
                  case 1:
                      if (iValue1 == null) return true;
              }
          } else {
              switch (iSize) {  // drop through
                  case 3:
                      if (value.equals(iValue3)) return true;
                  case 2:
                      if (value.equals(iValue2)) return true;
                  case 1:
                      if (value.equals(iValue1)) return true;
              }
          }
          return false;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Puts a key-value mapping into this map.
       * 
       * @param key  the key to add
       * @param value  the value to add
       * @return the value previously mapped to this key, null if none
       */
      public Object put(Object key, Object value) {
          if (iMap != null) {
              return iMap.put(key, value);
          }
          // change existing mapping
          if (key == null) {
              switch (iSize) {  // drop through
                  case 3:
                      if (iKey3 == null) {
                          Object old = iValue3;
                          iValue3 = value;
                          return old;
                      }
                  case 2:
                      if (iKey2 == null) {
                          Object old = iValue2;
                          iValue2 = value;
                          return old;
                      }
                  case 1:
                      if (iKey1 == null) {
                          Object old = iValue1;
                          iValue1 = value;
                          return old;
                      }
              }
          } else {
              if (iSize > 0) {
                  int hashCode = key.hashCode();
                  switch (iSize) {  // drop through
                      case 3:
                          if (iHash3 == hashCode && key.equals(iKey3)) {
                              Object old = iValue3;
                              iValue3 = value;
                              return old;
                          }
                      case 2:
                          if (iHash2 == hashCode && key.equals(iKey2)) {
                              Object old = iValue2;
                              iValue2 = value;
                              return old;
                          }
                      case 1:
                          if (iHash1 == hashCode && key.equals(iKey1)) {
                              Object old = iValue1;
                              iValue1 = value;
                              return old;
                          }
                  }
              }
          }
          
          // add new mapping
          switch (iSize) {
              default:
                  convertToMap();
                  iMap.put(key, value);
                  return null;
              case 2:
                  iHash3 = (key == null ? 0 : key.hashCode());
                  iKey3 = key;
                  iValue3 = value;
                  break;
              case 1:
                  iHash2 = (key == null ? 0 : key.hashCode());
                  iKey2 = key;
                  iValue2 = value;
                  break;
              case 0:
                  iHash1 = (key == null ? 0 : key.hashCode());
                  iKey1 = key;
                  iValue1 = value;
                  break;
          }
          iSize++;
          return null;
      }
  
      /**
       * Puts all the values from the specified map into this map.
       * 
       * @param map
       */
      public void putAll(Map map) {
          int size = map.size();
          if (size == 0) {
              return;
          }
          if (iMap != null) {
              iMap.putAll(map);
          }
          if (size < 4) {
              for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
                  Map.Entry entry = (Map.Entry) it.next();
                  put(entry.getKey(), entry.getValue());
              }
          } else {
              convertToMap();
              iMap.putAll(map);
          }
      }
  
      /**
       * Converts the flat map data to a HashMap.
       */
      private void convertToMap() {
          iMap = new HashMap();
          switch (iSize) {  // drop through
              case 3:
                  iMap.put(iKey3, iValue3);
              case 2:
                  iMap.put(iKey2, iValue2);
              case 1:
                  iMap.put(iKey1, iValue1);
          }
          
          iSize = 0;
          iHash1 = iHash2 = iHash3 = 0;
          iKey1 = iKey2 = iKey3 = null;
          iValue1 = iValue2 = iValue3 = null;
      }
  
      /**
       * Removes the specified mapping from this map.
       * 
       * @param key  the mapping to remove
       * @return the value mapped to the removed key, null if key not in map
       */
      public Object remove(Object key) {
          if (iMap != null) {
              return iMap.remove(key);
          }
          if (iSize == 0) {
              return null;
          }
          if (key == null) {
              switch (iSize) {  // drop through
                  case 3:
                      if (iKey3 == null) {
                          Object old = iValue3;
                          iHash3 = 0;
                          iKey3 = null;
                          iValue3 = null;
                          iSize = 2;
                          return old;
                      }
                      if (iKey2 == null) {
                          Object old = iValue3;
                          iHash2 = iHash3;
                          iKey2 = iKey3;
                          iValue2 = iValue3;
                          iHash3 = 0;
                          iKey3 = null;
                          iValue3 = null;
                          iSize = 2;
                          return old;
                      }
                      if (iKey1 == null) {
                          Object old = iValue3;
                          iHash1 = iHash3;
                          iKey1 = iKey3;
                          iValue1 = iValue3;
                          iHash3 = 0;
                          iKey3 = null;
                          iValue3 = null;
                          iSize = 2;
                          return old;
                      }
                      return null;
                  case 2:
                      if (iKey2 == null) {
                          Object old = iValue2;
                          iHash2 = 0;
                          iKey2 = null;
                          iValue2 = null;
                          iSize = 1;
                          return old;
                      }
                      if (iKey1 == null) {
                          Object old = iValue2;
                          iHash1 = iHash2;
                          iKey1 = iKey2;
                          iValue1 = iValue2;
                          iHash2 = 0;
                          iKey2 = null;
                          iValue2 = null;
                          iSize = 1;
                          return old;
                      }
                      return null;
                  case 1:
                      if (iKey1 == null) {
                          Object old = iValue1;
                          iHash1 = 0;
                          iKey1 = null;
                          iValue1 = null;
                          iSize = 0;
                          return old;
                      }
              }
          } else {
              if (iSize > 0) {
                  int hashCode = key.hashCode();
                  switch (iSize) {  // drop through
                      case 3:
                          if (iHash3 == hashCode && key.equals(iKey3)) {
                              Object old = iValue3;
                              iHash3 = 0;
                              iKey3 = null;
                              iValue3 = null;
                              iSize = 2;
                              return old;
                          }
                          if (iHash2 == hashCode && key.equals(iKey2)) {
                              Object old = iValue3;
                              iHash2 = iHash3;
                              iKey2 = iKey3;
                              iValue2 = iValue3;
                              iHash3 = 0;
                              iKey3 = null;
                              iValue3 = null;
                              iSize = 2;
                              return old;
                          }
                          if (iHash1 == hashCode && key.equals(iKey1)) {
                              Object old = iValue3;
                              iHash1 = iHash3;
                              iKey1 = iKey3;
                              iValue1 = iValue3;
                              iHash3 = 0;
                              iKey3 = null;
                              iValue3 = null;
                              iSize = 2;
                              return old;
                          }
                          return null;
                      case 2:
                          if (iHash2 == hashCode && key.equals(iKey2)) {
                              Object old = iValue2;
                              iHash2 = 0;
                              iKey2 = null;
                              iValue2 = null;
                              iSize = 1;
                              return old;
                          }
                          if (iHash1 == hashCode && key.equals(iKey1)) {
                              Object old = iValue2;
                              iHash1 = iHash2;
                              iKey1 = iKey2;
                              iValue1 = iValue2;
                              iHash2 = 0;
                              iKey2 = null;
                              iValue2 = null;
                              iSize = 1;
                              return old;
                          }
                          return null;
                      case 1:
                          if (iHash1 == hashCode && key.equals(iKey1)) {
                              Object old = iValue1;
                              iHash1 = 0;
                              iKey1 = null;
                              iValue1 = null;
                              iSize = 0;
                              return old;
                          }
                  }
              }
          }
          return null;
      }
  
      /**
       * Clears the map, resetting the size to zero and nullifying references
       * to avoid garbage collection issues.
       */
      public void clear() {
          if (iMap != null) {
              iMap.clear();
          }
          iSize = 0;
          iHash1 = iHash2 = iHash3 = 0;
          iKey1 = iKey2 = iKey3 = iValue1 = iValue2 = iValue3 = null;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Gets an iterator over the map.
       * Changes made to the iterator affect this map.
       * <p>
       * A MapIterator returns the keys in the map. It also provides convenient
       * methods to get the key and value, and set the value.
       * It avoids the need to create an entrySet/keySet/values object.
       * It also avoids creating the Mep Entry object.
       * 
       * @return the map iterator
       */
      public MapIterator mapIterator() {
          if (iMap != null) {
              return new DefaultMapIterator(this);
          }
          if (iSize == 0) {
              return IteratorUtils.EMPTY_MAP_ITERATOR;
          }
          return new FlatMapIterator(this);
      }
  
      /**
       * FlatMapIterator
       */
      static class FlatMapIterator implements ResetableMapIterator {
          private final Flat3Map iFlatMap;
          private int iIndex = 0;
          private boolean iCanRemove = false;
          
          FlatMapIterator(Flat3Map map) {
              super();
              iFlatMap = map;
          }
  
          public boolean hasNext() {
              return (iIndex < iFlatMap.iSize);
          }
  
          public Object next() {
              if (hasNext() == false) {
                  throw new NoSuchElementException("No more elements in the iteration");
              }
              iCanRemove = true;
              iIndex++;
              return getKey();
          }
  
          public void remove() {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Iterator remove() can only be called once after next()");
              }
              iFlatMap.remove(getKey());
              iIndex--;
              iCanRemove = false;
          }
  
          public Object getKey() {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Map Entry cannot be queried");
              }
              switch (iIndex) {
                  case 3:
                      return iFlatMap.iKey3;
                  case 2:
                      return iFlatMap.iKey2;
                  case 1:
                      return iFlatMap.iKey1;
              }
              throw new IllegalStateException("Invalid map index");
          }
  
          public Object getValue() {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Map Entry cannot be queried");
              }
              switch (iIndex) {
                  case 3:
                      return iFlatMap.iValue3;
                  case 2:
                      return iFlatMap.iValue2;
                  case 1:
                      return iFlatMap.iValue1;
              }
              throw new IllegalStateException("Invalid map index");
          }
  
          public Object setValue(Object value) {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Map Entry cannot be changed");
              }
              Object old = getValue();
              switch (iIndex) {
                  case 3: 
                      iFlatMap.iValue3 = value;
                  case 2:
                      iFlatMap.iValue2 = value;
                  case 1:
                      iFlatMap.iValue1 = value;
              }
              return old;
          }
          
          public void reset() {
              iIndex = 0;
              iCanRemove = false;
          }
          
          public Entry asMapEntry() {
              return new AbstractMapEntry(getKey(), getValue()) {
                  public Object setValue(Object value) {
                      FlatMapIterator.this.setValue(value);
                      return super.setValue(value);
                  }
              };
          }
          
          public String toString() {
              if (iCanRemove) {
                  return "MapIterator[" + getKey() + "=" + getValue() + "]";
              } else {
                  return "MapIterator[]";
              }
          }
      }
      
      /**
       * Gets the entrySet view of the map.
       * Changes made to the view affect this map.
       * The Map Entry is not an independent object and changes as the 
       * iterator progresses.
       * To simply iterate through the entries, use {@link #mapIterator()}.
       * 
       * @return the entrySet view
       */
      public Set entrySet() {
          if (iMap != null) {
              return iMap.entrySet();
          }
          return new EntrySet(this);
      }
      
      /**
       * EntrySet
       */
      static class EntrySet extends AbstractSet {
          private final Flat3Map iFlatMap;
          
          EntrySet(Flat3Map map) {
              super();
              iFlatMap = map;
          }
  
          public int size() {
              return iFlatMap.size();
          }
          
          public void clear() {
              iFlatMap.clear();
          }
          
          public boolean remove(Object obj) {
              if (obj instanceof Map.Entry == false) {
                  return false;
              }
              Map.Entry entry = (Map.Entry) obj;
              Object key = entry.getKey();
              boolean result = iFlatMap.containsKey(key);
              iFlatMap.remove(key);
              return result;
          }
  
          public Iterator iterator() {
              if (iFlatMap.iMap != null) {
                  return iFlatMap.iMap.entrySet().iterator();
              }
              if (iFlatMap.size() == 0) {
                  return IteratorUtils.EMPTY_ITERATOR;
              }
              return new EntrySetIterator(iFlatMap);
          }
      }
  
      /**
       * EntrySetIterator and MapEntry
       */
      static class EntrySetIterator implements Iterator, Map.Entry {
          private final Flat3Map iFlatMap;
          private int iIndex = 0;
          private boolean iCanRemove = false;
          
          EntrySetIterator(Flat3Map map) {
              super();
              iFlatMap = map;
          }
  
          public boolean hasNext() {
              return (iIndex < iFlatMap.iSize);
          }
  
          public Object next() {
              if (hasNext() == false) {
                  throw new NoSuchElementException("No more elements in the iteration");
              }
              iCanRemove = true;
              iIndex++;
              return this;
          }
  
          public void remove() {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Iterator remove() can only be called once after next()");
              }
              iFlatMap.remove(getKey());
              iIndex--;
              iCanRemove = false;
          }
  
          public Object getKey() {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Map Entry cannot be queried");
              }
              switch (iIndex) {
                  case 3:
                      return iFlatMap.iKey3;
                  case 2:
                      return iFlatMap.iKey2;
                  case 1:
                      return iFlatMap.iKey1;
              }
              throw new IllegalStateException("Invalid map index");
          }
  
          public Object getValue() {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Map Entry cannot be queried");
              }
              switch (iIndex) {
                  case 3:
                      return iFlatMap.iValue3;
                  case 2:
                      return iFlatMap.iValue2;
                  case 1:
                      return iFlatMap.iValue1;
              }
              throw new IllegalStateException("Invalid map index");
          }
  
          public Object setValue(Object value) {
              if (iCanRemove == false) {
                  throw new IllegalStateException("Map Entry cannot be changed");
              }
              Object old = getValue();
              switch (iIndex) {
                  case 3: 
                      iFlatMap.iValue3 = value;
                  case 2:
                      iFlatMap.iValue2 = value;
                  case 1:
                      iFlatMap.iValue1 = value;
              }
              return old;
          }
          
          public boolean equals(Object obj) {
              if (iCanRemove == false) {
                  return false;
              }
              if (obj instanceof Map.Entry == false) {
                  return false;
              }
              Map.Entry other = (Map.Entry) obj;
              Object key = getKey();
              Object value = getValue();
              return (key == null ? other.getKey() == null : key.equals(other.getKey())) &&
                     (value == null ? other.getValue() == null : value.equals(other.getValue()));
          }
          
          public int hashCode() {
              if (iCanRemove == false) {
                  return 0;
              }
              Object key = getKey();
              Object value = getValue();
              return (key == null ? 0 : key.hashCode()) ^
                     (value == null ? 0 : value.hashCode());
          }
          
          public String toString() {
              if (iCanRemove) {
                  return getKey() + "=" + getValue();
              } else {
                  return "";
              }
          }
      }
      
      /**
       * Gets the keySet view of the map.
       * Changes made to the view affect this map.
       * To simply iterate through the keys, use {@link #mapIterator()}.
       * 
       * @return the keySet view
       */
      public Set keySet() {
          if (iMap != null) {
              return iMap.keySet();
          }
          return new KeySet(this);
      }
  
      /**
       * KeySet
       */
      static class KeySet extends AbstractSet {
          private final Flat3Map iFlatMap;
          
          KeySet(Flat3Map map) {
              super();
              iFlatMap = map;
          }
  
          public int size() {
              return iFlatMap.size();
          }
          
          public void clear() {
              iFlatMap.clear();
          }
          
          public boolean contains(Object key) {
              return iFlatMap.containsKey(key);
          }
  
          public boolean remove(Object key) {
              boolean result = iFlatMap.containsKey(key);
              iFlatMap.remove(key);
              return result;
          }
  
          public Iterator iterator() {
              if (iFlatMap.iMap != null) {
                  return iFlatMap.iMap.keySet().iterator();
              }
              if (iFlatMap.size() == 0) {
                  return IteratorUtils.EMPTY_ITERATOR;
              }
              return new KeySetIterator(iFlatMap);
          }
      }
  
      /**
       * KeySetIterator
       */
      static class KeySetIterator extends EntrySetIterator {
          
          KeySetIterator(Flat3Map map) {
              super(map);
          }
  
          public Object next() {
              super.next();
              return getKey();
          }
      }
      
      /**
       * Gets the values view of the map.
       * Changes made to the view affect this map.
       * To simply iterate through the values, use {@link #mapIterator()}.
       * 
       * @return the values view
       */
      public Collection values() {
          if (iMap != null) {
              return iMap.values();
          }
          return new Values(this);
      }
  
      /**
       * Values
       */
      static class Values extends AbstractCollection {
          private final Flat3Map iFlatMap;
          
          Values(Flat3Map map) {
              super();
              iFlatMap = map;
          }
  
          public int size() {
              return iFlatMap.size();
          }
          
          public void clear() {
              iFlatMap.clear();
          }
          
          public boolean contains(Object value) {
              return iFlatMap.containsValue(value);
          }
  
          public Iterator iterator() {
              if (iFlatMap.iMap != null) {
                  return iFlatMap.iMap.values().iterator();
              }
              if (iFlatMap.size() == 0) {
                  return IteratorUtils.EMPTY_ITERATOR;
              }
              return new ValuesIterator(iFlatMap);
          }
      }
  
      /**
       * ValuesIterator
       */
      static class ValuesIterator extends EntrySetIterator {
          
          ValuesIterator(Flat3Map map) {
              super(map);
          }
  
          public Object next() {
              super.next();
              return getValue();
          }
      }
      
      //-----------------------------------------------------------------------
      /**
       * Compares this map with another.
       * 
       * @param obj  the object to compare to
       * @return true if equal
       */
      public boolean equals(Object obj) {
          if (obj == this) {
              return true;
          }
          if (iMap != null) {
              return iMap.equals(obj);
          }
          if (obj instanceof Map == false) {
              return false;
          }
          Map other = (Map) obj;
          if (iSize != other.size()) {
              return false;
          }
          if (iSize > 0) {
              Object otherValue = null;
              switch (iSize) {  // drop through
                  case 3:
                      if (other.containsKey(iKey3) == false) {
                          otherValue = other.get(iKey3);
                          if (iValue3 == null ? otherValue != null : !iValue3.equals(otherValue)) {
                              return false;
                          }
                      }
                  case 2:
                      if (other.containsKey(iKey2) == false) {
                          otherValue = other.get(iKey2);
                          if (iValue2 == null ? otherValue != null : !iValue2.equals(otherValue)) {
                              return false;
                          }
                      }
                  case 1:
                      if (other.containsKey(iKey1) == false) {
                          otherValue = other.get(iKey1);
                          if (iValue1 == null ? otherValue != null : !iValue1.equals(otherValue)) {
                              return false;
                          }
                      }
              }
          }
          return true;
      }
  
      /**
       * Gets the standard Map hashCode.
       * 
       * @return the hashcode defined in the Map interface
       */
      public int hashCode() {
          if (iMap != null) {
              return iMap.hashCode();
          }
          int total = 0;
          switch (iSize) {  // drop through
              case 3:
                  total += (iHash3 ^ (iValue3 == null ? 0 : iValue3.hashCode()));
              case 2:
                  total += (iHash2 ^ (iValue2 == null ? 0 : iValue2.hashCode()));
              case 1:
                  total += (iHash1 ^ (iValue1 == null ? 0 : iValue1.hashCode()));
          }
          return total;
      }
  
      /**
       * Gets the map as a String.
       * 
       * @return a string version of the map
       */
      public String toString() {
          if (iMap != null) {
              return iMap.toString();
          }
          if (iSize == 0) {
              return "{}";
          }
          StringBuffer buf = new StringBuffer(128);
          buf.append('{');
          switch (iSize) {  // drop through
              case 3:
                  buf.append(iKey3);
                  buf.append('=');
                  buf.append(iValue3);
                  buf.append(',');
              case 2:
                  buf.append(iKey2);
                  buf.append('=');
                  buf.append(iValue2);
                  buf.append(',');
              case 1:
                  buf.append(iKey1);
                  buf.append('=');
                  buf.append(iValue1);
          }
          buf.append('}');
          return buf.toString();
      }
  
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org