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