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