1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000,2008 Oracle. All rights reserved. 5 * 6 * $Id: StoredCollection.java,v 12.10 2008/02/07 17:12:26 mark Exp $ 7 */ 8 9package com.sleepycat.collections; 10 11import java.util.ArrayList; 12import java.util.Arrays; 13import java.util.Collection; 14import java.util.Iterator; 15import java.util.List; 16 17import com.sleepycat.compat.DbCompat; 18import com.sleepycat.db.CursorConfig; 19import com.sleepycat.db.DatabaseEntry; 20import com.sleepycat.db.DatabaseException; 21import com.sleepycat.db.JoinConfig; 22import com.sleepycat.db.OperationStatus; 23 24/** 25 * A abstract base class for all stored collections. This class, and its 26 * base class {@link StoredContainer}, provide implementations of most methods 27 * in the {@link Collection} interface. Other methods, such as {@link #add} 28 * and {@link #remove}, are provided by concrete classes that extend this 29 * class. 30 * 31 * <p>In addition, this class provides the following methods for stored 32 * collections only. Note that the use of these methods is not compatible with 33 * the standard Java collections interface.</p> 34 * <ul> 35 * <li>{@link #getIteratorBlockSize}</li> 36 * <li>{@link #setIteratorBlockSize}</li> 37 * <li>{@link #storedIterator()}</li> 38 * <li>{@link #storedIterator(boolean)}</li> 39 * <li>{@link #join}</li> 40 * <li>{@link #toList()}</li> 41 * </ul> 42 * 43 * @author Mark Hayes 44 */ 45public abstract class StoredCollection extends StoredContainer 46 implements Collection { 47 48 /** 49 * The default number of records read at one time by iterators. 50 * @see #setIteratorBlockSize 51 */ 52 public static final int DEFAULT_ITERATOR_BLOCK_SIZE = 10; 53 54 private int iteratorBlockSize = DEFAULT_ITERATOR_BLOCK_SIZE; 55 56 StoredCollection(DataView view) { 57 58 super(view); 59 } 60 61 /** 62 * Returns the number of records read at one time by iterators returned by 63 * the {@link #iterator} method. By default this value is {@link 64 * #DEFAULT_ITERATOR_BLOCK_SIZE}. 65 */ 66 public int getIteratorBlockSize() { 67 68 return iteratorBlockSize; 69 } 70 71 /** 72 * Changes the number of records read at one time by iterators returned by 73 * the {@link #iterator} method. By default this value is {@link 74 * #DEFAULT_ITERATOR_BLOCK_SIZE}. 75 * 76 * @throws IllegalArgumentException if the blockSize is less than two. 77 */ 78 public void setIteratorBlockSize(int blockSize) { 79 80 if (blockSize < 2) { 81 throw new IllegalArgumentException 82 ("blockSize is less than two: " + blockSize); 83 } 84 85 iteratorBlockSize = blockSize; 86 } 87 88 final boolean add(Object key, Object value) { 89 90 DataCursor cursor = null; 91 boolean doAutoCommit = beginAutoCommit(); 92 try { 93 cursor = new DataCursor(view, true); 94 OperationStatus status = 95 cursor.putNoDupData(key, value, null, false); 96 closeCursor(cursor); 97 commitAutoCommit(doAutoCommit); 98 return (status == OperationStatus.SUCCESS); 99 } catch (Exception e) { 100 closeCursor(cursor); 101 throw handleException(e, doAutoCommit); 102 } 103 } 104 105 BlockIterator blockIterator() { 106 return new BlockIterator(this, isWriteAllowed(), iteratorBlockSize); 107 } 108 109 /** 110 * Returns an iterator over the elements in this collection. 111 * The iterator will be read-only if the collection is read-only. 112 * This method conforms to the {@link Collection#iterator} interface. 113 * 114 * <p>The iterator returned by this method does not keep a database cursor 115 * open and therefore it does not need to be closed. It reads blocks of 116 * records as needed, opening and closing a cursor to read each block of 117 * records. The number of records per block is 10 by default and can be 118 * changed with {@link #setIteratorBlockSize}.</p> 119 * 120 * <p>Because this iterator does not keep a cursor open, if it is used 121 * without transactions, the iterator does not have <em>cursor 122 * stability</em> characteristics. In other words, the record at the 123 * current iterator position can be changed or deleted by another thread. 124 * To prevent this from happening, call this method within a transaction or 125 * use the {@link #storedIterator()} method instead.</p> 126 * 127 * @return a standard {@link Iterator} for this collection. 128 * 129 * @see #isWriteAllowed 130 */ 131 public Iterator iterator() { 132 return blockIterator(); 133 } 134 135 /** 136 * Returns an iterator over the elements in this collection. 137 * The iterator will be read-only if the collection is read-only. 138 * This method does not exist in the standard {@link Collection} interface. 139 * 140 * <p>If {@code Iterator.set} or {@code Iterator.remove} will be called 141 * and the underlying Database is transactional, then a transaction must be 142 * active when calling this method and must remain active while using the 143 * iterator.</p> 144 * 145 * <p><strong>Warning:</strong> The iterator returned must be explicitly 146 * closed using {@link StoredIterator#close()} or {@link 147 * StoredIterator#close(java.util.Iterator)} to release the underlying 148 * database cursor resources.</p> 149 * 150 * @return a {@link StoredIterator} for this collection. 151 * 152 * @see #isWriteAllowed 153 */ 154 public StoredIterator storedIterator() { 155 156 return storedIterator(isWriteAllowed()); 157 } 158 159 /** 160 * Returns a read or read-write iterator over the elements in this 161 * collection. 162 * This method does not exist in the standard {@link Collection} interface. 163 * 164 * <p>If {@code Iterator.set} or {@code Iterator.remove} will be called 165 * and the underlying Database is transactional, then a transaction must be 166 * active when calling this method and must remain active while using the 167 * iterator.</p> 168 * 169 * <p><strong>Warning:</strong> The iterator returned must be explicitly 170 * closed using {@link StoredIterator#close()} or {@link 171 * StoredIterator#close(java.util.Iterator)} to release the underlying 172 * database cursor resources.</p> 173 * 174 * @param writeAllowed is true to open a read-write iterator or false to 175 * open a read-only iterator. If the collection is read-only the iterator 176 * will always be read-only. 177 * 178 * @return a {@link StoredIterator} for this collection. 179 * 180 * @throws IllegalStateException if writeAllowed is true but the collection 181 * is read-only. 182 * 183 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 184 * thrown. 185 * 186 * @see #isWriteAllowed 187 */ 188 public StoredIterator storedIterator(boolean writeAllowed) { 189 190 try { 191 return new StoredIterator(this, writeAllowed && isWriteAllowed(), 192 null); 193 } catch (Exception e) { 194 throw StoredContainer.convertException(e); 195 } 196 } 197 198 /** 199 * @deprecated Please use {@link #storedIterator()} or {@link 200 * #storedIterator(boolean)} instead. Because the iterator returned must 201 * be closed, the method name {@code iterator} is confusing since standard 202 * Java iterators do not need to be closed. 203 */ 204 public StoredIterator iterator(boolean writeAllowed) { 205 206 return storedIterator(writeAllowed); 207 } 208 209 /** 210 * Returns an array of all the elements in this collection. 211 * This method conforms to the {@link Collection#toArray()} interface. 212 * 213 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 214 * thrown. 215 */ 216 public Object[] toArray() { 217 218 ArrayList list = new ArrayList(); 219 StoredIterator i = storedIterator(); 220 try { 221 while (i.hasNext()) { 222 list.add(i.next()); 223 } 224 } finally { 225 i.close(); 226 } 227 return list.toArray(); 228 } 229 230 /** 231 * Returns an array of all the elements in this collection whose runtime 232 * type is that of the specified array. 233 * This method conforms to the {@link Collection#toArray(Object[])} 234 * interface. 235 * 236 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 237 * thrown. 238 */ 239 public Object[] toArray(Object[] a) { 240 241 int j = 0; 242 StoredIterator i = storedIterator(); 243 try { 244 while (j < a.length && i.hasNext()) { 245 a[j++] = i.next(); 246 } 247 if (j < a.length) { 248 a[j] = null; 249 } else if (i.hasNext()) { 250 ArrayList list = new ArrayList(Arrays.asList(a)); 251 while (i.hasNext()) { 252 list.add(i.next()); 253 } 254 a = list.toArray(a); 255 } 256 } finally { 257 i.close(); 258 } 259 return a; 260 } 261 262 /** 263 * Returns true if this collection contains all of the elements in the 264 * specified collection. 265 * This method conforms to the {@link Collection#containsAll} interface. 266 * 267 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 268 * thrown. 269 */ 270 public boolean containsAll(Collection coll) { 271 Iterator i = storedOrExternalIterator(coll); 272 try { 273 while (i.hasNext()) { 274 if (!contains(i.next())) { 275 return false; 276 } 277 } 278 } finally { 279 StoredIterator.close(i); 280 } 281 return true; 282 } 283 284 /** 285 * Adds all of the elements in the specified collection to this collection 286 * (optional operation). 287 * This method calls the {@link #add(Object)} method of the concrete 288 * collection class, which may or may not be supported. 289 * This method conforms to the {@link Collection#addAll} interface. 290 * 291 * @throws UnsupportedOperationException if the collection is read-only, or 292 * if the collection is indexed, or if the add method is not supported by 293 * the concrete collection. 294 * 295 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 296 * thrown. 297 */ 298 public boolean addAll(Collection coll) { 299 Iterator i = null; 300 boolean doAutoCommit = beginAutoCommit(); 301 try { 302 i = storedOrExternalIterator(coll); 303 boolean changed = false; 304 while (i.hasNext()) { 305 if (add(i.next())) { 306 changed = true; 307 } 308 } 309 StoredIterator.close(i); 310 commitAutoCommit(doAutoCommit); 311 return changed; 312 } catch (Exception e) { 313 StoredIterator.close(i); 314 throw handleException(e, doAutoCommit); 315 } 316 } 317 318 /** 319 * Removes all this collection's elements that are also contained in the 320 * specified collection (optional operation). 321 * This method conforms to the {@link Collection#removeAll} interface. 322 * 323 * @throws UnsupportedOperationException if the collection is read-only. 324 * 325 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 326 * thrown. 327 */ 328 public boolean removeAll(Collection coll) { 329 330 return removeAll(coll, true); 331 } 332 333 /** 334 * Retains only the elements in this collection that are contained in the 335 * specified collection (optional operation). 336 * This method conforms to the {@link Collection#removeAll} interface. 337 * 338 * @throws UnsupportedOperationException if the collection is read-only. 339 * 340 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 341 * thrown. 342 */ 343 public boolean retainAll(Collection coll) { 344 345 return removeAll(coll, false); 346 } 347 348 private boolean removeAll(Collection coll, boolean ifExistsInColl) { 349 StoredIterator i = null; 350 boolean doAutoCommit = beginAutoCommit(); 351 try { 352 boolean changed = false; 353 i = storedIterator(); 354 while (i.hasNext()) { 355 if (ifExistsInColl == coll.contains(i.next())) { 356 i.remove(); 357 changed = true; 358 } 359 } 360 i.close(); 361 commitAutoCommit(doAutoCommit); 362 return changed; 363 } catch (Exception e) { 364 if (i != null) { 365 i.close(); 366 } 367 throw handleException(e, doAutoCommit); 368 } 369 } 370 371 /** 372 * Compares the specified object with this collection for equality. 373 * A value comparison is performed by this method and the stored values 374 * are compared rather than calling the equals() method of each element. 375 * This method conforms to the {@link Collection#equals} interface. 376 * 377 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 378 * thrown. 379 */ 380 public boolean equals(Object other) { 381 382 if (other instanceof Collection) { 383 Collection otherColl = StoredCollection.copyCollection(other); 384 StoredIterator i = storedIterator(); 385 try { 386 while (i.hasNext()) { 387 if (!otherColl.remove(i.next())) { 388 return false; 389 } 390 } 391 return otherColl.isEmpty(); 392 } finally { 393 i.close(); 394 } 395 } else { 396 return false; 397 } 398 } 399 400 /* 401 * Add this in to keep FindBugs from whining at us about implementing 402 * equals(), but not hashCode(). 403 */ 404 public int hashCode() { 405 return super.hashCode(); 406 } 407 408 /** 409 * Returns a copy of this collection as an ArrayList. This is the same as 410 * {@link #toArray()} but returns a collection instead of an array. 411 * 412 * @return an {@link ArrayList} containing a copy of all elements in this 413 * collection. 414 * 415 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 416 * thrown. 417 */ 418 public List toList() { 419 420 ArrayList list = new ArrayList(); 421 StoredIterator i = storedIterator(); 422 try { 423 while (i.hasNext()) list.add(i.next()); 424 return list; 425 } finally { 426 i.close(); 427 } 428 } 429 430 /** 431 * Converts the collection to a string representation for debugging. 432 * WARNING: The returned string may be very large. 433 * 434 * @return the string representation. 435 * 436 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 437 * thrown. 438 */ 439 public String toString() { 440 StringBuffer buf = new StringBuffer(); 441 buf.append("["); 442 StoredIterator i = storedIterator(); 443 try { 444 while (i.hasNext()) { 445 if (buf.length() > 1) buf.append(','); 446 buf.append(i.next().toString()); 447 } 448 buf.append(']'); 449 return buf.toString(); 450 } finally { 451 i.close(); 452 } 453 } 454 455 // Inherit javadoc 456 public int size() { 457 458 boolean countDups = iterateDuplicates(); 459 if (DbCompat.DATABASE_COUNT && countDups && !view.range.hasBound()) { 460 try { 461 return (int) DbCompat.getDatabaseCount(view.db); 462 } catch (Exception e) { 463 throw StoredContainer.convertException(e); 464 } 465 } else { 466 int count = 0; 467 CursorConfig cursorConfig = view.currentTxn.isLockingMode() ? 468 CursorConfig.READ_UNCOMMITTED : null; 469 DataCursor cursor = null; 470 try { 471 cursor = new DataCursor(view, false, cursorConfig); 472 OperationStatus status = cursor.getFirst(false); 473 while (status == OperationStatus.SUCCESS) { 474 if (countDups) { 475 count += cursor.count(); 476 } else { 477 count += 1; 478 } 479 status = cursor.getNextNoDup(false); 480 } 481 } catch (Exception e) { 482 throw StoredContainer.convertException(e); 483 } finally { 484 closeCursor(cursor); 485 } 486 return count; 487 } 488 } 489 490 /** 491 * Returns an iterator representing an equality join of the indices and 492 * index key values specified. 493 * This method does not exist in the standard {@link Collection} interface. 494 * 495 * <p><strong>Warning:</strong> The iterator returned must be explicitly 496 * closed using {@link StoredIterator#close()} or {@link 497 * StoredIterator#close(java.util.Iterator)} to release the underlying 498 * database cursor resources.</p> 499 * 500 * <p>The returned iterator supports only the two methods: hasNext() and 501 * next(). All other methods will throw UnsupportedOperationException.</p> 502 * 503 * @param indices is an array of indices with elements corresponding to 504 * those in the indexKeys array. 505 * 506 * @param indexKeys is an array of index key values identifying the 507 * elements to be selected. 508 * 509 * @param joinConfig is the join configuration, or null to use the 510 * default configuration. 511 * 512 * @return an iterator over the elements in this collection that match 513 * all specified index key values. 514 * 515 * @throws IllegalArgumentException if this collection is indexed or if a 516 * given index does not have the same store as this collection. 517 * 518 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 519 * thrown. 520 */ 521 public StoredIterator join(StoredContainer[] indices, Object[] indexKeys, 522 JoinConfig joinConfig) { 523 524 try { 525 DataView[] indexViews = new DataView[indices.length]; 526 for (int i = 0; i < indices.length; i += 1) { 527 indexViews[i] = indices[i].view; 528 } 529 DataCursor cursor = view.join(indexViews, indexKeys, joinConfig); 530 return new StoredIterator(this, false, cursor); 531 } catch (Exception e) { 532 throw StoredContainer.convertException(e); 533 } 534 } 535 536 final Object getFirstOrLast(boolean doGetFirst) { 537 538 DataCursor cursor = null; 539 try { 540 cursor = new DataCursor(view, false); 541 OperationStatus status; 542 if (doGetFirst) { 543 status = cursor.getFirst(false); 544 } else { 545 status = cursor.getLast(false); 546 } 547 return (status == OperationStatus.SUCCESS) ? 548 makeIteratorData(null, cursor) : null; 549 } catch (Exception e) { 550 throw StoredContainer.convertException(e); 551 } finally { 552 closeCursor(cursor); 553 } 554 } 555 556 Object makeIteratorData(BaseIterator iterator, DataCursor cursor) { 557 558 return makeIteratorData(iterator, 559 cursor.getKeyThang(), 560 cursor.getPrimaryKeyThang(), 561 cursor.getValueThang()); 562 } 563 564 abstract Object makeIteratorData(BaseIterator iterator, 565 DatabaseEntry keyEntry, 566 DatabaseEntry priKeyEntry, 567 DatabaseEntry valueEntry); 568 569 abstract boolean hasValues(); 570 571 boolean iterateDuplicates() { 572 573 return true; 574 } 575 576 void checkIterAddAllowed() 577 throws UnsupportedOperationException { 578 579 if (!areDuplicatesAllowed()) { 580 throw new UnsupportedOperationException("duplicates required"); 581 } 582 } 583 584 int getIndexOffset() { 585 586 return 0; 587 } 588 589 private static Collection copyCollection(Object other) { 590 591 if (other instanceof StoredCollection) { 592 return ((StoredCollection) other).toList(); 593 } else { 594 return new ArrayList((Collection) other); 595 } 596 } 597} 598