001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.util;
051:
052: import java.util.*;
053: import java.io.*;
054:
055: /**
056: * This class combines the utility of the HashMap & the ListSet. Features are:
057: * 1) Ensure quick random access to an object using a 'key'
058: * 2) Iterate through the Map in the order in which the entries were made
059: */
060: public class ListMap implements Map, Cloneable, Serializable {
061: private static final int TYPE_KEY_SET = 0;
062: private static final int TYPE_ENTRY_SET = 1;
063: private static final int TYPE_VALUES = 2;
064:
065: /** This Map will contain the key-value pairs */
066: private HashMap m_map = null;
067:
068: /** This List will maintain the keys in the order of entry */
069: private ListSet m_list = null;
070:
071: // fields to hold the return Collections.
072: private transient Set m_keySet = null;
073: private transient Set m_entrySet = null;
074: private transient Collection m_values = null;
075:
076: /** Creates new ListMap */
077: public ListMap() {
078: m_map = new HashMap();
079: m_list = new ListSet();
080: }
081:
082: /** Creates new ListMap specifying the initial capacity.
083: * @param initialCapacity The initial capacity.
084: */
085: public ListMap(int initialCapacity) {
086: m_map = new HashMap(initialCapacity);
087: m_list = new ListSet(initialCapacity);
088: }
089:
090: /** Creates new ListMap specifying the initial capacity and load factor.
091: * @param initialCapacity The initial capacity.
092: * @param loadFactor The loadFactor.
093: */
094: public ListMap(int initialCapacity, float loadFactor) {
095: m_map = new HashMap(initialCapacity, loadFactor);
096: m_list = new ListSet(initialCapacity);
097: }
098:
099: /** Creates new ListMap from an existing Map
100: * @param t An existing Map.
101: */
102: public ListMap(Map t) {
103: if (t == null) {
104: m_map = new HashMap();
105: m_list = new ListSet();
106: } else {
107: m_map = new HashMap(t);
108: m_list = new ListSet(t.keySet());
109: }
110: }
111:
112: // *** MAP INTERFACE METHODS ***
113: /** Adds an object to the Map. If the map previously contained a mapping for this key, the old value is replaced by the specified value.
114: * @param key The key used for adding the object.
115: * @param value The object to be added.
116: * @return previous value associated with specified key, or null if there was no mapping for key. A null return can also indicate that the map previously associated null with the specified key.
117: */
118: public Object put(Object key, Object value) {
119: return put(-1, key, value);
120: }
121:
122: /** Removes the mapping for this key from this map if it is present.
123: * @param key key whose mapping is to be removed from the map.
124: * @return previous value associated with specified key, or null if there was no mapping for key.
125: */
126: public Object remove(Object key) {
127: m_list.remove(key);
128: return m_map.remove(key);
129: }
130:
131: /** Returns a set view of the keys contained in this map.
132: * @return a set view of the keys contained in this map.
133: */
134: public Set keySet() {
135: if (m_keySet == null)
136: m_keySet = new ListMap.KeySet();
137: return m_keySet;
138: }
139:
140: /** Removes all mappings from this map .*/
141: public void clear() {
142: m_list.clear();
143: m_map.clear();
144: }
145:
146: /** Returns a collection view of the values contained in this map.
147: * @return a collection view of the values contained in this map.
148: */
149: public Collection values() {
150: if (m_values == null)
151: m_values = new ListMap.Values();
152: return m_values;
153: }
154:
155: /** Returns the hash code value for this map.
156: * @return the hash code value for this map.
157: */
158: public int hashCode() {
159: return m_map.hashCode();
160: }
161:
162: /** Returns true if this map contains a mapping for the specified key.
163: * @param key key whose presence in this map is to be tested.
164: * @return true if this map contains a mapping for the specified key.
165: */
166: public boolean containsKey(Object key) {
167: return m_map.containsKey(key);
168: }
169:
170: /** Returns the number of key-value mappings in this map.
171: * @return the number of key-value mappings in this map.
172: */
173: public int size() {
174: return m_map.size();
175: }
176:
177: /** Returns a set view of the mappings contained in this map.
178: * @return a set view of the mappings contained in this map.
179: */
180: public Set entrySet() {
181: if (m_entrySet == null)
182: m_entrySet = new ListMap.EntrySet();
183: return m_entrySet;
184: }
185:
186: /** Returns true if this map maps one or more keys to the specified value.
187: * @param value value whose presence in this map is to be tested.
188: * @return true if this map maps one or more keys to the specified value.
189: */
190: public boolean containsValue(Object value) {
191: return m_map.containsValue(value);
192: }
193:
194: /** Copies all of the mappings from the specified map to this map.
195: * @param t Mappings to be stored in this map.
196: */
197: public void putAll(Map t) {
198: if (t != null) {
199: m_map.putAll(t);
200: m_list.addAll(t.keySet());
201: }
202: }
203:
204: /** Compares the specified object with this map for equality.
205: * Returns true if the given object is also a ListMap and the two Maps represent the same mappings.
206: * @param o object to be compared for equality with this map.
207: * @return true if the specified object is equal to this map.
208: */
209: public boolean equals(Object o) {
210: boolean result = false;
211: if (o instanceof ListMap) {
212: ListMap listMap = (ListMap) o;
213: result = m_map.equals(listMap.m_map);
214: }
215: return result;
216: }
217:
218: /** Returns true if this map contains no key-value mappings.
219: * @return true if this map contains no key-value mappings.
220: */
221: public boolean isEmpty() {
222: return m_map.isEmpty();
223: }
224:
225: /** Returns the value to which this map maps the specified key.
226: * Returns null if the map contains no mapping for this key.
227: * A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null.
228: * The containsKey operation may be used to distinguish these two cases.
229: * @param key key whose associated value is to be returned.
230: * @return the value to which this map maps the specified key, or null if the map contains no mapping for this key.
231: */
232: public Object get(Object key) {
233: return m_map.get(key);
234: }
235:
236: // *** Additional Methods ***
237: /** Adds an object to the Map. If the map previously contained a mapping for this key, the old value is replaced by the specified value.
238: * @param index The position at which the the object will be added.
239: * @param key The key used for adding the object.
240: * @param value The object to be added.
241: * @return previous value associated with specified key, or null if there was no mapping for key. A null return can also indicate that the map previously associated null with the specified key.
242: */
243: public Object put(int index, Object key, Object value) {
244: Object returnObject = m_map.put(key, value);
245:
246: // add to the list, only if it doesn't already exist
247: if (!m_list.contains(key)) {
248: if (index == -1)
249: m_list.add(key);
250: else
251: m_list.add(index, key);
252: }
253:
254: return returnObject;
255: }
256:
257: /** Returns the mapping at the specified index.
258: * @param index The position at from which the mapping is to be retrieved.
259: * @return the mapping at the specified index.
260: */
261: public Object getByIndex(int index) {
262: return get(m_list.get(index));
263: }
264:
265: /** Removes the mapping for this index from this map if it is present.
266: * @param index The position at from which the mapping is to be removed.
267: * @return previous value associated with position, or null if there was no mapping for position.
268: */
269: public Object remove(int index) {
270: return remove(m_list.get(index));
271: }
272:
273: /** Returns the index in this Map of the specified key.
274: * A '-1' is returned in case no such key exists.
275: * @param key The key used for adding the object.
276: * @return the index in this Map of the specified key.
277: */
278: public int indexOf(Object key) {
279: return m_list.indexOf(key);
280: }
281:
282: // *** CLONEABLE INTERFACE METHODS ***
283: /** Returns a clone of the Map.
284: * @throws CloneNotSupportedException if cloning is not supported. Should never happen.
285: * @return a clone of the Map.
286: */
287: public Object clone() throws CloneNotSupportedException {
288: ListMap obj = (ListMap) super .clone();
289:
290: if (m_map != null && m_map instanceof HashMap)
291: obj.m_map = (HashMap) m_map.clone();
292:
293: if (m_list != null && m_list instanceof ListSet)
294: obj.m_list = (ListSet) m_list.clone();
295:
296: // reset the transient fields
297: obj.m_keySet = null;
298: obj.m_entrySet = null;
299: obj.m_values = null;
300:
301: return obj;
302: }
303:
304: // *** PRIVATE METHODS ***
305: private Iterator getIterator(int type) {
306: return new ListMap.ListMapIterator(type);
307: }
308:
309: private Map.Entry getEntry(Object key, Object value) {
310: return new ListMap.ListMapEntry(key, value);
311: }
312:
313: // *** INNER CLASSES ***
314: private class ListMapIterator implements Iterator {
315: private Iterator m_iterator = null;
316: private int m_type;
317: private Object m_lastReturned = null;
318:
319: private ListMapIterator(int type) {
320: m_type = type;
321: m_iterator = ListMap.this .m_list.iterator();
322: }
323:
324: /** Returns true if the iteration has more elements.
325: * @return true if the iteration has more elements.
326: */
327: public boolean hasNext() {
328: return m_iterator.hasNext();
329: }
330:
331: /** Returns the next element in the iteration.
332: * @return the next element in the iteration.
333: */
334: public Object next() {
335: m_lastReturned = m_iterator.next();
336: Object obj = null;
337: switch (m_type) {
338: case TYPE_KEY_SET:
339: obj = m_lastReturned;
340: break;
341: case TYPE_ENTRY_SET:
342: Object value = ListMap.this .get(m_lastReturned);
343: obj = ListMap.this .getEntry(m_lastReturned, value);
344: break;
345: case TYPE_VALUES:
346: obj = ListMap.this .get(m_lastReturned);
347: break;
348: }
349: return obj;
350: }
351:
352: /** Removes from the underlying collection the last element returned by the iterator.
353: */
354: public void remove() {
355: m_iterator.remove();
356: ListMap.this .remove(m_lastReturned);
357: }
358: }
359:
360: private class KeySet extends AbstractSet {
361: /** Returns the number of elements in this set.
362: * @return the number of elements in this set.
363: */
364: public int size() {
365: return ListMap.this .size();
366: }
367:
368: /** Returns true if this set contains the specified element.
369: * @param o element whose presence in this set is to be tested.
370: * @return true if this set contains the specified element.
371: */
372: public boolean contains(Object o) {
373: return ListMap.this .containsKey(o);
374: }
375:
376: /** Removes the specified element from this set if it is present.
377: * @param o object to be removed from this set, if present.
378: * @return true if the set contained the specified element.
379: */
380: public boolean remove(Object o) {
381: int i = size();
382: ListMap.this .remove(o);
383: return (size() != i);
384: }
385:
386: /** Removes all of the elements from this set.*/
387: public void clear() {
388: ListMap.this .clear();
389: }
390:
391: /** Returns an iterator over the elements in this set.
392: * @return an iterator over the elements in this set.
393: */
394: public Iterator iterator() {
395: return ListMap.this .getIterator(ListMap.TYPE_KEY_SET);
396: }
397: }
398:
399: private class EntrySet extends AbstractSet {
400: /** Returns the number of elements in this set.
401: * @return the number of elements in this set.
402: */
403: public int size() {
404: return ListMap.this .size();
405: }
406:
407: /** Returns true if this set contains the specified element.
408: * @param o element whose presence in this set is to be tested.
409: * @return true if this set contains the specified element.
410: */
411: public boolean contains(Object o) {
412: boolean result = false;
413: if (o != null && o instanceof Map.Entry) {
414: Map.Entry entry = (Map.Entry) o;
415: Object key = entry.getKey();
416: if (ListMap.this .containsKey(key)) {
417: Object value1 = ListMap.this .get(key);
418: Object value2 = entry.getValue();
419: result = (value1 == null ? value2 == null : value1
420: .equals(value2));
421: }
422: }
423: return result;
424: }
425:
426: /** Removes the specified element from this set if it is present.
427: * @param o object to be removed from this set, if present.
428: * @return true if the set contained the specified element.
429: */
430: public boolean remove(Object o) {
431: boolean result = false;
432: if (o != null && o instanceof Map.Entry) {
433: Map.Entry entry = (Map.Entry) o;
434: Object key = entry.getKey();
435: int i = size();
436: ListMap.this .remove(key);
437: result = (i != size());
438: }
439: return result;
440: }
441:
442: /** Removes all of the elements from this set.*/
443: public void clear() {
444: ListMap.this .clear();
445: }
446:
447: /** Returns an iterator over the elements in this set.
448: * @return an iterator over the elements in this set.
449: */
450: public Iterator iterator() {
451: return ListMap.this .getIterator(ListMap.TYPE_ENTRY_SET);
452: }
453: }
454:
455: private class Values extends AbstractCollection {
456: /** Returns the number of elements in this collection.
457: * @return the number of elements in this collection.
458: */
459: public int size() {
460: return ListMap.this .size();
461: }
462:
463: /** Returns true if this collection contains the specified element.
464: * @param o element whose presence in this collection is to be tested.
465: * @return true if this collection contains the specified element.
466: */
467: public boolean contains(Object o) {
468: return ListMap.this .containsValue(o);
469: }
470:
471: /** Removes all of the elements from this collection.
472: */
473: public void clear() {
474: ListMap.this .clear();
475: }
476:
477: /** Returns an iterator over the elements in this collection.
478: * @return an iterator over the elements in this collection.
479: */
480: public Iterator iterator() {
481: return ListMap.this .getIterator(ListMap.TYPE_VALUES);
482: }
483: }
484:
485: private class ListMapEntry implements Map.Entry {
486: Object m_key = null;
487: Object m_value = null;
488:
489: private ListMapEntry(Object key, Object value) {
490: m_key = key;
491: m_value = value;
492: }
493:
494: /** Returns the key corresponding to this entry.
495: * @return the key corresponding to this entry.
496: */
497: public Object getKey() {
498: return m_key;
499: }
500:
501: /** Returns the hash code value for this map entry.
502: * @return the hash code value for this map entry.
503: */
504: public int hashCode() {
505: return (m_key == null ? 0 : m_key.hashCode())
506: + (m_value == null ? 0 : m_value.hashCode());
507: }
508:
509: /** Returns the value corresponding to this entry.
510: * @return the value corresponding to this entry.
511: */
512: public Object getValue() {
513: return m_value;
514: }
515:
516: /** This is an Unsupported method. It throws the UnsupportedOperationException.
517: * @param value the value to be set.
518: * @return old value corresponding to the entry.
519: */
520: public Object setValue(Object value) {
521: throw new UnsupportedOperationException();
522: }
523:
524: /** Compares the specified object with this entry for equality.
525: * Returns true if the given object is also a ListMap.Entry object and the two entries represent the same mapping.
526: * @param o object to be compared for equality with this map entry.
527: * @return true if the specified object is equal to this map entry.
528: */
529: public boolean equals(Object o) {
530: boolean result = false;
531: if (o instanceof ListMap.ListMapEntry) {
532: ListMap.ListMapEntry e2 = (ListMap.ListMapEntry) o;
533: result = (getKey() == null ? e2.getKey() == null
534: : getKey().equals(e2.getKey()))
535: && (getValue() == null ? e2.getValue() == null
536: : getValue().equals(e2.getValue()));
537: }
538: return result;
539: }
540: }
541: }
|