/* * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package java.beans; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; /** * Hash table based mapping, which uses weak references to store keys * and reference-equality in place of object-equality to compare them. * An entry will automatically be removed when its key is no longer * in ordinary use. Both null values and the null key are supported. * This class does not require additional synchronization. * A thread-safety is provided by a fragile combination * of synchronized blocks and volatile fields. * Be very careful during editing! * * @see java.util.IdentityHashMap * @see java.util.WeakHashMap */ abstract class WeakIdentityMap { private static final int MAXIMUM_CAPACITY = 1 << 30; // it MUST be a power of two private static final Object NULL = new Object(); // special object for null key private final ReferenceQueue queue = new ReferenceQueue(); private volatile Entry[] table = newTable(1<<3); // table's length MUST be a power of two private int threshold = 6; // the next size value at which to resize private int size = 0; // the number of key-value mappings public T get(Object key) { removeStaleEntries(); if (key == null) { key = NULL; } int hash = key.hashCode(); Entry[] table = this.table; // unsynchronized search improves performance // the null value does not mean that there are no needed entry int index = getIndex(table, hash); for (Entry entry = table[index]; entry != null; entry = entry.next) { if (entry.isMatched(key, hash)) { return entry.value; } } synchronized (NULL) { // synchronized search improves stability // we must create and add new value if there are no needed entry index = getIndex(this.table, hash); for (Entry entry = this.table[index]; entry != null; entry = entry.next) { if (entry.isMatched(key, hash)) { return entry.value; } } T value = create(key); this.table[index] = new Entry(key, hash, value, this.queue, this.table[index]); if (++this.size >= this.threshold) { if (this.table.length == MAXIMUM_CAPACITY) { this.threshold = Integer.MAX_VALUE; } else { removeStaleEntries(); table = newTable(this.table.length * 2); transfer(this.table, table); // If ignoring null elements and processing ref queue caused massive // shrinkage, then restore old table. This should be rare, but avoids // unbounded expansion of garbage-filled tables. if (this.size >= this.threshold / 2) { this.table = table; this.threshold *= 2; } else { transfer(table, this.table); } } } return value; } } protected abstract T create(Object key); private void removeStaleEntries() { Object ref = this.queue.poll(); if (ref != null) { synchronized (NULL) { do { @SuppressWarnings("unchecked") Entry entry = (Entry) ref; int index = getIndex(this.table, entry.hash); Entry prev = this.table[index]; Entry current = prev; while (current != null) { Entry next = current.next; if (current == entry) { if (prev == entry) { this.table[index] = next; } else { prev.next = next; } entry.value = null; // Help GC entry.next = null; // Help GC this.size--; break; } prev = current; current = next; } ref = this.queue.poll(); } while (ref != null); } } } private void transfer(Entry[] oldTable, Entry[] newTable) { for (int i = 0; i < oldTable.length; i++) { Entry entry = oldTable[i]; oldTable[i] = null; while (entry != null) { Entry next = entry.next; Object key = entry.get(); if (key == null) { entry.value = null; // Help GC entry.next = null; // Help GC this.size--; } else { int index = getIndex(newTable, entry.hash); entry.next = newTable[index]; newTable[index] = entry; } entry = next; } } } @SuppressWarnings("unchecked") private Entry[] newTable(int length) { return (Entry[]) new Entry[length]; } private static int getIndex(Entry[] table, int hash) { return hash & (table.length - 1); } private static class Entry extends WeakReference { private final int hash; private volatile T value; private volatile Entry next; Entry(Object key, int hash, T value, ReferenceQueue queue, Entry next) { super(key, queue); this.hash = hash; this.value = value; this.next = next; } boolean isMatched(Object key, int hash) { return (this.hash == hash) && (key == get()); } } }