blob: 5904c2819e8070ed3acf542c06b7b9afa281abd1 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.collections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseEntry;
/* <!-- begin JE only --> */
import com.sleepycat.je.EnvironmentFailureException; // for javadoc
/* <!-- end JE only --> */
import com.sleepycat.je.JoinConfig;
/* <!-- begin JE only --> */
import com.sleepycat.je.OperationFailureException; // for javadoc
/* <!-- end JE only --> */
import com.sleepycat.je.OperationStatus;
import com.sleepycat.util.RuntimeExceptionWrapper;
/**
* A abstract base class for all stored collections. This class, and its
* base class {@link StoredContainer}, provide implementations of most methods
* in the {@link Collection} interface. Other methods, such as {@link #add}
* and {@link #remove}, are provided by concrete classes that extend this
* class.
*
* <p>In addition, this class provides the following methods for stored
* collections only. Note that the use of these methods is not compatible with
* the standard Java collections interface.</p>
* <ul>
* <li>{@link #getIteratorBlockSize}</li>
* <li>{@link #setIteratorBlockSize}</li>
* <li>{@link #storedIterator()}</li>
* <li>{@link #storedIterator(boolean)}</li>
* <li>{@link #join}</li>
* <li>{@link #toList()}</li>
* </ul>
*
* @author Mark Hayes
*/
public abstract class StoredCollection<E> extends StoredContainer
implements Collection<E> {
/**
* The default number of records read at one time by iterators.
* @see #setIteratorBlockSize
*/
public static final int DEFAULT_ITERATOR_BLOCK_SIZE = 10;
private int iteratorBlockSize = DEFAULT_ITERATOR_BLOCK_SIZE;
StoredCollection(DataView view) {
super(view);
}
/**
* Returns the number of records read at one time by iterators returned by
* the {@link #iterator} method. By default this value is {@link
* #DEFAULT_ITERATOR_BLOCK_SIZE}.
*
* @return the number of records.
*/
public int getIteratorBlockSize() {
return iteratorBlockSize;
}
/**
* Changes the number of records read at one time by iterators returned by
* the {@link #iterator} method. By default this value is {@link
* #DEFAULT_ITERATOR_BLOCK_SIZE}.
*
* @param blockSize the number of records.
*
* @throws IllegalArgumentException if the blockSize is less than two.
*/
public void setIteratorBlockSize(int blockSize) {
if (blockSize < 2) {
throw new IllegalArgumentException
("blockSize is less than two: " + blockSize);
}
iteratorBlockSize = blockSize;
}
final boolean add(Object key, Object value) {
DataCursor cursor = null;
boolean doAutoCommit = beginAutoCommit();
try {
cursor = new DataCursor(view, true);
OperationStatus status =
cursor.putNoDupData(key, value, null, false);
closeCursor(cursor);
commitAutoCommit(doAutoCommit);
return (status == OperationStatus.SUCCESS);
} catch (Exception e) {
closeCursor(cursor);
throw handleException(e, doAutoCommit);
}
}
BlockIterator blockIterator() {
return new BlockIterator(this, isWriteAllowed(), iteratorBlockSize);
}
/**
* Returns an iterator over the elements in this collection.
* The iterator will be read-only if the collection is read-only.
* This method conforms to the {@link Collection#iterator} interface.
*
* <p>The iterator returned by this method does not keep a database cursor
* open and therefore it does not need to be closed. It reads blocks of
* records as needed, opening and closing a cursor to read each block of
* records. The number of records per block is 10 by default and can be
* changed with {@link #setIteratorBlockSize}.</p>
*
* <p>Because this iterator does not keep a cursor open, if it is used
* without transactions, the iterator does not have <em>cursor
* stability</em> characteristics. In other words, the record at the
* current iterator position can be changed or deleted by another thread.
* To prevent this from happening, call this method within a transaction or
* use the {@link #storedIterator()} method instead.</p>
*
* @return a standard {@link Iterator} for this collection.
*
* @see #isWriteAllowed
*/
public Iterator<E> iterator() {
return blockIterator();
}
/**
* Returns an iterator over the elements in this collection.
* The iterator will be read-only if the collection is read-only.
* This method does not exist in the standard {@link Collection} interface.
*
* <p>If {@code Iterator.set} or {@code Iterator.remove} will be called
* and the underlying Database is transactional, then a transaction must be
* active when calling this method and must remain active while using the
* iterator.</p>
*
* <p><strong>Warning:</strong> The iterator returned must be explicitly
* closed using {@link StoredIterator#close()} or {@link
* StoredIterator#close(java.util.Iterator)} to release the underlying
* database cursor resources.</p>
*
* @return a {@link StoredIterator} for this collection.
*
* @see #isWriteAllowed
*/
public StoredIterator<E> storedIterator() {
return storedIterator(isWriteAllowed());
}
/**
* Returns a read or read-write iterator over the elements in this
* collection.
* This method does not exist in the standard {@link Collection} interface.
*
* <p>If {@code Iterator.set} or {@code Iterator.remove} will be called
* and the underlying Database is transactional, then a transaction must be
* active when calling this method and must remain active while using the
* iterator.</p>
*
* <p><strong>Warning:</strong> The iterator returned must be explicitly
* closed using {@link StoredIterator#close()} or {@link
* StoredIterator#close(java.util.Iterator)} to release the underlying
* database cursor resources.</p>
*
* @param writeAllowed is true to open a read-write iterator or false to
* open a read-only iterator. If the collection is read-only the iterator
* will always be read-only.
*
* @return a {@link StoredIterator} for this collection.
*
* @throws IllegalStateException if writeAllowed is true but the collection
* is read-only.
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*
* @see #isWriteAllowed
*/
public StoredIterator<E> storedIterator(boolean writeAllowed) {
try {
return new StoredIterator(this, writeAllowed && isWriteAllowed(),
null);
} catch (Exception e) {
throw StoredContainer.convertException(e);
}
}
/**
* @param writeAllowed is true to open a read-write iterator or false to
* open a read-only iterator. If the collection is read-only the iterator
* will always be read-only.
*
* @return a {@link StoredIterator} for this collection.
*
* @deprecated Please use {@link #storedIterator()} or {@link
* #storedIterator(boolean)} instead. Because the iterator returned must
* be closed, the method name {@code iterator} is confusing since standard
* Java iterators do not need to be closed.
*/
public StoredIterator<E> iterator(boolean writeAllowed) {
return storedIterator(writeAllowed);
}
/**
* Returns an array of all the elements in this collection.
* This method conforms to the {@link Collection#toArray()} interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public Object[] toArray() {
ArrayList<Object> list = new ArrayList<Object>();
StoredIterator i = null;
try {
i = storedIterator();
while (i.hasNext()) {
list.add(i.next());
}
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
StoredIterator.close(i);
}
return list.toArray();
}
/**
* Returns an array of all the elements in this collection whose runtime
* type is that of the specified array.
* This method conforms to the {@link Collection#toArray(Object[])}
* interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public <T> T[] toArray(T[] a) {
int j = 0;
StoredIterator i = null;
try {
i = storedIterator();
while (j < a.length && i.hasNext()) {
a[j++] = (T) i.next();
}
if (j < a.length) {
a[j] = null;
} else if (i.hasNext()) {
ArrayList<T> list = new ArrayList<T>(Arrays.asList(a));
while (i.hasNext()) {
list.add((T) i.next());
}
a = list.toArray(a);
}
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
StoredIterator.close(i);
}
return a;
}
/**
* Returns true if this collection contains all of the elements in the
* specified collection.
* This method conforms to the {@link Collection#containsAll} interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public boolean containsAll(Collection<?> coll) {
Iterator<?> i = null;
try {
i = storedOrExternalIterator(coll);
while (i.hasNext()) {
if (!contains(i.next())) {
return false;
}
}
return true;
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
StoredIterator.close(i);
}
}
/**
* Adds all of the elements in the specified collection to this collection
* (optional operation).
* This method calls the {@link #add(Object)} method of the concrete
* collection class, which may or may not be supported.
* This method conforms to the {@link Collection#addAll} interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws UnsupportedOperationException if the collection is read-only, or
* if the collection is indexed, or if the add method is not supported by
* the concrete collection.
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public boolean addAll(Collection<? extends E> coll) {
Iterator<? extends E> i = null;
boolean doAutoCommit = beginAutoCommit();
try {
i = storedOrExternalIterator(coll);
boolean changed = false;
while (i.hasNext()) {
if (add(i.next())) {
changed = true;
}
}
StoredIterator.close(i);
commitAutoCommit(doAutoCommit);
return changed;
} catch (Exception e) {
StoredIterator.close(i);
throw handleException(e, doAutoCommit);
}
}
/**
* Removes all this collection's elements that are also contained in the
* specified collection (optional operation).
* This method conforms to the {@link Collection#removeAll} interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws UnsupportedOperationException if the collection is read-only.
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public boolean removeAll(Collection<?> coll) {
Iterator<? extends E> i = null;
boolean doAutoCommit = beginAutoCommit();
try {
boolean changed = false;
i = storedOrExternalIterator(coll);
while (i.hasNext()) {
if (remove(i.next())) {
changed = true;
}
}
StoredIterator.close(i);
commitAutoCommit(doAutoCommit);
return changed;
} catch (Exception e) {
StoredIterator.close(i);
throw handleException(e, doAutoCommit);
}
}
/**
* Retains only the elements in this collection that are contained in the
* specified collection (optional operation).
* This method conforms to the {@link Collection#removeAll} interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws UnsupportedOperationException if the collection is read-only.
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public boolean retainAll(Collection<?> coll) {
StoredIterator i = null;
boolean doAutoCommit = beginAutoCommit();
try {
boolean changed = false;
i = storedIterator();
while (i.hasNext()) {
if (!coll.contains(i.next())) {
i.remove();
changed = true;
}
}
StoredIterator.close(i);
commitAutoCommit(doAutoCommit);
return changed;
} catch (Exception e) {
StoredIterator.close(i);
throw handleException(e, doAutoCommit);
}
}
/**
* Compares the specified object with this collection for equality.
* A value comparison is performed by this method and the stored values
* are compared rather than calling the equals() method of each element.
* This method conforms to the {@link Collection#equals} interface.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public boolean equals(Object other) {
if (other instanceof Collection) {
Collection otherColl = StoredCollection.copyCollection(other);
StoredIterator i = null;
try {
i = storedIterator();
boolean otherHasAll = true;
while (i.hasNext()) {
if (!otherColl.remove(i.next())) {
otherHasAll = false;
break;
}
}
return otherHasAll && otherColl.isEmpty();
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
StoredIterator.close(i);
}
} else {
return false;
}
}
/*
* Add this in to keep FindBugs from whining at us about implementing
* equals(), but not hashCode().
*/
public int hashCode() {
return super.hashCode();
}
/**
* Returns a copy of this collection as an ArrayList. This is the same as
* {@link #toArray()} but returns a collection instead of an array.
*
* @return an {@link ArrayList} containing a copy of all elements in this
* collection.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public List<E> toList() {
ArrayList<E> list = new ArrayList<E>();
StoredIterator<E> i = null;
try {
i = storedIterator();
while (i.hasNext()) {
list.add(i.next());
}
return list;
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
StoredIterator.close(i);
}
}
/**
* Converts the collection to a string representation for debugging.
* WARNING: The returned string may be very large.
*
* @return the string representation.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[");
StoredIterator i = null;
try {
i = storedIterator();
while (i.hasNext()) {
if (buf.length() > 1) buf.append(',');
buf.append(i.next().toString());
}
buf.append(']');
return buf.toString();
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
StoredIterator.close(i);
}
}
// Inherit javadoc
public int size() {
boolean countDups = iterateDuplicates();
if (DbCompat.DATABASE_COUNT && countDups && !view.range.hasBound()) {
try {
return (int) DbCompat.getDatabaseCount(view.db);
} catch (Exception e) {
throw StoredContainer.convertException(e);
}
} else {
int count = 0;
CursorConfig cursorConfig = view.currentTxn.isLockingMode() ?
CursorConfig.READ_UNCOMMITTED : null;
DataCursor cursor = null;
/* Auto-commit is not needed because ReadUncommitted is used. */
try {
cursor = new DataCursor(view, false, cursorConfig);
OperationStatus status = cursor.getFirst(false);
while (status == OperationStatus.SUCCESS) {
if (countDups) {
count += cursor.count();
} else {
count += 1;
}
status = cursor.getNextNoDup(false);
}
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
closeCursor(cursor);
}
return count;
}
}
/**
* Returns an iterator representing an equality join of the indices and
* index key values specified.
* This method does not exist in the standard {@link Collection} interface.
*
* <p><strong>Warning:</strong> The iterator returned must be explicitly
* closed using {@link StoredIterator#close()} or {@link
* StoredIterator#close(java.util.Iterator)} to release the underlying
* database cursor resources.</p>
*
* <p>The returned iterator supports only the two methods: hasNext() and
* next(). All other methods will throw UnsupportedOperationException.</p>
*
* @param indices is an array of indices with elements corresponding to
* those in the indexKeys array.
*
* @param indexKeys is an array of index key values identifying the
* elements to be selected.
*
* @param joinConfig is the join configuration, or null to use the
* default configuration.
*
* @return an iterator over the elements in this collection that match
* all specified index key values.
*
* <!-- begin JE only -->
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
* <!-- end JE only -->
*
* @throws IllegalArgumentException if this collection is indexed or if a
* given index does not have the same store as this collection.
*
* @throws RuntimeExceptionWrapper if a checked exception is thrown,
* including a {@code DatabaseException} on BDB (C Edition).
*/
public StoredIterator<E> join(StoredContainer[] indices,
Object[] indexKeys,
JoinConfig joinConfig) {
try {
DataView[] indexViews = new DataView[indices.length];
for (int i = 0; i < indices.length; i += 1) {
indexViews[i] = indices[i].view;
}
DataCursor cursor = view.join(indexViews, indexKeys, joinConfig);
return new StoredIterator<E>(this, false, cursor);
} catch (Exception e) {
throw StoredContainer.convertException(e);
}
}
final E getFirstOrLast(boolean doGetFirst) {
DataCursor cursor = null;
try {
cursor = new DataCursor(view, false);
OperationStatus status;
if (doGetFirst) {
status = cursor.getFirst(false);
} else {
status = cursor.getLast(false);
}
return (status == OperationStatus.SUCCESS) ?
makeIteratorData(null, cursor) :
null;
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
closeCursor(cursor);
}
}
E makeIteratorData(BaseIterator iterator, DataCursor cursor) {
return makeIteratorData(iterator,
cursor.getKeyThang(),
cursor.getPrimaryKeyThang(),
cursor.getValueThang());
}
abstract E makeIteratorData(BaseIterator iterator,
DatabaseEntry keyEntry,
DatabaseEntry priKeyEntry,
DatabaseEntry valueEntry);
abstract boolean hasValues();
boolean iterateDuplicates() {
return true;
}
void checkIterAddAllowed()
throws UnsupportedOperationException {
if (!areDuplicatesAllowed()) {
throw new UnsupportedOperationException("Duplicates required");
}
}
int getIndexOffset() {
return 0;
}
private static Collection copyCollection(Object other) {
if (other instanceof StoredCollection) {
return ((StoredCollection) other).toList();
} else {
return new ArrayList((Collection) other);
}
}
}