From d90258bfa37ec69cd4795feeb2d2ff94623b0199 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Fri, 18 Jul 2008 01:56:53 +0000 Subject: Moved transaction support classes to txn package. --- .../amazon/carbonado/spi/AbstractRepository.java | 3 + .../amazon/carbonado/spi/TransactionManager.java | 212 ------- .../com/amazon/carbonado/spi/TransactionPair.java | 109 ---- .../com/amazon/carbonado/spi/TransactionScope.java | 619 --------------------- 4 files changed, 3 insertions(+), 940 deletions(-) delete mode 100644 src/main/java/com/amazon/carbonado/spi/TransactionManager.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/TransactionPair.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/TransactionScope.java (limited to 'src/main/java/com/amazon/carbonado/spi') diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java index e73a8c9..688134b 100644 --- a/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java +++ b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java @@ -41,6 +41,9 @@ import com.amazon.carbonado.sequence.SequenceCapability; import com.amazon.carbonado.sequence.SequenceValueProducer; import com.amazon.carbonado.sequence.SequenceValueProducerPool; +import com.amazon.carbonado.txn.TransactionManager; +import com.amazon.carbonado.txn.TransactionScope; + /** * Implements basic functionality required by a core Repository. * diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java deleted file mode 100644 index 48f52af..0000000 --- a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * 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 com.amazon.carbonado.spi; - -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import org.cojen.util.WeakIdentityMap; - -import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Transaction; - -/** - * Generic transaction manager for repositories. - * - * @param Transaction implementation - * @author Brian S O'Neill - */ -public abstract class TransactionManager { - private static final int OPEN = 0, CLOSED = 1, SUSPENDED = 2; - - private final ThreadLocal> mLocalScope; - private final Map, ?> mAllScopes; - - private int mState; - - public TransactionManager() { - mLocalScope = new ThreadLocal>(); - mAllScopes = new WeakIdentityMap(); - } - - /** - * Returns the thread-local TransactionScope, creating it if needed. - */ - public TransactionScope localScope() { - TransactionScope scope = mLocalScope.get(); - if (scope == null) { - int state; - synchronized (this) { - state = mState; - scope = new TransactionScope(this, state != OPEN); - mAllScopes.put(scope, null); - } - mLocalScope.set(scope); - if (state == SUSPENDED) { - // Immediately suspend new scope. - scope.getLock().lock(); - } - } - return scope; - } - - /** - * Detaches the thread-local TransactionScope from the current thread. It - * can be {@link TransactionScope#attach attached} later, and to any thread - * which does not currently have a TransactionScope. - * - * @return detached thread-local TransactionScope or null if none - * @since 1.2 - */ - public TransactionScope detachLocalScope() { - TransactionScope scope = mLocalScope.get(); - if (scope != null) { - scope.markDetached(); - mLocalScope.remove(); - } - return scope; - } - - // Called by TransactionScope. - boolean removeLocalScope(TransactionScope scope) { - TransactionScope existing = mLocalScope.get(); - if (existing == scope) { - mLocalScope.remove(); - return true; - } - return false; - } - - // Called by TransactionScope. - boolean setLocalScope(TransactionScope scope, boolean detached) { - TransactionScope existing = mLocalScope.get(); - if ((existing == null && detached) || existing == scope) { - mLocalScope.set(scope); - return true; - } - return false; - } - - /** - * Closes all transaction scopes. Should be called only when repository is - * closed. - * - * @param suspend when true, indefinitely suspend all threads interacting - * with transactions - */ - public synchronized void close(boolean suspend) throws RepositoryException { - if (mState == SUSPENDED) { - // If suspended, attempting to close again will likely deadlock. - return; - } - - if (suspend) { - for (TransactionScope scope : mAllScopes.keySet()) { - // Lock scope but don't release it. This prevents other threads - // from beginning work during shutdown, which will likely fail - // along the way. - scope.getLock().lock(); - } - } - - mState = suspend ? SUSPENDED : CLOSED; - - for (TransactionScope scope : mAllScopes.keySet()) { - scope.close(); - } - } - - /** - * Returns supported isolation level, which may be higher. If isolation - * level cannot go higher (or lower than parent) then return null. - * - * @param parent optional parent transaction - * @param level desired isolation level (may be null) - */ - protected abstract IsolationLevel selectIsolationLevel(Transaction parent, - IsolationLevel level); - - /** - * Return true if transactions support "for update" mode. - * - * @since 1.2 - */ - protected abstract boolean supportsForUpdate(); - - /** - * Creates an internal transaction representation, with the optional parent - * transaction. If parent is not null and real nested transactions are not - * supported, simply return parent transaction for supporting fake nested - * transactions. - * - * @param parent optional parent transaction - * @param level required isolation level - * @return new transaction, parent transaction, or possibly null if required - * isolation level is none - */ - protected abstract Txn createTxn(Txn parent, IsolationLevel level) throws Exception; - - /** - * Creates an internal transaction representation, with the optional parent - * transaction. If parent is not null and real nested transactions are not - * supported, simply return parent transaction for supporting fake nested - * transactions. - * - *

The default implementation of this method just calls the regular - * createTxn method, ignoring the timeout parameter. - * - * @param parent optional parent transaction - * @param level required isolation level - * @param timeout desired timeout for lock acquisition, never negative - * @param unit timeout unit, never null - * @return new transaction, parent transaction, or possibly null if required - * isolation level is none - */ - protected Txn createTxn(Txn parent, IsolationLevel level, - int timeout, TimeUnit unit) - throws Exception - { - return createTxn(parent, level); - } - - /** - * Called when a transaction is about to be reused. The default - * implementation of this method does nothing. Override if any preparation - * is required to ready a transaction for reuse. - * - * @param txn transaction to reuse, never null - * @since 1.1.3 - */ - protected void reuseTxn(Txn txn) throws Exception { - } - - /** - * Commits and closes the given internal transaction. - * - * @return true if transaction object is still valid - */ - protected abstract boolean commitTxn(Txn txn) throws PersistException; - - /** - * Aborts and closes the given internal transaction. - */ - protected abstract void abortTxn(Txn txn) throws PersistException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionPair.java b/src/main/java/com/amazon/carbonado/spi/TransactionPair.java deleted file mode 100644 index ff07bc2..0000000 --- a/src/main/java/com/amazon/carbonado/spi/TransactionPair.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * 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 com.amazon.carbonado.spi; - -import java.util.concurrent.TimeUnit; - -import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Transaction; - -/** - * Pairs two transaction together into one. The transaction cannot be atomic, - * however. Inconsistencies can result if the primary transaction succeeds in - * committing, but the secondary fails. Therefore, the designated primary - * transaction should be the one that is more likely to fail. For example, the - * primary transaction might rely on the network, but the secondary operates - * locally. - * - * @author Don Schneider - * @author Brian S O'Neill - */ -public class TransactionPair implements Transaction { - private final Transaction mPrimaryTransaction; - private final Transaction mSecondaryTransaction; - - /** - * @param primaryTransaction is committed first, exited last - * @param secondaryTransaction is exited first, commited last - */ - public TransactionPair(Transaction primaryTransaction, Transaction secondaryTransaction) { - mPrimaryTransaction = primaryTransaction; - mSecondaryTransaction = secondaryTransaction; - } - - public void commit() throws PersistException { - mPrimaryTransaction.commit(); - try { - mSecondaryTransaction.commit(); - } catch (Exception e) { - throw new PersistException - ("Failure to commit secondary transaction has likely caused an inconsistency", e); - } - } - - public void exit() throws PersistException { - try { - mSecondaryTransaction.exit(); - } finally { - // Do this second so if there is an exception, the user sees the - // primary exception, which is presumably more important. - mPrimaryTransaction.exit(); - } - } - - public void setForUpdate(boolean forUpdate) { - mPrimaryTransaction.setForUpdate(forUpdate); - mSecondaryTransaction.setForUpdate(forUpdate); - } - - public boolean isForUpdate() { - return mPrimaryTransaction.isForUpdate() && mSecondaryTransaction.isForUpdate(); - } - - public void setDesiredLockTimeout(int timeout, TimeUnit unit) { - mPrimaryTransaction.setDesiredLockTimeout(timeout, unit); - mSecondaryTransaction.setDesiredLockTimeout(timeout, unit); - } - - public IsolationLevel getIsolationLevel() { - return mPrimaryTransaction.getIsolationLevel() - .lowestCommon(mSecondaryTransaction.getIsolationLevel()); - } - - public void detach() { - mPrimaryTransaction.detach(); - try { - mSecondaryTransaction.detach(); - } catch (IllegalStateException e) { - mPrimaryTransaction.attach(); - throw e; - } - } - - public void attach() { - mPrimaryTransaction.attach(); - try { - mSecondaryTransaction.attach(); - } catch (IllegalStateException e) { - mPrimaryTransaction.detach(); - throw e; - } - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java deleted file mode 100644 index bce7804..0000000 --- a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java +++ /dev/null @@ -1,619 +0,0 @@ -/* - * Copyright 2008 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * 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 com.amazon.carbonado.spi; - -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import com.amazon.carbonado.Cursor; -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Transaction; - -/** - * Container of thread local, scoped transactions. - * - * @param Transaction implementation - * @author Brian S O'Neill - * @since 1.2 - * @see TransactionManager - */ -public class TransactionScope { - final TransactionManager mTxnMgr; - final Lock mLock; - - TransactionImpl mActive; - - // Tracks all registered cursors by storage type. - private Map, CursorList>> mCursors; - - private boolean mClosed; - private boolean mDetached; - - TransactionScope(TransactionManager txnMgr, boolean closed) { - mTxnMgr = txnMgr; - mLock = new ReentrantLock(true); - } - - /** - * Enters a new transaction scope which becomes the active transaction. - * - * @param level desired isolation level (may be null) - * @throws UnsupportedOperationException if isolation level higher than - * supported by repository - */ - public Transaction enter(IsolationLevel level) { - mLock.lock(); - try { - TransactionImpl parent = mActive; - IsolationLevel actualLevel = mTxnMgr.selectIsolationLevel(parent, level); - if (actualLevel == null) { - if (parent == null) { - throw new UnsupportedOperationException - ("Desired isolation level not supported: " + level); - } else { - throw new UnsupportedOperationException - ("Desired isolation level not supported: " + level - + "; parent isolation level: " + parent.getIsolationLevel()); - } - } - - return mActive = new TransactionImpl(this, parent, false, actualLevel); - } finally { - mLock.unlock(); - } - } - - /** - * Enters a new top-level transaction scope which becomes the active - * transaction. - * - * @param level desired isolation level (may be null) - * @throws UnsupportedOperationException if isolation level higher than - * supported by repository - */ - public Transaction enterTop(IsolationLevel level) { - mLock.lock(); - try { - IsolationLevel actualLevel = mTxnMgr.selectIsolationLevel(null, level); - if (actualLevel == null) { - throw new UnsupportedOperationException - ("Desired isolation level not supported: " + level); - } - - return mActive = new TransactionImpl(this, mActive, true, actualLevel); - } finally { - mLock.unlock(); - } - } - - /** - * Registers the given cursor against the active transaction, allowing it - * to be closed on transaction exit or transaction manager close. If there - * is no active transaction in scope, the cursor is registered as not part - * of a transaction. Cursors should register when created. - */ - public void register(Class type, Cursor cursor) { - mLock.lock(); - try { - checkClosed(); - if (mCursors == null) { - mCursors = new IdentityHashMap, CursorList>>(); - } - - CursorList> cursorList = mCursors.get(type); - if (cursorList == null) { - cursorList = new CursorList>(); - mCursors.put(type, cursorList); - } - - cursorList.register(cursor, mActive); - - if (mActive != null) { - mActive.register(cursor); - } - } finally { - mLock.unlock(); - } - } - - /** - * Unregisters a previously registered cursor. Cursors should unregister - * when closed. - */ - public void unregister(Class type, Cursor cursor) { - mLock.lock(); - try { - if (mCursors != null) { - CursorList> cursorList = mCursors.get(type); - if (cursorList != null) { - TransactionImpl txnImpl = cursorList.unregister(cursor); - if (txnImpl != null) { - txnImpl.unregister(cursor); - } - } - } - } finally { - mLock.unlock(); - } - } - - /** - * Returns lock used by TransactionScope. While holding lock, operations - * are suspended. - */ - public Lock getLock() { - return mLock; - } - - /** - * Returns the implementation for the active transaction, or null if there - * is no active transaction. - * - * @throws Exception thrown by createTxn or reuseTxn - */ - public Txn getTxn() throws Exception { - mLock.lock(); - try { - checkClosed(); - return mActive == null ? null : mActive.getTxn(); - } finally { - mLock.unlock(); - } - } - - /** - * Returns true if an active transaction exists and it is for update. - */ - public boolean isForUpdate() { - mLock.lock(); - try { - return (mClosed || mActive == null) ? false : mActive.isForUpdate(); - } finally { - mLock.unlock(); - } - } - - /** - * Returns the isolation level of the active transaction, or null if there - * is no active transaction. - */ - public IsolationLevel getIsolationLevel() { - mLock.lock(); - try { - return (mClosed || mActive == null) ? null : mActive.getIsolationLevel(); - } finally { - mLock.unlock(); - } - } - - /** - * Attach this scope to the current thread, if it has been {@link - * TransactionManager#detachLocalScope detached}. - * - * @throws IllegalStateException if current thread has a different - * transaction already attached - */ - public void attach() { - mLock.lock(); - try { - if (mTxnMgr.setLocalScope(this, mDetached)) { - mDetached = false; - } else if (!mDetached) { - throw new IllegalStateException("Transaction scope is not detached"); - } else { - throw new IllegalStateException - ("Current thread has a different transaction already attached"); - } - } finally { - mLock.unlock(); - } - } - - // Called by TransactionImpl. - void detach() { - mLock.lock(); - try { - if (mDetached || mTxnMgr.removeLocalScope(this)) { - mDetached = true; - } else { - throw new IllegalStateException("Transaction is attached to a different thread"); - } - } finally { - mLock.unlock(); - } - } - - // Called by TransactionManager. - void markDetached() { - mLock.lock(); - try { - mDetached = true; - } finally { - mLock.unlock(); - } - } - - /** - * Exits all transactions and closes all cursors. Should be called only - * when repository is closed. - */ - void close() throws RepositoryException { - mLock.lock(); - try { - if (!mClosed) { - while (mActive != null) { - mActive.exit(); - } - if (mCursors != null) { - for (CursorList> cursorList : mCursors.values()) { - cursorList.closeCursors(); - } - } - } - } finally { - mClosed = true; - mLock.unlock(); - } - } - - /** - * Caller must hold mLock. - */ - private void checkClosed() { - if (mClosed) { - throw new IllegalStateException("Repository is closed"); - } - } - - private static class TransactionImpl implements Transaction { - private final TransactionScope mScope; - private final TransactionImpl mParent; - private final boolean mTop; - private final IsolationLevel mLevel; - - private boolean mForUpdate; - private int mDesiredLockTimeout; - private TimeUnit mTimeoutUnit; - - private TransactionImpl mChild; - private boolean mExited; - private Txn mTxn; - - // Tracks all registered cursors. - private CursorList mCursorList; - - TransactionImpl(TransactionScope scope, - TransactionImpl parent, - boolean top, - IsolationLevel level) { - mScope = scope; - mParent = parent; - mTop = top; - mLevel = level; - if (!top && parent != null) { - parent.mChild = this; - mDesiredLockTimeout = parent.mDesiredLockTimeout; - mTimeoutUnit = parent.mTimeoutUnit; - } - } - - public void commit() throws PersistException { - TransactionScope scope = mScope; - scope.mLock.lock(); - try { - if (!mExited) { - if (mChild != null) { - mChild.commit(); - } - - closeCursors(); - - if (mTxn != null) { - if (mParent == null || mParent.mTxn != mTxn) { - try { - if (!scope.mTxnMgr.commitTxn(mTxn)) { - mTxn = null; - } - } catch (Throwable e) { - mTxn = null; - throw ExceptionTransformer.getInstance().toPersistException(e); - } - } else { - // Indicate fake nested transaction committed. - mTxn = null; - } - } - } - } finally { - scope.mLock.unlock(); - } - } - - public void exit() throws PersistException { - TransactionScope scope = mScope; - scope.mLock.lock(); - try { - if (!mExited) { - if (mChild != null) { - mChild.exit(); - } - - closeCursors(); - - if (mTxn != null) { - try { - if (mParent == null || mParent.mTxn != mTxn) { - try { - scope.mTxnMgr.abortTxn(mTxn); - } catch (Throwable e) { - throw ExceptionTransformer.getInstance().toPersistException(e); - } - } - } finally { - mTxn = null; - } - } - - scope.mActive = mParent; - - mExited = true; - } - } finally { - scope.mLock.unlock(); - } - } - - public void setForUpdate(boolean forUpdate) { - mForUpdate = forUpdate && mScope.mTxnMgr.supportsForUpdate(); - } - - public boolean isForUpdate() { - return mForUpdate; - } - - public void setDesiredLockTimeout(int timeout, TimeUnit unit) { - TransactionScope scope = mScope; - scope.mLock.lock(); - try { - if (timeout < 0) { - mDesiredLockTimeout = 0; - mTimeoutUnit = null; - } else { - mDesiredLockTimeout = timeout; - mTimeoutUnit = unit; - } - } finally { - scope.mLock.unlock(); - } - } - - public IsolationLevel getIsolationLevel() { - return mLevel; - } - - public void detach() { - mScope.detach(); - } - - public void attach() { - mScope.attach(); - } - - // Caller must hold mLock. - void register(Cursor cursor) { - if (mCursorList == null) { - mCursorList = new CursorList(); - } - mCursorList.register(cursor, null); - } - - // Caller must hold mLock. - void unregister(Cursor cursor) { - if (mCursorList != null) { - mCursorList.unregister(cursor); - } - } - - // Caller must hold mLock. - Txn getTxn() throws Exception { - TransactionScope scope = mScope; - if (mTxn != null) { - scope.mTxnMgr.reuseTxn(mTxn); - } else { - Txn parentTxn; - if (mParent == null || mTop) { - parentTxn = null; - } else if ((parentTxn = mParent.mTxn) == null) { - // No point in creating nested transaction if parent - // has never been used. Create parent transaction - // and use it in child transaction, just like a fake - // nested transaction. - if ((parentTxn = mParent.getTxn()) != null) { - return mTxn = parentTxn; - } - // Isolation level of parent is none, so proceed to create - // a real transaction. - } - if (mTimeoutUnit == null) { - mTxn = scope.mTxnMgr.createTxn(parentTxn, mLevel); - } else { - mTxn = scope.mTxnMgr.createTxn(parentTxn, mLevel, - mDesiredLockTimeout, mTimeoutUnit); - } - } - return mTxn; - } - - // Caller must hold mLock. - private void closeCursors() throws PersistException { - if (mCursorList != null) { - mCursorList.closeCursors(); - } - } - } - - /** - * Simple fast list/map for holding a small amount of cursors. - */ - static class CursorList { - private int mSize; - private Cursor[] mCursors; - private V[] mValues; - - CursorList() { - mCursors = new Cursor[8]; - } - - /** - * @param value optional value to associate - */ - @SuppressWarnings("unchecked") - void register(Cursor cursor, V value) { - int size = mSize; - Cursor[] cursors = mCursors; - - if (size == cursors.length) { - int newLength = size << 1; - - Cursor[] newCursors = new Cursor[newLength]; - System.arraycopy(cursors, 0, newCursors, 0, size); - mCursors = cursors = newCursors; - - if (mValues != null) { - V[] newValues = (V[]) new Object[newLength]; - System.arraycopy(mValues, 0, newValues, 0, size); - mValues = newValues; - } - } - - cursors[size] = cursor; - - if (value != null) { - V[] values = mValues; - if (values == null) { - mValues = values = (V[]) new Object[cursors.length]; - } - values[size] = value; - } - - mSize = size + 1; - } - - V unregister(Cursor cursor) { - // Assuming that cursors are opened and closed in LIFO order - // (stack order), search backwards to optimize. - Cursor[] cursors = mCursors; - int size = mSize; - int i = size; - search: { - while (--i >= 0) { - if (cursors[i] == cursor) { - break search; - } - } - // Not found. - return null; - } - - V[] values = mValues; - V value; - - if (values == null) { - value = null; - if (i == size - 1) { - // Clear reference so that it can be garbage collected. - cursors[i] = null; - } else { - // Shift array elements down. - System.arraycopy(cursors, i + 1, cursors, i, size - i - 1); - } - } else { - value = values[i]; - if (i == size - 1) { - // Clear references so that they can be garbage collected. - cursors[i] = null; - values[i] = null; - } else { - // Shift array elements down. - System.arraycopy(cursors, i + 1, cursors, i, size - i - 1); - System.arraycopy(values, i + 1, values, i, size - i - 1); - } - } - - mSize = size - 1; - return value; - } - - int size() { - return mSize; - } - - Cursor getCursor(int index) { - return mCursors[index]; - } - - V getValue(int index) { - V[] values = mValues; - return values == null ? null : values[index]; - } - - /** - * Closes all cursors and resets the size of this list to 0. - */ - void closeCursors() throws PersistException { - // Note: Iteration must be in reverse order. Calling close on the - // cursor should cause it to unregister from this list. This will - // cause only a modification to the end of the list, which is no - // longer needed by this method. - try { - Cursor[] cursors = mCursors; - V[] values = mValues; - int i = mSize; - if (values == null) { - while (--i >= 0) { - Cursor cursor = cursors[i]; - if (cursor != null) { - cursor.close(); - cursors[i] = null; - } - } - } else { - while (--i >= 0) { - Cursor cursor = cursors[i]; - if (cursor != null) { - cursor.close(); - cursors[i] = null; - values[i] = null; - } - } - } - } catch (FetchException e) { - throw e.toPersistException(); - } - mSize = 0; - } - } -} -- cgit v1.2.3