0001: /*
0002: * Copyright 2003-2004 The Apache Software Foundation
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.apache.commons.collections.map;
0017:
0018: import java.io.IOException;
0019: import java.io.ObjectInputStream;
0020: import java.io.ObjectOutputStream;
0021: import java.io.Serializable;
0022: import java.util.AbstractCollection;
0023: import java.util.AbstractSet;
0024: import java.util.Collection;
0025: import java.util.Iterator;
0026: import java.util.Map;
0027: import java.util.NoSuchElementException;
0028: import java.util.Set;
0029:
0030: import org.apache.commons.collections.IterableMap;
0031: import org.apache.commons.collections.MapIterator;
0032: import org.apache.commons.collections.ResettableIterator;
0033: import org.apache.commons.collections.iterators.EmptyIterator;
0034: import org.apache.commons.collections.iterators.EmptyMapIterator;
0035:
0036: /**
0037: * A <code>Map</code> implementation that stores data in simple fields until
0038: * the size is greater than 3.
0039: * <p>
0040: * This map is designed for performance and can outstrip HashMap.
0041: * It also has good garbage collection characteristics.
0042: * <ul>
0043: * <li>Optimised for operation at size 3 or less.
0044: * <li>Still works well once size 3 exceeded.
0045: * <li>Gets at size 3 or less are about 0-10% faster than HashMap,
0046: * <li>Puts at size 3 or less are over 4 times faster than HashMap.
0047: * <li>Performance 5% slower than HashMap once size 3 exceeded once.
0048: * </ul>
0049: * The design uses two distinct modes of operation - flat and delegate.
0050: * While the map is size 3 or less, operations map straight onto fields using
0051: * switch statements. Once size 4 is reached, the map switches to delegate mode
0052: * and only switches back when cleared. In delegate mode, all operations are
0053: * forwarded straight to a HashMap resulting in the 5% performance loss.
0054: * <p>
0055: * The performance gains on puts are due to not needing to create a Map Entry
0056: * object. This is a large saving not only in performance but in garbage collection.
0057: * <p>
0058: * Whilst in flat mode this map is also easy for the garbage collector to dispatch.
0059: * This is because it contains no complex objects or arrays which slow the progress.
0060: * <p>
0061: * Do not use <code>Flat3Map</code> if the size is likely to grow beyond 3.
0062: * <p>
0063: * <strong>Note that Flat3Map is not synchronized and is not thread-safe.</strong>
0064: * If you wish to use this map from multiple threads concurrently, you must use
0065: * appropriate synchronization. The simplest approach is to wrap this map
0066: * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
0067: * exceptions when accessed by concurrent threads without synchronization.
0068: *
0069: * @since Commons Collections 3.0
0070: * @version $Revision: 348007 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
0071: *
0072: * @author Stephen Colebourne
0073: */
0074: public class Flat3Map implements IterableMap, Serializable, Cloneable {
0075:
0076: /** Serialization version */
0077: private static final long serialVersionUID = -6701087419741928296L;
0078:
0079: /** The size of the map, used while in flat mode */
0080: private transient int size;
0081: /** Hash, used while in flat mode */
0082: private transient int hash1;
0083: /** Hash, used while in flat mode */
0084: private transient int hash2;
0085: /** Hash, used while in flat mode */
0086: private transient int hash3;
0087: /** Key, used while in flat mode */
0088: private transient Object key1;
0089: /** Key, used while in flat mode */
0090: private transient Object key2;
0091: /** Key, used while in flat mode */
0092: private transient Object key3;
0093: /** Value, used while in flat mode */
0094: private transient Object value1;
0095: /** Value, used while in flat mode */
0096: private transient Object value2;
0097: /** Value, used while in flat mode */
0098: private transient Object value3;
0099: /** Map, used while in delegate mode */
0100: private transient AbstractHashedMap delegateMap;
0101:
0102: /**
0103: * Constructor.
0104: */
0105: public Flat3Map() {
0106: super ();
0107: }
0108:
0109: /**
0110: * Constructor copying elements from another map.
0111: *
0112: * @param map the map to copy
0113: * @throws NullPointerException if the map is null
0114: */
0115: public Flat3Map(Map map) {
0116: super ();
0117: putAll(map);
0118: }
0119:
0120: //-----------------------------------------------------------------------
0121: /**
0122: * Gets the value mapped to the key specified.
0123: *
0124: * @param key the key
0125: * @return the mapped value, null if no match
0126: */
0127: public Object get(Object key) {
0128: if (delegateMap != null) {
0129: return delegateMap.get(key);
0130: }
0131: if (key == null) {
0132: switch (size) {
0133: // drop through
0134: case 3:
0135: if (key3 == null)
0136: return value3;
0137: case 2:
0138: if (key2 == null)
0139: return value2;
0140: case 1:
0141: if (key1 == null)
0142: return value1;
0143: }
0144: } else {
0145: if (size > 0) {
0146: int hashCode = key.hashCode();
0147: switch (size) {
0148: // drop through
0149: case 3:
0150: if (hash3 == hashCode && key.equals(key3))
0151: return value3;
0152: case 2:
0153: if (hash2 == hashCode && key.equals(key2))
0154: return value2;
0155: case 1:
0156: if (hash1 == hashCode && key.equals(key1))
0157: return value1;
0158: }
0159: }
0160: }
0161: return null;
0162: }
0163:
0164: /**
0165: * Gets the size of the map.
0166: *
0167: * @return the size
0168: */
0169: public int size() {
0170: if (delegateMap != null) {
0171: return delegateMap.size();
0172: }
0173: return size;
0174: }
0175:
0176: /**
0177: * Checks whether the map is currently empty.
0178: *
0179: * @return true if the map is currently size zero
0180: */
0181: public boolean isEmpty() {
0182: return (size() == 0);
0183: }
0184:
0185: //-----------------------------------------------------------------------
0186: /**
0187: * Checks whether the map contains the specified key.
0188: *
0189: * @param key the key to search for
0190: * @return true if the map contains the key
0191: */
0192: public boolean containsKey(Object key) {
0193: if (delegateMap != null) {
0194: return delegateMap.containsKey(key);
0195: }
0196: if (key == null) {
0197: switch (size) { // drop through
0198: case 3:
0199: if (key3 == null)
0200: return true;
0201: case 2:
0202: if (key2 == null)
0203: return true;
0204: case 1:
0205: if (key1 == null)
0206: return true;
0207: }
0208: } else {
0209: if (size > 0) {
0210: int hashCode = key.hashCode();
0211: switch (size) { // drop through
0212: case 3:
0213: if (hash3 == hashCode && key.equals(key3))
0214: return true;
0215: case 2:
0216: if (hash2 == hashCode && key.equals(key2))
0217: return true;
0218: case 1:
0219: if (hash1 == hashCode && key.equals(key1))
0220: return true;
0221: }
0222: }
0223: }
0224: return false;
0225: }
0226:
0227: /**
0228: * Checks whether the map contains the specified value.
0229: *
0230: * @param value the value to search for
0231: * @return true if the map contains the key
0232: */
0233: public boolean containsValue(Object value) {
0234: if (delegateMap != null) {
0235: return delegateMap.containsValue(value);
0236: }
0237: if (value == null) { // drop through
0238: switch (size) {
0239: case 3:
0240: if (value3 == null)
0241: return true;
0242: case 2:
0243: if (value2 == null)
0244: return true;
0245: case 1:
0246: if (value1 == null)
0247: return true;
0248: }
0249: } else {
0250: switch (size) { // drop through
0251: case 3:
0252: if (value.equals(value3))
0253: return true;
0254: case 2:
0255: if (value.equals(value2))
0256: return true;
0257: case 1:
0258: if (value.equals(value1))
0259: return true;
0260: }
0261: }
0262: return false;
0263: }
0264:
0265: //-----------------------------------------------------------------------
0266: /**
0267: * Puts a key-value mapping into this map.
0268: *
0269: * @param key the key to add
0270: * @param value the value to add
0271: * @return the value previously mapped to this key, null if none
0272: */
0273: public Object put(Object key, Object value) {
0274: if (delegateMap != null) {
0275: return delegateMap.put(key, value);
0276: }
0277: // change existing mapping
0278: if (key == null) {
0279: switch (size) { // drop through
0280: case 3:
0281: if (key3 == null) {
0282: Object old = value3;
0283: value3 = value;
0284: return old;
0285: }
0286: case 2:
0287: if (key2 == null) {
0288: Object old = value2;
0289: value2 = value;
0290: return old;
0291: }
0292: case 1:
0293: if (key1 == null) {
0294: Object old = value1;
0295: value1 = value;
0296: return old;
0297: }
0298: }
0299: } else {
0300: if (size > 0) {
0301: int hashCode = key.hashCode();
0302: switch (size) { // drop through
0303: case 3:
0304: if (hash3 == hashCode && key.equals(key3)) {
0305: Object old = value3;
0306: value3 = value;
0307: return old;
0308: }
0309: case 2:
0310: if (hash2 == hashCode && key.equals(key2)) {
0311: Object old = value2;
0312: value2 = value;
0313: return old;
0314: }
0315: case 1:
0316: if (hash1 == hashCode && key.equals(key1)) {
0317: Object old = value1;
0318: value1 = value;
0319: return old;
0320: }
0321: }
0322: }
0323: }
0324:
0325: // add new mapping
0326: switch (size) {
0327: default:
0328: convertToMap();
0329: delegateMap.put(key, value);
0330: return null;
0331: case 2:
0332: hash3 = (key == null ? 0 : key.hashCode());
0333: key3 = key;
0334: value3 = value;
0335: break;
0336: case 1:
0337: hash2 = (key == null ? 0 : key.hashCode());
0338: key2 = key;
0339: value2 = value;
0340: break;
0341: case 0:
0342: hash1 = (key == null ? 0 : key.hashCode());
0343: key1 = key;
0344: value1 = value;
0345: break;
0346: }
0347: size++;
0348: return null;
0349: }
0350:
0351: /**
0352: * Puts all the values from the specified map into this map.
0353: *
0354: * @param map the map to add
0355: * @throws NullPointerException if the map is null
0356: */
0357: public void putAll(Map map) {
0358: int size = map.size();
0359: if (size == 0) {
0360: return;
0361: }
0362: if (delegateMap != null) {
0363: delegateMap.putAll(map);
0364: return;
0365: }
0366: if (size < 4) {
0367: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
0368: Map.Entry entry = (Map.Entry) it.next();
0369: put(entry.getKey(), entry.getValue());
0370: }
0371: } else {
0372: convertToMap();
0373: delegateMap.putAll(map);
0374: }
0375: }
0376:
0377: /**
0378: * Converts the flat map data to a map.
0379: */
0380: private void convertToMap() {
0381: delegateMap = createDelegateMap();
0382: switch (size) { // drop through
0383: case 3:
0384: delegateMap.put(key3, value3);
0385: case 2:
0386: delegateMap.put(key2, value2);
0387: case 1:
0388: delegateMap.put(key1, value1);
0389: }
0390:
0391: size = 0;
0392: hash1 = hash2 = hash3 = 0;
0393: key1 = key2 = key3 = null;
0394: value1 = value2 = value3 = null;
0395: }
0396:
0397: /**
0398: * Create an instance of the map used for storage when in delegation mode.
0399: * <p>
0400: * This can be overridden by subclasses to provide a different map implementation.
0401: * Not every AbstractHashedMap is suitable, identity and reference based maps
0402: * would be poor choices.
0403: *
0404: * @return a new AbstractHashedMap or subclass
0405: * @since Commons Collections 3.1
0406: */
0407: protected AbstractHashedMap createDelegateMap() {
0408: return new HashedMap();
0409: }
0410:
0411: /**
0412: * Removes the specified mapping from this map.
0413: *
0414: * @param key the mapping to remove
0415: * @return the value mapped to the removed key, null if key not in map
0416: */
0417: public Object remove(Object key) {
0418: if (delegateMap != null) {
0419: return delegateMap.remove(key);
0420: }
0421: if (size == 0) {
0422: return null;
0423: }
0424: if (key == null) {
0425: switch (size) { // drop through
0426: case 3:
0427: if (key3 == null) {
0428: Object old = value3;
0429: hash3 = 0;
0430: key3 = null;
0431: value3 = null;
0432: size = 2;
0433: return old;
0434: }
0435: if (key2 == null) {
0436: Object old = value3;
0437: hash2 = hash3;
0438: key2 = key3;
0439: value2 = value3;
0440: hash3 = 0;
0441: key3 = null;
0442: value3 = null;
0443: size = 2;
0444: return old;
0445: }
0446: if (key1 == null) {
0447: Object old = value3;
0448: hash1 = hash3;
0449: key1 = key3;
0450: value1 = value3;
0451: hash3 = 0;
0452: key3 = null;
0453: value3 = null;
0454: size = 2;
0455: return old;
0456: }
0457: return null;
0458: case 2:
0459: if (key2 == null) {
0460: Object old = value2;
0461: hash2 = 0;
0462: key2 = null;
0463: value2 = null;
0464: size = 1;
0465: return old;
0466: }
0467: if (key1 == null) {
0468: Object old = value2;
0469: hash1 = hash2;
0470: key1 = key2;
0471: value1 = value2;
0472: hash2 = 0;
0473: key2 = null;
0474: value2 = null;
0475: size = 1;
0476: return old;
0477: }
0478: return null;
0479: case 1:
0480: if (key1 == null) {
0481: Object old = value1;
0482: hash1 = 0;
0483: key1 = null;
0484: value1 = null;
0485: size = 0;
0486: return old;
0487: }
0488: }
0489: } else {
0490: if (size > 0) {
0491: int hashCode = key.hashCode();
0492: switch (size) { // drop through
0493: case 3:
0494: if (hash3 == hashCode && key.equals(key3)) {
0495: Object old = value3;
0496: hash3 = 0;
0497: key3 = null;
0498: value3 = null;
0499: size = 2;
0500: return old;
0501: }
0502: if (hash2 == hashCode && key.equals(key2)) {
0503: Object old = value3;
0504: hash2 = hash3;
0505: key2 = key3;
0506: value2 = value3;
0507: hash3 = 0;
0508: key3 = null;
0509: value3 = null;
0510: size = 2;
0511: return old;
0512: }
0513: if (hash1 == hashCode && key.equals(key1)) {
0514: Object old = value3;
0515: hash1 = hash3;
0516: key1 = key3;
0517: value1 = value3;
0518: hash3 = 0;
0519: key3 = null;
0520: value3 = null;
0521: size = 2;
0522: return old;
0523: }
0524: return null;
0525: case 2:
0526: if (hash2 == hashCode && key.equals(key2)) {
0527: Object old = value2;
0528: hash2 = 0;
0529: key2 = null;
0530: value2 = null;
0531: size = 1;
0532: return old;
0533: }
0534: if (hash1 == hashCode && key.equals(key1)) {
0535: Object old = value2;
0536: hash1 = hash2;
0537: key1 = key2;
0538: value1 = value2;
0539: hash2 = 0;
0540: key2 = null;
0541: value2 = null;
0542: size = 1;
0543: return old;
0544: }
0545: return null;
0546: case 1:
0547: if (hash1 == hashCode && key.equals(key1)) {
0548: Object old = value1;
0549: hash1 = 0;
0550: key1 = null;
0551: value1 = null;
0552: size = 0;
0553: return old;
0554: }
0555: }
0556: }
0557: }
0558: return null;
0559: }
0560:
0561: /**
0562: * Clears the map, resetting the size to zero and nullifying references
0563: * to avoid garbage collection issues.
0564: */
0565: public void clear() {
0566: if (delegateMap != null) {
0567: delegateMap.clear(); // should aid gc
0568: delegateMap = null; // switch back to flat mode
0569: } else {
0570: size = 0;
0571: hash1 = hash2 = hash3 = 0;
0572: key1 = key2 = key3 = null;
0573: value1 = value2 = value3 = null;
0574: }
0575: }
0576:
0577: //-----------------------------------------------------------------------
0578: /**
0579: * Gets an iterator over the map.
0580: * Changes made to the iterator affect this map.
0581: * <p>
0582: * A MapIterator returns the keys in the map. It also provides convenient
0583: * methods to get the key and value, and set the value.
0584: * It avoids the need to create an entrySet/keySet/values object.
0585: * It also avoids creating the Map Entry object.
0586: *
0587: * @return the map iterator
0588: */
0589: public MapIterator mapIterator() {
0590: if (delegateMap != null) {
0591: return delegateMap.mapIterator();
0592: }
0593: if (size == 0) {
0594: return EmptyMapIterator.INSTANCE;
0595: }
0596: return new FlatMapIterator(this );
0597: }
0598:
0599: /**
0600: * FlatMapIterator
0601: */
0602: static class FlatMapIterator implements MapIterator,
0603: ResettableIterator {
0604: private final Flat3Map parent;
0605: private int nextIndex = 0;
0606: private boolean canRemove = false;
0607:
0608: FlatMapIterator(Flat3Map parent) {
0609: super ();
0610: this .parent = parent;
0611: }
0612:
0613: public boolean hasNext() {
0614: return (nextIndex < parent.size);
0615: }
0616:
0617: public Object next() {
0618: if (hasNext() == false) {
0619: throw new NoSuchElementException(
0620: AbstractHashedMap.NO_NEXT_ENTRY);
0621: }
0622: canRemove = true;
0623: nextIndex++;
0624: return getKey();
0625: }
0626:
0627: public void remove() {
0628: if (canRemove == false) {
0629: throw new IllegalStateException(
0630: AbstractHashedMap.REMOVE_INVALID);
0631: }
0632: parent.remove(getKey());
0633: nextIndex--;
0634: canRemove = false;
0635: }
0636:
0637: public Object getKey() {
0638: if (canRemove == false) {
0639: throw new IllegalStateException(
0640: AbstractHashedMap.GETKEY_INVALID);
0641: }
0642: switch (nextIndex) {
0643: case 3:
0644: return parent.key3;
0645: case 2:
0646: return parent.key2;
0647: case 1:
0648: return parent.key1;
0649: }
0650: throw new IllegalStateException("Invalid map index");
0651: }
0652:
0653: public Object getValue() {
0654: if (canRemove == false) {
0655: throw new IllegalStateException(
0656: AbstractHashedMap.GETVALUE_INVALID);
0657: }
0658: switch (nextIndex) {
0659: case 3:
0660: return parent.value3;
0661: case 2:
0662: return parent.value2;
0663: case 1:
0664: return parent.value1;
0665: }
0666: throw new IllegalStateException("Invalid map index");
0667: }
0668:
0669: public Object setValue(Object value) {
0670: if (canRemove == false) {
0671: throw new IllegalStateException(
0672: AbstractHashedMap.SETVALUE_INVALID);
0673: }
0674: Object old = getValue();
0675: switch (nextIndex) {
0676: case 3:
0677: parent.value3 = value;
0678: case 2:
0679: parent.value2 = value;
0680: case 1:
0681: parent.value1 = value;
0682: }
0683: return old;
0684: }
0685:
0686: public void reset() {
0687: nextIndex = 0;
0688: canRemove = false;
0689: }
0690:
0691: public String toString() {
0692: if (canRemove) {
0693: return "Iterator[" + getKey() + "=" + getValue() + "]";
0694: } else {
0695: return "Iterator[]";
0696: }
0697: }
0698: }
0699:
0700: /**
0701: * Gets the entrySet view of the map.
0702: * Changes made to the view affect this map.
0703: * The Map Entry is not an independent object and changes as the
0704: * iterator progresses.
0705: * To simply iterate through the entries, use {@link #mapIterator()}.
0706: *
0707: * @return the entrySet view
0708: */
0709: public Set entrySet() {
0710: if (delegateMap != null) {
0711: return delegateMap.entrySet();
0712: }
0713: return new EntrySet(this );
0714: }
0715:
0716: /**
0717: * EntrySet
0718: */
0719: static class EntrySet extends AbstractSet {
0720: private final Flat3Map parent;
0721:
0722: EntrySet(Flat3Map parent) {
0723: super ();
0724: this .parent = parent;
0725: }
0726:
0727: public int size() {
0728: return parent.size();
0729: }
0730:
0731: public void clear() {
0732: parent.clear();
0733: }
0734:
0735: public boolean remove(Object obj) {
0736: if (obj instanceof Map.Entry == false) {
0737: return false;
0738: }
0739: Map.Entry entry = (Map.Entry) obj;
0740: Object key = entry.getKey();
0741: boolean result = parent.containsKey(key);
0742: parent.remove(key);
0743: return result;
0744: }
0745:
0746: public Iterator iterator() {
0747: if (parent.delegateMap != null) {
0748: return parent.delegateMap.entrySet().iterator();
0749: }
0750: if (parent.size() == 0) {
0751: return EmptyIterator.INSTANCE;
0752: }
0753: return new EntrySetIterator(parent);
0754: }
0755: }
0756:
0757: /**
0758: * EntrySetIterator and MapEntry
0759: */
0760: static class EntrySetIterator implements Iterator, Map.Entry {
0761: private final Flat3Map parent;
0762: private int nextIndex = 0;
0763: private boolean canRemove = false;
0764:
0765: EntrySetIterator(Flat3Map parent) {
0766: super ();
0767: this .parent = parent;
0768: }
0769:
0770: public boolean hasNext() {
0771: return (nextIndex < parent.size);
0772: }
0773:
0774: public Object next() {
0775: if (hasNext() == false) {
0776: throw new NoSuchElementException(
0777: AbstractHashedMap.NO_NEXT_ENTRY);
0778: }
0779: canRemove = true;
0780: nextIndex++;
0781: return this ;
0782: }
0783:
0784: public void remove() {
0785: if (canRemove == false) {
0786: throw new IllegalStateException(
0787: AbstractHashedMap.REMOVE_INVALID);
0788: }
0789: parent.remove(getKey());
0790: nextIndex--;
0791: canRemove = false;
0792: }
0793:
0794: public Object getKey() {
0795: if (canRemove == false) {
0796: throw new IllegalStateException(
0797: AbstractHashedMap.GETKEY_INVALID);
0798: }
0799: switch (nextIndex) {
0800: case 3:
0801: return parent.key3;
0802: case 2:
0803: return parent.key2;
0804: case 1:
0805: return parent.key1;
0806: }
0807: throw new IllegalStateException("Invalid map index");
0808: }
0809:
0810: public Object getValue() {
0811: if (canRemove == false) {
0812: throw new IllegalStateException(
0813: AbstractHashedMap.GETVALUE_INVALID);
0814: }
0815: switch (nextIndex) {
0816: case 3:
0817: return parent.value3;
0818: case 2:
0819: return parent.value2;
0820: case 1:
0821: return parent.value1;
0822: }
0823: throw new IllegalStateException("Invalid map index");
0824: }
0825:
0826: public Object setValue(Object value) {
0827: if (canRemove == false) {
0828: throw new IllegalStateException(
0829: AbstractHashedMap.SETVALUE_INVALID);
0830: }
0831: Object old = getValue();
0832: switch (nextIndex) {
0833: case 3:
0834: parent.value3 = value;
0835: case 2:
0836: parent.value2 = value;
0837: case 1:
0838: parent.value1 = value;
0839: }
0840: return old;
0841: }
0842:
0843: public boolean equals(Object obj) {
0844: if (canRemove == false) {
0845: return false;
0846: }
0847: if (obj instanceof Map.Entry == false) {
0848: return false;
0849: }
0850: Map.Entry other = (Map.Entry) obj;
0851: Object key = getKey();
0852: Object value = getValue();
0853: return (key == null ? other.getKey() == null : key
0854: .equals(other.getKey()))
0855: && (value == null ? other.getValue() == null
0856: : value.equals(other.getValue()));
0857: }
0858:
0859: public int hashCode() {
0860: if (canRemove == false) {
0861: return 0;
0862: }
0863: Object key = getKey();
0864: Object value = getValue();
0865: return (key == null ? 0 : key.hashCode())
0866: ^ (value == null ? 0 : value.hashCode());
0867: }
0868:
0869: public String toString() {
0870: if (canRemove) {
0871: return getKey() + "=" + getValue();
0872: } else {
0873: return "";
0874: }
0875: }
0876: }
0877:
0878: /**
0879: * Gets the keySet view of the map.
0880: * Changes made to the view affect this map.
0881: * To simply iterate through the keys, use {@link #mapIterator()}.
0882: *
0883: * @return the keySet view
0884: */
0885: public Set keySet() {
0886: if (delegateMap != null) {
0887: return delegateMap.keySet();
0888: }
0889: return new KeySet(this );
0890: }
0891:
0892: /**
0893: * KeySet
0894: */
0895: static class KeySet extends AbstractSet {
0896: private final Flat3Map parent;
0897:
0898: KeySet(Flat3Map parent) {
0899: super ();
0900: this .parent = parent;
0901: }
0902:
0903: public int size() {
0904: return parent.size();
0905: }
0906:
0907: public void clear() {
0908: parent.clear();
0909: }
0910:
0911: public boolean contains(Object key) {
0912: return parent.containsKey(key);
0913: }
0914:
0915: public boolean remove(Object key) {
0916: boolean result = parent.containsKey(key);
0917: parent.remove(key);
0918: return result;
0919: }
0920:
0921: public Iterator iterator() {
0922: if (parent.delegateMap != null) {
0923: return parent.delegateMap.keySet().iterator();
0924: }
0925: if (parent.size() == 0) {
0926: return EmptyIterator.INSTANCE;
0927: }
0928: return new KeySetIterator(parent);
0929: }
0930: }
0931:
0932: /**
0933: * KeySetIterator
0934: */
0935: static class KeySetIterator extends EntrySetIterator {
0936:
0937: KeySetIterator(Flat3Map parent) {
0938: super (parent);
0939: }
0940:
0941: public Object next() {
0942: super .next();
0943: return getKey();
0944: }
0945: }
0946:
0947: /**
0948: * Gets the values view of the map.
0949: * Changes made to the view affect this map.
0950: * To simply iterate through the values, use {@link #mapIterator()}.
0951: *
0952: * @return the values view
0953: */
0954: public Collection values() {
0955: if (delegateMap != null) {
0956: return delegateMap.values();
0957: }
0958: return new Values(this );
0959: }
0960:
0961: /**
0962: * Values
0963: */
0964: static class Values extends AbstractCollection {
0965: private final Flat3Map parent;
0966:
0967: Values(Flat3Map parent) {
0968: super ();
0969: this .parent = parent;
0970: }
0971:
0972: public int size() {
0973: return parent.size();
0974: }
0975:
0976: public void clear() {
0977: parent.clear();
0978: }
0979:
0980: public boolean contains(Object value) {
0981: return parent.containsValue(value);
0982: }
0983:
0984: public Iterator iterator() {
0985: if (parent.delegateMap != null) {
0986: return parent.delegateMap.values().iterator();
0987: }
0988: if (parent.size() == 0) {
0989: return EmptyIterator.INSTANCE;
0990: }
0991: return new ValuesIterator(parent);
0992: }
0993: }
0994:
0995: /**
0996: * ValuesIterator
0997: */
0998: static class ValuesIterator extends EntrySetIterator {
0999:
1000: ValuesIterator(Flat3Map parent) {
1001: super (parent);
1002: }
1003:
1004: public Object next() {
1005: super .next();
1006: return getValue();
1007: }
1008: }
1009:
1010: //-----------------------------------------------------------------------
1011: /**
1012: * Write the map out using a custom routine.
1013: */
1014: private void writeObject(ObjectOutputStream out) throws IOException {
1015: out.defaultWriteObject();
1016: out.writeInt(size());
1017: for (MapIterator it = mapIterator(); it.hasNext();) {
1018: out.writeObject(it.next()); // key
1019: out.writeObject(it.getValue()); // value
1020: }
1021: }
1022:
1023: /**
1024: * Read the map in using a custom routine.
1025: */
1026: private void readObject(ObjectInputStream in) throws IOException,
1027: ClassNotFoundException {
1028: in.defaultReadObject();
1029: int count = in.readInt();
1030: if (count > 3) {
1031: delegateMap = createDelegateMap();
1032: }
1033: for (int i = count; i > 0; i--) {
1034: put(in.readObject(), in.readObject());
1035: }
1036: }
1037:
1038: //-----------------------------------------------------------------------
1039: /**
1040: * Clones the map without cloning the keys or values.
1041: *
1042: * @return a shallow clone
1043: * @since Commons Collections 3.1
1044: */
1045: public Object clone() {
1046: try {
1047: Flat3Map cloned = (Flat3Map) super .clone();
1048: if (cloned.delegateMap != null) {
1049: cloned.delegateMap = (HashedMap) cloned.delegateMap
1050: .clone();
1051: }
1052: return cloned;
1053: } catch (CloneNotSupportedException ex) {
1054: throw new InternalError();
1055: }
1056: }
1057:
1058: /**
1059: * Compares this map with another.
1060: *
1061: * @param obj the object to compare to
1062: * @return true if equal
1063: */
1064: public boolean equals(Object obj) {
1065: if (obj == this ) {
1066: return true;
1067: }
1068: if (delegateMap != null) {
1069: return delegateMap.equals(obj);
1070: }
1071: if (obj instanceof Map == false) {
1072: return false;
1073: }
1074: Map other = (Map) obj;
1075: if (size != other.size()) {
1076: return false;
1077: }
1078: if (size > 0) {
1079: Object otherValue = null;
1080: switch (size) { // drop through
1081: case 3:
1082: if (other.containsKey(key3) == false) {
1083: return false;
1084: }
1085: otherValue = other.get(key3);
1086: if (value3 == null ? otherValue != null : !value3
1087: .equals(otherValue)) {
1088: return false;
1089: }
1090: case 2:
1091: if (other.containsKey(key2) == false) {
1092: return false;
1093: }
1094: otherValue = other.get(key2);
1095: if (value2 == null ? otherValue != null : !value2
1096: .equals(otherValue)) {
1097: return false;
1098: }
1099: case 1:
1100: if (other.containsKey(key1) == false) {
1101: return false;
1102: }
1103: otherValue = other.get(key1);
1104: if (value1 == null ? otherValue != null : !value1
1105: .equals(otherValue)) {
1106: return false;
1107: }
1108: }
1109: }
1110: return true;
1111: }
1112:
1113: /**
1114: * Gets the standard Map hashCode.
1115: *
1116: * @return the hash code defined in the Map interface
1117: */
1118: public int hashCode() {
1119: if (delegateMap != null) {
1120: return delegateMap.hashCode();
1121: }
1122: int total = 0;
1123: switch (size) { // drop through
1124: case 3:
1125: total += (hash3 ^ (value3 == null ? 0 : value3.hashCode()));
1126: case 2:
1127: total += (hash2 ^ (value2 == null ? 0 : value2.hashCode()));
1128: case 1:
1129: total += (hash1 ^ (value1 == null ? 0 : value1.hashCode()));
1130: }
1131: return total;
1132: }
1133:
1134: /**
1135: * Gets the map as a String.
1136: *
1137: * @return a string version of the map
1138: */
1139: public String toString() {
1140: if (delegateMap != null) {
1141: return delegateMap.toString();
1142: }
1143: if (size == 0) {
1144: return "{}";
1145: }
1146: StringBuffer buf = new StringBuffer(128);
1147: buf.append('{');
1148: switch (size) { // drop through
1149: case 3:
1150: buf.append((key3 == this ? "(this Map)" : key3));
1151: buf.append('=');
1152: buf.append((value3 == this ? "(this Map)" : value3));
1153: buf.append(',');
1154: case 2:
1155: buf.append((key2 == this ? "(this Map)" : key2));
1156: buf.append('=');
1157: buf.append((value2 == this ? "(this Map)" : value2));
1158: buf.append(',');
1159: case 1:
1160: buf.append((key1 == this ? "(this Map)" : key1));
1161: buf.append('=');
1162: buf.append((value1 == this ? "(this Map)" : value1));
1163: }
1164: buf.append('}');
1165: return buf.toString();
1166: }
1167:
1168: }
|