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]