]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scl.compiler/src/org/cojen/util/WeakCanonicalSet.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / cojen / util / WeakCanonicalSet.java
diff --git a/bundles/org.simantics.scl.compiler/src/org/cojen/util/WeakCanonicalSet.java b/bundles/org.simantics.scl.compiler/src/org/cojen/util/WeakCanonicalSet.java
new file mode 100644 (file)
index 0000000..3e8620f
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ *  Copyright 2004-2010 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.
+ */
+
+package org.cojen.util;
+
+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
+ */
+@SuppressWarnings({ "unchecked", "rawtypes" })
+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;
+    }
+
+    public synchronized String toString() {
+        return WeakIdentityMap.toString(this);
+    }
+
+    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();
+        }
+    }
+}