1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: StoredContainer.java,v 12.10 2008/02/07 17:12:26 mark Exp $
7 */
8
9package com.sleepycat.collections;
10
11import java.util.Collection;
12import java.util.Iterator;
13
14import com.sleepycat.compat.DbCompat;
15import com.sleepycat.db.CursorConfig;
16import com.sleepycat.db.DatabaseException;
17import com.sleepycat.db.OperationStatus;
18import com.sleepycat.util.RuntimeExceptionWrapper;
19
20/**
21 * A abstract base class for all stored collections and maps.  This class
22 * provides implementations of methods that are common to the {@link
23 * java.util.Collection} and the {@link java.util.Map} interfaces, namely
24 * {@link #clear}, {@link #isEmpty} and {@link #size}.
25 *
26 * <p>In addition, this class provides the following methods for stored
27 * collections only.  Note that the use of these methods is not compatible with
28 * the standard Java collections interface.</p>
29 * <ul>
30 * <li>{@link #isWriteAllowed()}</li>
31 * <li>{@link #isSecondary()}</li>
32 * <li>{@link #isOrdered()}</li>
33 * <li>{@link #areKeyRangesAllowed()}</li>
34 * <li>{@link #areDuplicatesAllowed()}</li>
35 * <li>{@link #areDuplicatesOrdered()}</li>
36 * <li>{@link #areKeysRenumbered()}</li>
37 * <li>{@link #getCursorConfig()}</li>
38 * <li>{@link #isTransactional()}</li>
39 * </ul>
40 *
41 * @author Mark Hayes
42 */
43public abstract class StoredContainer implements Cloneable {
44
45    DataView view;
46
47    StoredContainer(DataView view) {
48
49        this.view = view;
50    }
51
52    /**
53     * Returns true if this is a read-write container or false if this is a
54     * read-only container.
55     * This method does not exist in the standard {@link java.util.Map} or
56     * {@link java.util.Collection} interfaces.
57     *
58     * @return whether write is allowed.
59     */
60    public final boolean isWriteAllowed() {
61
62        return view.writeAllowed;
63    }
64
65    /**
66     * Returns the cursor configuration that is used for all operations
67     * performed via this container.
68     * For example, if <code>CursorConfig.getReadUncommitted</code> returns
69     * true, data will be read that is modified but not committed.
70     * This method does not exist in the standard {@link java.util.Map} or
71     * {@link java.util.Collection} interfaces.
72     *
73     * @return the cursor configuration, or null if no configuration has been
74     * specified.
75     */
76    public final CursorConfig getCursorConfig() {
77
78        return DbCompat.cloneCursorConfig(view.cursorConfig);
79    }
80
81    /**
82     * Returns whether read-uncommitted is allowed for this container.
83     * For the JE product, read-uncommitted is always allowed; for the DB
84     * product, read-uncommitted is allowed if it was configured for the
85     * underlying database for this container.
86     * Even when read-uncommitted is allowed it must specifically be enabled by
87     * calling one of the {@link StoredCollections} methods.
88     * This method does not exist in the standard {@link java.util.Map} or
89     * {@link java.util.Collection} interfaces.
90     *
91     * @return whether read-uncommitted is allowed.
92     *
93     * @deprecated This method is deprecated with no replacement in this class.
94     * In the DB product, <code>DatabaseConfig.getReadUncommitted</code> may be
95     * called.
96     */
97    public final boolean isDirtyReadAllowed() {
98
99        return view.readUncommittedAllowed;
100    }
101
102    /**
103     * @deprecated This method has been replaced by {@link #getCursorConfig}.
104     * <code>CursorConfig.isReadUncommitted</code> may be called to determine
105     * whether dirty-read is enabled.
106     */
107    public final boolean isDirtyRead() {
108
109        return view.cursorConfig.getReadUncommitted();
110    }
111
112    /**
113     * Returns whether the databases underlying this container are
114     * transactional.
115     * Even in a transactional environment, a database will be transactional
116     * only if it was opened within a transaction or if the auto-commit option
117     * was specified when it was opened.
118     * This method does not exist in the standard {@link java.util.Map} or
119     * {@link java.util.Collection} interfaces.
120     *
121     * @return whether the database is transactional.
122     */
123    public final boolean isTransactional() {
124
125        return view.transactional;
126    }
127
128    /**
129     * Clones a container with a specified cursor configuration.
130     */
131    final StoredContainer configuredClone(CursorConfig config) {
132
133        try {
134            StoredContainer cont = (StoredContainer) clone();
135            cont.view = cont.view.configuredView(config);
136            cont.initAfterClone();
137            return cont;
138        } catch (CloneNotSupportedException willNeverOccur) { return null; }
139    }
140
141    /**
142     * Override this method to initialize view-dependent fields.
143     */
144    void initAfterClone() {
145    }
146
147    /**
148     * Returns whether duplicate keys are allowed in this container.
149     * Duplicates are optionally allowed for HASH and BTREE databases.
150     * This method does not exist in the standard {@link java.util.Map} or
151     * {@link java.util.Collection} interfaces.
152     *
153     * <p>Note that the JE product only supports BTREE databases.</p>
154     *
155     * @return whether duplicates are allowed.
156     */
157    public final boolean areDuplicatesAllowed() {
158
159        return view.dupsAllowed;
160    }
161
162    /**
163     * Returns whether duplicate keys are allowed and sorted by element value.
164     * Duplicates are optionally sorted for HASH and BTREE databases.
165     * This method does not exist in the standard {@link java.util.Map} or
166     * {@link java.util.Collection} interfaces.
167     *
168     * <p>Note that the JE product only supports BTREE databases, and
169     * duplicates are always sorted.</p>
170     *
171     * @return whether duplicates are ordered.
172     */
173    public final boolean areDuplicatesOrdered() {
174
175        return view.dupsOrdered;
176    }
177
178    /**
179     * Returns whether keys are renumbered when insertions and deletions occur.
180     * Keys are optionally renumbered for RECNO databases.
181     * This method does not exist in the standard {@link java.util.Map} or
182     * {@link java.util.Collection} interfaces.
183     *
184     * <p>Note that the JE product does not support RECNO databases, and
185     * therefore keys are never renumbered.</p>
186     *
187     * @return whether keys are renumbered.
188     */
189    public final boolean areKeysRenumbered() {
190
191        return view.keysRenumbered;
192    }
193
194    /**
195     * Returns whether keys are ordered in this container.
196     * Keys are ordered for BTREE, RECNO and QUEUE databases.
197     * This method does not exist in the standard {@link java.util.Map} or
198     * {@link java.util.Collection} interfaces.
199     *
200     * <p>Note that the JE product only support BTREE databases, and
201     * therefore keys are always ordered.</p>
202     *
203     * @return whether keys are ordered.
204     */
205    public final boolean isOrdered() {
206
207        return view.ordered;
208    }
209
210    /**
211     * Returns whether key ranges are allowed in this container.
212     * Key ranges are allowed only for BTREE databases.
213     * This method does not exist in the standard {@link java.util.Map} or
214     * {@link java.util.Collection} interfaces.
215     *
216     * <p>Note that the JE product only supports BTREE databases, and
217     * therefore key ranges are always allowed.</p>
218     *
219     * @return whether keys are ordered.
220     */
221    public final boolean areKeyRangesAllowed() {
222
223        return view.keyRangesAllowed;
224    }
225
226    /**
227     * Returns whether this container is a view on a secondary database rather
228     * than directly on a primary database.
229     * This method does not exist in the standard {@link java.util.Map} or
230     * {@link java.util.Collection} interfaces.
231     *
232     * @return whether the view is for a secondary database.
233     */
234    public final boolean isSecondary() {
235
236        return view.isSecondary();
237    }
238
239    /**
240     * Returns a non-transactional count of the records in the collection or
241     * map.  This method conforms to the {@link java.util.Collection#size} and
242     * {@link java.util.Map#size} interfaces.
243     *
244     *
245     * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown.
246     */
247    public abstract int size();
248
249    /**
250     * Returns true if this map or collection contains no mappings or elements.
251     * This method conforms to the {@link java.util.Collection#isEmpty} and
252     * {@link java.util.Map#isEmpty} interfaces.
253     *
254     * @return whether the container is empty.
255     *
256     * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown.
257     */
258    public boolean isEmpty() {
259
260        try {
261            return view.isEmpty();
262        } catch (Exception e) {
263            throw convertException(e);
264        }
265    }
266
267    /**
268     * Removes all mappings or elements from this map or collection (optional
269     * operation).
270     * This method conforms to the {@link java.util.Collection#clear} and
271     * {@link java.util.Map#clear} interfaces.
272     *
273     * @throws UnsupportedOperationException if the container is read-only.
274     *
275     * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown.
276     */
277    public void clear() {
278
279        boolean doAutoCommit = beginAutoCommit();
280        try {
281            view.clear();
282            commitAutoCommit(doAutoCommit);
283        } catch (Exception e) {
284            throw handleException(e, doAutoCommit);
285        }
286    }
287
288    Object get(Object key) {
289
290        DataCursor cursor = null;
291        try {
292            cursor = new DataCursor(view, false);
293            if (OperationStatus.SUCCESS ==
294                cursor.getSearchKey(key, null, false)) {
295                return cursor.getCurrentValue();
296            } else {
297                return null;
298            }
299        } catch (Exception e) {
300            throw StoredContainer.convertException(e);
301        } finally {
302            closeCursor(cursor);
303        }
304    }
305
306    Object put(final Object key, final Object value) {
307
308        DataCursor cursor = null;
309        boolean doAutoCommit = beginAutoCommit();
310        try {
311            cursor = new DataCursor(view, true);
312            Object[] oldValue = new Object[1];
313            cursor.put(key, value, oldValue, false);
314            closeCursor(cursor);
315            commitAutoCommit(doAutoCommit);
316            return oldValue[0];
317        } catch (Exception e) {
318            closeCursor(cursor);
319            throw handleException(e, doAutoCommit);
320        }
321    }
322
323    final boolean removeKey(final Object key, final Object[] oldVal) {
324
325        DataCursor cursor = null;
326        boolean doAutoCommit = beginAutoCommit();
327        try {
328            cursor = new DataCursor(view, true);
329            boolean found = false;
330            OperationStatus status = cursor.getSearchKey(key, null, true);
331            while (status == OperationStatus.SUCCESS) {
332                cursor.delete();
333                found = true;
334                if (oldVal != null && oldVal[0] == null) {
335                    oldVal[0] = cursor.getCurrentValue();
336                }
337                status = areDuplicatesAllowed() ?
338                    cursor.getNextDup(true): OperationStatus.NOTFOUND;
339            }
340            closeCursor(cursor);
341            commitAutoCommit(doAutoCommit);
342            return found;
343        } catch (Exception e) {
344            closeCursor(cursor);
345            throw handleException(e, doAutoCommit);
346        }
347    }
348
349    boolean containsKey(Object key) {
350
351        DataCursor cursor = null;
352        try {
353            cursor = new DataCursor(view, false);
354            return OperationStatus.SUCCESS ==
355                   cursor.getSearchKey(key, null, false);
356        } catch (Exception e) {
357            throw StoredContainer.convertException(e);
358        } finally {
359            closeCursor(cursor);
360        }
361    }
362
363    final boolean removeValue(Object value) {
364
365        DataCursor cursor = null;
366        boolean doAutoCommit = beginAutoCommit();
367        try {
368            cursor = new DataCursor(view, true);
369            OperationStatus status = cursor.findValue(value, true);
370            if (status == OperationStatus.SUCCESS) {
371                cursor.delete();
372            }
373            closeCursor(cursor);
374            commitAutoCommit(doAutoCommit);
375            return (status == OperationStatus.SUCCESS);
376        } catch (Exception e) {
377            closeCursor(cursor);
378            throw handleException(e, doAutoCommit);
379        }
380    }
381
382    boolean containsValue(Object value) {
383
384        DataCursor cursor = null;
385        try {
386            cursor = new DataCursor(view, false);
387            OperationStatus status = cursor.findValue(value, true);
388            return (status == OperationStatus.SUCCESS);
389        } catch (Exception e) {
390            throw StoredContainer.convertException(e);
391        } finally {
392            closeCursor(cursor);
393        }
394    }
395
396    /**
397     * Returns a StoredIterator if the given collection is a StoredCollection,
398     * else returns a regular/external Iterator.  The iterator returned should
399     * be closed with the static method StoredIterator.close(Iterator).
400     */
401    final Iterator storedOrExternalIterator(Collection coll) {
402
403        if (coll instanceof StoredCollection) {
404            return ((StoredCollection) coll).storedIterator();
405        } else {
406            return coll.iterator();
407        }
408    }
409
410    final void closeCursor(DataCursor cursor) {
411
412        if (cursor != null) {
413            try {
414                cursor.close();
415            } catch (Exception e) {
416                throw StoredContainer.convertException(e);
417            }
418        }
419    }
420
421    final boolean beginAutoCommit() {
422
423        if (view.transactional) {
424            CurrentTransaction currentTxn = view.getCurrentTxn();
425            try {
426                if (currentTxn.isAutoCommitAllowed()) {
427                    currentTxn.beginTransaction(null);
428                    return true;
429                }
430            } catch (DatabaseException e) {
431                throw new RuntimeExceptionWrapper(e);
432            }
433        }
434        return false;
435    }
436
437    final void commitAutoCommit(boolean doAutoCommit)
438        throws DatabaseException {
439
440        if (doAutoCommit) view.getCurrentTxn().commitTransaction();
441    }
442
443    final RuntimeException handleException(Exception e, boolean doAutoCommit) {
444
445        if (doAutoCommit) {
446            try {
447                view.getCurrentTxn().abortTransaction();
448            } catch (DatabaseException ignored) {
449		/* Klockwork - ok */
450            }
451        }
452        return StoredContainer.convertException(e);
453    }
454
455    static RuntimeException convertException(Exception e) {
456
457        if (e instanceof RuntimeException) {
458            return (RuntimeException) e;
459        } else {
460            return new RuntimeExceptionWrapper(e);
461        }
462    }
463}
464