1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2000, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.collections;
9 
10 import java.util.Collection;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.ListIterator;
14 
15 import com.sleepycat.bind.EntityBinding;
16 import com.sleepycat.bind.EntryBinding;
17 import com.sleepycat.bind.RecordNumberBinding;
18 import com.sleepycat.db.Database;
19 import com.sleepycat.db.DatabaseEntry;
20 import com.sleepycat.db.OperationStatus;
21 import com.sleepycat.util.RuntimeExceptionWrapper;
22 import com.sleepycat.util.keyrange.KeyRangeException;
23 
24 /**
25  * A List view of a {@link Database}.
26  *
27  * <p>For all stored lists the keys of the underlying Database
28  * must have record number format, and therefore the store or index must be a
29  * RECNO, RECNO-RENUMBER, QUEUE, or BTREE-RECNUM database.  Only RECNO-RENUMBER
30  * allows true list behavior where record numbers are renumbered following the
31  * position of an element that is added or removed.  For the other access
32  * methods (RECNO, QUEUE, and BTREE-RECNUM), stored Lists are most useful as
33  * read-only collections where record numbers are not required to be
34  * sequential.</p>
35  *
36  * <p>In addition to the standard List methods, this class provides the
37  * following methods for stored lists only.  Note that the use of these methods
38  * is not compatible with the standard Java collections interface.</p>
39  * <ul>
40  * <li>{@link #append(Object)}</li>
41  * </ul>
42  * @author Mark Hayes
43  */
44 public class StoredList<E> extends StoredCollection<E> implements List<E> {
45 
46     private static final EntryBinding DEFAULT_KEY_BINDING =
47         new IndexKeyBinding(1);
48 
49     private int baseIndex = 1;
50     private boolean isSubList;
51 
52     /**
53      * Creates a list view of a {@link Database}.
54      *
55      * @param database is the Database underlying the new collection.
56      *
57      * @param valueBinding is the binding used to translate between value
58      * buffers and value objects.
59      *
60      * @param writeAllowed is true to create a read-write collection or false
61      * to create a read-only collection.
62      *
63      * @throws IllegalArgumentException if formats are not consistently
64      * defined or a parameter is invalid.
65      *
66      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
67      * including a {@code DatabaseException} on BDB (C Edition).
68      */
StoredList(Database database, EntryBinding<E> valueBinding, boolean writeAllowed)69     public StoredList(Database database,
70                       EntryBinding<E> valueBinding,
71                       boolean writeAllowed) {
72 
73         super(new DataView(database, DEFAULT_KEY_BINDING, valueBinding, null,
74                            writeAllowed, null));
75     }
76 
77     /**
78      * Creates a list entity view of a {@link Database}.
79      *
80      * @param database is the Database underlying the new collection.
81      *
82      * @param valueEntityBinding is the binding used to translate between
83      * key/value buffers and entity value objects.
84      *
85      * @param writeAllowed is true to create a read-write collection or false
86      * to create a read-only collection.
87      *
88      * @throws IllegalArgumentException if formats are not consistently
89      * defined or a parameter is invalid.
90      *
91      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
92      * including a {@code DatabaseException} on BDB (C Edition).
93      */
StoredList(Database database, EntityBinding<E> valueEntityBinding, boolean writeAllowed)94     public StoredList(Database database,
95                       EntityBinding<E> valueEntityBinding,
96                       boolean writeAllowed) {
97 
98         super(new DataView(database, DEFAULT_KEY_BINDING, null,
99                            valueEntityBinding, writeAllowed, null));
100     }
101 
102     /**
103      * Creates a list view of a {@link Database} with a {@link
104      * PrimaryKeyAssigner}.  Writing is allowed for the created list.
105      *
106      * @param database is the Database underlying the new collection.
107      *
108      * @param valueBinding is the binding used to translate between value
109      * buffers and value objects.
110      *
111      * @param keyAssigner is used by the {@link #add} and {@link #append}
112      * methods to assign primary keys.
113      *
114      * @throws IllegalArgumentException if formats are not consistently
115      * defined or a parameter is invalid.
116      *
117      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
118      * including a {@code DatabaseException} on BDB (C Edition).
119      */
StoredList(Database database, EntryBinding<E> valueBinding, PrimaryKeyAssigner keyAssigner)120     public StoredList(Database database,
121                       EntryBinding<E> valueBinding,
122                       PrimaryKeyAssigner keyAssigner) {
123 
124         super(new DataView(database, DEFAULT_KEY_BINDING, valueBinding,
125                            null, true, keyAssigner));
126     }
127 
128     /**
129      * Creates a list entity view of a {@link Database} with a {@link
130      * PrimaryKeyAssigner}.  Writing is allowed for the created list.
131      *
132      * @param database is the Database underlying the new collection.
133      *
134      * @param valueEntityBinding is the binding used to translate between
135      * key/value buffers and entity value objects.
136      *
137      * @param keyAssigner is used by the {@link #add} and {@link #append}
138      * methods to assign primary keys.
139      *
140      * @throws IllegalArgumentException if formats are not consistently
141      * defined or a parameter is invalid.
142      *
143      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
144      * including a {@code DatabaseException} on BDB (C Edition).
145      */
StoredList(Database database, EntityBinding<E> valueEntityBinding, PrimaryKeyAssigner keyAssigner)146     public StoredList(Database database,
147                       EntityBinding<E> valueEntityBinding,
148                       PrimaryKeyAssigner keyAssigner) {
149 
150         super(new DataView(database, DEFAULT_KEY_BINDING, null,
151                            valueEntityBinding, true, keyAssigner));
152     }
153 
StoredList(DataView view, int baseIndex)154     private StoredList(DataView view, int baseIndex) {
155 
156         super(view);
157         this.baseIndex = baseIndex;
158         this.isSubList = true;
159     }
160 
161     /**
162      * Inserts the specified element at the specified position in this list
163      * (optional operation).
164      * This method conforms to the {@link List#add(int, Object)} interface.
165      *
166      *
167      * @throws UnsupportedOperationException if the collection is a sublist, or
168      * if the collection is indexed, or if the collection is read-only, or if
169      * the RECNO-RENUMBER access method was not used.
170      *
171      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
172      * including a {@code DatabaseException} on BDB (C Edition).
173      */
add(int index, E value)174     public void add(int index, E value) {
175 
176         checkIterAddAllowed();
177         DataCursor cursor = null;
178         boolean doAutoCommit = beginAutoCommit();
179         try {
180             cursor = new DataCursor(view, true);
181             OperationStatus status =
182                 cursor.getSearchKey(Long.valueOf(index), null, false);
183             if (status == OperationStatus.SUCCESS) {
184                 cursor.putBefore(value);
185                 closeCursor(cursor);
186             } else {
187                 closeCursor(cursor);
188                 cursor = null;
189                 view.append(value, null, null);
190             }
191             commitAutoCommit(doAutoCommit);
192         } catch (Exception e) {
193             closeCursor(cursor);
194             throw handleException(e, doAutoCommit);
195         }
196     }
197 
198     /**
199      * Appends the specified element to the end of this list (optional
200      * operation).
201      * This method conforms to the {@link List#add(Object)} interface.
202      *
203      *
204      * @throws UnsupportedOperationException if the collection is a sublist, or
205      * if the collection is indexed, or if the collection is read-only, or if
206      * the RECNO-RENUMBER access method was not used.
207      *
208      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
209      * including a {@code DatabaseException} on BDB (C Edition).
210      */
add(E value)211     public boolean add(E value) {
212 
213         checkIterAddAllowed();
214         boolean doAutoCommit = beginAutoCommit();
215         try {
216             view.append(value, null, null);
217             commitAutoCommit(doAutoCommit);
218             return true;
219         } catch (Exception e) {
220             throw handleException(e, doAutoCommit);
221         }
222     }
223 
224     /**
225      * Appends a given value returning the newly assigned index.
226      * If a {@link com.sleepycat.collections.PrimaryKeyAssigner} is associated
227      * with Store for this list, it will be used to assigned the returned
228      * index.  Otherwise the Store must be a QUEUE or RECNO database and the
229      * next available record number is assigned as the index.  This method does
230      * not exist in the standard {@link List} interface.
231      *
232      * @param value the value to be appended.
233      *
234      * @return the assigned index.
235      *
236      *
237      * @throws UnsupportedOperationException if the collection is indexed, or
238      * if the collection is read-only, or if the Store has no {@link
239      * com.sleepycat.collections.PrimaryKeyAssigner} and is not a QUEUE or
240      * RECNO database.
241      *
242      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
243      * including a {@code DatabaseException} on BDB (C Edition).
244      */
append(E value)245     public int append(E value) {
246 
247         boolean doAutoCommit = beginAutoCommit();
248         try {
249             Object[] key = new Object[1];
250             view.append(value, key, null);
251             commitAutoCommit(doAutoCommit);
252             return ((Number) key[0]).intValue();
253         } catch (Exception e) {
254             throw handleException(e, doAutoCommit);
255         }
256     }
257 
checkIterAddAllowed()258     void checkIterAddAllowed()
259         throws UnsupportedOperationException {
260 
261         if (isSubList) {
262             throw new UnsupportedOperationException("Cannot add to subList");
263         }
264         if (!view.keysRenumbered) { // RECNO-RENUM
265             throw new UnsupportedOperationException
266                 ("Requires renumbered keys");
267         }
268     }
269 
270     /**
271      * Inserts all of the elements in the specified collection into this list
272      * at the specified position (optional operation).
273      * This method conforms to the {@link List#addAll(int, Collection)}
274      * interface.
275      *
276      *
277      * @throws UnsupportedOperationException if the collection is a sublist, or
278      * if the collection is indexed, or if the collection is read-only, or if
279      * the RECNO-RENUMBER access method was not used.
280      *
281      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
282      * including a {@code DatabaseException} on BDB (C Edition).
283      */
addAll(int index, Collection<? extends E> coll)284     public boolean addAll(int index, Collection<? extends E> coll) {
285 
286         checkIterAddAllowed();
287         DataCursor cursor = null;
288         Iterator<? extends E> i = null;
289         boolean doAutoCommit = beginAutoCommit();
290         try {
291             i = storedOrExternalIterator(coll);
292             if (!i.hasNext()) {
293                 return false;
294             }
295             cursor = new DataCursor(view, true);
296             OperationStatus status =
297                 cursor.getSearchKey(Long.valueOf(index), null, false);
298             if (status == OperationStatus.SUCCESS) {
299                 while (i.hasNext()) {
300                     cursor.putBefore(i.next());
301                 }
302                 closeCursor(cursor);
303             } else {
304                 closeCursor(cursor);
305                 cursor = null;
306                 while (i.hasNext()) {
307                     view.append(i.next(), null, null);
308                 }
309             }
310             StoredIterator.close(i);
311             commitAutoCommit(doAutoCommit);
312             return true;
313         } catch (Exception e) {
314             closeCursor(cursor);
315             StoredIterator.close(i);
316             throw handleException(e, doAutoCommit);
317         }
318     }
319 
320     /**
321      * Returns true if this list contains the specified element.
322      * This method conforms to the {@link List#contains} interface.
323      *
324      *
325      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
326      * including a {@code DatabaseException} on BDB (C Edition).
327      */
contains(Object value)328     public boolean contains(Object value) {
329 
330         return containsValue(value);
331     }
332 
333     /**
334      * Returns the element at the specified position in this list.
335      * This method conforms to the {@link List#get} interface.
336      *
337      *
338      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
339      * including a {@code DatabaseException} on BDB (C Edition).
340      */
get(int index)341     public E get(int index) {
342 
343         return (E) getValue(Long.valueOf(index));
344     }
345 
346     /**
347      * Returns the index in this list of the first occurrence of the specified
348      * element, or -1 if this list does not contain this element.
349      * This method conforms to the {@link List#indexOf} interface.
350      *
351      *
352      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
353      * including a {@code DatabaseException} on BDB (C Edition).
354      */
indexOf(Object value)355     public int indexOf(Object value) {
356 
357         return indexOf(value, true);
358     }
359 
360     /**
361      * Returns the index in this list of the last occurrence of the specified
362      * element, or -1 if this list does not contain this element.
363      * This method conforms to the {@link List#lastIndexOf} interface.
364      *
365      *
366      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
367      * including a {@code DatabaseException} on BDB (C Edition).
368      */
lastIndexOf(Object value)369     public int lastIndexOf(Object value) {
370 
371         return indexOf(value, false);
372     }
373 
indexOf(Object value, boolean findFirst)374     private int indexOf(Object value, boolean findFirst) {
375 
376         DataCursor cursor = null;
377         try {
378             cursor = new DataCursor(view, false);
379             OperationStatus status = cursor.findValue(value, findFirst);
380             return (status == OperationStatus.SUCCESS) ?
381                    (cursor.getCurrentRecordNumber() - baseIndex) :
382                    (-1);
383         } catch (Exception e) {
384             throw StoredContainer.convertException(e);
385         } finally {
386             closeCursor(cursor);
387         }
388     }
389 
getIndexOffset()390     int getIndexOffset() {
391 
392         return baseIndex;
393     }
394 
395     /**
396      * Returns a list iterator of the elements in this list (in proper
397      * sequence).
398      * The iterator will be read-only if the collection is read-only.
399      * This method conforms to the {@link List#listIterator()} interface.
400      *
401      * <p>For information on cursor stability and iterator block size, see
402      * {@link #iterator()}.</p>
403      *
404      * @return a {@link ListIterator} for this collection.
405      *
406      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
407      * including a {@code DatabaseException} on BDB (C Edition).
408      *
409      * @see #isWriteAllowed
410      */
listIterator()411     public ListIterator<E> listIterator() {
412 
413         return blockIterator();
414     }
415 
416     /**
417      * Returns a list iterator of the elements in this list (in proper
418      * sequence), starting at the specified position in this list.
419      * The iterator will be read-only if the collection is read-only.
420      * This method conforms to the {@link List#listIterator(int)} interface.
421      *
422      * <p>For information on cursor stability and iterator block size, see
423      * {@link #iterator()}.</p>
424      *
425      * @return a {@link ListIterator} for this collection.
426      *
427      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
428      * including a {@code DatabaseException} on BDB (C Edition).
429      *
430      * @see #isWriteAllowed
431      */
listIterator(int index)432     public ListIterator<E> listIterator(int index) {
433 
434         BlockIterator i = blockIterator();
435         if (i.moveToIndex(index)) {
436             return i;
437         } else {
438             throw new IndexOutOfBoundsException(String.valueOf(index));
439         }
440     }
441 
442     /**
443      * Removes the element at the specified position in this list (optional
444      * operation).
445      * This method conforms to the {@link List#remove(int)} interface.
446      *
447      *
448      * @throws UnsupportedOperationException if the collection is a sublist, or
449      * if the collection is read-only.
450      *
451      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
452      * including a {@code DatabaseException} on BDB (C Edition).
453      */
remove(int index)454     public E remove(int index) {
455 
456         try {
457             Object[] oldVal = new Object[1];
458             removeKey(Long.valueOf(index), oldVal);
459             return (E) oldVal[0];
460         } catch (IllegalArgumentException e) {
461             throw new IndexOutOfBoundsException(e.getMessage());
462         }
463     }
464 
465     /**
466      * Removes the first occurrence in this list of the specified element
467      * (optional operation).
468      * This method conforms to the {@link List#remove(Object)} interface.
469      *
470      *
471      * @throws UnsupportedOperationException if the collection is a sublist, or
472      * if the collection is read-only.
473      *
474      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
475      * including a {@code DatabaseException} on BDB (C Edition).
476      */
remove(Object value)477     public boolean remove(Object value) {
478 
479         return removeValue(value);
480     }
481 
482     /**
483      * Replaces the element at the specified position in this list with the
484      * specified element (optional operation).
485      * This method conforms to the {@link List#set} interface.
486      *
487      *
488      * @throws UnsupportedOperationException if the collection is indexed, or
489      * if the collection is read-only.
490      *
491      * @throws IllegalArgumentException if an entity value binding is used and
492      * the primary key of the value given is different than the existing stored
493      * primary key.
494      *
495      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
496      * including a {@code DatabaseException} on BDB (C Edition).
497      */
set(int index, E value)498     public E set(int index, E value) {
499 
500         try {
501             return (E) putKeyValue(Long.valueOf(index), value);
502         } catch (IllegalArgumentException e) {
503             throw new IndexOutOfBoundsException(e.getMessage());
504         }
505     }
506 
507     /**
508      * Returns a view of the portion of this list between the specified
509      * fromIndex, inclusive, and toIndex, exclusive.
510      * Note that add() and remove() may not be called for the returned sublist.
511      * This method conforms to the {@link List#subList} interface.
512      *
513      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
514      * including a {@code DatabaseException} on BDB (C Edition).
515      */
subList(int fromIndex, int toIndex)516     public List<E> subList(int fromIndex, int toIndex) {
517 
518         if (fromIndex < 0 || fromIndex > toIndex) {
519             throw new IndexOutOfBoundsException(String.valueOf(fromIndex));
520         }
521         try {
522             int newBaseIndex = baseIndex + fromIndex;
523             return new StoredList(
524                 view.subView(Long.valueOf(fromIndex), true,
525                              Long.valueOf(toIndex), false,
526                              new IndexKeyBinding(newBaseIndex)),
527                 newBaseIndex);
528         } catch (KeyRangeException e) {
529             throw new IndexOutOfBoundsException(e.getMessage());
530         } catch (Exception e) {
531             throw StoredContainer.convertException(e);
532         }
533     }
534 
535     /**
536      * Compares the specified object with this list for equality.
537      * A value comparison is performed by this method and the stored values
538      * are compared rather than calling the equals() method of each element.
539      * This method conforms to the {@link List#equals} interface.
540      *
541      *
542      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
543      * including a {@code DatabaseException} on BDB (C Edition).
544      */
equals(Object other)545     public boolean equals(Object other) {
546 
547         if (!(other instanceof List)) return false;
548         List otherList = (List) other;
549         StoredIterator i1 = null;
550         ListIterator i2 = null;
551         try {
552             i1 = storedIterator();
553             i2 = storedOrExternalListIterator(otherList);
554             while (i1.hasNext()) {
555                 if (!i2.hasNext()) {
556                     return false;
557                 }
558                 if (i1.nextIndex() != i2.nextIndex()) {
559                     return false;
560                 }
561                 Object o1 = i1.next();
562                 Object o2 = i2.next();
563                 if (o1 == null) {
564                     if (o2 != null) {
565                         return false;
566                     }
567                 } else {
568                     if (!o1.equals(o2)) {
569                         return false;
570                     }
571                 }
572             }
573             return !i2.hasNext();
574         } finally {
575             StoredIterator.close(i1);
576             StoredIterator.close(i2);
577         }
578     }
579 
580     /**
581      * Returns a StoredIterator if the given collection is a StoredCollection,
582      * else returns a regular/external ListIterator.  The iterator returned
583      * should be closed with the static method StoredIterator.close(Iterator).
584      */
storedOrExternalListIterator(List list)585     final ListIterator storedOrExternalListIterator(List list) {
586 
587         if (list instanceof StoredCollection) {
588             return ((StoredCollection) list).storedIterator();
589         } else {
590             return list.listIterator();
591         }
592     }
593 
594     /*
595      * Add this in to keep FindBugs from whining at us about implementing
596      * equals(), but not hashCode().
597      */
hashCode()598     public int hashCode() {
599         return super.hashCode();
600     }
601 
makeIteratorData(BaseIterator iterator, DatabaseEntry keyEntry, DatabaseEntry priKeyEntry, DatabaseEntry valueEntry)602     E makeIteratorData(BaseIterator iterator,
603                        DatabaseEntry keyEntry,
604                        DatabaseEntry priKeyEntry,
605                        DatabaseEntry valueEntry) {
606 
607         return (E) view.makeValue(priKeyEntry, valueEntry);
608     }
609 
hasValues()610     boolean hasValues() {
611 
612         return true;
613     }
614 
615     private static class IndexKeyBinding extends RecordNumberBinding {
616 
617         private int baseIndex;
618 
IndexKeyBinding(int baseIndex)619         private IndexKeyBinding(int baseIndex) {
620 
621             this.baseIndex = baseIndex;
622         }
623 
624         @Override
entryToObject(DatabaseEntry data)625         public Long entryToObject(DatabaseEntry data) {
626 
627             return Long.valueOf(entryToRecordNumber(data) - baseIndex);
628         }
629 
630         @Override
objectToEntry(Object object, DatabaseEntry data)631         public void objectToEntry(Object object, DatabaseEntry data) {
632 
633             recordNumberToEntry(((Number) object).intValue() + baseIndex,
634                                 data);
635         }
636     }
637 }
638