1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: StoredMap.java,v 12.11 2008/02/07 17:12:26 mark Exp $
7 */
8
9package com.sleepycat.collections;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.Map;
15import java.util.Set;
16
17import com.sleepycat.bind.EntityBinding;
18import com.sleepycat.bind.EntryBinding;
19import com.sleepycat.db.Database;
20import com.sleepycat.util.keyrange.KeyRangeException;
21
22/**
23 * A Map view of a {@link Database}.
24 *
25 * <p>In addition to the standard Map methods, this class provides the
26 * following methods for stored maps only.  Note that the use of these methods
27 * is not compatible with the standard Java collections interface.</p>
28 * <ul>
29 * <li>{@link #duplicates}</li>
30 * <li>{@link #duplicatesMap}</li>
31 * <li>{@link #append}</li>
32 * </ul>
33 *
34 * @author Mark Hayes
35 */
36public class StoredMap extends StoredContainer implements Map {
37
38    private StoredKeySet keySet;
39    private StoredEntrySet entrySet;
40    private StoredValueSet valueSet;
41
42    /**
43     * Creates a map view of a {@link Database}.
44     *
45     * @param database is the Database underlying the new collection.
46     *
47     * @param keyBinding is the binding used to translate between key buffers
48     * and key objects.
49     *
50     * @param valueBinding is the binding used to translate between value
51     * buffers and value objects.
52     *
53     * @param writeAllowed is true to create a read-write collection or false
54     * to create a read-only collection.
55     *
56     * @throws IllegalArgumentException if formats are not consistently
57     * defined or a parameter is invalid.
58     *
59     * @throws RuntimeExceptionWrapper if a {@link
60     * com.sleepycat.db.DatabaseException} is thrown.
61     */
62    public StoredMap(Database database, EntryBinding keyBinding,
63                     EntryBinding valueBinding, boolean writeAllowed) {
64
65        super(new DataView(database, keyBinding, valueBinding, null,
66                           writeAllowed, null));
67        initView();
68    }
69
70    /**
71     * Creates a map view of a {@link Database} with a {@link
72     * PrimaryKeyAssigner}.  Writing is allowed for the created map.
73     *
74     * @param database is the Database underlying the new collection.
75     *
76     * @param keyBinding is the binding used to translate between key buffers
77     * and key objects.
78     *
79     * @param valueBinding is the binding used to translate between value
80     * buffers and value objects.
81     *
82     * @param keyAssigner is used by the {@link #append} method to assign
83     * primary keys.
84     *
85     * @throws IllegalArgumentException if formats are not consistently
86     * defined or a parameter is invalid.
87     *
88     * @throws RuntimeExceptionWrapper if a {@link
89     * com.sleepycat.db.DatabaseException} is thrown.
90     */
91    public StoredMap(Database database, EntryBinding keyBinding,
92                     EntryBinding valueBinding,
93                     PrimaryKeyAssigner keyAssigner) {
94
95        super(new DataView(database, keyBinding, valueBinding, null,
96                           true, keyAssigner));
97        initView();
98    }
99
100    /**
101     * Creates a map entity view of a {@link Database}.
102     *
103     * @param database is the Database underlying the new collection.
104     *
105     * @param keyBinding is the binding used to translate between key buffers
106     * and key objects.
107     *
108     * @param valueEntityBinding is the binding used to translate between
109     * key/value buffers and entity value objects.
110     *
111     * @param writeAllowed is true to create a read-write collection or false
112     * to create a read-only collection.
113     *
114     * @throws IllegalArgumentException if formats are not consistently
115     * defined or a parameter is invalid.
116     *
117     * @throws RuntimeExceptionWrapper if a {@link
118     * com.sleepycat.db.DatabaseException} is thrown.
119     */
120    public StoredMap(Database database, EntryBinding keyBinding,
121                     EntityBinding valueEntityBinding, boolean writeAllowed) {
122
123        super(new DataView(database, keyBinding, null, valueEntityBinding,
124                           writeAllowed, null));
125        initView();
126    }
127
128    /**
129     * Creates a map entity view of a {@link Database} with a {@link
130     * PrimaryKeyAssigner}.  Writing is allowed for the created map.
131     *
132     * @param database is the Database underlying the new collection.
133     *
134     * @param keyBinding is the binding used to translate between key buffers
135     * and key objects.
136     *
137     * @param valueEntityBinding is the binding used to translate between
138     * key/value buffers and entity value objects.
139     *
140     * @param keyAssigner is used by the {@link #append} method to assign
141     * primary keys.
142     *
143     * @throws IllegalArgumentException if formats are not consistently
144     * defined or a parameter is invalid.
145     *
146     * @throws RuntimeExceptionWrapper if a {@link
147     * com.sleepycat.db.DatabaseException} is thrown.
148     */
149    public StoredMap(Database database, EntryBinding keyBinding,
150                     EntityBinding valueEntityBinding,
151                     PrimaryKeyAssigner keyAssigner) {
152
153        super(new DataView(database, keyBinding, null, valueEntityBinding,
154                           true, keyAssigner));
155        initView();
156    }
157
158    StoredMap(DataView view) {
159
160        super(view);
161        initView();
162    }
163
164    /**
165     * Override this method to initialize view-dependent fields.
166     */
167    void initAfterClone() {
168        initView();
169    }
170
171    /**
172     * The keySet, entrySet and valueSet are created during Map construction
173     * rather than lazily when requested (as done with the java.util.Map
174     * implementations).  This is done to avoid synchronization every time they
175     * are requested.  Since they are requested often but a StoredMap is
176     * created infrequently, this gives the best performance.  The additional
177     * views are small objects and are cheap to construct.
178     */
179    private void initView() {
180
181        /* entrySet */
182        if (areKeyRangesAllowed()) {
183            entrySet = new StoredSortedEntrySet(view);
184        } else {
185            entrySet = new StoredEntrySet(view);
186        }
187
188        /* keySet */
189        DataView newView = view.keySetView();
190        if (areKeyRangesAllowed()) {
191            keySet = new StoredSortedKeySet(newView);
192        } else {
193            keySet = new StoredKeySet(newView);
194        }
195
196        /* valueSet */
197        newView = view.valueSetView();
198        if (areKeyRangesAllowed() && newView.canDeriveKeyFromValue()) {
199            valueSet = new StoredSortedValueSet(newView);
200        } else {
201            valueSet = new StoredValueSet(newView);
202        }
203    }
204
205    /**
206     * Returns the value to which this map maps the specified key.  If
207     * duplicates are allowed, this method returns the first duplicate, in the
208     * order in which duplicates are configured, that maps to the specified
209     * key.
210     *
211     * This method conforms to the {@link Map#get} interface.
212     *
213     * @throws RuntimeExceptionWrapper if a {@link
214     * com.sleepycat.db.DatabaseException} is thrown.
215     */
216    public Object get(Object key) {
217
218        return super.get(key);
219    }
220
221    /**
222     * Associates the specified value with the specified key in this map
223     * (optional operation).  If duplicates are allowed and the specified key
224     * is already mapped to a value, this method appends the new duplicate
225     * after the existing duplicates.  This method conforms to the {@link
226     * Map#put} interface.
227     *
228     * <p>The key parameter may be null if an entity binding is used and the
229     * key will be derived from the value (entity) parameter.  If an entity
230     * binding is used and the key parameter is non-null, then the key
231     * parameter must be equal to the key derived from the value parameter.</p>
232     *
233     * @return the previous value associated with specified key, or null if
234     * there was no mapping for the key or if duplicates are allowed.
235     *
236     * @throws UnsupportedOperationException if the collection is indexed, or
237     * if the collection is read-only.
238     *
239     * @throws IllegalArgumentException if an entity value binding is used and
240     * the primary key of the value given is different than the existing stored
241     * primary key.
242     *
243     * @throws RuntimeExceptionWrapper if a {@link
244     * com.sleepycat.db.DatabaseException} is thrown.
245     */
246    public Object put(Object key, Object value) {
247
248        return super.put(key, value);
249    }
250
251    /**
252     * Appends a given value returning the newly assigned key.  If a {@link
253     * PrimaryKeyAssigner} is associated with Store for this map, it will be
254     * used to assigned the returned key.  Otherwise the Store must be a QUEUE
255     * or RECNO database and the next available record number is assigned as
256     * the key.  This method does not exist in the standard {@link Map}
257     * interface.
258     *
259     * <p>Note that for the JE product, QUEUE and RECNO databases are not
260     * supported, and therefore a PrimaryKeyAssigner must be associated with
261     * the map in order to call this method.</p>
262     *
263     * @param value the value to be appended.
264     *
265     * @return the assigned key.
266     *
267     * @throws UnsupportedOperationException if the collection is indexed, or
268     * if the collection is read-only, or if the Store has no {@link
269     * PrimaryKeyAssigner} and is not a QUEUE or RECNO database.
270     *
271     * @throws RuntimeExceptionWrapper if a {@link
272     * com.sleepycat.db.DatabaseException} is thrown.
273     */
274    public Object append(Object value) {
275
276        boolean doAutoCommit = beginAutoCommit();
277        try {
278            Object[] key = new Object[1];
279            view.append(value, key, null);
280            commitAutoCommit(doAutoCommit);
281            return key[0];
282        } catch (Exception e) {
283            throw handleException(e, doAutoCommit);
284        }
285    }
286
287    /**
288     * Removes the mapping for this key from this map if present (optional
289     * operation).  If duplicates are allowed, this method removes all
290     * duplicates for the given key.  This method conforms to the {@link
291     * Map#remove} interface.
292     *
293     * @throws UnsupportedOperationException if the collection is read-only.
294     *
295     * @throws RuntimeExceptionWrapper if a {@link
296     * com.sleepycat.db.DatabaseException} is thrown.
297     */
298    public Object remove(Object key) {
299
300        Object[] oldVal = new Object[1];
301        removeKey(key, oldVal);
302        return oldVal[0];
303    }
304
305    /**
306     * Returns true if this map contains the specified key.  This method
307     * conforms to the {@link Map#containsKey} interface.
308     *
309     * @throws RuntimeExceptionWrapper if a {@link
310     * com.sleepycat.db.DatabaseException} is thrown.
311     */
312    public boolean containsKey(Object key) {
313
314        return super.containsKey(key);
315    }
316
317    /**
318     * Returns true if this map contains the specified value.  When an entity
319     * binding is used, this method returns whether the map contains the
320     * primary key and value mapping of the entity.  This method conforms to
321     * the {@link Map#containsValue} interface.
322     *
323     * @throws RuntimeExceptionWrapper if a {@link
324     * com.sleepycat.db.DatabaseException} is thrown.
325     */
326    public boolean containsValue(Object value) {
327
328        return super.containsValue(value);
329    }
330
331    /**
332     * Copies all of the mappings from the specified map to this map (optional
333     * operation).  When duplicates are allowed, the mappings in the specified
334     * map are effectively appended to the existing mappings in this map, that
335     * is no previously existing mappings in this map are replaced.  This
336     * method conforms to the {@link Map#putAll} interface.
337     *
338     * @throws UnsupportedOperationException if the collection is read-only, or
339     * if the collection is indexed.
340     *
341     * @throws RuntimeExceptionWrapper if a {@link
342     * com.sleepycat.db.DatabaseException} is thrown.
343     */
344    public void putAll(Map map) {
345
346        boolean doAutoCommit = beginAutoCommit();
347        Iterator i = null;
348        try {
349            Collection coll = map.entrySet();
350            i = storedOrExternalIterator(coll);
351            while (i.hasNext()) {
352                Map.Entry entry = (Map.Entry) i.next();
353                put(entry.getKey(), entry.getValue());
354            }
355            StoredIterator.close(i);
356            commitAutoCommit(doAutoCommit);
357        } catch (Exception e) {
358            StoredIterator.close(i);
359            throw handleException(e, doAutoCommit);
360        }
361    }
362
363    /**
364     * Returns a set view of the keys contained in this map.  A {@link
365     * java.util.SortedSet} is returned if the map supports key ranges.  The
366     * returned collection will be read-only if the map is read-only.  This
367     * method conforms to the {@link Map#keySet()} interface.
368     *
369     * <p>Note that the return value is a StoredCollection and must be treated
370     * as such; for example, its iterators must be explicitly closed.</p>
371     *
372     * @return a {@link StoredKeySet} or a {@link StoredSortedKeySet} for this
373     * map.
374     *
375     * @throws RuntimeExceptionWrapper if a {@link
376     * com.sleepycat.db.DatabaseException} is thrown.
377     *
378     * @see #areKeyRangesAllowed
379     * @see #isWriteAllowed
380     */
381    public Set keySet() {
382
383        return keySet;
384    }
385
386    /**
387     * Returns a set view of the mappings contained in this map.  A {@link
388     * java.util.SortedSet} is returned if the map supports key ranges.  The
389     * returned collection will be read-only if the map is read-only.  This
390     * method conforms to the {@link Map#entrySet()} interface.
391     *
392     * <p>Note that the return value is a StoredCollection and must be treated
393     * as such; for example, its iterators must be explicitly closed.</p>
394     *
395     * @return a {@link StoredEntrySet} or a {@link StoredSortedEntrySet} for
396     * this map.
397     *
398     * @throws RuntimeExceptionWrapper if a {@link
399     * com.sleepycat.db.DatabaseException} is thrown.
400     *
401     * @see #areKeyRangesAllowed
402     * @see #isWriteAllowed
403     */
404    public Set entrySet() {
405
406        return entrySet;
407    }
408
409    /**
410     * Returns a collection view of the values contained in this map.  A {@link
411     * java.util.SortedSet} is returned if the map supports key ranges and the
412     * value/entity binding can be used to derive the map's key from its
413     * value/entity object.  The returned collection will be read-only if the
414     * map is read-only.  This method conforms to the {@link Map#values()}
415     * interface.
416     *
417     * <p>Note that the return value is a StoredCollection and must be treated
418     * as such; for example, its iterators must be explicitly closed.</p>
419     *
420     * @return a {@link StoredValueSet} or a {@link StoredSortedValueSet} for
421     * this map.
422     *
423     * @throws RuntimeExceptionWrapper if a {@link
424     * com.sleepycat.db.DatabaseException} is thrown.
425     *
426     * @see #areKeyRangesAllowed
427     * @see #isWriteAllowed
428     */
429    public Collection values() {
430
431        return valueSet;
432    }
433
434    /**
435     * Returns a new collection containing the values mapped to the given key
436     * in this map.  This collection's iterator() method is particularly useful
437     * for iterating over the duplicates for a given key, since this is not
438     * supported by the standard Map interface.  This method does not exist in
439     * the standard {@link Map} interface.
440     *
441     * <p>If no mapping for the given key is present, an empty collection is
442     * returned.  If duplicates are not allowed, at most a single value will be
443     * in the collection returned.  If duplicates are allowed, the returned
444     * collection's add() method may be used to add values for the given
445     * key.</p>
446     *
447     * @param key is the key for which values are to be returned.
448     *
449     * @throws RuntimeExceptionWrapper if a {@link
450     * com.sleepycat.db.DatabaseException} is thrown.
451     */
452    public Collection duplicates(Object key) {
453
454        try {
455            DataView newView = view.valueSetView(key);
456            return new StoredValueSet(newView);
457        } catch (KeyRangeException e) {
458            return Collections.EMPTY_SET;
459        } catch (Exception e) {
460            throw StoredContainer.convertException(e);
461        }
462    }
463
464    /**
465     * Returns a new map from primary key to value for the subset of records
466     * having a given secondary key (duplicates).  This method does not exist
467     * in the standard {@link Map} interface.
468     *
469     * <p>If no mapping for the given key is present, an empty collection is
470     * returned.  If duplicates are not allowed, at most a single value will be
471     * in the collection returned.  If duplicates are allowed, the returned
472     * collection's add() method may be used to add values for the given
473     * key.</p>
474     *
475     * @param secondaryKey is the secondary key for which duplicates values
476     * will be represented by the returned map.
477     *
478     * @param primaryKeyBinding is the binding used for keys in the returned
479     * map.
480     *
481     * @throws RuntimeExceptionWrapper if a {@link
482     * com.sleepycat.db.DatabaseException} is thrown.
483     */
484    public Map duplicatesMap(Object secondaryKey,
485                             EntryBinding primaryKeyBinding) {
486        try {
487            DataView newView =
488                view.duplicatesView(secondaryKey, primaryKeyBinding);
489            if (isOrdered()) {
490                return new StoredSortedMap(newView);
491            } else {
492                return new StoredMap(newView);
493            }
494        } catch (Exception e) {
495            throw StoredContainer.convertException(e);
496        }
497    }
498
499    /**
500     * Compares the specified object with this map for equality.  A value
501     * comparison is performed by this method and the stored values are
502     * compared rather than calling the equals() method of each element.  This
503     * method conforms to the {@link Map#equals} interface.
504     *
505     * @throws RuntimeExceptionWrapper if a {@link
506     * com.sleepycat.db.DatabaseException} is thrown.
507     */
508    public boolean equals(Object other) {
509
510        if (other instanceof Map) {
511            return entrySet().equals(((Map) other).entrySet());
512        } else {
513            return false;
514        }
515    }
516
517    /*
518     * Add this in to keep FindBugs from whining at us about implementing
519     * equals(), but not hashCode().
520     */
521    public int hashCode() {
522	return super.hashCode();
523    }
524
525    // Inherit javadoc
526    public int size() {
527        return values().size();
528    }
529
530    /**
531     * Converts the map to a string representation for debugging.  WARNING: All
532     * mappings will be converted to strings and returned and therefore the
533     * returned string may be very large.
534     *
535     * @return the string representation.
536     *
537     * @throws RuntimeExceptionWrapper if a {@link
538     * com.sleepycat.db.DatabaseException} is thrown.
539     */
540    public String toString() {
541
542        return entrySet().toString();
543    }
544}
545
546