001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2000,2008 Oracle. All rights reserved.
005: *
006: * $Id: StoredList.java,v 1.47.2.2 2008/01/07 15:14:06 cwl Exp $
007: */
008:
009: package com.sleepycat.collections;
010:
011: import java.util.Collection;
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.ListIterator;
015:
016: import com.sleepycat.bind.EntityBinding;
017: import com.sleepycat.bind.EntryBinding;
018: import com.sleepycat.bind.RecordNumberBinding;
019: import com.sleepycat.je.Database;
020: import com.sleepycat.je.DatabaseEntry;
021: import com.sleepycat.je.DatabaseException;
022: import com.sleepycat.je.OperationStatus;
023: import com.sleepycat.util.keyrange.KeyRangeException;
024:
025: /**
026: * A List view of a {@link Database}.
027: *
028: * <p>For all stored lists the keys of the underlying Database
029: * must have record number format, and therefore the store or index must be a
030: * RECNO, RECNO-RENUMBER, QUEUE, or BTREE-RECNUM database. Only RECNO-RENUMBER
031: * allows true list behavior where record numbers are renumbered following the
032: * position of an element that is added or removed. For the other access
033: * methods (RECNO, QUEUE, and BTREE-RECNUM), stored Lists are most useful as
034: * read-only collections where record numbers are not required to be
035: * sequential.</p>
036: *
037: * <p>In addition to the standard List methods, this class provides the
038: * following methods for stored lists only. Note that the use of these methods
039: * is not compatible with the standard Java collections interface.</p>
040: * <ul>
041: * <li>{@link #append(Object)}</li>
042: * </ul>
043: *
044: * @author Mark Hayes
045: */
046: public class StoredList extends StoredCollection implements List {
047:
048: private static final EntryBinding DEFAULT_KEY_BINDING = new IndexKeyBinding(
049: 1);
050:
051: private int baseIndex = 1;
052: private boolean isSubList;
053:
054: /**
055: * Creates a list view of a {@link Database}.
056: *
057: * @param database is the Database underlying the new collection.
058: *
059: * @param valueBinding is the binding used to translate between value
060: * buffers and value objects.
061: *
062: * @param writeAllowed is true to create a read-write collection or false
063: * to create a read-only collection.
064: *
065: * @throws IllegalArgumentException if formats are not consistently
066: * defined or a parameter is invalid.
067: *
068: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
069: * thrown.
070: */
071: public StoredList(Database database, EntryBinding valueBinding,
072: boolean writeAllowed) {
073:
074: super (new DataView(database, DEFAULT_KEY_BINDING, valueBinding,
075: null, writeAllowed, null));
076: }
077:
078: /**
079: * Creates a list entity view of a {@link Database}.
080: *
081: * @param database is the Database underlying the new collection.
082: *
083: * @param valueEntityBinding is the binding used to translate between
084: * key/value buffers and entity value objects.
085: *
086: * @param writeAllowed is true to create a read-write collection or false
087: * to create a read-only collection.
088: *
089: * @throws IllegalArgumentException if formats are not consistently
090: * defined or a parameter is invalid.
091: *
092: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
093: * thrown.
094: */
095: public StoredList(Database database,
096: EntityBinding valueEntityBinding, boolean writeAllowed) {
097:
098: super (new DataView(database, DEFAULT_KEY_BINDING, null,
099: 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 {@link DatabaseException} is
118: * thrown.
119: */
120: public StoredList(Database database, EntryBinding valueBinding,
121: PrimaryKeyAssigner keyAssigner) {
122:
123: super (new DataView(database, DEFAULT_KEY_BINDING, valueBinding,
124: null, true, keyAssigner));
125: }
126:
127: /**
128: * Creates a list entity view of a {@link Database} with a {@link
129: * PrimaryKeyAssigner}. Writing is allowed for the created list.
130: *
131: * @param database is the Database underlying the new collection.
132: *
133: * @param valueEntityBinding is the binding used to translate between
134: * key/value buffers and entity value objects.
135: *
136: * @param keyAssigner is used by the {@link #add} and {@link #append}
137: * methods to assign primary keys.
138: *
139: * @throws IllegalArgumentException if formats are not consistently
140: * defined or a parameter is invalid.
141: *
142: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
143: * thrown.
144: */
145: public StoredList(Database database,
146: EntityBinding valueEntityBinding,
147: PrimaryKeyAssigner keyAssigner) {
148:
149: super (new DataView(database, DEFAULT_KEY_BINDING, null,
150: valueEntityBinding, true, keyAssigner));
151: }
152:
153: private StoredList(DataView view, int baseIndex) {
154:
155: super (view);
156: this .baseIndex = baseIndex;
157: this .isSubList = true;
158: }
159:
160: /**
161: * Inserts the specified element at the specified position in this list
162: * (optional operation).
163: * This method conforms to the {@link List#add(int, Object)} interface.
164: *
165: * @throws UnsupportedOperationException if the collection is a sublist, or
166: * if the collection is indexed, or if the collection is read-only, or if
167: * the RECNO-RENUMBER access method was not used.
168: *
169: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
170: * thrown.
171: */
172: public void add(int index, Object value) {
173:
174: checkIterAddAllowed();
175: DataCursor cursor = null;
176: boolean doAutoCommit = beginAutoCommit();
177: try {
178: cursor = new DataCursor(view, true);
179: OperationStatus status = cursor.getSearchKey(
180: new Long(index), null, false);
181: if (status == OperationStatus.SUCCESS) {
182: cursor.putBefore(value);
183: closeCursor(cursor);
184: } else {
185: closeCursor(cursor);
186: cursor = null;
187: view.append(value, null, null);
188: }
189: commitAutoCommit(doAutoCommit);
190: } catch (Exception e) {
191: closeCursor(cursor);
192: throw handleException(e, doAutoCommit);
193: }
194: }
195:
196: /**
197: * Appends the specified element to the end of this list (optional
198: * operation).
199: * This method conforms to the {@link List#add(Object)} interface.
200: *
201: * @throws UnsupportedOperationException if the collection is a sublist, or
202: * if the collection is indexed, or if the collection is read-only, or if
203: * the RECNO-RENUMBER access method was not used.
204: *
205: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
206: * thrown.
207: */
208: public boolean add(Object value) {
209:
210: checkIterAddAllowed();
211: boolean doAutoCommit = beginAutoCommit();
212: try {
213: view.append(value, null, null);
214: commitAutoCommit(doAutoCommit);
215: return true;
216: } catch (Exception e) {
217: throw handleException(e, doAutoCommit);
218: }
219: }
220:
221: /**
222: * Appends a given value returning the newly assigned index.
223: * If a {@link com.sleepycat.collections.PrimaryKeyAssigner} is associated
224: * with Store for this list, it will be used to assigned the returned
225: * index. Otherwise the Store must be a QUEUE or RECNO database and the
226: * next available record number is assigned as the index. This method does
227: * not exist in the standard {@link List} interface.
228: *
229: * @param value the value to be appended.
230: *
231: * @return the assigned index.
232: *
233: * @throws UnsupportedOperationException if the collection is indexed, or
234: * if the collection is read-only, or if the Store has no {@link
235: * com.sleepycat.collections.PrimaryKeyAssigner} and is not a QUEUE or
236: * RECNO database.
237: *
238: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
239: * thrown.
240: */
241: public int append(Object value) {
242:
243: boolean doAutoCommit = beginAutoCommit();
244: try {
245: Object[] key = new Object[1];
246: view.append(value, key, null);
247: commitAutoCommit(doAutoCommit);
248: return ((Number) key[0]).intValue();
249: } catch (Exception e) {
250: throw handleException(e, doAutoCommit);
251: }
252: }
253:
254: void checkIterAddAllowed() throws UnsupportedOperationException {
255:
256: if (isSubList) {
257: throw new UnsupportedOperationException(
258: "cannot add to subList");
259: }
260: if (!view.keysRenumbered) { // RECNO-RENUM
261: throw new UnsupportedOperationException(
262: "requires renumbered keys");
263: }
264: }
265:
266: /**
267: * Inserts all of the elements in the specified collection into this list
268: * at the specified position (optional operation).
269: * This method conforms to the {@link List#addAll(int, Collection)}
270: * interface.
271: *
272: * @throws UnsupportedOperationException if the collection is a sublist, or
273: * if the collection is indexed, or if the collection is read-only, or if
274: * the RECNO-RENUMBER access method was not used.
275: *
276: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
277: * thrown.
278: */
279: public boolean addAll(int index, Collection coll) {
280:
281: checkIterAddAllowed();
282: DataCursor cursor = null;
283: Iterator i = null;
284: boolean doAutoCommit = beginAutoCommit();
285: try {
286: i = storedOrExternalIterator(coll);
287: if (!i.hasNext()) {
288: return false;
289: }
290: cursor = new DataCursor(view, true);
291: OperationStatus status = cursor.getSearchKey(
292: new Long(index), null, false);
293: if (status == OperationStatus.SUCCESS) {
294: while (i.hasNext()) {
295: cursor.putBefore(i.next());
296: }
297: closeCursor(cursor);
298: } else {
299: closeCursor(cursor);
300: cursor = null;
301: while (i.hasNext()) {
302: view.append(i.next(), null, null);
303: }
304: }
305: StoredIterator.close(i);
306: commitAutoCommit(doAutoCommit);
307: return true;
308: } catch (Exception e) {
309: closeCursor(cursor);
310: StoredIterator.close(i);
311: throw handleException(e, doAutoCommit);
312: }
313: }
314:
315: /**
316: * Returns true if this list contains the specified element.
317: * This method conforms to the {@link List#contains} interface.
318: *
319: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
320: * thrown.
321: */
322: public boolean contains(Object value) {
323:
324: return containsValue(value);
325: }
326:
327: /**
328: * Returns the element at the specified position in this list.
329: * This method conforms to the {@link List#get} interface.
330: *
331: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
332: * thrown.
333: */
334: public Object get(int index) {
335:
336: return super .get(new Long(index));
337: }
338:
339: /**
340: * Returns the index in this list of the first occurrence of the specified
341: * element, or -1 if this list does not contain this element.
342: * This method conforms to the {@link List#indexOf} interface.
343: *
344: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
345: * thrown.
346: */
347: public int indexOf(Object value) {
348:
349: return indexOf(value, true);
350: }
351:
352: /**
353: * Returns the index in this list of the last occurrence of the specified
354: * element, or -1 if this list does not contain this element.
355: * This method conforms to the {@link List#lastIndexOf} interface.
356: *
357: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
358: * thrown.
359: */
360: public int lastIndexOf(Object value) {
361:
362: return indexOf(value, false);
363: }
364:
365: private int indexOf(Object value, boolean findFirst) {
366:
367: DataCursor cursor = null;
368: try {
369: cursor = new DataCursor(view, false);
370: OperationStatus status = cursor.findValue(value, findFirst);
371: return (status == OperationStatus.SUCCESS) ? (cursor
372: .getCurrentRecordNumber() - baseIndex) : (-1);
373: } catch (Exception e) {
374: throw StoredContainer.convertException(e);
375: } finally {
376: closeCursor(cursor);
377: }
378: }
379:
380: int getIndexOffset() {
381:
382: return baseIndex;
383: }
384:
385: /**
386: * Returns a list iterator of the elements in this list (in proper
387: * sequence).
388: * The iterator will be read-only if the collection is read-only.
389: * This method conforms to the {@link List#listIterator()} interface.
390: *
391: * <p>For information on cursor stability and iterator block size, see
392: * {@link #iterator()}.</p>
393: *
394: * @return a {@link ListIterator} for this collection.
395: *
396: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
397: * thrown.
398: *
399: * @see #isWriteAllowed
400: */
401: public ListIterator listIterator() {
402:
403: return blockIterator();
404: }
405:
406: /**
407: * Returns a list iterator of the elements in this list (in proper
408: * sequence), starting at the specified position in this list.
409: * The iterator will be read-only if the collection is read-only.
410: * This method conforms to the {@link List#listIterator(int)} interface.
411: *
412: * <p>For information on cursor stability and iterator block size, see
413: * {@link #iterator()}.</p>
414: *
415: * @return a {@link ListIterator} for this collection.
416: *
417: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
418: * thrown.
419: *
420: * @see #isWriteAllowed
421: */
422: public ListIterator listIterator(int index) {
423:
424: BlockIterator i = blockIterator();
425: if (i.moveToIndex(index)) {
426: return i;
427: } else {
428: throw new IndexOutOfBoundsException(String.valueOf(index));
429: }
430: }
431:
432: /**
433: * Removes the element at the specified position in this list (optional
434: * operation).
435: * This method conforms to the {@link List#remove(int)} interface.
436: *
437: * @throws UnsupportedOperationException if the collection is a sublist, or
438: * if the collection is read-only.
439: *
440: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
441: * thrown.
442: */
443: public Object remove(int index) {
444:
445: try {
446: Object[] oldVal = new Object[1];
447: removeKey(new Long(index), oldVal);
448: return oldVal[0];
449: } catch (IllegalArgumentException e) {
450: throw new IndexOutOfBoundsException(e.getMessage());
451: }
452: }
453:
454: /**
455: * Removes the first occurrence in this list of the specified element
456: * (optional operation).
457: * This method conforms to the {@link List#remove(Object)} interface.
458: *
459: * @throws UnsupportedOperationException if the collection is a sublist, or
460: * if the collection is read-only.
461: *
462: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
463: * thrown.
464: */
465: public boolean remove(Object value) {
466:
467: return removeValue(value);
468: }
469:
470: /**
471: * Replaces the element at the specified position in this list with the
472: * specified element (optional operation).
473: * This method conforms to the {@link List#set} interface.
474: *
475: * @throws UnsupportedOperationException if the collection is indexed, or
476: * if the collection is read-only.
477: *
478: * @throws IllegalArgumentException if an entity value binding is used and
479: * the primary key of the value given is different than the existing stored
480: * primary key.
481: *
482: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
483: * thrown.
484: */
485: public Object set(int index, Object value) {
486:
487: try {
488: return put(new Long(index), value);
489: } catch (IllegalArgumentException e) {
490: throw new IndexOutOfBoundsException(e.getMessage());
491: }
492: }
493:
494: /**
495: * Returns a view of the portion of this list between the specified
496: * fromIndex, inclusive, and toIndex, exclusive.
497: * Note that add() and remove() may not be called for the returned sublist.
498: * This method conforms to the {@link List#subList} interface.
499: *
500: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
501: * thrown.
502: */
503: public List subList(int fromIndex, int toIndex) {
504:
505: if (fromIndex < 0 || fromIndex > toIndex) {
506: throw new IndexOutOfBoundsException(String
507: .valueOf(fromIndex));
508: }
509: try {
510: int newBaseIndex = baseIndex + fromIndex;
511: return new StoredList(view.subView(new Long(fromIndex),
512: true, new Long(toIndex), false,
513: new IndexKeyBinding(newBaseIndex)), newBaseIndex);
514: } catch (KeyRangeException e) {
515: throw new IndexOutOfBoundsException(e.getMessage());
516: } catch (Exception e) {
517: throw StoredContainer.convertException(e);
518: }
519: }
520:
521: /**
522: * Compares the specified object with this list for equality.
523: * A value comparison is performed by this method and the stored values
524: * are compared rather than calling the equals() method of each element.
525: * This method conforms to the {@link List#equals} interface.
526: *
527: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
528: * thrown.
529: */
530: public boolean equals(Object other) {
531:
532: if (!(other instanceof List))
533: return false;
534: List otherList = (List) other;
535: StoredIterator i1 = null;
536: ListIterator i2 = null;
537: try {
538: i1 = storedIterator();
539: i2 = storedOrExternalListIterator(otherList);
540: while (i1.hasNext()) {
541: if (!i2.hasNext())
542: return false;
543: if (i1.nextIndex() != i2.nextIndex())
544: return false;
545: Object o1 = i1.next();
546: Object o2 = i2.next();
547: if (o1 == null) {
548: if (o2 != null)
549: return false;
550: } else {
551: if (!o1.equals(o2))
552: return false;
553: }
554: }
555: if (i2.hasNext())
556: return false;
557: return true;
558: } finally {
559: if (i1 != null) {
560: i1.close();
561: }
562: StoredIterator.close(i2);
563: }
564: }
565:
566: /**
567: * Returns a StoredIterator if the given collection is a StoredCollection,
568: * else returns a regular/external ListIterator. The iterator returned
569: * should be closed with the static method StoredIterator.close(Iterator).
570: */
571: final ListIterator storedOrExternalListIterator(List list) {
572:
573: if (list instanceof StoredCollection) {
574: return ((StoredCollection) list).storedIterator();
575: } else {
576: return list.listIterator();
577: }
578: }
579:
580: /*
581: * Add this in to keep FindBugs from whining at us about implementing
582: * equals(), but not hashCode().
583: */
584: public int hashCode() {
585: return super .hashCode();
586: }
587:
588: Object makeIteratorData(BaseIterator iterator,
589: DatabaseEntry keyEntry, DatabaseEntry priKeyEntry,
590: DatabaseEntry valueEntry) {
591:
592: return view.makeValue(priKeyEntry, valueEntry);
593: }
594:
595: boolean hasValues() {
596:
597: return true;
598: }
599:
600: private static class IndexKeyBinding extends RecordNumberBinding {
601:
602: private int baseIndex;
603:
604: private IndexKeyBinding(int baseIndex) {
605:
606: this .baseIndex = baseIndex;
607: }
608:
609: public Object entryToObject(DatabaseEntry data) {
610:
611: return new Long(entryToRecordNumber(data) - baseIndex);
612: }
613:
614: public void objectToEntry(Object object, DatabaseEntry data) {
615:
616: recordNumberToEntry(((Number) object).intValue()
617: + baseIndex, data);
618: }
619: }
620: }
|