001 package org.apache.ojb.broker.util;
002
003 /* Copyright 2004-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.io.IOException;
019 import java.io.ObjectInputStream;
020 import java.io.ObjectOutputStream;
021 import java.lang.ref.Reference;
022 import java.lang.ref.ReferenceQueue;
023 import java.lang.ref.SoftReference;
024 import java.lang.ref.WeakReference;
025 import java.util.AbstractCollection;
026 import java.util.AbstractMap;
027 import java.util.AbstractSet;
028 import java.util.ArrayList;
029 import java.util.Arrays;
030 import java.util.Collection;
031 import java.util.ConcurrentModificationException;
032 import java.util.Iterator;
033 import java.util.Map;
034 import java.util.NoSuchElementException;
035 import java.util.Set;
036
037 /**
038 * <p>
039 * Modified version of {@link org.apache.commons.collections.ReferenceMap}, using
040 * object identity for key comparison (). This class
041 * simply extended {@link org.apache.commons.collections.ReferenceMap} with an extra field
042 * "useSystemIdentity" which is initialized in constructor and is used every time we
043 * want to compare (or hash) keys or values.
044 * </p>
045 * <p>
046 * Javadoc of ReferenceMap starts here:
047 * <br/>
048 * Hashtable-based {@link java.util.Map} implementation that allows
049 * mappings to be removed by the garbage collector.
050 * </p>
051 * <P>
052 * When you construct a <Code>ReferenceMap</Code>, you can
053 * specify what kind of references are used to store the
054 * map's keys and values. If non-hard references are
055 * used, then the garbage collector can remove mappings
056 * if a key or value becomes unreachable, or if the
057 * JVM's memory is running low. For information on how
058 * the different reference types behave, see
059 * {@link java.lang.ref.Reference}.<P>
060 *
061 * Different types of references can be specified for keys
062 * and values. The keys can be configured to be weak but
063 * the values hard, in which case this class will behave
064 * like a <A HREF="http://java.sun.com/j2se/1.4/docs/api/java/util/WeakHashMap.html">
065 * <Code>WeakHashMap</Code></A>. However, you
066 * can also specify hard keys and weak values, or any other
067 * combination. The default constructor uses hard keys
068 * and soft values, providing a memory-sensitive cache.<P>
069 *
070 * The algorithms used are basically the same as those
071 * in {@link java.util.HashMap}. In particular, you
072 * can specify a load factor and capacity to suit your
073 * needs. All optional {@link java.util.Map} operations are
074 * supported.<P>
075 *
076 * However, this {@link java.util.Map} implementation does <I>not</I>
077 * allow null elements. Attempting to add a null key or
078 * or a null value to the map will raise a
079 * <Code>NullPointerException</Code>.<P>
080 *
081 * As usual, this implementation is not synchronized. You
082 * can use {@link java.util.Collections#synchronizedMap} to
083 * provide synchronized access to a <Code>ReferenceMap</Code>.
084 *
085 * @deprecated use {@link org.apache.commons.collections.map.ReferenceIdentityMap} instead.
086 * @author Andy Malakov
087 * @version $Id: ReferenceMap.java,v 1.1 2007-08-24 22:17:36 ewestfal Exp $
088 */
089 public class ReferenceMap extends AbstractMap
090 {
091
092 /**
093 * For serialization.
094 */
095 final private static long serialVersionUID = -3370601314380922368L;
096
097
098 /**
099 * Constant indicating that hard references should be used.
100 */
101 final public static int HARD = 0;
102
103
104 /**
105 * Constant indiciating that soft references should be used.
106 */
107 final public static int SOFT = 1;
108
109
110 /**
111 * Constant indicating that weak references should be used.
112 */
113 final public static int WEAK = 2;
114
115
116 // --- serialized instance variables:
117
118
119 /**
120 * The reference type for keys. Must be HARD, SOFT, WEAK.
121 * Note: I originally marked this field as final, but then this class
122 * didn't compile under JDK1.2.2.
123 * @serial
124 */
125 private int keyType;
126
127
128 /**
129 * The reference type for values. Must be HARD, SOFT, WEAK.
130 * Note: I originally marked this field as final, but then this class
131 * didn't compile under JDK1.2.2.
132 * @serial
133 */
134 private int valueType;
135
136
137 /**
138 * The threshold variable is calculated by multiplying
139 * table.length and loadFactor.
140 * Note: I originally marked this field as final, but then this class
141 * didn't compile under JDK1.2.2.
142 * @serial
143 */
144 private float loadFactor;
145
146
147 // -- Non-serialized instance variables
148
149 /**
150 * ReferenceQueue used to eliminate stale mappings.
151 * @see #purge
152 */
153 private transient ReferenceQueue queue = new ReferenceQueue();
154
155
156 /**
157 * The hash table. Its length is always a power of two.
158 */
159 private transient Entry[] table;
160
161
162 /**
163 * Number of mappings in this map.
164 */
165 private transient int size;
166
167
168 /**
169 * When size reaches threshold, the map is resized.
170 * @see #resize
171 */
172 private transient int threshold;
173
174
175 /**
176 * Number of times this map has been modified.
177 */
178 private transient volatile int modCount;
179
180
181 /**
182 * Cached key set. May be null if key set is never accessed.
183 */
184 private transient Set keySet;
185
186
187 /**
188 * Cached entry set. May be null if entry set is never accessed.
189 */
190 private transient Set entrySet;
191
192
193 /**
194 * Cached values. May be null if values() is never accessed.
195 */
196 private transient Collection values;
197
198 /**
199 * Note: I originally marked this field as final, but then this class
200 * didn't compile under JDK1.2.2.
201 */
202 private boolean useSystemIdentity;
203
204 /**
205 * Constructs a new <Code>ReferenceMap</Code> that will
206 * use hard references to keys and soft references to values.
207 */
208 public ReferenceMap()
209 {
210 this(HARD, SOFT);
211 }
212
213
214 /**
215 * Constructs a new <Code>ReferenceMap</Code> that will
216 * use the specified types of references.
217 *
218 * @param keyType the type of reference to use for keys;
219 * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
220 * @param valueType the type of reference to use for values;
221 * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
222 */
223 public ReferenceMap(int keyType, int valueType)
224 {
225 this(keyType, valueType, 16, 0.75f, false);
226 }
227
228
229 /**
230 * Constructs a new <Code>ReferenceMap</Code> with the
231 * specified reference types, load factor and initial
232 * capacity.
233 *
234 * @param keyType the type of reference to use for keys;
235 * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
236 * @param valueType the type of reference to use for values;
237 * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
238 * @param capacity the initial capacity for the map
239 * @param loadFactor the load factor for the map
240 * @param useSystemIdentity if true System.identityHashCode() and comparision operator (==) will be used
241 * in place of Object.hashCode and Object.equals (Please read {@link java.util.WeakHashMap}
242 * java doc for more information).
243 */
244 public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor, boolean useSystemIdentity)
245 {
246 super();
247
248 verify("keyType", keyType);
249 verify("valueType", valueType);
250
251 this.useSystemIdentity = useSystemIdentity;
252
253 if (capacity <= 0)
254 {
255 throw new IllegalArgumentException("capacity must be positive");
256 }
257 if ((loadFactor <= 0.0f) || (loadFactor >= 1.0f))
258 {
259 throw new IllegalArgumentException("Load factor must be greater than 0 and less than 1.");
260 }
261
262 this.keyType = keyType;
263 this.valueType = valueType;
264
265 int v = 1;
266 while (v < capacity) v *= 2;
267
268 this.table = new Entry[v];
269 this.loadFactor = loadFactor;
270 this.threshold = (int) (v * loadFactor);
271 }
272
273
274 // used by constructor
275 private static void verify(String name, int type)
276 {
277 if ((type < HARD) || (type > WEAK))
278 {
279 throw new IllegalArgumentException(name +
280 " must be HARD, SOFT, WEAK.");
281 }
282 }
283
284
285 /**
286 * Writes this object to the given output stream.
287 *
288 * @param out the output stream to write to
289 * @throws java.io.IOException if the stream raises it
290 */
291 private void writeObject(ObjectOutputStream out) throws IOException
292 {
293 out.defaultWriteObject();
294 out.writeInt(table.length);
295
296 // Have to use null-terminated list because size might shrink
297 // during iteration
298
299 for (Iterator iter = entrySet().iterator(); iter.hasNext();)
300 {
301 Map.Entry entry = (Map.Entry) iter.next();
302 out.writeObject(entry.getKey());
303 out.writeObject(entry.getValue());
304 }
305 out.writeObject(null);
306 }
307
308
309 /**
310 * Reads the contents of this object from the given input stream.
311 *
312 * @param inp the input stream to read from
313 * @throws java.io.IOException if the stream raises it
314 * @throws java.lang.ClassNotFoundException if the stream raises it
315 */
316 private void readObject(ObjectInputStream inp) throws IOException, ClassNotFoundException
317 {
318 inp.defaultReadObject();
319 table = new Entry[inp.readInt()];
320 threshold = (int) (table.length * loadFactor);
321 queue = new ReferenceQueue();
322 Object key = inp.readObject();
323 while (key != null)
324 {
325 Object value = inp.readObject();
326 put(key, value);
327 key = inp.readObject();
328 }
329 }
330
331
332 /**
333 * Constructs a reference of the given type to the given
334 * referent. The reference is registered with the queue
335 * for later purging.
336 *
337 * @param type HARD, SOFT or WEAK
338 * @param referent the object to refer to
339 * @param hash the hash code of the <I>key</I> of the mapping;
340 * this number might be different from referent.hashCode() if
341 * the referent represents a value and not a key
342 */
343 private Object toReference(int type, Object referent, int hash)
344 {
345 switch (type)
346 {
347 case HARD:
348 return referent;
349 case SOFT:
350 return new SoftRef(hash, referent, queue);
351 case WEAK:
352 return new WeakRef(hash, referent, queue);
353 default:
354 throw new Error();
355 }
356 }
357
358 /**
359 * Returns the entry associated with the given key.
360 *
361 * @param key the key of the entry to look up
362 * @return the entry associated with that key, or null
363 * if the key is not in this map
364 */
365 private Entry getEntry(Object key)
366 {
367 if (key == null) return null;
368 int hash = hashCode(key);
369 int index = indexFor(hash);
370 for (Entry entry = table[index]; entry != null; entry = entry.next)
371 {
372 if ((entry.hash == hash) && equals(key, entry.getKey()))
373 {
374 return entry;
375 }
376 }
377 return null;
378 }
379
380
381 /**
382 * Converts the given hash code into an index into the
383 * hash table.
384 */
385 private int indexFor(int hash)
386 {
387 // mix the bits to avoid bucket collisions...
388 hash += ~(hash << 15);
389 hash ^= (hash >>> 10);
390 hash += (hash << 3);
391 hash ^= (hash >>> 6);
392 hash += ~(hash << 11);
393 hash ^= (hash >>> 16);
394 return hash & (table.length - 1);
395 }
396
397
398 /**
399 * Resizes this hash table by doubling its capacity.
400 * This is an expensive operation, as entries must
401 * be copied from the old smaller table to the new
402 * bigger table.
403 */
404 private void resize()
405 {
406 Entry[] old = table;
407 table = new Entry[old.length * 2];
408
409 for (int i = 0; i < old.length; i++)
410 {
411 Entry next = old[i];
412 while (next != null)
413 {
414 Entry entry = next;
415 next = next.next;
416 int index = indexFor(entry.hash);
417 entry.next = table[index];
418 table[index] = entry;
419 }
420 old[i] = null;
421 }
422 threshold = (int) (table.length * loadFactor);
423 }
424
425
426 /**
427 * Purges stale mappings from this map.<P>
428 *
429 * Ordinarily, stale mappings are only removed during
430 * a write operation; typically a write operation will
431 * occur often enough that you'll never need to manually
432 * invoke this method.<P>
433 *
434 * Note that this method is not synchronized! Special
435 * care must be taken if, for instance, you want stale
436 * mappings to be removed on a periodic basis by some
437 * background thread.
438 */
439 private void purge()
440 {
441 Reference ref = queue.poll();
442 while (ref != null)
443 {
444 purge(ref);
445 ref = queue.poll();
446 }
447 }
448
449
450 private void purge(Reference ref)
451 {
452 // The hashCode of the reference is the hashCode of the
453 // mapping key, even if the reference refers to the
454 // mapping value...
455 int hash = ref.hashCode(); // note: hashCode() is referined
456 int index = indexFor(hash);
457 Entry previous = null;
458 Entry entry = table[index];
459 while (entry != null)
460 {
461 if (entry.purge(ref))
462 {
463 if (previous == null)
464 table[index] = entry.next;
465 else
466 previous.next = entry.next;
467 this.size--;
468 return;
469 }
470 previous = entry;
471 entry = entry.next;
472 }
473
474 }
475
476
477 /**
478 * Returns the size of this map.
479 *
480 * @return the size of this map
481 */
482 public int size()
483 {
484 purge();
485 return size;
486 }
487
488
489 /**
490 * Returns <Code>true</Code> if this map is empty.
491 *
492 * @return <Code>true</Code> if this map is empty
493 */
494 public boolean isEmpty()
495 {
496 purge();
497 return size == 0;
498 }
499
500
501 /**
502 * Returns <Code>true</Code> if this map contains the given key.
503 *
504 * @return true if the given key is in this map
505 */
506 public boolean containsKey(Object key)
507 {
508 purge();
509 Entry entry = getEntry(key);
510 if (entry == null) return false;
511 return entry.getValue() != null;
512 }
513
514
515 /**
516 * Returns the value associated with the given key, if any.
517 *
518 * @return the value associated with the given key, or <Code>null</Code>
519 * if the key maps to no value
520 */
521 public Object get(Object key)
522 {
523 purge();
524 Entry entry = getEntry(key);
525 if (entry == null) return null;
526 return entry.getValue();
527 }
528
529
530 /**
531 * Associates the given key with the given value.<P>
532 * Neither the key nor the value may be null.
533 *
534 * @param key the key of the mapping
535 * @param value the value of the mapping
536 * @return the last value associated with that key, or
537 * null if no value was associated with the key
538 * @throws java.lang.NullPointerException if either the key or value
539 * is null
540 */
541 public Object put(Object key, Object value)
542 {
543 if (key == null) throw new NullPointerException("null keys not allowed");
544 if (value == null) throw new NullPointerException("null values not allowed");
545
546 purge();
547 if (size + 1 > threshold) resize();
548
549 int hash = hashCode(key);
550 int index = indexFor(hash);
551 Entry entry = table[index];
552 while (entry != null)
553 {
554 if ((hash == entry.hash) && equals(key, entry.getKey()))
555 {
556 Object result = entry.getValue();
557 entry.setValue(value);
558 return result;
559 }
560 entry = entry.next;
561 }
562 this.size++;
563 modCount++;
564 key = toReference(keyType, key, hash);
565 value = toReference(valueType, value, hash);
566 table[index] = new Entry(key, hash, value, table[index]);
567 return null;
568 }
569
570
571 /**
572 * Removes the key and its associated value from this map.
573 *
574 * @param key the key to remove
575 * @return the value associated with that key, or null if
576 * the key was not in the map
577 */
578 public Object remove(Object key)
579 {
580 if (key == null) return null;
581 purge();
582 int hash = hashCode(key);
583 int index = indexFor(hash);
584 Entry previous = null;
585 Entry entry = table[index];
586 while (entry != null)
587 {
588 if ((hash == entry.hash) && equals(key, entry.getKey()))
589 {
590 if (previous == null)
591 table[index] = entry.next;
592 else
593 previous.next = entry.next;
594 this.size--;
595 modCount++;
596 return entry.getValue();
597 }
598 previous = entry;
599 entry = entry.next;
600 }
601 return null;
602 }
603
604
605 /**
606 * Clears this map.
607 */
608 public void clear()
609 {
610 Arrays.fill(table, null);
611 size = 0;
612 while (queue.poll() != null)
613 {
614 // drain the queue
615 }
616 }
617
618
619 /**
620 * Returns a set view of this map's entries.
621 *
622 * @return a set view of this map's entries
623 */
624 public Set entrySet()
625 {
626 if (entrySet != null) return entrySet;
627 entrySet = new AbstractSet()
628 {
629 public int size()
630 {
631 return ReferenceMap.this.size();
632 }
633
634
635 public void clear()
636 {
637 ReferenceMap.this.clear();
638 }
639
640
641 public boolean contains(Object o)
642 {
643 if (o == null) return false;
644 if (!(o instanceof Map.Entry)) return false;
645 Map.Entry e = (Map.Entry) o;
646 Entry e2 = getEntry(e.getKey());
647 return (e2 != null) && e.equals(e2);
648 }
649
650
651 public boolean remove(Object o)
652 {
653 boolean r = contains(o);
654 if (r)
655 {
656 Map.Entry e = (Map.Entry) o;
657 ReferenceMap.this.remove(e.getKey());
658 }
659 return r;
660 }
661
662
663 public Iterator iterator()
664 {
665 return new EntryIterator();
666 }
667
668 public Object[] toArray()
669 {
670 return toArray(new Object[0]);
671 }
672
673
674 public Object[] toArray(Object[] arr)
675 {
676 ArrayList list = new ArrayList();
677 Iterator iterator = iterator();
678 while (iterator.hasNext())
679 {
680 Entry e = (Entry) iterator.next();
681 list.add(new DefaultMapEntry(e.getKey(), e.getValue()));
682 }
683 return list.toArray(arr);
684 }
685 };
686 return entrySet;
687 }
688
689
690 /**
691 * Returns a set view of this map's keys.
692 *
693 * @return a set view of this map's keys
694 */
695 public Set keySet()
696 {
697 if (keySet != null) return keySet;
698 keySet = new AbstractSet()
699 {
700 public int size()
701 {
702 return size;
703 }
704
705 public Iterator iterator()
706 {
707 return new KeyIterator();
708 }
709
710 public boolean contains(Object o)
711 {
712 return containsKey(o);
713 }
714
715
716 public boolean remove(Object o)
717 {
718 Object r = ReferenceMap.this.remove(o);
719 return r != null;
720 }
721
722 public void clear()
723 {
724 ReferenceMap.this.clear();
725 }
726
727 };
728 return keySet;
729 }
730
731
732 /**
733 * Returns a collection view of this map's values.
734 *
735 * @return a collection view of this map's values.
736 */
737 public Collection values()
738 {
739 if (values != null) return values;
740 values = new AbstractCollection()
741 {
742 public int size()
743 {
744 return size;
745 }
746
747 public void clear()
748 {
749 ReferenceMap.this.clear();
750 }
751
752 public Iterator iterator()
753 {
754 return new ValueIterator();
755 }
756 };
757 return values;
758 }
759
760
761 // If getKey() or getValue() returns null, it means
762 // the mapping is stale and should be removed.
763 private class Entry implements Map.Entry
764 {
765
766 Object key;
767 Object value;
768 int hash;
769 Entry next;
770
771
772 public Entry(Object key, int hash, Object value, Entry next)
773 {
774 this.key = key;
775 this.hash = hash;
776 this.value = value;
777 this.next = next;
778 }
779
780
781 public Object getKey()
782 {
783 return (keyType > HARD) ? ((Reference) key).get() : key;
784 }
785
786
787 public Object getValue()
788 {
789 return (valueType > HARD) ? ((Reference) value).get() : value;
790 }
791
792
793 public Object setValue(Object object)
794 {
795 Object old = getValue();
796 if (valueType > HARD) ((Reference) value).clear();
797 value = toReference(valueType, object, hash);
798 return old;
799 }
800
801
802 public boolean equals(Object o)
803 {
804 if (o == null) return false;
805 if (o == this) return true;
806 if (!(o instanceof Map.Entry)) return false;
807
808 Map.Entry entry = (Map.Entry) o;
809 Object _key = entry.getKey();
810 Object _value = entry.getValue();
811 if ((_key == null) || (_value == null)) return false;
812 return ReferenceMap.this.equals(_key, getKey()) &&
813 ReferenceMap.this.equals(_value, getValue());
814 }
815
816
817 public int hashCode()
818 {
819 Object v = getValue();
820 return hash ^ ((v == null) ? 0 : v.hashCode());
821 }
822
823
824 public String toString()
825 {
826 return getKey() + "=" + getValue();
827 }
828
829
830 boolean purge(Reference ref)
831 {
832 boolean r = (keyType > HARD) && (key == ref);
833 r = r || ((valueType > HARD) && (value == ref));
834 if (r)
835 {
836 if (keyType > HARD) ((Reference) key).clear();
837 if (valueType > HARD) ((Reference) value).clear();
838 }
839 return r;
840 }
841 }
842
843
844 private class EntryIterator implements Iterator
845 {
846 // These fields keep track of where we are in the table.
847 int index;
848 Entry entry;
849 Entry previous;
850
851 // These Object fields provide hard references to the
852 // current and next entry; this assures that if hasNext()
853 // returns true, next() will actually return a valid element.
854 Object nextKey, nextValue;
855 Object currentKey, currentValue;
856
857 int expectedModCount;
858
859
860 public EntryIterator()
861 {
862 index = (size() != 0 ? table.length : 0);
863 // have to do this here! size() invocation above
864 // may have altered the modCount.
865 expectedModCount = modCount;
866 }
867
868
869 public boolean hasNext()
870 {
871 checkMod();
872 while (nextNull())
873 {
874 Entry e = entry;
875 int i = index;
876 while ((e == null) && (i > 0))
877 {
878 i--;
879 e = table[i];
880 }
881 entry = e;
882 index = i;
883 if (e == null)
884 {
885 currentKey = null;
886 currentValue = null;
887 return false;
888 }
889 nextKey = e.getKey();
890 nextValue = e.getValue();
891 if (nextNull()) entry = entry.next;
892 }
893 return true;
894 }
895
896
897 private void checkMod()
898 {
899 if (modCount != expectedModCount)
900 {
901 throw new ConcurrentModificationException();
902 }
903 }
904
905
906 private boolean nextNull()
907 {
908 return (nextKey == null) || (nextValue == null);
909 }
910
911 protected Entry nextEntry()
912 {
913 checkMod();
914 if (nextNull() && !hasNext()) throw new NoSuchElementException();
915 previous = entry;
916 entry = entry.next;
917 currentKey = nextKey;
918 currentValue = nextValue;
919 nextKey = null;
920 nextValue = null;
921 return previous;
922 }
923
924
925 public Object next()
926 {
927 return nextEntry();
928 }
929
930
931 public void remove()
932 {
933 checkMod();
934 if (previous == null) throw new IllegalStateException();
935 ReferenceMap.this.remove(currentKey);
936 previous = null;
937 currentKey = null;
938 currentValue = null;
939 expectedModCount = modCount;
940 }
941
942 }
943
944
945 private class ValueIterator extends EntryIterator
946 {
947 public Object next()
948 {
949 return nextEntry().getValue();
950 }
951 }
952
953
954 private class KeyIterator extends EntryIterator
955 {
956 public Object next()
957 {
958 return nextEntry().getKey();
959 }
960 }
961
962
963
964 // These two classes store the hashCode of the key of
965 // of the mapping, so that after they're dequeued a quick
966 // lookup of the bucket in the table can occur.
967
968
969 private static class SoftRef extends SoftReference
970 {
971 private int hash;
972
973
974 public SoftRef(int hash, Object r, ReferenceQueue q)
975 {
976 super(r, q);
977 this.hash = hash;
978 }
979
980
981 public int hashCode()
982 {
983 return hash;
984 }
985 }
986
987
988 private static class WeakRef extends WeakReference
989 {
990 private int hash;
991
992
993 public WeakRef(int hash, Object r, ReferenceQueue q)
994 {
995 super(r, q);
996 this.hash = hash;
997 }
998
999
1000 public int hashCode()
1001 {
1002 return hash;
1003 }
1004 }
1005
1006 private int hashCode(Object obj)
1007 {
1008 return useSystemIdentity ? System.identityHashCode(obj) : obj.hashCode(); // null keys|values are not supported
1009 }
1010
1011 private boolean equals(Object obj1, Object obj2)
1012 {
1013 return (obj1 == obj2) ||
1014 (!useSystemIdentity && obj1.equals(obj2)); // null keys|values are not supported
1015 }
1016
1017 //***********************************************
1018 // inner class
1019 //***********************************************
1020 /** A default implementation of {@link java.util.Map.Entry}
1021 *
1022 * @since 1.0
1023 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
1024 * @author <a href="mailto:mas@apache.org">Michael A. Smith</a>
1025 */
1026
1027 public class DefaultMapEntry implements Map.Entry {
1028
1029 private Object key;
1030 private Object value;
1031
1032 /**
1033 * Constructs a new <Code>DefaultMapEntry</Code> with a null key
1034 * and null value.
1035 */
1036 public DefaultMapEntry() {
1037 }
1038
1039 /**
1040 * Constructs a new <Code>DefaultMapEntry</Code> with the given
1041 * key and given value.
1042 *
1043 * @param key the key for the entry, may be null
1044 * @param value the value for the entyr, may be null
1045 */
1046 public DefaultMapEntry(Object key, Object value) {
1047 this.key = key;
1048 this.value = value;
1049 }
1050
1051 /**
1052 * Implemented per API documentation of
1053 * {@link java.util.Map.Entry#equals(java.lang.Object)}
1054 **/
1055 public boolean equals(Object o) {
1056 if( o == null ) return false;
1057 if( o == this ) return true;
1058
1059 if ( ! (o instanceof Map.Entry ) )
1060 return false;
1061 Map.Entry e2 = (Map.Entry)o;
1062 return ((getKey() == null ?
1063 e2.getKey() == null : getKey().equals(e2.getKey())) &&
1064 (getValue() == null ?
1065 e2.getValue() == null : getValue().equals(e2.getValue())));
1066 }
1067
1068
1069 /**
1070 * Implemented per API documentation of
1071 * {@link java.util.Map.Entry#hashCode()}
1072 **/
1073 public int hashCode() {
1074 return ( ( getKey() == null ? 0 : getKey().hashCode() ) ^
1075 ( getValue() == null ? 0 : getValue().hashCode() ) );
1076 }
1077
1078
1079
1080 // Map.Entry interface
1081 //-------------------------------------------------------------------------
1082
1083 /**
1084 * Returns the key.
1085 *
1086 * @return the key
1087 */
1088 public Object getKey() {
1089 return key;
1090 }
1091
1092
1093 /**
1094 * Returns the value.
1095 *
1096 * @return the value
1097 */
1098 public Object getValue() {
1099 return value;
1100 }
1101
1102 // Properties
1103 //-------------------------------------------------------------------------
1104
1105 /**
1106 * Sets the key. This method does not modify any map.
1107 *
1108 * @param key the new key
1109 */
1110 public void setKey(Object key) {
1111 this.key = key;
1112 }
1113
1114 /** Note that this method only sets the local reference inside this object and
1115 * does not modify the original Map.
1116 *
1117 * @return the old value of the value
1118 * @param value the new value
1119 */
1120 public Object setValue(Object value) {
1121 Object answer = this.value;
1122 this.value = value;
1123 return answer;
1124 }
1125
1126 }
1127 }