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