| /*- |
| * 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); |
| } |
| } |
| } |