WeakCanonicalSet.java Source code

Java tutorial

Introduction

Here is the source code for WeakCanonicalSet.java

Source

/*
 *  Copyright 2004 Brian S O'Neill
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

//revised from cojen

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

import java.util.AbstractSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A thread-safe Set that manages canonical objects: sharable objects that are
 * typically immutable. Call the {@link #put put} method for supplying the
 * WeakCanonicalSet with candidate canonical instances.
 * <p>
 * Objects that do not customize the hashCode and equals methods don't make
 * sense to be canonicalized because each instance will be considered unique.
 * The object returned from the {@link #put put} method will always be the same
 * as the one passed in.
 *
 * @author Brian S O'Neill
 */
public class WeakCanonicalSet<T> extends AbstractSet<T> {
    private Entry<T>[] table;
    private int count;
    private int threshold;
    private final float loadFactor;
    private final ReferenceQueue<T> queue;

    public WeakCanonicalSet() {
        final int initialCapacity = 101;
        final float loadFactor = 0.75f;
        this.loadFactor = loadFactor;
        this.table = new Entry[initialCapacity];
        this.threshold = (int) (initialCapacity * loadFactor);
        this.queue = new ReferenceQueue<T>();
    }

    /**
     * Pass in a candidate canonical object and get a unique instance from this
     * set. The returned object will always be of the same type as that passed
     * in. If the object passed in does not equal any object currently in the
     * set, it will be added to the set, becoming canonical.
     *
     * @param obj candidate canonical object; null is also accepted
     */
    public synchronized <U extends T> U put(U obj) {
        // This implementation is based on the WeakIdentityMap.put method.

        if (obj == null) {
            return null;
        }

        Entry<T>[] tab = this.table;

        // Cleanup after cleared References.
        {
            ReferenceQueue queue = this.queue;
            Reference ref;
            while ((ref = queue.poll()) != null) {
                // Since buckets are single-linked, traverse entire list and
                // cleanup all cleared references in it.
                int index = (((Entry) ref).hash & 0x7fffffff) % tab.length;
                for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
                    if (e.get() == null) {
                        if (prev != null) {
                            prev.next = e.next;
                        } else {
                            tab[index] = e.next;
                        }
                        this.count--;
                    } else {
                        prev = e;
                    }
                }
            }
        }

        int hash = hashCode(obj);
        int index = (hash & 0x7fffffff) % tab.length;

        for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
            T iobj = e.get();
            if (iobj == null) {
                // Clean up after a cleared Reference.
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                this.count--;
            } else if (e.hash == hash && obj.getClass() == iobj.getClass() && equals(obj, iobj)) {
                // Found canonical instance.
                return (U) iobj;
            } else {
                prev = e;
            }
        }

        if (this.count >= this.threshold) {
            // Rehash the table if the threshold is exceeded.
            rehash();
            tab = this.table;
            index = (hash & 0x7fffffff) % tab.length;
        }

        // Create a new entry.
        tab[index] = new Entry<T>(obj, this.queue, hash, tab[index]);
        this.count++;
        return obj;
    }

    public Iterator<T> iterator() {
        return new SetIterator();
    }

    public int size() {
        return this.count;
    }

    public synchronized boolean contains(Object obj) {
        if (obj == null) {
            return false;
        }

        Entry<T>[] tab = this.table;
        int hash = hashCode(obj);
        int index = (hash & 0x7fffffff) % tab.length;

        for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
            Object iobj = e.get();
            if (iobj == null) {
                // Clean up after a cleared Reference.
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                this.count--;
            } else if (e.hash == hash && obj.getClass() == iobj.getClass() && equals(obj, iobj)) {
                // Found canonical instance.
                return true;
            } else {
                prev = e;
            }
        }

        return false;
    }

    protected int hashCode(Object obj) {
        return obj.hashCode();
    }

    protected boolean equals(Object a, Object b) {
        return a.equals(b);
    }

    private void rehash() {
        int oldCapacity = this.table.length;
        Entry<T>[] tab = this.table;

        int newCapacity = oldCapacity * 2 + 1;
        Entry<T>[] newTab = new Entry[newCapacity];

        this.threshold = (int) (newCapacity * this.loadFactor);
        this.table = newTab;

        for (int i = oldCapacity; i-- > 0;) {
            for (Entry<T> old = tab[i]; old != null;) {
                Entry<T> e = old;
                old = old.next;

                // Only copy entry if it hasn't been cleared.
                if (e.get() == null) {
                    this.count--;
                } else {
                    int index = (e.hash & 0x7fffffff) % newCapacity;
                    e.next = newTab[index];
                    newTab[index] = e;
                }
            }
        }
    }

    private static class Entry<T> extends WeakReference<T> {
        int hash;
        Entry<T> next;

        Entry(T canonical, ReferenceQueue<T> queue, int hash, Entry<T> next) {
            super(canonical, queue);
            this.hash = hash;
            this.next = next;
        }
    }

    private class SetIterator implements Iterator<T> {
        private final Entry<T>[] table;

        private int index;

        // To ensure that the iterator doesn't return cleared entries, keep a
        // hard reference to the canonical object. Its existence will prevent
        // the weak reference from being cleared.
        private T entryCanonical;
        private Entry<T> entry;

        SetIterator() {
            this.table = WeakCanonicalSet.this.table;
            this.index = table.length;
        }

        public boolean hasNext() {
            while (this.entry == null || (this.entryCanonical = this.entry.get()) == null) {
                if (this.entry != null) {
                    // Skip past a cleared Reference.
                    this.entry = this.entry.next;
                } else {
                    if (this.index <= 0) {
                        return false;
                    } else {
                        this.entry = this.table[--this.index];
                    }
                }
            }

            return true;
        }

        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }

            this.entry = this.entry.next;
            return this.entryCanonical;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}