2 * Copyright 2004-2010 Brian S O'Neill
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.cojen.util;
19 import java.lang.ref.Reference;
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.WeakReference;
22 import java.util.AbstractSet;
23 import java.util.Iterator;
24 import java.util.NoSuchElementException;
27 * A thread-safe Set that manages canonical objects: sharable objects that are
28 * typically immutable. Call the {@link #put put} method for supplying the
29 * WeakCanonicalSet with candidate canonical instances.
31 * Objects that do not customize the hashCode and equals methods don't make
32 * sense to be canonicalized because each instance will be considered unique.
33 * The object returned from the {@link #put put} method will always be the same
34 * as the one passed in.
36 * @author Brian S O'Neill
38 @SuppressWarnings({ "unchecked", "rawtypes" })
39 public class WeakCanonicalSet<T> extends AbstractSet<T> {
40 private Entry<T>[] table;
42 private int threshold;
43 private final float loadFactor;
44 private final ReferenceQueue<T> queue;
46 public WeakCanonicalSet() {
47 final int initialCapacity = 101;
48 final float loadFactor = 0.75f;
49 this.loadFactor = loadFactor;
50 this.table = new Entry[initialCapacity];
51 this.threshold = (int)(initialCapacity * loadFactor);
52 this.queue = new ReferenceQueue<T>();
56 * Pass in a candidate canonical object and get a unique instance from this
57 * set. The returned object will always be of the same type as that passed
58 * in. If the object passed in does not equal any object currently in the
59 * set, it will be added to the set, becoming canonical.
61 * @param obj candidate canonical object; null is also accepted
63 public synchronized <U extends T> U put(U obj) {
64 // This implementation is based on the WeakIdentityMap.put method.
70 Entry<T>[] tab = this.table;
72 // Cleanup after cleared References.
74 ReferenceQueue queue = this.queue;
76 while ((ref = queue.poll()) != null) {
77 // Since buckets are single-linked, traverse entire list and
78 // cleanup all cleared references in it.
79 int index = (((Entry) ref).hash & 0x7fffffff) % tab.length;
80 for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
81 if (e.get() == null) {
95 int hash = hashCode(obj);
96 int index = (hash & 0x7fffffff) % tab.length;
98 for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
101 // Clean up after a cleared Reference.
108 } else if (e.hash == hash &&
109 obj.getClass() == iobj.getClass() &&
111 // Found canonical instance.
118 if (this.count >= this.threshold) {
119 // Rehash the table if the threshold is exceeded.
122 index = (hash & 0x7fffffff) % tab.length;
125 // Create a new entry.
126 tab[index] = new Entry<T>(obj, this.queue, hash, tab[index]);
131 public Iterator<T> iterator() {
132 return new SetIterator();
139 public synchronized boolean contains(Object obj) {
144 Entry<T>[] tab = this.table;
145 int hash = hashCode(obj);
146 int index = (hash & 0x7fffffff) % tab.length;
148 for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
149 Object iobj = e.get();
151 // Clean up after a cleared Reference.
158 } else if (e.hash == hash &&
159 obj.getClass() == iobj.getClass() &&
161 // Found canonical instance.
171 public synchronized String toString() {
172 return WeakIdentityMap.toString(this);
175 protected int hashCode(Object obj) {
176 return obj.hashCode();
179 protected boolean equals(Object a, Object b) {
183 private void rehash() {
184 int oldCapacity = this.table.length;
185 Entry<T>[] tab = this.table;
187 int newCapacity = oldCapacity * 2 + 1;
188 Entry<T>[] newTab = new Entry[newCapacity];
190 this.threshold = (int)(newCapacity * this.loadFactor);
193 for (int i = oldCapacity; i-- > 0; ) {
194 for (Entry<T> old = tab[i]; old != null; ) {
198 // Only copy entry if it hasn't been cleared.
199 if (e.get() == null) {
202 int index = (e.hash & 0x7fffffff) % newCapacity;
203 e.next = newTab[index];
210 private static class Entry<T> extends WeakReference<T> {
214 Entry(T canonical, ReferenceQueue<T> queue, int hash, Entry<T> next) {
215 super(canonical, queue);
221 private class SetIterator implements Iterator<T> {
222 private final Entry<T>[] table;
226 // To ensure that the iterator doesn't return cleared entries, keep a
227 // hard reference to the canonical object. Its existence will prevent
228 // the weak reference from being cleared.
229 private T entryCanonical;
230 private Entry<T> entry;
233 this.table = WeakCanonicalSet.this.table;
234 this.index = table.length;
237 public boolean hasNext() {
238 while (this.entry == null || (this.entryCanonical = this.entry.get()) == null) {
239 if (this.entry != null) {
240 // Skip past a cleared Reference.
241 this.entry = this.entry.next;
243 if (this.index <= 0) {
246 this.entry = this.table[--this.index];
256 throw new NoSuchElementException();
259 this.entry = this.entry.next;
260 return this.entryCanonical;
263 public void remove() {
264 throw new UnsupportedOperationException();