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