Author: ozeigermann
Date: Fri Jul 20 09:13:20 2007
New Revision: 558031

URL: http://svn.apache.org/viewvc?view=rev&rev=558031
Log:
Reintroduces transactional memory implementations. However, they no longer wrap 
a map, but hide the wrapped map as an implementation detail.

Added:
    
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/
    
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
    
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
    
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java

Added: 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java?view=auto&rev=558031
==============================================================================
--- 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
 (added)
+++ 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
 Fri Jul 20 09:13:20 2007
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.transaction.memory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.commons.transaction.TransactionalResourceManager;
+import org.apache.commons.transaction.locking.LockException;
+
+/**
+ * Wrapper that adds transactional control to all kinds of maps that implement
+ * the [EMAIL PROTECTED] Map} interface. By using a naive optimistic 
transaction control
+ * this wrapper has better isolation than [EMAIL PROTECTED] TxMap}, but
+ * may also fail to commit.
+ * 
+ * <br>
+ * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then 
perform the
+ * normal actions on the map and finally either call
+ * [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or
+ * [EMAIL PROTECTED] #rollbackTransaction()} to undo them. <br>
+ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED] 
#get(Object)} as
+ * this will circumvent the transactional mechanism. Rather clone the value or
+ * copy it in a way you see fit and store it back using
+ * [EMAIL PROTECTED] #put(Object, Object)}. <br>
+ * <em>Note:</em> This wrapper guarantees isolation level
+ * <code>SERIALIZABLE</code>. <br>
+ * <em>Caution:</em> This implementation might be slow when large amounts of
+ * data is changed in a transaction as much references will need to be copied
+ * around.
+ * 
+ * @version $Id: OptimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $
+ * @see TxMap
+ * @see PessimisticTxMap
+ */
+public class OptimisticTxMap<K, V> extends TxMap<K, V> implements Map<K, V>,
+TransactionalResourceManager {
+
+    private Set<CopyingTxContext> activeTransactions = new 
HashSet<CopyingTxContext>();
+    private ReadWriteLock commitLock = new ReentrantReadWriteLock();
+
+    private long commitTimeout = 1000 * 60; // 1 minute
+
+    private long accessTimeout = 1000 * 30; // 30 seconds
+    public void rollbackTransaction() {
+        MapTxContext txContext = getActiveTx();
+        super.rollbackTransaction();
+        activeTransactions.remove(txContext);
+    }
+
+    public void commitTransaction() throws LockException {
+        commitTransaction(false);
+    }
+
+    public void commitTransaction(boolean force) throws LockException {
+        MapTxContext txContext = getActiveTx();
+        
+        if (txContext == null) {
+            throw new IllegalStateException(
+                "Active thread " + Thread.currentThread() + " not associated 
with a transaction!");
+        }
+
+        if (txContext.isMarkedForRollback()) {
+            throw new IllegalStateException("Active thread " + 
Thread.currentThread() + " is marked for rollback!");
+        }
+        
+        try {
+            // in this final commit phase we need to be the only one access the
+            // map
+            // to make sure no one adds an entry after we checked for conflicts
+            commitLock.writeLock().tryLock(getCommitTimeout(), 
TimeUnit.MILLISECONDS);
+
+            if (!force) {
+                Object conflictKey = checkForConflicts();
+                if (conflictKey != null) {
+                    throw new LockException(LockException.Code.CONFLICT, 
conflictKey);
+                }
+            }
+    
+            activeTransactions.remove(txContext);
+            copyChangesToConcurrentTransactions();
+            super.commitTransaction();
+            
+        } catch (InterruptedException e) {
+            throw new LockException(e);
+        } finally {
+            commitLock.writeLock().unlock();
+        }
+    }
+
+    // TODO: Shouldn't we return a collection rather than a single key here?
+    public Object checkForConflicts() {
+        CopyingTxContext txContext = (CopyingTxContext) getActiveTx();
+
+        Set keys = txContext.changedKeys();
+        Set externalKeys = txContext.externalChangedKeys();
+
+        for (Iterator it2 = keys.iterator(); it2.hasNext();) {
+            Object key = it2.next();
+            if (externalKeys.contains(key)) {
+                return key;
+            }
+        }
+        return null;
+    }
+
+    protected void copyChangesToConcurrentTransactions() {
+        CopyingTxContext thisTxContext = (CopyingTxContext) getActiveTx();
+
+        synchronized (activeTransactions) {
+            for (Iterator it = activeTransactions.iterator(); it.hasNext();) {
+                CopyingTxContext otherTxContext = (CopyingTxContext) it.next();
+
+                // no need to copy data if the other transaction does not 
access
+                // global map anyway
+                if (otherTxContext.cleared)
+                    continue;
+
+                if (thisTxContext.cleared) {
+                    // we will clear everything, so we have to copy everything
+                    // before
+                    otherTxContext.externalChanges.putAll(wrapped);
+                } else // no need to check if we have already copied everthing
+                {
+                    for (Iterator it2 = 
thisTxContext.changes.entrySet().iterator(); it2.hasNext();) {
+                        Map.Entry<K, V> entry = (Map.Entry) it2.next();
+                        V value = wrapped.get(entry.getKey());
+                        if (value != null) {
+                            // undo change
+                            otherTxContext.externalChanges.put(entry.getKey(), 
value);
+                        } else {
+                            // undo add
+                            otherTxContext.externalDeletes.add(entry.getKey());
+                        }
+                    }
+
+                    for (Iterator it2 = thisTxContext.deletes.iterator(); 
it2.hasNext();) {
+                        // undo delete
+                        K key = (K) it2.next(); /* FIXME: This could crash */
+                        V value = wrapped.get(key);
+                        otherTxContext.externalChanges.put(key, value);
+                    }
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected CopyingTxContext createContext() {
+        return new CopyingTxContext();
+    }
+
+    public class CopyingTxContext extends MapTxContext {
+        protected Map<K, V> externalChanges;
+
+        protected Map<K, V> externalAdds;
+
+        protected Set externalDeletes;
+
+        protected CopyingTxContext() {
+            super();
+            externalChanges = new HashMap();
+            externalDeletes = new HashSet();
+            externalAdds = new HashMap();
+        }
+
+        protected Set externalChangedKeys() {
+            Set keySet = new HashSet();
+            keySet.addAll(externalDeletes);
+            keySet.addAll(externalChanges.keySet());
+            keySet.addAll(externalAdds.keySet());
+            return keySet;
+        }
+
+        protected Set changedKeys() {
+            Set keySet = new HashSet();
+            keySet.addAll(deletes);
+            keySet.addAll(changes.keySet());
+            keySet.addAll(adds.keySet());
+            return keySet;
+        }
+
+        protected Set keys() {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+                Set keySet = super.keys();
+                keySet.removeAll(externalDeletes);
+                keySet.addAll(externalAdds.keySet());
+                return keySet;
+            } catch (InterruptedException e) {
+                return null;
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+        protected V get(Object key) {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+
+                if (deletes.contains(key)) {
+                    // reflects that entry has been deleted in this tx
+                    return null;
+                }
+
+                V changed = changes.get(key);
+                if (changed != null) {
+                    return changed;
+                }
+
+                V added = adds.get(key);
+                if (added != null) {
+                    return added;
+                }
+
+                if (cleared) {
+                    return null;
+                } else {
+                    if (externalDeletes.contains(key)) {
+                        // reflects that entry has been deleted in this tx
+                        return null;
+                    }
+
+                    changed = externalChanges.get(key);
+                    if (changed != null) {
+                        return changed;
+                    }
+
+                    added = externalAdds.get(key);
+                    if (added != null) {
+                        return added;
+                    }
+
+                    // not modified in this tx
+                    return wrapped.get(key);
+                }
+            } catch (InterruptedException e) {
+                return null;
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+        protected void put(K key, V value) {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+                super.put(key, value);
+            } catch (InterruptedException e) {
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+        protected void remove(Object key) {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+                super.remove(key);
+            } catch (InterruptedException e) {
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+        protected int size() {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+                int size = super.size();
+
+                size -= externalDeletes.size();
+                size += externalAdds.size();
+
+                return size;
+            } catch (InterruptedException e) {
+                return -1;
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+        protected void clear() {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+                super.clear();
+                externalDeletes.clear();
+                externalChanges.clear();
+                externalAdds.clear();
+            } catch (InterruptedException e) {
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+        public void commit() {
+            try {
+                commitLock.readLock().tryLock(getAccessTimeout(), 
TimeUnit.MILLISECONDS);
+                super.commit();
+            } catch (InterruptedException e) {
+            } finally {
+                commitLock.readLock().unlock();
+            }
+        }
+
+    }
+
+    public long getAccessTimeout() {
+        return accessTimeout;
+    }
+
+    public void setAccessTimeout(long accessTimeout) {
+        this.accessTimeout = accessTimeout;
+    }
+
+    public long getCommitTimeout() {
+        return commitTimeout;
+    }
+
+    public void setCommitTimeout(long commitTimeout) {
+        this.commitTimeout = commitTimeout;
+    }
+}

Added: 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java?view=auto&rev=558031
==============================================================================
--- 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
 (added)
+++ 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
 Fri Jul 20 09:13:20 2007
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.transaction.memory;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.transaction.TransactionalResourceManager;
+
+/**
+ * Wrapper that adds transactional control to all kinds of maps that implement
+ * the [EMAIL PROTECTED] Map} interface. By using pessimistic transaction 
control (blocking
+ * locks) this wrapper has better isolation than [EMAIL PROTECTED] TxMap},
+ * but also has less possible concurrency and may even deadlock. A commit,
+ * however, will never fail. <br>
+ * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then 
perform the
+ * normal actions on the map and finally either call
+ * [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or
+ * [EMAIL PROTECTED] #rollbackTransaction()} to undo them. <br>
+ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED] 
#get(Object)} as
+ * this will circumvent the transactional mechanism. Rather clone the value or
+ * copy it in a way you see fit and store it back using
+ * [EMAIL PROTECTED] #put(Object, Object)}. <br>
+ * <em>Note:</em> This wrapper guarantees isolation level
+ * <code>SERIALIZABLE</code>.
+ * 
+ * @version $Id: PessimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $
+ * @see TxMap
+ * @see OptimisticTxMap
+ */
+public class PessimisticTxMap<K, V> extends TxMap<K, V> implements Map<K, V>,
+TransactionalResourceManager {
+
+    protected static final Object GLOBAL_LOCK = "GLOBAL";
+
+    public Collection values() {
+        assureGlobalReadLock();
+        return super.values();
+    }
+
+    public Set entrySet() {
+        assureGlobalReadLock();
+        return super.entrySet();
+    }
+
+    public Set keySet() {
+        assureGlobalReadLock();
+        return super.keySet();
+    }
+
+    public V remove(Object key) {
+        // assure we get a write lock before super can get a read lock to avoid
+        // lots
+        // of deadlocks
+        assureWriteLock(key);
+        return super.remove(key);
+    }
+
+    public V put(K key, V value) {
+        // assure we get a write lock before super can get a read lock to avoid
+        // lots
+        // of deadlocks
+        assureWriteLock(key);
+        return super.put(key, value);
+    }
+
+    protected void assureWriteLock(Object key) {
+        LockingTxContext txContext = (LockingTxContext) getActiveTx();
+        if (txContext != null) {
+            txContext.writeLock(key);
+            // XXX fake intention lock (prohibits global WRITE)
+            txContext.readLock(GLOBAL_LOCK);
+        }
+    }
+
+    protected void assureGlobalReadLock() {
+        LockingTxContext txContext = (LockingTxContext) getActiveTx();
+        if (txContext != null) {
+            // XXX fake intention lock (prohibits global WRITE)
+            txContext.readLock(GLOBAL_LOCK);
+        }
+    }
+
+    @Override
+    protected LockingTxContext createContext() {
+        return new LockingTxContext();
+    }
+
+    public class LockingTxContext extends MapTxContext {
+
+        protected Set keys() {
+            readLock(GLOBAL_LOCK);
+            return super.keys();
+        }
+
+        protected V get(Object key) {
+            readLock(key);
+            // XXX fake intention lock (prohibits global WRITE)
+            readLock(GLOBAL_LOCK);
+            return super.get(key);
+        }
+
+        protected void put(K key, V value) {
+            writeLock(key);
+            // XXX fake intention lock (prohibits global WRITE)
+            readLock(GLOBAL_LOCK);
+            super.put(key, value);
+        }
+
+        protected void remove(Object key) {
+            writeLock(key);
+            // XXX fake intention lock (prohibits global WRITE)
+            readLock(GLOBAL_LOCK);
+            super.remove(key);
+        }
+
+        protected int size() {
+            // XXX this is bad luck, we need a global read lock just for the
+            // size :( :( :(
+            readLock(GLOBAL_LOCK);
+            return super.size();
+        }
+
+        protected void clear() {
+            writeLock(GLOBAL_LOCK);
+            super.clear();
+        }
+
+    }
+
+}

Added: 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java?view=auto&rev=558031
==============================================================================
--- 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
 (added)
+++ 
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
 Fri Jul 20 09:13:20 2007
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.transaction.memory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.transaction.AbstractTransactionalResourceManager;
+import org.apache.commons.transaction.TransactionalResourceManager;
+import 
org.apache.commons.transaction.AbstractTransactionalResourceManager.AbstractTxContext;
+
+/**
+ * Wrapper that adds transactional control to all kinds of maps that implement
+ * the [EMAIL PROTECTED] Map} interface. This wrapper has rather weak 
isolation, but is
+ * simply, neven blocks and commits will never fail for logical reasons. <br>
+ * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then 
perform the
+ * normal actions on the map and finally either call
+ * [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or
+ * [EMAIL PROTECTED] #rollbackTransaction()} to undo them. <br>
+ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED] 
#get(Object)} as
+ * this will circumvent the transactional mechanism. Rather clone the value or
+ * copy it in a way you see fit and store it back using
+ * [EMAIL PROTECTED] #put(Object, Object)}. <br>
+ * <em>Note:</em> This wrapper guarantees isolation level
+ * <code>READ COMMITTED</code> only. I.e. as soon a value is committed in one
+ * transaction it will be immediately visible in all other concurrent
+ * transactions.
+ * 
+ * @see OptimisticTxMap
+ * @see PessimisticTxMap
+ */
+public class TxMap<K, V> extends
+        AbstractTransactionalResourceManager<TxMap.MapTxContext> implements 
Map<K, V>,
+        TransactionalResourceManager {
+
+    protected Map<K, V> wrapped = new ConcurrentHashMap<K, V>();
+
+    //
+    // Map methods
+    // 
+
+    /**
+     * @see Map#clear()
+     */
+    public void clear() {
+        MapTxContext txContext = getActiveTx();
+        if (txContext != null) {
+            txContext.clear();
+        } else {
+            wrapped.clear();
+        }
+    }
+
+    /**
+     * @see Map#size()
+     */
+    public int size() {
+        MapTxContext txContext = getActiveTx();
+        if (txContext != null) {
+            return txContext.size();
+        } else {
+            return wrapped.size();
+        }
+    }
+
+    /**
+     * @see Map#isEmpty()
+     */
+    public boolean isEmpty() {
+        MapTxContext txContext = getActiveTx();
+        if (txContext == null) {
+            return wrapped.isEmpty();
+        } else {
+            return txContext.isEmpty();
+        }
+    }
+
+    /**
+     * @see Map#containsKey(java.lang.Object)
+     */
+    public boolean containsKey(Object key) {
+        return keySet().contains(key);
+    }
+
+    /**
+     * @see Map#containsValue(java.lang.Object)
+     */
+    public boolean containsValue(Object value) {
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext == null) {
+            return wrapped.containsValue(value);
+        } else {
+            return values().contains(value);
+        }
+    }
+
+    /**
+     * @see Map#values()
+     */
+    public Collection values() {
+
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext == null) {
+            return wrapped.values();
+        } else {
+            // XXX expensive :(
+            Collection values = new ArrayList();
+            for (Iterator it = keySet().iterator(); it.hasNext();) {
+                Object key = it.next();
+                Object value = get(key);
+                // XXX we have no isolation, so get entry might have been
+                // deleted in the meantime
+                if (value != null) {
+                    values.add(value);
+                }
+            }
+            return values;
+        }
+    }
+
+    /**
+     * @see Map#putAll(java.util.Map)
+     */
+    public void putAll(Map map) {
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext == null) {
+            wrapped.putAll(map);
+        } else {
+            for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
+                Map.Entry<K, V> entry = (Map.Entry) it.next();
+                txContext.put(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * @see Map#entrySet()
+     */
+    public Set entrySet() {
+        MapTxContext txContext = getActiveTx();
+        if (txContext == null) {
+            return wrapped.entrySet();
+        } else {
+            Set entrySet = new HashSet();
+            // XXX expensive :(
+            for (Iterator it = keySet().iterator(); it.hasNext();) {
+                Object key = it.next();
+                Object value = get(key);
+                // XXX we have no isolation, so get entry might have been
+                // deleted in the meantime
+                if (value != null) {
+                    entrySet.add(new HashEntry(key, value));
+                }
+            }
+            return entrySet;
+        }
+    }
+
+    /**
+     * @see Map#keySet()
+     */
+    public Set keySet() {
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext == null) {
+            return wrapped.keySet();
+        } else {
+            return txContext.keys();
+        }
+    }
+
+    /**
+     * @see Map#get(java.lang.Object)
+     */
+    public V get(Object key) {
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext != null) {
+            return txContext.get(key);
+        } else {
+            return wrapped.get(key);
+        }
+    }
+
+    /**
+     * @see Map#remove(java.lang.Object)
+     */
+    public V remove(Object key) {
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext == null) {
+            return wrapped.remove(key);
+        } else {
+            V oldValue = get(key);
+            txContext.remove(key);
+            return oldValue;
+        }
+    }
+
+    /**
+     * @see Map#put(java.lang.Object, java.lang.Object)
+     */
+    public V put(K key, V value) {
+        MapTxContext txContext = getActiveTx();
+
+        if (txContext == null) {
+            return wrapped.put(key, value);
+        } else {
+            V oldValue = get(key);
+            txContext.put(key, value);
+            return oldValue;
+        }
+
+    }
+
+    @Override
+    protected MapTxContext createContext() {
+        return new MapTxContext();
+    }
+
+    @Override
+    protected MapTxContext getActiveTx() {
+        return activeTx.get();
+    }
+
+    // mostly copied from org.apache.commons.collections.map.AbstractHashedMap
+    protected static class HashEntry implements Map.Entry {
+        /** The key */
+        protected Object key;
+
+        /** The value */
+        protected Object value;
+
+        protected HashEntry(Object key, Object value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        public Object getKey() {
+            return key;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public Object setValue(Object value) {
+            Object old = this.value;
+            this.value = value;
+            return old;
+        }
+
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry other = (Map.Entry) obj;
+            return (getKey() == null ? other.getKey() == null : 
getKey().equals(other.getKey()))
+                    && (getValue() == null ? other.getValue() == null : 
getValue().equals(
+                            other.getValue()));
+        }
+
+        public int hashCode() {
+            return (getKey() == null ? 0 : getKey().hashCode())
+                    ^ (getValue() == null ? 0 : getValue().hashCode());
+        }
+
+        public String toString() {
+            return new 
StringBuffer().append(getKey()).append('=').append(getValue()).toString();
+        }
+    }
+
+    public class MapTxContext extends AbstractTxContext {
+        protected Set deletes;
+
+        protected Map<K, V> changes;
+
+        protected Map<K, V> adds;
+
+        protected boolean cleared;
+
+        protected MapTxContext() {
+            deletes = new HashSet();
+            changes = new HashMap<K, V>();
+            adds = new HashMap<K, V>();
+            cleared = false;
+        }
+
+        protected Set keys() {
+            Set keySet = new HashSet();
+            if (!cleared) {
+                keySet.addAll(wrapped.keySet());
+                keySet.removeAll(deletes);
+            }
+            keySet.addAll(adds.keySet());
+            return keySet;
+        }
+
+        protected V get(Object key) {
+
+            if (deletes.contains(key)) {
+                // reflects that entry has been deleted in this tx
+                return null;
+            }
+
+            if (changes.containsKey(key)) {
+                return changes.get(key);
+            }
+
+            if (adds.containsKey(key)) {
+                return adds.get(key);
+            }
+
+            if (cleared) {
+                return null;
+            } else {
+                // not modified in this tx
+                return wrapped.get(key);
+            }
+        }
+
+        protected void put(K key, V value) {
+            try {
+                setReadOnly(false);
+                deletes.remove(key);
+                if (wrapped.containsKey(key)) {
+                    changes.put(key, value);
+                } else {
+                    adds.put(key, value);
+                }
+            } catch (RuntimeException e) {
+                markForRollback();
+                throw e;
+            } catch (Error e) {
+                markForRollback();
+                throw e;
+            }
+        }
+
+        protected void remove(Object key) {
+
+            try {
+                setReadOnly(false);
+                changes.remove(key);
+                adds.remove(key);
+                if (wrapped.containsKey(key) && !cleared) {
+                    deletes.add(key);
+                }
+            } catch (RuntimeException e) {
+                markForRollback();
+                throw e;
+            } catch (Error e) {
+                markForRollback();
+                throw e;
+            }
+        }
+
+        protected int size() {
+            int size = (cleared ? 0 : wrapped.size());
+
+            size -= deletes.size();
+            size += adds.size();
+
+            return size;
+        }
+
+        protected void clear() {
+            setReadOnly(false);
+            cleared = true;
+            deletes.clear();
+            changes.clear();
+            adds.clear();
+        }
+
+        protected boolean isEmpty() {
+            return (size() == 0);
+        }
+
+        public void commit() {
+            if (!isReadOnly()) {
+
+                if (cleared) {
+                    wrapped.clear();
+                }
+
+                wrapped.putAll(changes);
+                wrapped.putAll(adds);
+
+                for (Iterator it = deletes.iterator(); it.hasNext();) {
+                    Object key = it.next();
+                    wrapped.remove(key);
+                }
+            }
+        }
+
+    }
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to