Return-Path: Delivered-To: apmail-incubator-open-jpa-commits-archive@locus.apache.org Received: (qmail 2583 invoked from network); 28 Jun 2006 19:36:30 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 28 Jun 2006 19:36:30 -0000 Received: (qmail 30115 invoked by uid 500); 28 Jun 2006 19:36:30 -0000 Delivered-To: apmail-incubator-open-jpa-commits-archive@incubator.apache.org Received: (qmail 30096 invoked by uid 500); 28 Jun 2006 19:36:29 -0000 Mailing-List: contact open-jpa-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: open-jpa-dev@incubator.apache.org Delivered-To: mailing list open-jpa-commits@incubator.apache.org Received: (qmail 30086 invoked by uid 99); 28 Jun 2006 19:36:29 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 28 Jun 2006 12:36:29 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 28 Jun 2006 12:36:21 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id 5327A1A9874; Wed, 28 Jun 2006 12:35:12 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r417856 [19/22] - in /incubator/openjpa/trunk/openjpa-lib: java/ main/ main/java/ main/java/org/apache/openjpa/lib/ant/ main/java/org/apache/openjpa/lib/conf/ main/java/org/apache/openjpa/lib/jdbc/ main/java/org/apache/openjpa/lib/log/ main... Date: Wed, 28 Jun 2006 19:34:40 -0000 To: open-jpa-commits@incubator.apache.org From: pcl@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060628193512.5327A1A9874@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashMap.java URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashMap.java?rev=417856&view=auto ============================================================================== --- incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashMap.java (added) +++ incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashMap.java Wed Jun 28 12:34:33 2006 @@ -0,0 +1,1161 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * 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.apache.openjpa.lib.util.concurrent; + +import org.apache.openjpa.lib.util.ReferenceMap; +import org.apache.openjpa.lib.util.SizedMap; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; + + +/** + * This class implements a HashMap which has limited synchronization + * and reference keys or values (but not both). In particular mutators are + * generally synchronized while accessors are generally not. Additionally the + * Iterators returned by this class are not "fail-fast", but instead try to + * continue to iterate over the data structure after changes have been + * made. Finally purging of the reference queue is only done inside + * mutators. + * + * Null keys are not supported if keys use references. Null values are not + * supported if values use references. + * + * This class is based heavily on the WeakHashMap class in the Java + * collections package. + */ +public class ConcurrentReferenceHashMap extends AbstractMap + implements ConcurrentMap, ReferenceMap, SizedMap, Cloneable { + /** + * Cache of random numbers used in "random" methods, since generating them + * is expensive. We hope each map changes enough between cycling through + * this list that the overall effect is random enough. + */ + static final double[] RANDOMS = new double[1000]; + + static { + Random random = new Random(); + + for (int i = 0; i < RANDOMS.length; i++) + RANDOMS[i] = random.nextDouble(); + } + + // Types of Enumerations/Iterations + private static final int KEYS = 0; + private static final int VALUES = 1; + private static final int ENTRIES = 2; + + /** + * The hash table data. + */ + private transient Entry[] table; + + /** + * The total number of entries in the hash table. + */ + private transient int count; + + /** + * Rehashes the table when count exceeds this threshold. + */ + private int threshold; + + /** + * The load factor for the HashMap. + */ + private float loadFactor; + + /** + * The key reference type. + */ + private int keyType; + + /** + * The value reference type. + */ + private int valueType; + + /** + * Reference queue for cleared Entries + */ + private final ReferenceQueue queue = new ReferenceQueue(); + + /** + * Spread "random" removes and iteration. + */ + private int randomEntry = 0; + + /** + * Maximum entries. + */ + private int maxSize = Integer.MAX_VALUE; + + // Views + private transient Set keySet = null; + private transient Set entrySet = null; + private transient Collection values = null; + + /** + * Constructs a new, empty HashMap with the specified initial + * capacity and the specified load factor. + * + * @param keyType the reference type of map keys + * @param valueType the reference type of map values + * @param initialCapacity the initial capacity of the HashMap. + * @param loadFactor a number between 0.0 and 1.0. + * @exception IllegalArgumentException if neither keys nor values use hard + * references, if the initial capacity is less than or equal to zero, or if + * the load factor is less than or equal to zero + */ + public ConcurrentReferenceHashMap(int keyType, int valueType, + int initialCapacity, float loadFactor) { + if (initialCapacity < 0) { + throw new IllegalArgumentException("Illegal Initial Capacity: " + + initialCapacity); + } + + if ((loadFactor > 1) || (loadFactor <= 0)) { + throw new IllegalArgumentException("Illegal Load factor: " + + loadFactor); + } + + if ((keyType != HARD) && (valueType != HARD)) { + throw new IllegalArgumentException("Either keys or values must " + + "use hard references."); + } + + this.keyType = keyType; + this.valueType = valueType; + this.loadFactor = loadFactor; + table = new Entry[initialCapacity]; + threshold = (int) (initialCapacity * loadFactor); + } + + /** + * Constructs a new, empty HashMap with the specified initial capacity + * and default load factor. + * + * @param keyType the reference type of map keys + * @param valueType the reference type of map values + * @param initialCapacity the initial capacity of the HashMap. + */ + public ConcurrentReferenceHashMap(int keyType, int valueType, + int initialCapacity) { + this(keyType, valueType, initialCapacity, 0.75f); + } + + /** + * Constructs a new, empty HashMap with a default capacity and load + * factor. + * + * @param keyType the reference type of map keys + * @param valueType the reference type of map values + */ + public ConcurrentReferenceHashMap(int keyType, int valueType) { + this(keyType, valueType, 11, 0.75f); + } + + /** + * Constructs a new HashMap with the same mappings as the given + * Map. The HashMap is created with a capacity of thrice the number + * of entries in the given Map or 11 (whichever is greater), and a + * default load factor. + * + * @param keyType the reference type of map keys + * @param valueType the reference type of map values + */ + public ConcurrentReferenceHashMap(int keyType, int valueType, Map t) { + this(keyType, valueType, Math.max(3 * t.size(), 11), 0.75f); + putAll(t); + } + + private static boolean eq(Object x, Object y) { + return (x == y) || ((x != null) && x.equals(y)); + } + + public int getMaxSize() { + return maxSize; + } + + public void setMaxSize(int maxSize) { + this.maxSize = (maxSize < 0) ? Integer.MAX_VALUE : maxSize; + + if (this.maxSize != Integer.MAX_VALUE) { + removeOverflow(this.maxSize); + } + } + + public boolean isFull() { + return (maxSize != Integer.MAX_VALUE) && (size() >= maxSize); + } + + public void overflowRemoved(Object key, Object value) { + } + + /** + * Returns the number of key-value mappings in this Map. This + * result is a snapshot, and may not reflect unprocessed entries + * that will be removed before next attempted access because they + * are no longer referenced. + */ + public int size() { + return count; + } + + /** + * Returns true if this Map contains no key-value mappings. This + * result is a snapshot, and may not reflect unprocessed entries + * that will be removed before next attempted access because they + * are no longer referenced. + */ + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns true if this HashMap maps one or more keys to the specified + * value. + * + * @param value value whose presence in this Map is to be tested. + */ + public boolean containsValue(Object value) { + Entry[] tab = table; + + if (value == null) { + if (valueType != HARD) { + return false; + } + + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.getNext()) + if (e.getValue() == null) { + return true; + } + } else { + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.getNext()) + if (eq(value, e.getValue())) { + return true; + } + } + + return false; + } + + /** + * Returns true if this HashMap contains a mapping for the specified key. + * + * @param key key whose presence in this Map is to be tested. + */ + public boolean containsKey(Object key) { + if ((key == null) && (keyType != HARD)) { + return false; + } + + Entry[] tab = table; + int hash = (key == null) ? 0 : key.hashCode(); + int index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index]; e != null; e = e.getNext()) + if ((e.getHash() == hash) && eq(key, e.getKey())) { + return true; + } + + return false; + } + + /** + * Returns the value to which this HashMap maps the specified key. + * Returns null if the HashMap contains no mapping for this key. + * + * @param key key whose associated value is to be returned. + */ + public Object get(Object key) { + if ((key == null) && (keyType != HARD)) { + return null; + } + + Entry[] tab = table; + int hash = (key == null) ? 0 : key.hashCode(); + int index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index]; e != null; e = e.getNext()) + if ((e.getHash() == hash) && eq(key, e.getKey())) { + return e.getValue(); + } + + return null; + } + + /** + * Rehashes the contents of the HashMap into a HashMap with a + * larger capacity. This method is called automatically when the + * number of keys in the HashMap exceeds this HashMap's capacity + * and load factor. + */ + private void rehash() { + int oldCapacity = table.length; + Entry[] oldMap = table; + + int newCapacity = (oldCapacity * 2) + 1; + Entry[] newMap = new Entry[newCapacity]; + + for (int i = oldCapacity; i-- > 0;) { + for (Entry old = oldMap[i]; old != null;) { + if (((keyType != HARD) && (old.getKey() == null)) || + ((valueType != HARD) && (old.getValue() == null))) { + Entry e = old; + old = old.getNext(); + e.setNext(null); + count--; + } else { + Entry e = (Entry) old.clone(queue); + old = old.getNext(); + + int index = (e.getHash() & 0x7FFFFFFF) % newCapacity; + e.setNext(newMap[index]); + newMap[index] = e; + } + } + } + + threshold = (int) (newCapacity * loadFactor); + table = newMap; + } + + /** + * Associates the specified value with the specified key in this HashMap. + * If the HashMap previously contained a mapping for this key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * @return previous value associated with specified key, or null if there + * was no mapping for key. A null return can also indicate that + * the HashMap previously associated null with the specified key. + */ + public Object put(Object key, Object value) { + if (((key == null) && (keyType != HARD)) || + ((value == null) && (valueType != HARD))) { + throw new IllegalArgumentException("Null references not supported"); + } + + int hash = (key == null) ? 0 : key.hashCode(); + + synchronized (this) { + expungeStaleEntries(); + + Entry[] tab = table; + int index = 0; + + index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index], prev = null; e != null; + prev = e, e = e.getNext()) { + if ((e.getHash() == hash) && eq(key, e.getKey())) { + Object old = e.getValue(); + + if (valueType == HARD) { + e.setValue(value); + } else { + e = newEntry(hash, e.getKey(), value, e.getNext()); + + if (prev == null) { + tab[index] = e; + } else { + prev.setNext(e); + } + } + + return old; + } + } + + if (count >= threshold) { + // Rehash the table if the threshold is exceeded + rehash(); + + tab = table; + index = (hash & 0x7FFFFFFF) % tab.length; + } + + if (maxSize != Integer.MAX_VALUE) { + removeOverflow(maxSize - 1); + } + + tab[index] = newEntry(hash, key, value, tab[index]); + count++; + } + + return null; + } + + /** + * Creates a new entry. + */ + private Entry newEntry(int hash, Object key, Object value, Entry next) { + int refType = (keyType != HARD) ? keyType : valueType; + + switch (refType) { + case WEAK: + return new WeakEntry(hash, key, value, refType == keyType, next, + queue); + + case SOFT: + return new SoftEntry(hash, key, value, refType == keyType, next, + queue); + + default: + return new HardEntry(hash, key, value, next); + } + } + + /** + * Remove any entries equal to or over the max size. + */ + private void removeOverflow(int maxSize) { + while (count > maxSize) { + Map.Entry entry = removeRandom(); + + if (entry == null) { + break; + } + + overflowRemoved(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes the mapping for this key from this HashMap if present. + * + * @param key key whose mapping is to be removed from the Map. + * @return previous value associated with specified key, or null if there + * was no mapping for key. A null return can also indicate that + * the HashMap previously associated null with the specified key. + */ + public Object remove(Object key) { + if ((key == null) && (keyType != HARD)) { + return null; + } + + int hash = (key == null) ? 0 : key.hashCode(); + + synchronized (this) { + expungeStaleEntries(); + + Entry[] tab = table; + + int index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index], prev = null; e != null; + prev = e, e = e.getNext()) { + if ((e.getHash() == hash) && eq(key, e.getKey())) { + if (prev != null) { + prev.setNext(e.getNext()); + } + // otherwise put the bucket after us + else { + tab[index] = e.getNext(); + } + + count--; + + return e.getValue(); + } + } + } + + return null; + } + + public void removeExpired() { + synchronized (this) { + expungeStaleEntries(); + } + } + + public void keyExpired(Object value) { + } + + public void valueExpired(Object key) { + } + + /** + * Return an arbitrary entry index. + */ + private int randomEntryIndex() { + if (randomEntry == RANDOMS.length) { + randomEntry = 0; + } + + return (int) (RANDOMS[randomEntry++] * table.length); + } + + public Map.Entry removeRandom() { + synchronized (this) { + expungeStaleEntries(); + + if (count == 0) { + return null; + } + + int random = randomEntryIndex(); + int index = findEntry(random, (random % 2) == 0, false); + + if (index == -1) { + return null; + } + + Entry rem = table[index]; + table[index] = rem.getNext(); + count--; + + return rem; + } + } + + /** + * Find the index of the entry nearest the given index, starting in the + * given direction. + */ + private int findEntry(int start, boolean forward, boolean searchedOther) { + if (forward) { + for (int i = start; i < table.length; i++) + if (table[i] != null) { + return i; + } + + return (searchedOther || (start == 0)) ? (-1) + : findEntry(start - 1, + false, true); + } else { + for (int i = start; i >= 0; i--) + if (table[i] != null) { + return i; + } + + return (searchedOther || (start == (table.length - 1))) ? (-1) + : findEntry(start + + 1, true, true); + } + } + + public Iterator randomEntryIterator() { + // pass index so calculated before iterator refs table, in case table + // gets replace with a larger one + return new HashIterator(ENTRIES, randomEntryIndex()); + } + + /** + * Copies all of the mappings from the specified Map to this HashMap + * These mappings will replace any mappings that this HashMap had for any + * of the keys currently in the specified Map. + * + * @param t Mappings to be stored in this Map. + */ + public void putAll(Map t) { + Iterator i = t.entrySet().iterator(); + + while (i.hasNext()) { + Map.Entry e = (Map.Entry) i.next(); + put(e.getKey(), e.getValue()); + } + } + + /** + * Removes all mappings from this HashMap. + */ + public synchronized void clear() { + // clear out ref queue. We don't need to expunge entries + // since table is getting cleared. + while (queue.poll() != null) + ; + + table = new Entry[table.length]; + count = 0; + + // Allocation of array may have caused GC, which may have caused + // additional entries to go stale. Removing these entries from + // the reference queue will make them eligible for reclamation. + while (queue.poll() != null) + ; + } + + /** + * Returns a shallow copy of this HashMap. The keys and values + * themselves are not cloned. + */ + public synchronized Object clone() { + try { + expungeStaleEntries(); + + ConcurrentReferenceHashMap t = (ConcurrentReferenceHashMap) super.clone(); + t.table = new Entry[table.length]; + + for (int i = table.length; i-- > 0;) { + Entry e = table[i]; + + if (e != null) { + t.table[i] = (Entry) e.clone(t.queue); + e = e.getNext(); + + for (Entry k = t.table[i]; e != null; e = e.getNext()) { + k.setNext((Entry) e.clone(t.queue)); + k = k.getNext(); + } + } + } + + t.keySet = null; + t.entrySet = null; + t.values = null; + + return t; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + /** + * Returns a Set view of the keys contained in this HashMap. The Set is + * backed by the HashMap, so changes to the HashMap are reflected in the + * Set, and vice-versa. The Set supports element removal, which removes + * the corresponding mapping from the HashMap, via the Iterator.remove, + * Set.remove, removeAll retainAll, and clear operations. It does not + * support the add or addAll operations. + */ + public Set keySet() { + if (keySet == null) { + keySet = new java.util.AbstractSet() { + public Iterator iterator() { + return new HashIterator(KEYS, table.length - 1); + } + + public int size() { + return count; + } + + public boolean contains(Object o) { + return containsKey(o); + } + + public boolean remove(Object o) { + return ConcurrentReferenceHashMap.this.remove(o) != null; + } + + public void clear() { + ConcurrentReferenceHashMap.this.clear(); + } + }; + } + + return keySet; + } + + /** + * Returns a Collection view of the values contained in this HashMap. + * The Collection is backed by the HashMap, so changes to the HashMap are + * reflected in the Collection, and vice-versa. The Collection supports + * element removal, which removes the corresponding mapping from the + * HashMap, via the Iterator.remove, Collection.remove, removeAll, + * retainAll and clear operations. It does not support the add or addAll + * operations. + */ + public Collection values() { + if (values == null) { + values = new java.util.AbstractCollection() { + public Iterator iterator() { + return new HashIterator(VALUES, table.length - 1); + } + + public int size() { + return count; + } + + public boolean contains(Object o) { + return containsValue(o); + } + + public void clear() { + ConcurrentReferenceHashMap.this.clear(); + } + }; + } + + return values; + } + + /** + * Returns a Collection view of the mappings contained in this HashMap. + * Each element in the returned collection is a Map.Entry. The Collection + * is backed by the HashMap, so changes to the HashMap are reflected in the + * Collection, and vice-versa. The Collection supports element removal, + * which removes the corresponding mapping from the HashMap, via the + * Iterator.remove, Collection.remove, removeAll, retainAll and clear + * operations. It does not support the add or addAll operations. + * + * @see Map.Entry + */ + public Set entrySet() { + if (entrySet == null) { + entrySet = new java.util.AbstractSet() { + public Iterator iterator() { + return new HashIterator(ENTRIES, table.length - 1); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + Map.Entry entry = (Map.Entry) o; + Object key = entry.getKey(); + Entry[] tab = table; + int hash = ((key == null) ? 0 : key.hashCode()); + int index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index]; e != null; + e = e.getNext()) + if ((e.getHash() == hash) && eq(e, entry)) { + return true; + } + + return false; + } + + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + Map.Entry entry = (Map.Entry) o; + Object key = entry.getKey(); + + synchronized (ConcurrentReferenceHashMap.this) { + Entry[] tab = table; + int hash = ((key == null) ? 0 : key.hashCode()); + int index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index], prev = null; + e != null; prev = e, e = e.getNext()) { + if ((e.getHash() == hash) && eq(e, entry)) { + if (prev != null) { + prev.setNext(e.getNext()); + } else { + tab[index] = e.getNext(); + } + + count--; + + return true; + } + } + + return false; + } + } + + public int size() { + return count; + } + + public void clear() { + ConcurrentReferenceHashMap.this.clear(); + } + }; + } + + return entrySet; + } + + /** + * Expunge stale entries from the table. + */ + private void expungeStaleEntries() { + Object r; + + while ((r = queue.poll()) != null) { + Entry entry = (Entry) r; + int hash = entry.getHash(); + Entry[] tab = table; + int index = (hash & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index], prev = null; e != null; + prev = e, e = e.getNext()) { + if (e == entry) { + if (prev != null) { + prev.setNext(e.getNext()); + } + // otherwise put the bucket after us + else { + tab[index] = e.getNext(); + } + + count--; + + if (keyType == HARD) { + valueExpired(e.getKey()); + } else { + keyExpired(e.getValue()); + } + } + } + } + } + + int capacity() { + return table.length; + } + + float loadFactor() { + return loadFactor; + } + + /** + * HashMap collision list entry. + */ + private static interface Entry extends Map.Entry { + public int getHash(); + + public Entry getNext(); + + public void setNext(Entry next); + + public Object clone(ReferenceQueue queue); + } + + /** + * Hard entry. + */ + private static class HardEntry implements Entry { + private int hash; + private Object key; + private Object value; + private Entry next; + + HardEntry(int hash, Object key, Object value, Entry next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + public int getHash() { + return hash; + } + + public Entry getNext() { + return next; + } + + public void setNext(Entry next) { + this.next = next; + } + + public Object clone(ReferenceQueue queue) { + // It is the callers responsibility to set the next field + // correctly. + return new HardEntry(hash, key, value, null); + } + + // Map.Entry Ops + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object value) { + Object oldValue = this.value; + this.value = value; + + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + Map.Entry e = (Map.Entry) o; + + Object k1 = key; + Object k2 = e.getKey(); + + return ((k1 == null) ? (k2 == null) : eq(k1, k2)) && + ((value == null) ? (e.getValue() == null) : eq(value, e.getValue())); + } + + public int hashCode() { + return hash ^ ((value == null) ? 0 : value.hashCode()); + } + + public String toString() { + return key + "=" + value.toString(); + } + } + + /** + * Weak entry. + */ + private static class WeakEntry extends WeakReference implements Entry { + private int hash; + private Object hard; + private boolean keyRef; + private Entry next; + + WeakEntry(int hash, Object key, Object value, boolean keyRef, + Entry next, ReferenceQueue queue) { + super((keyRef) ? key : value, queue); + this.hash = hash; + this.hard = (keyRef) ? value : key; + this.keyRef = keyRef; + this.next = next; + } + + public int getHash() { + return hash; + } + + public Entry getNext() { + return next; + } + + public void setNext(Entry next) { + this.next = next; + } + + public Object clone(ReferenceQueue queue) { + // It is the callers responsibility to set the next field + // correctly. + return new WeakEntry(hash, getKey(), getValue(), keyRef, null, queue); + } + + // Map.Entry Ops + public Object getKey() { + return (keyRef) ? super.get() : hard; + } + + public Object getValue() { + return (keyRef) ? hard : super.get(); + } + + public Object setValue(Object value) { + if (!keyRef) { + throw new Error("Attempt to reset reference value."); + } + + Object oldValue = hard; + hard = value; + + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + Map.Entry e = (Map.Entry) o; + + return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue()); + } + + public int hashCode() { + Object val = getValue(); + + return hash ^ ((val == null) ? 0 : val.hashCode()); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } + + /** + * Soft entry. + */ + private static class SoftEntry extends SoftReference implements Entry { + private int hash; + private Object hard; + private boolean keyRef; + private Entry next; + + SoftEntry(int hash, Object key, Object value, boolean keyRef, + Entry next, ReferenceQueue queue) { + super((keyRef) ? key : value, queue); + this.hash = hash; + this.hard = (keyRef) ? value : key; + this.keyRef = keyRef; + this.next = next; + } + + public int getHash() { + return hash; + } + + public Entry getNext() { + return next; + } + + public void setNext(Entry next) { + this.next = next; + } + + public Object clone(ReferenceQueue queue) { + // It is the callers responsibility to set the next field + // correctly. + return new SoftEntry(hash, getKey(), getValue(), keyRef, null, queue); + } + + // Map.Entry Ops + public Object getKey() { + return (keyRef) ? super.get() : hard; + } + + public Object getValue() { + return (keyRef) ? hard : super.get(); + } + + public Object setValue(Object value) { + if (!keyRef) { + throw new Error("Attempt to reset reference value."); + } + + Object oldValue = hard; + hard = value; + + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + Map.Entry e = (Map.Entry) o; + + return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue()); + } + + public int hashCode() { + Object val = getValue(); + + return hash ^ ((val == null) ? 0 : val.hashCode()); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } + + /** + * Map iterator. + */ + private class HashIterator implements Iterator { + final Entry[] table = ConcurrentReferenceHashMap.this.table; + final int type; + int startIndex; + int stopIndex = 0; + int index; + Entry entry = null; + Entry lastReturned = null; + + HashIterator(int type, int startIndex) { + this.type = type; + this.startIndex = startIndex; + index = startIndex; + } + + public boolean hasNext() { + if (entry != null) { + return true; + } + + while (index >= stopIndex) { + if ((entry = table[index--]) != null) { + return true; + } + } + + if (stopIndex == 0) { + index = table.length - 1; + stopIndex = startIndex + 1; + + while (index >= stopIndex) { + if ((entry = table[index--]) != null) { + return true; + } + } + } + + return false; + } + + public Object next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + Entry e = lastReturned = entry; + entry = e.getNext(); + + return (type == KEYS) ? e.getKey() + : ((type == VALUES) ? e.getValue() : e); + } + + public void remove() { + if (lastReturned == null) { + throw new IllegalStateException(); + } + + synchronized (ConcurrentReferenceHashMap.this) { + Entry[] tab = ConcurrentReferenceHashMap.this.table; + int index = (lastReturned.getHash() & 0x7FFFFFFF) % tab.length; + + for (Entry e = tab[index], prev = null; e != null; + prev = e, e = e.getNext()) { + if (e == lastReturned) { + if (prev == null) { + tab[index] = e.getNext(); + } else { + prev.setNext(e.getNext()); + } + + count--; + lastReturned = null; + + return; + } + } + + throw new Error("Iterated off table when doing remove"); + } + } + } +} Propchange: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashMap.java ------------------------------------------------------------------------------ svn:executable = * Added: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashSet.java URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashSet.java?rev=417856&view=auto ============================================================================== --- incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashSet.java (added) +++ incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashSet.java Wed Jun 28 12:34:33 2006 @@ -0,0 +1,132 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * 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.apache.openjpa.lib.util.concurrent; + +import org.apache.commons.collections.map.*; +import org.apache.commons.collections.set.*; + +import java.io.*; + +import java.util.*; + + +/** + *

A concurrent set whose values may be stored as weak or soft + * references.

+ * + * @author Abe White + * @nojavadoc */ +public class ConcurrentReferenceHashSet implements Set, Serializable { + /** + * Hard reference marker. + */ + public static final int HARD = 0; + + /** + * Soft reference marker. + */ + public static final int SOFT = 1; + + /** + * Weak reference marker. + */ + public static final int WEAK = 2; + private static final Object DUMMY_VAL = new Object(); + private final Set _set; + + /** + * Construct a set with the given reference type. + */ + public ConcurrentReferenceHashSet(int refType) { + if (refType == HARD) { + _set = MapBackedSet.decorate(new ConcurrentHashMap(), DUMMY_VAL); + } else { + int mapRefType = (refType == WEAK) + ? ConcurrentReferenceHashMap.WEAK + : ConcurrentReferenceHashMap.SOFT; + _set = MapBackedSet.decorate(new ConcurrentReferenceHashMap( + mapRefType, ConcurrentReferenceHashMap.HARD), DUMMY_VAL); + } + } + + public boolean add(Object obj) { + return _set.add(obj); + } + + public boolean addAll(Collection coll) { + return _set.addAll(coll); + } + + public void clear() { + _set.clear(); + } + + public boolean contains(Object obj) { + return _set.contains(obj); + } + + public boolean containsAll(Collection coll) { + return _set.containsAll(coll); + } + + public boolean isEmpty() { + return _set.isEmpty(); + } + + public Iterator iterator() { + return _set.iterator(); + } + + public boolean remove(Object obj) { + return _set.remove(obj); + } + + public boolean removeAll(Collection coll) { + return _set.removeAll(coll); + } + + public boolean retainAll(Collection coll) { + return _set.retainAll(coll); + } + + public int size() { + return _set.size(); + } + + public Object[] toArray() { + return _set.toArray(); + } + + public Object[] toArray(Object[] arr) { + return _set.toArray(arr); + } + + public int hashCode() { + return _set.hashCode(); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof ConcurrentReferenceHashSet) { + obj = ((ConcurrentReferenceHashSet) obj)._set; + } + + return _set.equals(obj); + } +} Propchange: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/ConcurrentReferenceHashSet.java ------------------------------------------------------------------------------ svn:executable = * Added: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/CondVar.java URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/CondVar.java?rev=417856&view=auto ============================================================================== --- incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/CondVar.java (added) +++ incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/CondVar.java Wed Jun 28 12:34:33 2006 @@ -0,0 +1,238 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * 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. + */ + +/* + * Originally written by Doug Lea and released into the public domain. + * This may be used for any purposes whatsoever without acknowledgment. + * Thanks for the assistance and support of Sun Microsystems Labs, + * and everyone contributing, testing, and using this code. + */ +package org.apache.openjpa.lib.util.concurrent; + +import java.util.*; + + +class CondVar implements Condition, java.io.Serializable { + /** The lock */ + protected final ExclusiveLock lock; + + /** + * Create a new CondVar that relies on the given mutual + * exclusion lock. + * @param lock A non-reentrant mutual exclusion lock. + */ + CondVar(ExclusiveLock lock) { + this.lock = lock; + } + + public void awaitUninterruptibly() { + int holdCount = lock.getHoldCount(); + + if (holdCount == 0) { + throw new IllegalMonitorStateException(); + } + + // avoid instant spurious wakeup if thread already interrupted + boolean wasInterrupted = Thread.interrupted(); + + try { + synchronized (this) { + for (int i = holdCount; i > 0; i--) + lock.unlock(); + + while (true) { + try { + wait(); + + break; + } catch (InterruptedException ex) { + wasInterrupted = true; + // may have masked the signal and there is no way + // to tell; defensively propagate the signal + notify(); + } + } + } + } finally { + for (int i = holdCount; i > 0; i--) + lock.lock(); + + if (wasInterrupted) { + Thread.currentThread().interrupt(); + } + } + } + + public void await() throws InterruptedException { + int holdCount = lock.getHoldCount(); + + if (holdCount == 0) { + throw new IllegalMonitorStateException(); + } + + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + try { + synchronized (this) { + for (int i = holdCount; i > 0; i--) + lock.unlock(); + + try { + wait(); + } catch (InterruptedException ex) { + notify(); + throw ex; + } + } + } finally { + for (int i = holdCount; i > 0; i--) + lock.lock(); + } + } + + public boolean await(long timeout, TimeUnit unit) + throws InterruptedException { + int holdCount = lock.getHoldCount(); + + if (holdCount == 0) { + throw new IllegalMonitorStateException(); + } + + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + long nanos = unit.toNanos(timeout); + boolean success = false; + + try { + synchronized (this) { + for (int i = holdCount; i > 0; i--) + lock.unlock(); + + try { + if (nanos > 0) { + long start = Utils.nanoTime(); + TimeUnit.NANOSECONDS.timedWait(this, nanos); + // DK: due to coarse-grained (millis) clock, it seems + // preferable to acknowledge timeout (success == false) + // when the equality holds (timing is exact) + success = (Utils.nanoTime() - start) < nanos; + } + } catch (InterruptedException ex) { + notify(); + throw ex; + } + } + } finally { + for (int i = holdCount; i > 0; i--) + lock.lock(); + } + + return success; + } + + // public long awaitNanos(long timeout) throws InterruptedException { + // throw new UnsupportedOperationException(); + // } + public boolean awaitUntil(Date deadline) throws InterruptedException { + if (deadline == null) { + throw new NullPointerException(); + } + + int holdCount = lock.getHoldCount(); + + if (holdCount == 0) { + throw new IllegalMonitorStateException(); + } + + long abstime = deadline.getTime(); + + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + boolean success = false; + + try { + synchronized (this) { + for (int i = holdCount; i > 0; i--) + lock.unlock(); + + try { + long start = System.currentTimeMillis(); + long msecs = abstime - start; + + if (msecs > 0) { + wait(msecs); + // DK: due to coarse-grained (millis) clock, it seems + // preferable to acknowledge timeout (success == false) + // when the equality holds (timing is exact) + success = (System.currentTimeMillis() - start) < msecs; + } + } catch (InterruptedException ex) { + notify(); + throw ex; + } + } + } finally { + for (int i = holdCount; i > 0; i--) + lock.lock(); + } + + return success; + } + + public synchronized void signal() { + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + + notify(); + } + + public synchronized void signalAll() { + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + + notifyAll(); + } + + protected ExclusiveLock getLock() { + return lock; + } + + protected boolean hasWaiters() { + throw new UnsupportedOperationException("Use FAIR version"); + } + + protected int getWaitQueueLength() { + throw new UnsupportedOperationException("Use FAIR version"); + } + + protected Collection getWaitingThreads() { + throw new UnsupportedOperationException("Use FAIR version"); + } + + static interface ExclusiveLock extends Lock { + boolean isHeldByCurrentThread(); + + int getHoldCount(); + } +} Propchange: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/CondVar.java ------------------------------------------------------------------------------ svn:executable = * Added: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/Condition.java URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/Condition.java?rev=417856&view=auto ============================================================================== --- incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/Condition.java (added) +++ incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/Condition.java Wed Jun 28 12:34:33 2006 @@ -0,0 +1,445 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * 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. + */ + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ +package org.apache.openjpa.lib.util.concurrent; + +import java.util.Date; + + +/** + * Condition factors out the Object monitor + * methods ({@link Object#wait() wait}, {@link Object#notify notify} + * and {@link Object#notifyAll notifyAll}) into distinct objects to + * give the effect of having multiple wait-sets per object, by + * combining them with the use of arbitrary {@link Lock} implementations. + * Where a Lock replaces the use of synchronized methods + * and statements, a Condition replaces the use of the Object + * monitor methods. + * + *

Conditions (also known as condition queues or + * condition variables) provide a means for one thread to + * suspend execution (to "wait") until notified by another + * thread that some state condition may now be true. Because access + * to this shared state information occurs in different threads, it + * must be protected, so a lock of some form is associated with the + * condition. The key property that waiting for a condition provides + * is that it atomically releases the associated lock and + * suspends the current thread, just like Object.wait. + * + *

A Condition instance is intrinsically bound to a lock. + * To obtain a Condition instance for a particular {@link Lock} + * instance use its {@link Lock#newCondition newCondition()} method. + * + *

As an example, suppose we have a bounded buffer which supports + * put and take methods. If a + * take is attempted on an empty buffer, then the thread will block + * until an item becomes available; if a put is attempted on a + * full buffer, then the thread will block until a space becomes available. + * We would like to keep waiting put threads and take + * threads in separate wait-sets so that we can use the optimization of + * only notifying a single thread at a time when items or spaces become + * available in the buffer. This can be achieved using two + * {@link Condition} instances. + *

+ * class BoundedBuffer {
+ *   final Lock lock = new ReentrantLock();
+ *   final Condition notFull  = lock.newCondition(); 
+ *   final Condition notEmpty = lock.newCondition(); 
+ *
+ *   final Object[] items = new Object[100];
+ *   int putptr, takeptr, count;
+ *
+ *   public void put(Object x) throws InterruptedException {
+ *     lock.lock();
+ *     try {
+ *       while (count == items.length)
+ *         notFull.await();
+ *       items[putptr] = x;
+ *       if (++putptr == items.length) putptr = 0;
+ *       ++count;
+ *       notEmpty.signal();
+ *     } finally {
+ *       lock.unlock();
+ *     }
+ *   }
+ *
+ *   public Object take() throws InterruptedException {
+ *     lock.lock();
+ *     try {
+ *       while (count == 0)
+ *         notEmpty.await();
+ *       Object x = items[takeptr];
+ *       if (++takeptr == items.length) takeptr = 0;
+ *       --count;
+ *       notFull.signal();
+ *       return x;
+ *     } finally {
+ *       lock.unlock();
+ *     }
+ *   }
+ * }
+ * 
+ * + * (The {@link edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue} class provides + * this functionality, so there is no reason to implement this + * sample usage class.) + * + *

A Condition implementation can provide behavior and semantics + * that is + * different from that of the Object monitor methods, such as + * guaranteed ordering for notifications, or not requiring a lock to be held + * when performing notifications. + * If an implementation provides such specialized semantics then the + * implementation must document those semantics. + * + *

Note that Condition instances are just normal objects and can + * themselves be used as the target in a synchronized statement, + * and can have their own monitor {@link Object#wait wait} and + * {@link Object#notify notification} methods invoked. + * Acquiring the monitor lock of a Condition instance, or using its + * monitor methods, has no specified relationship with acquiring the + * {@link Lock} associated with that Condition or the use of its + * {@link #await waiting} and {@link #signal signalling} methods. + * It is recommended that to avoid confusion you never use Condition + * instances in this way, except perhaps within their own implementation. + * + *

Except where noted, passing a null value for any parameter + * will result in a {@link NullPointerException} being thrown. + * + *

Implementation Considerations

+ * + *

When waiting upon a Condition, a "spurious + * wakeup" is permitted to occur, in + * general, as a concession to the underlying platform semantics. + * This has little practical impact on most application programs as a + * Condition should always be waited upon in a loop, testing + * the state predicate that is being waited for. An implementation is + * free to remove the possibility of spurious wakeups but it is + * recommended that applications programmers always assume that they can + * occur and so always wait in a loop. + * + *

The three forms of condition waiting + * (interruptible, non-interruptible, and timed) may differ in their ease of + * implementation on some platforms and in their performance characteristics. + * In particular, it may be difficult to provide these features and maintain + * specific semantics such as ordering guarantees. + * Further, the ability to interrupt the actual suspension of the thread may + * not always be feasible to implement on all platforms. + *

Consequently, an implementation is not required to define exactly the + * same guarantees or semantics for all three forms of waiting, nor is it + * required to support interruption of the actual suspension of the thread. + *

An implementation is required to + * clearly document the semantics and guarantees provided by each of the + * waiting methods, and when an implementation does support interruption of + * thread suspension then it must obey the interruption semantics as defined + * in this interface. + *

As interruption generally implies cancellation, and checks for + * interruption are often infrequent, an implementation can favor responding + * to an interrupt over normal method return. This is true even if it can be + * shown that the interrupt occurred after another action may have unblocked + * the thread. An implementation should document this behavior. + * + * + * @since 1.5 + * @author Doug Lea + */ +public interface Condition { + /** + * Causes the current thread to wait until it is signalled or + * {@link Thread#interrupt interrupted}. + * + *

The lock associated with this Condition is atomically + * released and the current thread becomes disabled for thread scheduling + * purposes and lies dormant until one of four things happens: + *

    + *
  • Some other thread invokes the {@link #signal} method for this + * Condition and the current thread happens to be chosen as the + * thread to be awakened; or + *
  • Some other thread invokes the {@link #signalAll} method for this + * Condition; or + *
  • Some other thread {@link Thread#interrupt interrupts} the current + * thread, and interruption of thread suspension is supported; or + *
  • A "spurious wakeup" occurs + *
+ * + *

In all cases, before this method can return the current thread must + * re-acquire the lock associated with this condition. When the + * thread returns it is guaranteed to hold this lock. + * + *

If the current thread: + *

    + *
  • has its interrupted status set on entry to this method; or + *
  • is {@link Thread#interrupt interrupted} while waiting + * and interruption of thread suspension is supported, + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. It is not specified, in the first + * case, whether or not the test for interruption occurs before the lock + * is released. + * + *

Implementation Considerations + *

The current thread is assumed to hold the lock associated with this + * Condition when this method is called. + * It is up to the implementation to determine if this is + * the case and if not, how to respond. Typically, an exception will be + * thrown (such as {@link IllegalMonitorStateException}) and the + * implementation must document that fact. + * + *

An implementation can favor responding to an interrupt over normal + * method return in response to a signal. In that case the implementation + * must ensure that the signal is redirected to another waiting thread, if + * there is one. + * + * @throws InterruptedException if the current thread is interrupted (and + * interruption of thread suspension is supported). + */ + void await() throws InterruptedException; + + /** + * Causes the current thread to wait until it is signalled. + * + *

The lock associated with this condition is atomically + * released and the current thread becomes disabled for thread scheduling + * purposes and lies dormant until one of three things happens: + *

    + *
  • Some other thread invokes the {@link #signal} method for this + * Condition and the current thread happens to be chosen as the + * thread to be awakened; or + *
  • Some other thread invokes the {@link #signalAll} method for this + * Condition; or + *
  • A "spurious wakeup" occurs + *
+ * + *

In all cases, before this method can return the current thread must + * re-acquire the lock associated with this condition. When the + * thread returns it is guaranteed to hold this lock. + * + *

If the current thread's interrupted status is set when it enters + * this method, or it is {@link Thread#interrupt interrupted} + * while waiting, it will continue to wait until signalled. When it finally + * returns from this method its interrupted status will still + * be set. + * + *

Implementation Considerations + *

The current thread is assumed to hold the lock associated with this + * Condition when this method is called. + * It is up to the implementation to determine if this is + * the case and if not, how to respond. Typically, an exception will be + * thrown (such as {@link IllegalMonitorStateException}) and the + * implementation must document that fact. + */ + void awaitUninterruptibly(); + + // /** + // * Causes the current thread to wait until it is signalled or interrupted, + // * or the specified waiting time elapses. + // * + // *

The lock associated with this condition is atomically + // * released and the current thread becomes disabled for thread scheduling + // * purposes and lies dormant until one of five things happens: + // *

    + // *
  • Some other thread invokes the {@link #signal} method for this + // * Condition and the current thread happens to be chosen as the + // * thread to be awakened; or + // *
  • Some other thread invokes the {@link #signalAll} method for this + // * Condition; or + // *
  • Some other thread {@link Thread#interrupt interrupts} the current + // * thread, and interruption of thread suspension is supported; or + // *
  • The specified waiting time elapses; or + // *
  • A "spurious wakeup" occurs. + // *
+ // * + // *

In all cases, before this method can return the current thread must + // * re-acquire the lock associated with this condition. When the + // * thread returns it is guaranteed to hold this lock. + // * + // *

If the current thread: + // *

    + // *
  • has its interrupted status set on entry to this method; or + // *
  • is {@link Thread#interrupt interrupted} while waiting + // * and interruption of thread suspension is supported, + // *
+ // * then {@link InterruptedException} is thrown and the current thread's + // * interrupted status is cleared. It is not specified, in the first + // * case, whether or not the test for interruption occurs before the lock + // * is released. + // * + // *

The method returns an estimate of the number of nanoseconds + // * remaining to wait given the supplied nanosTimeout + // * value upon return, or a value less than or equal to zero if it + // * timed out. This value can be used to determine whether and how + // * long to re-wait in cases where the wait returns but an awaited + // * condition still does not hold. Typical uses of this method take + // * the following form: + // * + // *

+    //     * synchronized boolean aMethod(long timeout, TimeUnit unit) {
+    //     *   long nanosTimeout = unit.toNanos(timeout);
+    //     *   while (!conditionBeingWaitedFor) {
+    //     *     if (nanosTimeout > 0)
+    //     *         nanosTimeout = theCondition.awaitNanos(nanosTimeout);
+    //     *      else
+    //     *        return false;
+    //     *   }
+    //     *   // ...
+    //     * }
+    //     * 
+ // * + // *

Design note: This method requires a nanosecond argument so + // * as to avoid truncation errors in reporting remaining times. + // * Such precision loss would make it difficult for programmers to + // * ensure that total waiting times are not systematically shorter + // * than specified when re-waits occur. + // * + // *

Implementation Considerations + // *

The current thread is assumed to hold the lock associated with this + // * Condition when this method is called. + // * It is up to the implementation to determine if this is + // * the case and if not, how to respond. Typically, an exception will be + // * thrown (such as {@link IllegalMonitorStateException}) and the + // * implementation must document that fact. + // * + // *

An implementation can favor responding to an interrupt over normal + // * method return in response to a signal, or over indicating the elapse + // * of the specified waiting time. In either case the implementation + // * must ensure that the signal is redirected to another waiting thread, if + // * there is one. + // * + // * @param nanosTimeout the maximum time to wait, in nanoseconds + // * @return A value less than or equal to zero if the wait has + // * timed out; otherwise an estimate, that + // * is strictly less than the nanosTimeout argument, + // * of the time still remaining when this method returned. + // * + // * @throws InterruptedException if the current thread is interrupted (and + // * interruption of thread suspension is supported). + // */ + // long awaitNanos(long nanosTimeout) throws InterruptedException; + + /** + * Causes the current thread to wait until it is signalled or interrupted, + * or the specified waiting time elapses. This method is behaviorally + * equivalent to:
+ *

+     *   awaitNanos(unit.toNanos(time)) > 0
+     * 
+ * @param time the maximum time to wait + * @param unit the time unit of the time argument. + * @return false if the waiting time detectably elapsed + * before return from the method, else true. + * @throws InterruptedException if the current thread is interrupted (and + * interruption of thread suspension is supported). + */ + boolean await(long time, TimeUnit unit) throws InterruptedException; + + /** + * Causes the current thread to wait until it is signalled or interrupted, + * or the specified deadline elapses. + * + *

The lock associated with this condition is atomically + * released and the current thread becomes disabled for thread scheduling + * purposes and lies dormant until one of five things happens: + *

    + *
  • Some other thread invokes the {@link #signal} method for this + * Condition and the current thread happens to be chosen as the + * thread to be awakened; or + *
  • Some other thread invokes the {@link #signalAll} method for this + * Condition; or + *
  • Some other thread {@link Thread#interrupt interrupts} the current + * thread, and interruption of thread suspension is supported; or + *
  • The specified deadline elapses; or + *
  • A "spurious wakeup" occurs. + *
+ * + *

In all cases, before this method can return the current thread must + * re-acquire the lock associated with this condition. When the + * thread returns it is guaranteed to hold this lock. + * + * + *

If the current thread: + *

    + *
  • has its interrupted status set on entry to this method; or + *
  • is {@link Thread#interrupt interrupted} while waiting + * and interruption of thread suspension is supported, + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. It is not specified, in the first + * case, whether or not the test for interruption occurs before the lock + * is released. + * + * + *

The return value indicates whether the deadline has elapsed, + * which can be used as follows: + *

+     * synchronized boolean aMethod(Date deadline) {
+     *   boolean stillWaiting = true;
+     *   while (!conditionBeingWaitedFor) {
+     *     if (stillWaiting)
+     *         stillWaiting = theCondition.awaitUntil(deadline);
+     *      else
+     *        return false;
+     *   }
+     *   // ...
+     * }
+     * 
+ * + *

Implementation Considerations + *

The current thread is assumed to hold the lock associated with this + * Condition when this method is called. + * It is up to the implementation to determine if this is + * the case and if not, how to respond. Typically, an exception will be + * thrown (such as {@link IllegalMonitorStateException}) and the + * implementation must document that fact. + * + *

An implementation can favor responding to an interrupt over normal + * method return in response to a signal, or over indicating the passing + * of the specified deadline. In either case the implementation + * must ensure that the signal is redirected to another waiting thread, if + * there is one. + * + * + * @param deadline the absolute time to wait until + * @return false if the deadline has + * elapsed upon return, else true. + * + * @throws InterruptedException if the current thread is interrupted (and + * interruption of thread suspension is supported). + */ + boolean awaitUntil(Date deadline) throws InterruptedException; + + /** + * Wakes up one waiting thread. + * + *

If any threads are waiting on this condition then one + * is selected for waking up. That thread must then re-acquire the + * lock before returning from await. + */ + void signal(); + + /** + * Wakes up all waiting threads. + * + *

If any threads are waiting on this condition then they are + * all woken up. Each thread must re-acquire the lock before it can + * return from await. + */ + void signalAll(); +} Propchange: incubator/openjpa/trunk/openjpa-lib/main/java/org/apache/openjpa/lib/util/concurrent/Condition.java ------------------------------------------------------------------------------ svn:executable = *