| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Gordon Yorke |
| package org.eclipse.persistence.internal.helper; |
| |
| |
| /** |
| * INTERNAL: |
| * <p> |
| * <b>Purpose</b>: Define a {@link Map} that manages key equality by reference, |
| * not equals(). This is required to track objects throughout the lifecycle |
| * of a {@link org.eclipse.persistence.sessions.UnitOfWork}, regardless if the domain |
| * object redefines its equals() method. Additionally, this implementation does |
| * <b>not</b> permit nulls either as values or as keys. Any Entry that has a null in the key or |
| * in the value will be assumed to have garbage collected. |
| * This class also uses weak references to the contents of the map allowing for garbage |
| * collection to reduce the size of the Map |
| * |
| * This work is an extension of the original work completed on the IdentityWeakHashMap as completed by |
| * Mike Norman. |
| * |
| * @author Gordon Yorke (EclipseLink 1.0M4) |
| * |
| */ |
| |
| // J2SE imports |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.WeakReference; |
| import java.util.AbstractCollection; |
| import java.util.AbstractMap; |
| import java.util.AbstractSet; |
| import java.util.Collection; |
| import java.util.ConcurrentModificationException; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.internal.localization.ExceptionLocalization; |
| |
| public class IdentityWeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { |
| static final long serialVersionUID = -5176951017503351630L; |
| |
| // the default initial capacity |
| static final int DEFAULT_INITIAL_CAPACITY = 32; |
| |
| // the maximum capacity. |
| static final int MAXIMUM_CAPACITY = 1 << 30; |
| |
| // the loadFactor used when none specified in constructor. |
| static final float DEFAULT_LOAD_FACTOR = 0.75f; |
| protected transient WeakEntry<K,V>[] entries;// internal array of Entry's |
| protected transient int count = 0; |
| private transient int modCount = 0;// # of times this Map has been modified |
| protected int threshold = 0; |
| protected float loadFactor = 0; |
| |
| /** This is used by the garbage collector. Every weak reference that is garbage collected |
| * will be enqueued on this. Then only this queue needs to be checked to remove empty |
| * references. |
| */ |
| protected ReferenceQueue referenceQueue; |
| |
| /** |
| * Constructs a new <code>IdentityWeakHashMap</code> with the given |
| * initial capacity and the given loadFactor. |
| * |
| * @param initialCapacity the initial capacity of this |
| * <code>IdentityWeakHashMap</code>. |
| * @param loadFactor the loadFactor of the <code>IdentityWeakHashMap</code>. |
| * @throws IllegalArgumentException if the initial capacity is less |
| * than zero, or if the loadFactor is nonpositive. |
| */ |
| public IdentityWeakHashMap(int initialCapacity, float loadFactor) { |
| if (initialCapacity < 0) { |
| throw new IllegalArgumentException("Illegal initialCapacity: " + initialCapacity); |
| } |
| if (initialCapacity > MAXIMUM_CAPACITY) { |
| initialCapacity = MAXIMUM_CAPACITY; |
| } |
| if ((loadFactor <= 0) || Float.isNaN(loadFactor)) { |
| throw new IllegalArgumentException("Illegal loadFactor: " + loadFactor); |
| } |
| |
| // Find a power of 2 >= initialCapacity |
| int capacity = 1; |
| while (capacity < initialCapacity) { |
| capacity <<= 1; |
| } |
| this.loadFactor = loadFactor; |
| threshold = (int)(capacity * loadFactor); |
| entries = new WeakEntry[capacity]; |
| referenceQueue = new ReferenceQueue(); |
| } |
| |
| /** |
| * Constructs a new <code>IdentityWeakHashMap</code> with the given |
| * initial capacity and a default loadFactor of <code>0.75</code>. |
| * |
| * @param initialCapacity the initial capacity of the |
| * <code>IdentityWeakHashMap</code>. |
| * @throws IllegalArgumentException if the initial capacity is less |
| * than zero. |
| */ |
| public IdentityWeakHashMap(int initialCapacity) { |
| this(initialCapacity, DEFAULT_LOAD_FACTOR); |
| } |
| |
| /** |
| * Constructs a new <code>IdentityWeakHashMap</code> with a default initial |
| * capacity of <code>32</code> and a loadfactor of <code>0.75</code>. |
| */ |
| public IdentityWeakHashMap() { |
| loadFactor = DEFAULT_LOAD_FACTOR; |
| threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); |
| entries = new WeakEntry[DEFAULT_INITIAL_CAPACITY]; |
| referenceQueue = new ReferenceQueue(); |
| } |
| |
| /** |
| * Constructs a new <code>IdentityWeakHashMap</code> with the same mappings |
| * as the given map. The <code>IdentityWeakHashMap</code> is created with a |
| * capacity sufficient to hold the elements of the given map. |
| * |
| * @param m the map whose mappings are to be placed in the |
| * <code>IdentityWeakHashMap</code>. |
| */ |
| public IdentityWeakHashMap(Map m) { |
| this(Math.max((int)(m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); |
| putAll(m); |
| } |
| |
| /** |
| * @return the size of this <code>IdentityWeakHashMap</code>. |
| */ |
| @Override |
| public int size() { |
| cleanUp(); |
| return count; |
| } |
| |
| /** |
| * @return <code>true</code> if this <code>IdentityWeakHashMap</code> is empty. |
| */ |
| @Override |
| public boolean isEmpty() { |
| return (count == 0); |
| } |
| |
| /** |
| * Returns <code>true</code> if this <code>IdentityWeakHashMap</code> contains |
| * the given object. Equality is tested by the equals() method. |
| * |
| * @param obj the object to find. |
| * @return <code>true</code> if this <code>IdentityWeakHashMap</code> contains |
| * obj. |
| * @throws NullPointerException if obj is <code>null</code>. |
| */ |
| @Override |
| public boolean containsValue(Object obj) { |
| if (obj == null) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_not_supported_identityweakhashmap")); |
| } |
| //cleanup before searching as to reduce number of possible empty Entries |
| cleanUp(); |
| WeakEntry<K,V>[] copyOfEntries = entries; |
| for (int i = copyOfEntries.length; i-- > 0;) { |
| for (WeakEntry<K,V> e = copyOfEntries[i]; e != null; e = e.next) { |
| if (obj.equals(e.value.get())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if this <code>IdentityWeakHashMap</code> contains a |
| * mapping for the given key. Equality is tested by reference. |
| * |
| * @param key object to be used as a key into this |
| * <code>IdentityWeakHashMap</code>. |
| * @return <code>true</code> if this <code>IdentityWeakHashMap</code> contains a |
| * mapping for key. |
| */ |
| @Override |
| public boolean containsKey(Object key) { |
| if (key == null) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_not_supported_identityweakhashmap")); |
| } |
| cleanUp(); |
| WeakEntry[] copyOfEntries = entries; |
| int hash = System.identityHashCode(key); |
| int index = (hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index]; e != null; e = e.next) { |
| if (e.key.get() == key) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the value to which the given key is mapped in this |
| * <code>IdentityWeakHashMap</code>. Returns <code>null</code> if this |
| * <code>IdentityWeakHashMap</code> contains no mapping for this key. |
| * |
| * @return the value to which this <code>IdentityWeakHashMap</code> maps the |
| * given key. |
| * @param key key whose associated value is to be returned. |
| */ |
| @Override |
| public V get(Object key) { |
| if (key == null) return null; |
| cleanUp(); |
| WeakEntry[] copyOfEntries = entries; |
| int hash = System.identityHashCode(key); |
| int index = (hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index]; e != null; e = e.next) { |
| if (e.key.get() == key) { |
| return (V)e.value.get(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Re-builds the internal array of Entry's with a larger capacity. |
| * This method is called automatically when the number of objects in this |
| * IdentityWeakHashMap exceeds its current threshold. |
| */ |
| private void rehash() { |
| int oldCapacity = entries.length; |
| WeakEntry[] oldEntries = entries; |
| int newCapacity = (oldCapacity * 2) + 1; |
| WeakEntry[] newEntries = new WeakEntry[newCapacity]; |
| modCount++; |
| threshold = (int)(newCapacity * loadFactor); |
| entries = newEntries; |
| for (int i = oldCapacity; i-- > 0;) { |
| for (WeakEntry old = oldEntries[i]; old != null;) { |
| WeakEntry e = old; |
| old = old.next; |
| int index = (e.hash & 0x7FFFFFFF) % newCapacity; |
| e.next = newEntries[index]; |
| newEntries[index] = e; |
| } |
| } |
| } |
| |
| /** |
| * Associate the given object with the given key in this |
| * <code>IdentityWeakHashMap</code>, replacing any existing mapping. |
| * |
| * @param key key to map to given object. |
| * @param obj object to be associated with key. |
| * @return the previous object for key or <code>null</code> if this |
| * <code>IdentityWeakHashMap</code> did not have one. |
| * @throws NullPointerException if obj is <code>null</code>. |
| */ |
| @Override |
| public V put(K key, V obj) { |
| if (obj == null || key == null) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_not_supported_identityweakhashmap")); |
| } |
| cleanUp(); |
| WeakEntry[] copyOfEntries = entries; |
| int hash = System.identityHashCode(key); |
| int index = (hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index]; e != null; e = e.next) { |
| if (e.key.get() == key) { |
| EntryReference<V> old = e.value; |
| if (key == obj){ |
| e.value = e.key; |
| }else{ |
| e.value = new HardEntryReference<>(obj); |
| } |
| return old.get(); |
| } |
| } |
| |
| modCount++; |
| if (count >= threshold) { |
| rehash(); |
| copyOfEntries = entries; |
| index = (hash & 0x7FFFFFFF) % copyOfEntries.length; |
| } |
| WeakEntry<K,V> e = new WeakEntry<K,V>(hash, key, obj, copyOfEntries[index], referenceQueue); |
| copyOfEntries[index] = e; |
| count++; |
| return null; |
| } |
| |
| /** |
| * Removes the mapping (key and its corresponding value) from this |
| * <code>IdentityWeakHashMap</code>, if present. |
| * |
| * @param key key whose mapping is to be removed from the map. |
| * @return the previous object for key or <code>null</code> if this |
| * <code>IdentityWeakHashMap</code> did not have one. |
| */ |
| @Override |
| public V remove(Object key) { |
| if (key == null) return null; |
| cleanUp(); |
| WeakEntry[] copyOfEntries = entries; |
| int hash = System.identityHashCode(key); |
| int index = (hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index], prev = null; e != null; prev = e, e = e.next) { |
| if (e.key.get() == key) { |
| if (prev != null) { |
| prev.next = e.next; |
| } else { |
| copyOfEntries[index] = e.next; |
| } |
| count--; |
| return (V)e.value.get(); |
| } |
| } |
| return null; |
| } |
| |
| protected boolean removeEntry(WeakEntry o, boolean userModification) { |
| |
| WeakEntry[] copyOfEntries = entries; |
| int index = (o.hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index], prev = null; e != null; |
| prev = e, e = e.next) { |
| if (e == o) { |
| // if this method was called as a result of a user action, |
| // increment the modification count |
| // this method is also called by our cleanup code and |
| // that code should not cause a concurrent modification |
| // exception |
| if (userModification){ |
| modCount++; |
| } |
| if (prev != null) { |
| prev.next = e.next; |
| } else { |
| copyOfEntries[index] = e.next; |
| } |
| count--; |
| e.value = null; |
| e.next = null; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Copies all of the mappings from the given map to this |
| * <code>IdentityWeakHashMap</code>, replacing any existing mappings. |
| * |
| * @param m mappings to be stored in this <code>IdentityWeakHashMap</code>. |
| * @throws NullPointerException if m is null. |
| */ |
| @Override |
| public void putAll(Map<? extends K, ? extends V> m) { |
| if (m == null) { |
| throw new NullPointerException(); |
| } |
| |
| Iterator<? extends Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry<? extends K, ? extends V> me = i.next(); |
| put(me.getKey(), me.getValue()); |
| } |
| } |
| |
| /** |
| * Removes all of the mappings from this <code>IdentityWeakHashMap</code>. |
| */ |
| @Override |
| public void clear() { |
| if (count > 0) { |
| modCount++; |
| WeakEntry[] copyOfEntries = entries; |
| for (int i = copyOfEntries.length; --i >= 0;) { |
| copyOfEntries[i] = null; |
| } |
| count = 0; |
| } |
| } |
| |
| protected void cleanUp(){ |
| WeakEntryReference reference = (WeakEntryReference)referenceQueue.poll(); |
| while (reference != null){ |
| // remove the entry but do not increment the modcount |
| // since this is not a user action |
| removeEntry(reference.owner, false); |
| reference = (WeakEntryReference)referenceQueue.poll(); |
| } |
| } |
| |
| /** |
| * Returns a shallow copy of this <code>IdentityWeakHashMap</code> (the |
| * elements are not cloned). |
| * |
| * @return a shallow copy of this <code>IdentityWeakHashMap</code>. |
| */ |
| @Override |
| public Object clone() { |
| try { |
| WeakEntry[] copyOfEntries = entries; |
| IdentityWeakHashMap clone = (IdentityWeakHashMap)super.clone(); |
| clone.referenceQueue = new ReferenceQueue(); |
| clone.entries = new WeakEntry[copyOfEntries.length]; |
| for (int i = copyOfEntries.length; i-- > 0;) { |
| clone.entries[i] = (copyOfEntries[i] != null) ? (WeakEntry)copyOfEntries[i].clone(clone.referenceQueue) : null; |
| } |
| clone.keySet = null; |
| clone.entrySet = null; |
| clone.values = null; |
| clone.modCount = 0; |
| return clone; |
| } catch (CloneNotSupportedException e) { |
| // this shouldn't happen, since we are Cloneable |
| throw new InternalError(); |
| } |
| } |
| |
| // Views - the following is standard 'boiler-plate' Map stuff |
| private transient Set keySet = null; |
| private transient Set entrySet = null; |
| private transient Collection values = null; |
| |
| /** |
| * Returns a set view of the keys contained in this |
| * <code>IdentityWeakHashMap</code>. The set is backed by the map, so |
| * changes to the map are reflected in the set, and vice versa. The set |
| * supports element removal, which removes the corresponding mapping from |
| * this map, via the <code>Iterator.remove</code>, <code>Set.remove</code>, |
| * <code>removeAll</code>, <code>retainAll</code>, and <code>clear</code> operations. |
| * It does not support the <code>add</code> or <code>addAll</code> operations. |
| * |
| * @return a set view of the keys contained in this |
| * <code>IdentityWeakHashMap</code>. |
| */ |
| @Override |
| public Set keySet() { |
| if (keySet == null) { |
| keySet = new AbstractSet() { |
| @Override |
| public Iterator iterator() { |
| return getHashIterator(COMPONENT_TYPES.KEYS); |
| } |
| |
| @Override |
| public int size() { |
| return count; |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| return containsKey(o); |
| } |
| |
| @Override |
| public boolean remove(Object o) { |
| int oldSize = count; |
| IdentityWeakHashMap.this.remove(o); |
| return count != oldSize; |
| } |
| |
| @Override |
| public void clear() { |
| IdentityWeakHashMap.this.clear(); |
| } |
| }; |
| } |
| return keySet; |
| } |
| |
| /** |
| * Returns a collection view of the values contained in this |
| * <code>IdentityWeakHashMap</code>. The collection is backed by the map, so |
| * changes to the map are reflected in the collection, and vice versa. The |
| * collection supports element removal, which removes the corresponding |
| * mapping from this map, via the <code>Iterator.remove</code>, |
| * <code>Collection.remove</code>, <code>removeAll</code>, <code>retainAll</code>, and |
| * <code>clear</code> operations. It does not support the <code>add</code> or |
| * <code>addAll</code> operations. |
| * |
| * @return a collection view of the values contained in this |
| * <code>IdentityWeakHashMap</code>. |
| */ |
| @Override |
| public Collection values() { |
| if (values == null) { |
| values = new AbstractCollection() { |
| @Override |
| public Iterator iterator() { |
| return getHashIterator(COMPONENT_TYPES.VALUES); |
| } |
| |
| @Override |
| public int size() { |
| return count; |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| return containsValue(o); |
| } |
| |
| @Override |
| public void clear() { |
| IdentityWeakHashMap.this.clear(); |
| } |
| }; |
| } |
| return values; |
| } |
| |
| /** |
| * Returns a collection view of the mappings contained in this |
| * <code>IdentityWeakHashMap</code>. Each element in the returned collection |
| * is a <code>Map.Entry</code>. The collection is backed by the map, so changes |
| * to the map are reflected in the collection, and vice versa. The |
| * collection supports element removal, which removes the corresponding |
| * mapping from the map, via the <code>Iterator.remove</code>, |
| * <code>Collection.remove</code>, <code>removeAll</code>, <code>retainAll</code>, and |
| * <code>clear</code> operations. It does not support the <code>add</code> or |
| * <code>addAll</code> operations. |
| * |
| * @return a collection view of the mappings contained in this |
| * <code>IdentityWeakHashMap</code>. |
| */ |
| @Override |
| public Set entrySet() { |
| if (entrySet == null) { |
| entrySet = new AbstractSet() { |
| @Override |
| public Iterator iterator() { |
| return getHashIterator(COMPONENT_TYPES.ENTRIES); |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| if (!(o instanceof Map.Entry)) { |
| return false; |
| } |
| |
| Map.Entry entry = (Map.Entry)o; |
| Object key = entry.getKey(); |
| WeakEntry[] copyOfEntries = entries; |
| int hash = System.identityHashCode(key); |
| int index = (hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index]; e != null; e = e.next) { |
| if ((e.hash == hash) && e.equals(entry)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean remove(Object o) { |
| if (!(o instanceof WeakEntry)) { |
| return false; |
| } |
| WeakEntry entry = (WeakEntry)o; |
| // remove the entry but and increment the modcount |
| // because this is a user action |
| return removeEntry(entry, true); |
| } |
| |
| @Override |
| public int size() { |
| return count; |
| } |
| |
| @Override |
| public void clear() { |
| IdentityWeakHashMap.this.clear(); |
| } |
| }; |
| } |
| return entrySet; |
| } |
| |
| private Iterator getHashIterator(COMPONENT_TYPES type) { |
| if (count == 0) { |
| return emptyHashIterator; |
| } else { |
| return new HashIterator(type); |
| } |
| } |
| |
| /** |
| * IdentityWeakHashMap entry. |
| */ |
| static class WeakEntry<K,V> implements Map.Entry<K,V> { |
| boolean removed = false; |
| int hash; |
| EntryReference<K> key; |
| EntryReference<V> value; |
| WeakEntry<K,V> next; |
| |
| WeakEntry(int hash, K key, V value, WeakEntry<K,V> next, ReferenceQueue refQueue) { |
| this.hash = hash; |
| this.key = new WeakEntryReference<K>(key, refQueue, this); |
| if (key == value){ |
| this.value = (EntryReference<V>)this.key; |
| }else{ |
| this.value = new HardEntryReference<V>(value); |
| } |
| this.next = next; |
| } |
| |
| protected Object clone(ReferenceQueue refQueue) { |
| WeakEntry<K, V> current = this; |
| WeakEntry root = new WeakEntry(current.hash, current.key.get(), current.value.get(), null, refQueue); |
| WeakEntry currentClone = root; |
| |
| while (current.next != null) { |
| currentClone.next = new WeakEntry(current.next.hash, current.next.key.get(), current.next.value.get(), null, refQueue); |
| current = current.next; |
| currentClone = currentClone.next; |
| } |
| |
| return root; |
| } |
| |
| // Map.Entry Ops |
| @Override |
| public K getKey() { |
| return key.get(); |
| } |
| |
| @Override |
| public V getValue() { |
| return value.get(); |
| } |
| |
| @Override |
| public V setValue(V value) { |
| EntryReference<V> oldValue = this.value; |
| if (value == this.key.get()){ |
| this.value = (EntryReference<V>)this.key; |
| }else{ |
| this.value = new HardEntryReference<V>(value); |
| } |
| return oldValue.get(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof Map.Entry)) { |
| return false; |
| } |
| |
| Map.Entry e = (Map.Entry)o; |
| Object v = value.get(); |
| return (key == e.getKey()) && ((v == null) ? (e.getValue() == null) : v.equals(e.getValue())); |
| } |
| |
| @Override |
| public int hashCode() { |
| Object v = value.get(); |
| return hash ^ ((v == null) ? 0 : v.hashCode()); |
| } |
| |
| @Override |
| public String toString() { |
| return key.get() + "=" + value.get(); |
| } |
| |
| public boolean shouldBeIgnored(){ |
| return key.get() == null || value.get() == null; |
| } |
| } |
| |
| interface EntryReference<T> { |
| T get(); |
| } |
| |
| static class WeakEntryReference<T> extends WeakReference<T> implements EntryReference{ |
| protected WeakEntry owner; |
| protected boolean trashed = false; |
| protected ReferenceQueue referenceQueue; |
| |
| public WeakEntryReference(T referent, ReferenceQueue<? super T> q, WeakEntry owner) { |
| super(referent, q); |
| this.owner = owner; |
| this.referenceQueue = q; |
| } |
| } |
| //This limited class is here to allow the value to be switched from a weak reference to a hard |
| // referernce. This Map only makes the key weak but inorder to allow for garbage collection |
| //of the key when the key and the value are the same object the same weak reference will be used |
| static class HardEntryReference<T> implements EntryReference{ |
| protected T referent; |
| |
| public HardEntryReference(T referent){ |
| this.referent = referent; |
| } |
| |
| @Override |
| public T get(){ |
| return referent; |
| } |
| } |
| |
| // Types of Iterators |
| private enum COMPONENT_TYPES {KEYS, VALUES, ENTRIES} |
| |
| private static EmptyHashIterator emptyHashIterator = new EmptyHashIterator(); |
| |
| private static class EmptyHashIterator implements Iterator { |
| EmptyHashIterator() { |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return false; |
| } |
| |
| @Override |
| public Object next() { |
| throw new NoSuchElementException(); |
| } |
| |
| @Override |
| public void remove() { |
| throw new IllegalStateException(); |
| } |
| } |
| |
| private class HashIterator implements Iterator { |
| WeakEntry[] entries = IdentityWeakHashMap.this.entries; |
| int index = entries.length; |
| WeakEntry entry = null; |
| WeakEntry lastReturned = null; |
| COMPONENT_TYPES type; |
| Object currentEntryRef; |
| |
| /** |
| * The modCount value that the iterator believes that the backing |
| * List should have. If this expectation is violated, the iterator |
| * has detected concurrent modification. |
| */ |
| private int expectedModCount = modCount; |
| |
| HashIterator(COMPONENT_TYPES type) { |
| this.type = type; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| WeakEntry e = entry; |
| int i = index; |
| WeakEntry[] copyOfEntries = IdentityWeakHashMap.this.entries; |
| while ((e == null || currentEntryRef == null) && (i > 0)) { |
| e = copyOfEntries[--i]; |
| if (e != null) { |
| currentEntryRef = e.key.get(); |
| }else{ |
| currentEntryRef = null; |
| } |
| } |
| entry = e; |
| index = i; |
| return e != null && currentEntryRef != null; |
| } |
| |
| @Override |
| public Object next() { |
| if (modCount != expectedModCount) { |
| throw new ConcurrentModificationException(); |
| } |
| |
| WeakEntry et = entry; |
| int i = index; |
| WeakEntry[] copyOfEntries = IdentityWeakHashMap.this.entries; |
| while ((et == null || currentEntryRef == null) && (i > 0)) { |
| et = copyOfEntries[--i]; |
| if (et != null) { |
| currentEntryRef = et.key.get(); |
| }else{ |
| currentEntryRef = null; |
| } |
| } |
| entry = et; |
| index = i; |
| if (et != null) { |
| WeakEntry e = lastReturned = entry; |
| entry = e.next; |
| if (entry != null) { |
| currentEntryRef = entry.key.get(); |
| }else{ |
| currentEntryRef = null; |
| } |
| return (type == COMPONENT_TYPES.KEYS) ? e.key.get() : ((type == COMPONENT_TYPES.VALUES) ? e.value.get() : e); |
| } |
| throw new NoSuchElementException(); |
| } |
| |
| @Override |
| public void remove() { |
| if (lastReturned == null) { |
| throw new IllegalStateException(); |
| } |
| if (modCount != expectedModCount) { |
| throw new ConcurrentModificationException(); |
| } |
| |
| WeakEntry[] copyOfEntries = IdentityWeakHashMap.this.entries; |
| int index = (lastReturned.hash & 0x7FFFFFFF) % copyOfEntries.length; |
| for (WeakEntry e = copyOfEntries[index], prev = null; e != null; prev = e, e = e.next) { |
| if (e == lastReturned) { |
| modCount++; |
| expectedModCount++; |
| if (prev == null) { |
| copyOfEntries[index] = e.next; |
| } else { |
| prev.next = e.next; |
| } |
| count--; |
| lastReturned = null; |
| return; |
| } |
| } |
| throw new ConcurrentModificationException(); |
| } |
| } |
| |
| /** |
| * Serialize the state of this <code>IdentityWeakHashMap</code> to a stream. |
| * |
| * @serialData The <i>capacity</i> of the <code>IdentityWeakHashMap</code> |
| * (the length of the bucket array) is emitted (int), followed by the |
| * <i>size</i> of the <code>IdentityWeakHashMap</code>, followed by the |
| * key-value mappings (in no particular order). |
| */ |
| private void writeObject(ObjectOutputStream s) throws IOException { |
| // Write out the threshold, loadfactor (and any hidden 'magic' stuff). |
| s.defaultWriteObject(); |
| |
| // Write out number of buckets |
| s.writeInt(entries.length); |
| // Write out count |
| s.writeInt(count); |
| // Write out contents |
| for (int i = entries.length - 1; i >= 0; i--) { |
| WeakEntry<K, V> entry = entries[i]; |
| while (entry != null) { |
| s.writeObject(entry.key.get()); |
| s.writeObject(entry.value.get()); |
| entry = entry.next; |
| } |
| } |
| } |
| |
| /** |
| * Deserialize the <code>IdentityWeakHashMap</code> from a stream. |
| */ |
| private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { |
| // Read in the threshold, loadfactor (and any hidden 'magic' stuff). |
| s.defaultReadObject(); |
| |
| // Read in number of buckets and allocate the bucket array; |
| int numBuckets = s.readInt(); |
| entries = new WeakEntry[numBuckets]; |
| // Read in size (count) |
| int size = s.readInt(); |
| |
| // Read the mappings and add to the IdentityWeakHashMap |
| for (int i = 0; i < size; i++) { |
| Object key = s.readObject(); |
| Object value = s.readObject(); |
| //only re-add if not null as could have been garbage collected at any time |
| //before the writeObject |
| if (key != null && value != null){ |
| put((K)key, (V)value); |
| } |
| } |
| } |
| } |