/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2000-2003
* Sleepycat Software. All rights reserved.
*
* $Id: StoredList.java,v 1.18 2003/10/18 19:54:29 mhayes Exp $
*/
package com.sleepycat.bdb.collection;
import com.sleepycat.bdb.bind.DataBinding;
import com.sleepycat.bdb.bind.EntityBinding;
import com.sleepycat.bdb.bind.DataBuffer;
import com.sleepycat.bdb.bind.DataFormat;
import com.sleepycat.bdb.RecordNumberFormat;
import com.sleepycat.bdb.DataCursor;
import com.sleepycat.bdb.DataIndex;
import com.sleepycat.bdb.DataStore;
import com.sleepycat.bdb.DataView;
import com.sleepycat.bdb.KeyRangeException;
import com.sleepycat.db.Db;
import com.sleepycat.db.DbException;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/**
* A List view of a {@link DataStore} or {@link DataIndex}.
*
* <p>For all stored lists the keys of the underlying DataStore or DataIndex
* must have record number format, and therefore the store or index must be a
* RECNO, RECNO-RENUMBER, QUEUE, or BTREE-RECNUM database. Only RECNO-RENUMBER
* allows true list behavior where record numbers are renumbered following the
* position of an element that is added or removed. For the other access
* methods (RECNO, QUEUE, and BTREE-RECNUM), stored Lists are most useful as
* read-only collections where record numbers are not required to be
* sequential.</p>
*
* <p>In addition to the standard List methods, this class provides the
* following methods for stored lists only. Note that the use of these methods
* is not compatible with the standard Java collections interface.</p>
* <ul>
* <li>{@link #append(Object)}</li>
* </ul>
*
* @author Mark Hayes
*/
public class StoredList extends StoredCollection implements List {
private static final DataBinding DEFAULT_KEY_BINDING =
new IndexKeyBinding(1);
private int baseIndex = 1;
private boolean isSubList;
/**
* Creates a list view of a {@link DataStore}.
*
* @param store is the DataStore underlying the new collection.
*
* @param valueBinding is the binding used to translate between value
* buffers and value objects.
*
* @param writeAllowed is true to create a read-write collection or false
* to create a read-only collection.
*
* @throws IllegalArgumentException if formats are not consistently
* defined or a parameter is invalid.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public StoredList(DataStore store, DataBinding valueBinding,
boolean writeAllowed) {
super(new DataView(store, null, DEFAULT_KEY_BINDING, valueBinding,
null, writeAllowed));
}
/**
* Creates a list entity view of a {@link DataStore}.
*
* @param store is the DataStore underlying the new collection.
*
* @param valueEntityBinding is the binding used to translate between
* key/value buffers and entity value objects.
*
* @param writeAllowed is true to create a read-write collection or false
* to create a read-only collection.
*
* @throws IllegalArgumentException if formats are not consistently
* defined or a parameter is invalid.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public StoredList(DataStore store, EntityBinding valueEntityBinding,
boolean writeAllowed) {
super(new DataView(store, null, DEFAULT_KEY_BINDING, null,
valueEntityBinding, writeAllowed));
}
/**
* Creates a list view of a {@link DataIndex}.
*
* @param index is the DataIndex underlying the new collection.
*
* @param valueBinding is the binding used to translate between value
* buffers and value objects.
*
* @param writeAllowed is true to create a read-write collection or false
* to create a read-only collection.
*
* @throws IllegalArgumentException if formats are not consistently
* defined or a parameter is invalid.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public StoredList(DataIndex index, DataBinding valueBinding,
boolean writeAllowed) {
super(new DataView(null, index, DEFAULT_KEY_BINDING, valueBinding,
null, writeAllowed));
}
/**
* Creates a list entity view of a {@link DataIndex}.
*
* @param index is the DataIndex underlying the new collection.
*
* @param valueEntityBinding is the binding used to translate between
* key/value buffers and entity value objects.
*
* @param writeAllowed is true to create a read-write collection or false
* to create a read-only collection.
*
* @throws IllegalArgumentException if formats are not consistently
* defined or a parameter is invalid.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public StoredList(DataIndex index, EntityBinding valueEntityBinding,
boolean writeAllowed) {
super(new DataView(null, index, DEFAULT_KEY_BINDING, null,
valueEntityBinding, writeAllowed));
}
private StoredList(DataView view, int baseIndex) {
super(view);
this.baseIndex = baseIndex;
this.isSubList = true;
}
/**
* Inserts the specified element at the specified position in this list
* (optional operation).
* This method conforms to the {@link List#add(int, Object)} interface.
*
* @throws UnsupportedOperationException if the collection is a sublist, or
* if the collection is indexed, or if the collection is read-only, or if
* the RECNO-RENUMBER access method was not used.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public void add(int index, Object value) {
checkIterAddAllowed();
DataCursor cursor = null;
boolean doAutoCommit = beginAutoCommit();
try {
cursor = new DataCursor(view, true);
int err = cursor.get(new Long(index), null, Db.DB_SET, false);
if (err == 0) {
cursor.put(null, value, Db.DB_BEFORE, null);
closeCursor(cursor);
} else {
closeCursor(cursor);
cursor = null;
view.append(value, null, null);
}
commitAutoCommit(doAutoCommit);
} catch (Exception e) {
closeCursor(cursor);
throw handleException(e, doAutoCommit);
}
}
/**
* Appends the specified element to the end of this list (optional
* operation).
* This method conforms to the {@link List#add(Object)} interface.
*
* @throws UnsupportedOperationException if the collection is a sublist, or
* if the collection is indexed, or if the collection is read-only, or if
* the RECNO-RENUMBER access method was not used.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public boolean add(Object value) {
checkIterAddAllowed();
boolean doAutoCommit = beginAutoCommit();
try {
view.append(value, null, null);
commitAutoCommit(doAutoCommit);
return true;
} catch (Exception e) {
throw handleException(e, doAutoCommit);
}
}
/**
* Appends a given value returning the newly assigned index.
* If a {@link com.sleepycat.bdb.PrimaryKeyAssigner} is associated with
* Store for this list, it will be used to assigned the returned index.
* Otherwise the Store must be a QUEUE or RECNO database and the next
* available record number is assigned as the index.
* This method does not exist in the standard {@link List} interface.
*
* @param value the value to be appended.
*
* @return the assigned index.
*
* @throws UnsupportedOperationException if the collection is indexed, or
* if the collection is read-only, or if the Store has no {@link
* com.sleepycat.bdb.PrimaryKeyAssigner} and is not a QUEUE or RECNO
* database.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public int append(Object value) {
boolean doAutoCommit = beginAutoCommit();
try {
Object[] key = new Object[1];
view.append(value, key, null);
commitAutoCommit(doAutoCommit);
return ((Number) key[0]).intValue();
} catch (Exception e) {
throw handleException(e, doAutoCommit);
}
}
void checkIterAddAllowed()
throws UnsupportedOperationException {
if (isSubList) {
throw new UnsupportedOperationException("cannot add to subList");
}
if (!view.getDb().areKeysRenumbered()) { // checks for RECNO-RENUM
throw new UnsupportedOperationException(
"requires renumbered keys");
}
}
/**
* Inserts all of the elements in the specified collection into this list
* at the specified position (optional operation).
* This method conforms to the {@link List#addAll(int, Collection)}
* interface.
*
* @throws UnsupportedOperationException if the collection is a sublist, or
* if the collection is indexed, or if the collection is read-only, or if
* the RECNO-RENUMBER access method was not used.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public boolean addAll(int index, Collection coll) {
checkIterAddAllowed();
DataCursor cursor = null;
Iterator i = null;
boolean doAutoCommit = beginAutoCommit();
try {
i = coll.iterator();
if (!i.hasNext()) {
return false;
}
cursor = new DataCursor(view, true);
int err = cursor.get(new Long(index), null, Db.DB_SET, false);
if (err == 0) {
while (i.hasNext()) {
cursor.put(null, i.next(), Db.DB_BEFORE, null);
}
closeCursor(cursor);
} else {
closeCursor(cursor);
cursor = null;
while (i.hasNext()) {
view.append(i.next(), null, null);
}
}
StoredIterator.close(i);
commitAutoCommit(doAutoCommit);
return true;
} catch (Exception e) {
closeCursor(cursor);
StoredIterator.close(i);
throw handleException(e, doAutoCommit);
}
}
/**
* Returns true if this list contains the specified element.
* This method conforms to the {@link List#contains} interface.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public boolean contains(Object value) {
return containsValue(value);
}
/**
* Returns the element at the specified position in this list.
* This method conforms to the {@link List#get} interface.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public Object get(int index) {
return super.get(new Long(index));
}
/**
* Returns the index in this list of the first occurrence of the specified
* element, or -1 if this list does not contain this element.
* This method conforms to the {@link List#indexOf} interface.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public int indexOf(Object value) {
return indexOf(value, true);
}
/**
* Returns the index in this list of the last occurrence of the specified
* element, or -1 if this list does not contain this element.
* This method conforms to the {@link List#lastIndexOf} interface.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public int lastIndexOf(Object value) {
return indexOf(value, false);
}
private int indexOf(Object value, boolean findFirst) {
DataCursor cursor = null;
try {
cursor = new DataCursor(view, false);
int err = cursor.find(value, findFirst);
return (err == 0)
? (cursor.getCurrentRecordNumber() - baseIndex)
: (-1);
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
closeCursor(cursor);
}
}
int getIndexOffset() {
return baseIndex;
}
/**
* Returns a list iterator of the elements in this list (in proper
* sequence).
* The iterator will be read-only if the collection is read-only.
* This method conforms to the {@link List#listIterator()} interface.
*
* @return a {@link StoredIterator} for this collection.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*
* @see #isWriteAllowed
*/
public ListIterator listIterator() {
return iterator(isWriteAllowed());
}
/**
* Returns a list iterator of the elements in this list (in proper
* sequence), starting at the specified position in this list.
* The iterator will be read-only if the collection is read-only.
* This method conforms to the {@link List#listIterator(int)} interface.
*
* @return a {@link StoredIterator} for this collection.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*
* @see #isWriteAllowed
*/
public ListIterator listIterator(int index) {
StoredIterator i = iterator(isWriteAllowed());
if (i.moveToIndex(index)) {
return i;
} else {
i.close();
throw new IndexOutOfBoundsException(String.valueOf(index));
}
}
/**
* Removes the element at the specified position in this list (optional
* operation).
* This method conforms to the {@link List#remove(int)} interface.
*
* @throws UnsupportedOperationException if the collection is a sublist, or
* if the collection is read-only.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public Object remove(int index) {
try {
Object[] oldVal = new Object[1];
removeKey(new Long(index), oldVal);
return oldVal[0];
} catch (IllegalArgumentException e) {
throw new IndexOutOfBoundsException(e.getMessage());
}
}
/**
* Removes the first occurrence in this list of the specified element
* (optional operation).
* This method conforms to the {@link List#remove(Object)} interface.
*
* @throws UnsupportedOperationException if the collection is a sublist, or
* if the collection is read-only.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public boolean remove(Object value) {
return removeValue(value);
}
/**
* Replaces the element at the specified position in this list with the
* specified element (optional operation).
* This method conforms to the {@link List#set} interface.
*
* @throws UnsupportedOperationException if the collection is indexed, or
* if the collection is read-only.
*
* @throws IllegalArgumentException if an entity value binding is used and
* the primary key of the value given is different than the existing stored
* primary key.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public Object set(int index, Object value) {
try {
return put(new Long(index), value);
} catch (IllegalArgumentException e) {
throw new IndexOutOfBoundsException(e.getMessage());
}
}
/**
* Returns a view of the portion of this list between the specified
* fromIndex, inclusive, and toIndex, exclusive.
* Note that add() and remove() may not be called for the returned sublist.
* This method conforms to the {@link List#subList} interface.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public List subList(int fromIndex, int toIndex) {
if (fromIndex < 0 || fromIndex > toIndex) {
throw new IndexOutOfBoundsException(String.valueOf(fromIndex));
}
try {
int newBaseIndex = baseIndex + fromIndex;
return new StoredList(
view.subView(new Long(fromIndex), true,
new Long(toIndex), false,
new IndexKeyBinding(newBaseIndex)),
newBaseIndex);
} catch (KeyRangeException e) {
throw new IndexOutOfBoundsException(e.getMessage());
} catch (Exception e) {
throw StoredContainer.convertException(e);
}
}
/**
* Compares the specified object with this list 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 List#equals} interface.
*
* @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
*/
public boolean equals(Object other) {
if (!(other instanceof List)) return false;
List otherList = (List) other;
ListIterator i1 = null;
ListIterator i2 = null;
try {
i1 = listIterator();
i2 = otherList.listIterator();
while (i1.hasNext()) {
if (!i2.hasNext()) return false;
if (i1.nextIndex() != i2.nextIndex()) return false;
Object o1 = i1.next();
Object o2 = i2.next();
if (o1 == null) {
if (o2 != null) return false;
} else {
if (!o1.equals(o2)) return false;
}
}
if (i2.hasNext()) return false;
return true;
} finally {
StoredIterator.close(i1);
StoredIterator.close(i2);
}
}
Object makeIteratorData(StoredIterator iterator, DataCursor cursor)
throws DbException, IOException {
return cursor.getCurrentValue();
}
boolean hasValues() {
return true;
}
private static class IndexKeyBinding implements DataBinding {
private static RecordNumberFormat format = new RecordNumberFormat();
private int baseIndex;
private IndexKeyBinding(int baseIndex) {
this.baseIndex = baseIndex;
}
public DataFormat getDataFormat() {
return format;
}
public Object dataToObject(DataBuffer data)
throws IOException {
return new Long(format.dataToRecordNumber(data) - baseIndex);
}
public void objectToData(Object object, DataBuffer data)
throws IOException {
format.recordNumberToData(((Number) object).intValue() + baseIndex,
data);
}
}
}