1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package net.sf.ehcache.store.disk;
18
19 import net.sf.ehcache.Cache;
20 import net.sf.ehcache.CacheEntry;
21 import net.sf.ehcache.CacheException;
22 import net.sf.ehcache.Ehcache;
23 import net.sf.ehcache.Element;
24 import net.sf.ehcache.Status;
25 import net.sf.ehcache.concurrent.CacheLockProvider;
26 import net.sf.ehcache.concurrent.LockType;
27 import net.sf.ehcache.concurrent.Sync;
28 import net.sf.ehcache.config.CacheConfiguration;
29 import net.sf.ehcache.config.CacheConfigurationListener;
30 import net.sf.ehcache.pool.Pool;
31 import net.sf.ehcache.pool.PoolAccessor;
32 import net.sf.ehcache.pool.PoolableStore;
33 import net.sf.ehcache.pool.impl.UnboundedPool;
34 import net.sf.ehcache.store.AbstractStore;
35 import net.sf.ehcache.store.ElementValueComparator;
36 import net.sf.ehcache.store.Policy;
37 import net.sf.ehcache.store.TierableStore;
38 import net.sf.ehcache.writer.CacheWriterManager;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.util.AbstractSet;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Random;
48 import java.util.Set;
49 import java.util.concurrent.TimeUnit;
50 import java.util.concurrent.atomic.AtomicReference;
51 import java.util.concurrent.locks.ReentrantReadWriteLock;
52
53 /***
54 * Implements a persistent-to-disk store.
55 * <p>
56 * All new elements are automatically scheduled for writing to disk.
57 *
58 * @author Chris Dennis
59 * @author Ludovic Orban
60 */
61 public final class DiskStore extends AbstractStore implements TierableStore, PoolableStore {
62
63 /***
64 * If the CacheManager needs to resolve a conflict with the disk path, it will create a
65 * subdirectory in the given disk path with this prefix followed by a number. The presence of this
66 * name is used to determined whether it makes sense for a persistent DiskStore to be loaded. Loading
67 * persistent DiskStores will only have useful semantics where the diskStore path has not changed.
68 */
69 public static final String AUTO_DISK_PATH_DIRECTORY_PREFIX = "ehcache_auto_created";
70
71 private static final int FFFFCD7D = 0xffffcd7d;
72 private static final int FIFTEEN = 15;
73 private static final int TEN = 10;
74 private static final int THREE = 3;
75 private static final int SIX = 6;
76 private static final int FOURTEEN = 14;
77 private static final int SIXTEEN = 16;
78
79 private static final int RETRIES_BEFORE_LOCK = 2;
80 private static final int DEFAULT_INITIAL_CAPACITY = 16;
81 private static final int DEFAULT_SEGMENT_COUNT = 64;
82 private static final float DEFAULT_LOAD_FACTOR = 0.75f;
83 private static final int SLEEP_INTERVAL_MS = 10;
84
85 private final DiskStorageFactory disk;
86 private final Random rndm = new Random();
87 private final Segment[] segments;
88 private final int segmentShift;
89 private final AtomicReference<Status> status = new AtomicReference<Status>(Status.STATUS_UNINITIALISED);
90
91 private volatile CacheLockProvider lockProvider;
92 private volatile Set<Object> keySet;
93 private volatile PoolAccessor onHeapPoolAccessor;
94 private volatile PoolAccessor onDiskPoolAccessor;
95
96
97 private DiskStore(DiskStorageFactory disk, CacheConfiguration cacheConfiguration, Pool onHeapPool, Pool onDiskPool) {
98 this.segments = new Segment[DEFAULT_SEGMENT_COUNT];
99 this.segmentShift = Integer.numberOfLeadingZeros(segments.length - 1);
100 this.onHeapPoolAccessor = onHeapPool.createPoolAccessor(this);
101 this.onDiskPoolAccessor = onDiskPool.createPoolAccessor(this, new DiskSizeOfEngine());
102
103 for (int i = 0; i < this.segments.length; ++i) {
104 this.segments[i] = new Segment(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR,
105 disk, cacheConfiguration, onHeapPoolAccessor, onDiskPoolAccessor);
106 }
107
108 this.disk = disk;
109 this.disk.bind(this);
110 status.set(Status.STATUS_ALIVE);
111 }
112
113 /***
114 * Wait until all elements have been written to disk
115 *
116 * @throws InterruptedException if the thread was interrupted while waiting
117 * @param delayInMs the maximum time to wait, in milliseconds
118 */
119 void waitUntilEverythingGotFlushedToDisk(long delayInMs) throws InterruptedException {
120 int iterations = (int) (delayInMs / SLEEP_INTERVAL_MS);
121
122 for (Segment segment : segments) {
123 int count = 0;
124 while (segment.countOnHeap() != 0) {
125 Thread.sleep(SLEEP_INTERVAL_MS);
126 count++;
127
128 if (count > iterations) {
129 throw new CacheException(delayInMs + " ms delay expired");
130 }
131 }
132 }
133 }
134
135 /***
136 * Creates a persitent-to-disk store for the given cache, using the given disk path.
137 *
138 * @param cache cache that fronts this store
139 * @param diskStorePath disk path to store data in
140 * @param onHeapPool pool to track heap usage
141 * @param onDiskPool pool to track disk usage
142 * @return a fully initialized store
143 */
144 public static DiskStore create(Ehcache cache, String diskStorePath, Pool onHeapPool, Pool onDiskPool) {
145 DiskStorageFactory disk = new DiskStorageFactory(cache, diskStorePath, cache.getCacheEventNotificationService());
146 DiskStore store = new DiskStore(disk, cache.getCacheConfiguration(), onHeapPool, onDiskPool);
147 cache.getCacheConfiguration().addConfigurationListener(new CacheConfigurationListenerAdapter(disk));
148 return store;
149 }
150
151 /***
152 * Creates a persitent-to-disk store for the given cache, using the given disk path.
153 * Heap and disk usage are not tracked by the returned store.
154 *
155 * @param cache cache that fronts this store
156 * @param diskStorePath disk path to store data in
157 * @return a fully initialized store
158 */
159 public static DiskStore create(Cache cache, String diskStorePath) {
160 return create(cache, diskStorePath, new UnboundedPool(), new UnboundedPool());
161 }
162
163 /***
164 * Generates a unique directory name for use in automatically creating a diskStorePath where there is a conflict.
165 *
166 * @return a path consisting of {@link #AUTO_DISK_PATH_DIRECTORY_PREFIX} followed by "_" followed by the current
167 * time as a long e.g. ehcache_auto_created_1149389837006
168 */
169 public static String generateUniqueDirectory() {
170 return DiskStore.AUTO_DISK_PATH_DIRECTORY_PREFIX + "_" + System.currentTimeMillis();
171 }
172
173 /***
174 *
175 */
176 private static final class CacheConfigurationListenerAdapter implements CacheConfigurationListener {
177
178 private final DiskStorageFactory disk;
179
180 private CacheConfigurationListenerAdapter(DiskStorageFactory disk) {
181 this.disk = disk;
182 }
183
184 /***
185 * {@inheritDoc}
186 */
187 public void timeToIdleChanged(long oldTimeToIdle, long newTimeToIdle) {
188
189 }
190
191 /***
192 * {@inheritDoc}
193 */
194 public void timeToLiveChanged(long oldTimeToLive, long newTimeToLive) {
195
196 }
197
198 /***
199 * {@inheritDoc}
200 */
201 public void diskCapacityChanged(int oldCapacity, int newCapacity) {
202 disk.setOnDiskCapacity(newCapacity);
203 }
204
205 /***
206 * {@inheritDoc}
207 */
208 public void memoryCapacityChanged(int oldCapacity, int newCapacity) {
209
210 }
211
212 /***
213 * {@inheritDoc}
214 */
215 public void loggingChanged(boolean oldValue, boolean newValue) {
216
217 }
218
219 /***
220 * {@inheritDoc}
221 */
222 public void registered(CacheConfiguration config) {
223
224 }
225
226 /***
227 * {@inheritDoc}
228 */
229 public void deregistered(CacheConfiguration config) {
230
231 }
232 }
233
234 /***
235 * Change the disk capacity, in number of elements
236 * @param newCapacity the new max elements on disk
237 */
238 public void changeDiskCapacity(int newCapacity) {
239 disk.setOnDiskCapacity(newCapacity);
240 }
241
242 /***
243 * {@inheritDoc}
244 */
245 public boolean bufferFull() {
246 return disk.bufferFull();
247 }
248
249 /***
250 * {@inheritDoc}
251 */
252 public boolean containsKeyInMemory(Object key) {
253 return false;
254 }
255
256 /***
257 * {@inheritDoc}
258 */
259 public boolean containsKeyOffHeap(Object key) {
260 return false;
261 }
262
263 /***
264 * {@inheritDoc}
265 */
266 public boolean containsKeyOnDisk(Object key) {
267 return containsKey(key);
268 }
269
270 /***
271 * {@inheritDoc}
272 */
273 public void expireElements() {
274 disk.expireElements();
275 }
276
277 /***
278 * {@inheritDoc}
279 */
280 public void flush() throws IOException {
281 disk.flush();
282 }
283
284 /***
285 * {@inheritDoc}
286 */
287 public Policy getInMemoryEvictionPolicy() {
288 return null;
289 }
290
291 /***
292 * {@inheritDoc}
293 */
294 public int getInMemorySize() {
295 return 0;
296 }
297
298 /***
299 * {@inheritDoc}
300 */
301 public long getInMemorySizeInBytes() {
302 long size = onHeapPoolAccessor.getSize();
303 if (size < 0) {
304 return 0;
305 } else {
306 return size;
307 }
308 }
309
310 /***
311 * {@inheritDoc}
312 */
313 public int getOffHeapSize() {
314 return 0;
315 }
316
317 /***
318 * {@inheritDoc}
319 */
320 public long getOffHeapSizeInBytes() {
321 return 0;
322 }
323
324 /***
325 * {@inheritDoc}
326 */
327 public int getOnDiskSize() {
328 return disk.getOnDiskSize();
329 }
330
331 /***
332 * {@inheritDoc}
333 */
334 public long getOnDiskSizeInBytes() {
335 long size = onDiskPoolAccessor.getSize();
336 if (size < 0) {
337 return disk.getOnDiskSizeInBytes();
338 } else {
339 return size;
340 }
341 }
342
343 /***
344 * {@inheritDoc}
345 */
346 public int getTerracottaClusteredSize() {
347 return 0;
348 }
349
350 /***
351 * {@inheritDoc}
352 */
353 public void setInMemoryEvictionPolicy(Policy policy) {
354 }
355
356 /***
357 * Return a reference to the data file backing this store.
358 *
359 * @return a reference to the data file backing this store.
360 */
361 public File getDataFile() {
362 return disk.getDataFile();
363 }
364
365 /***
366 * Return a reference to the index file for this store.
367 *
368 * @return a reference to the index file for this store.
369 */
370 public File getIndexFile() {
371 return disk.getIndexFile();
372 }
373
374 /***
375 * {@inheritDoc}
376 */
377 public Object getMBean() {
378 return null;
379 }
380
381 /***
382 * {@inheritDoc}
383 */
384 public void fill(Element e) {
385 put(e);
386 }
387
388 /***
389 * {@inheritDoc}
390 */
391 public boolean put(Element element) {
392 if (element == null) {
393 return false;
394 } else {
395 Object key = element.getObjectKey();
396 int hash = hash(key.hashCode());
397 Element oldElement = segmentFor(hash).put(key, hash, element, false);
398 return oldElement == null;
399 }
400 }
401
402 /***
403 * {@inheritDoc}
404 */
405 public boolean putWithWriter(Element element, CacheWriterManager writerManager) {
406 boolean newPut = put(element);
407 if (writerManager != null) {
408 try {
409 writerManager.put(element);
410 } catch (RuntimeException e) {
411 throw new StoreUpdateException(e, !newPut);
412 }
413 }
414 return newPut;
415 }
416
417 /***
418 * {@inheritDoc}
419 */
420 public Element get(Object key) {
421 if (key == null) {
422 return null;
423 }
424
425 int hash = hash(key.hashCode());
426 return segmentFor(hash).get(key, hash);
427 }
428
429 /***
430 * {@inheritDoc}
431 */
432 public Element getQuiet(Object key) {
433 return get(key);
434 }
435
436 /***
437 * Return the unretrieved (undecoded) value for this key
438 *
439 * @param key key to lookup
440 * @return Element or ElementSubstitute
441 */
442 public Object unretrievedGet(Object key) {
443 if (key == null) {
444 return null;
445 }
446
447 int hash = hash(key.hashCode());
448 return segmentFor(hash).unretrievedGet(key, hash);
449 }
450
451 /***
452 * Put the given encoded element directly into the store
453 *
454 * @param key the key of the element
455 * @param encoded the encoded element
456 * @return true if the encoded element was installed
457 * @throws IllegalArgumentException if the supplied key is already present
458 */
459 public boolean putRawIfAbsent(Object key, Object encoded) throws IllegalArgumentException {
460 int hash = hash(key.hashCode());
461 return segmentFor(hash).putRawIfAbsent(key, hash, encoded);
462 }
463
464 /***
465 * {@inheritDoc}
466 */
467 public List getKeys() {
468 return new ArrayList(keySet());
469 }
470
471 /***
472 * Get a set view of the keys in this store
473 *
474 * @return a set view of the keys in this store
475 */
476 public Set<Object> keySet() {
477 if (keySet != null) {
478 return keySet;
479 } else {
480 keySet = new KeySet();
481 return keySet;
482 }
483 }
484
485 /***
486 * {@inheritDoc}
487 */
488 public Element remove(Object key) {
489 if (key == null) {
490 return null;
491 }
492
493 int hash = hash(key.hashCode());
494 return segmentFor(hash).remove(key, hash, null, null);
495 }
496
497 /***
498 * {@inheritDoc}
499 */
500 public Element removeWithWriter(Object key, CacheWriterManager writerManager) {
501 Element removed = remove(key);
502 if (writerManager != null) {
503 writerManager.remove(new CacheEntry(key, removed));
504 }
505 return removed;
506 }
507
508 /***
509 * {@inheritDoc}
510 */
511 public void removeAll() {
512 for (Segment s : segments) {
513 s.clear();
514 }
515 }
516
517 /***
518 * {@inheritDoc}
519 */
520 public void dispose() {
521 if (status.compareAndSet(Status.STATUS_ALIVE, Status.STATUS_SHUTDOWN)) {
522 disk.unbind();
523 onHeapPoolAccessor.unlink();
524 onDiskPoolAccessor.unlink();
525 }
526 }
527
528 /***
529 * {@inheritDoc}
530 */
531 public int getSize() {
532 final Segment[] segs = this.segments;
533 long size = -1;
534
535
536 for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
537 size = volatileSize(segs);
538 if (size >= 0) {
539 break;
540 }
541 }
542 if (size < 0) {
543
544 size = lockedSize(segs);
545 }
546 if (size > Integer.MAX_VALUE) {
547 return Integer.MAX_VALUE;
548 } else {
549 return (int) size;
550 }
551 }
552
553 private static long volatileSize(Segment[] segs) {
554 int[] mc = new int[segs.length];
555 long check = 0;
556 long sum = 0;
557 int mcsum = 0;
558 for (int i = 0; i < segs.length; ++i) {
559 sum += segs[i].count;
560 mc[i] = segs[i].modCount;
561 mcsum += mc[i];
562 }
563 if (mcsum != 0) {
564 for (int i = 0; i < segs.length; ++i) {
565 check += segs[i].count;
566 if (mc[i] != segs[i].modCount) {
567 return -1;
568 }
569 }
570 }
571 if (check == sum) {
572 return sum;
573 } else {
574 return -1;
575 }
576 }
577
578 private static long lockedSize(Segment[] segs) {
579 long size = 0;
580 for (Segment seg : segs) {
581 seg.readLock().lock();
582 }
583 for (Segment seg : segs) {
584 size += seg.count;
585 }
586 for (Segment seg : segs) {
587 seg.readLock().unlock();
588 }
589
590 return size;
591 }
592
593 /***
594 * {@inheritDoc}
595 */
596 public Status getStatus() {
597 return status.get();
598 }
599
600 /***
601 * {@inheritDoc}
602 */
603 public boolean evictFromOnHeap(int count, long size) {
604
605 return disk.evict(count) == count;
606 }
607
608 /***
609 * {@inheritDoc}
610 */
611 public boolean evictFromOnDisk(int count, long size) {
612 return disk.evict(count) == count;
613 }
614
615 /***
616 * {@inheritDoc}
617 */
618 public float getApproximateDiskHitRate() {
619 float sum = 0;
620 for (Segment s : segments) {
621 sum += s.getDiskHitRate();
622 }
623 return sum;
624 }
625
626 /***
627 * {@inheritDoc}
628 */
629 public float getApproximateDiskMissRate() {
630 float sum = 0;
631 for (Segment s : segments) {
632 sum += s.getDiskMissRate();
633 }
634 return sum;
635 }
636
637 /***
638 * {@inheritDoc}
639 */
640 public long getApproximateDiskCountSize() {
641 return getOnDiskSize();
642 }
643
644 /***
645 * {@inheritDoc}
646 */
647 public long getApproximateDiskByteSize() {
648 return getOnDiskSizeInBytes();
649 }
650
651 /***
652 * {@inheritDoc}
653 */
654 public float getApproximateHeapHitRate() {
655 return 0;
656 }
657
658 /***
659 * {@inheritDoc}
660 */
661 public float getApproximateHeapMissRate() {
662 return 0;
663 }
664
665 /***
666 * {@inheritDoc}
667 */
668 public long getApproximateHeapCountSize() {
669 return getInMemorySize();
670 }
671
672 /***
673 * {@inheritDoc}
674 */
675 public long getApproximateHeapByteSize() {
676 return getInMemorySizeInBytes();
677 }
678
679 /***
680 * {@inheritDoc}
681 */
682 public boolean containsKey(Object key) {
683 int hash = hash(key.hashCode());
684 return segmentFor(hash).containsKey(key, hash);
685 }
686
687 /***
688 * {@inheritDoc}
689 */
690 public Object getInternalContext() {
691 if (lockProvider != null) {
692 return lockProvider;
693 } else {
694 lockProvider = new LockProvider();
695 return lockProvider;
696 }
697 }
698
699 /***
700 * {@inheritDoc}
701 */
702 public Element putIfAbsent(Element element) throws NullPointerException {
703 Object key = element.getObjectKey();
704 int hash = hash(key.hashCode());
705 return segmentFor(hash).put(key, hash, element, true);
706 }
707
708 /***
709 * {@inheritDoc}
710 */
711 public Element removeElement(Element element, ElementValueComparator comparator) throws NullPointerException {
712 Object key = element.getObjectKey();
713 int hash = hash(key.hashCode());
714 return segmentFor(hash).remove(key, hash, element, comparator);
715 }
716
717 /***
718 * {@inheritDoc}
719 */
720 public boolean replace(Element old, Element element, ElementValueComparator comparator)
721 throws NullPointerException, IllegalArgumentException {
722 Object key = element.getObjectKey();
723 int hash = hash(key.hashCode());
724 return segmentFor(hash).replace(key, hash, old, element, comparator);
725 }
726
727 /***
728 * {@inheritDoc}
729 */
730 public Element replace(Element element) throws NullPointerException {
731 Object key = element.getObjectKey();
732 int hash = hash(key.hashCode());
733 return segmentFor(hash).replace(key, hash, element);
734 }
735
736 /***
737 * Atomically switch (CAS) the <code>expect</code> representation of this element for the
738 * <code>fault</code> representation.
739 * <p>
740 * A successful switch will return <code>true</code>, and free the replaced element/element-proxy.
741 * A failed switch will return <code>false</code> and free the element/element-proxy which was not
742 * installed.
743 *
744 * @param key key to which this element (proxy) is mapped
745 * @param expect element (proxy) expected
746 * @param fault element (proxy) to install
747 * @return <code>true</code> if <code>fault</code> was installed
748 */
749 public boolean fault(Object key, Object expect, Object fault) {
750 int hash = hash(key.hashCode());
751 return segmentFor(hash).fault(key, hash, expect, fault);
752 }
753
754 /***
755 * Remove the matching mapping. The evict method does referential comparison
756 * of the unretrieved substitute against the argument value.
757 *
758 * @param key key to match against
759 * @param substitute optional value to match against
760 * @return <code>true</code> on a successful remove
761 */
762 public boolean evict(Object key, Object substitute) {
763 return evictElement(key, substitute) != null;
764 }
765
766 /***
767 * Remove the matching mapping. The evict method does referential comparison
768 * of the unretrieved substitute against the argument value.
769 *
770 * @param key key to match against
771 * @param substitute optional value to match against
772 * @return the evicted element on a successful remove
773 */
774 public Element evictElement(Object key, Object substitute) {
775 int hash = hash(key.hashCode());
776 return segmentFor(hash).evict(key, hash, substitute);
777 }
778
779 /***
780 * Select a random sample of elements generated by the supplied factory.
781 *
782 * @param factory generator of the given type
783 * @param sampleSize minimum number of elements to return
784 * @param keyHint a key on which we are currently working
785 * @return list of sampled elements/element substitute
786 */
787 public List<DiskStorageFactory.DiskSubstitute> getRandomSample(ElementSubstituteFilter factory, int sampleSize, Object keyHint) {
788 ArrayList<DiskStorageFactory.DiskSubstitute> sampled = new ArrayList<DiskStorageFactory.DiskSubstitute>(sampleSize);
789
790
791 int randomHash = rndm.nextInt();
792
793 final int segmentStart;
794 if (keyHint == null) {
795 segmentStart = (randomHash >>> segmentShift);
796 } else {
797 segmentStart = (hash(keyHint.hashCode()) >>> segmentShift);
798 }
799
800 int segmentIndex = segmentStart;
801 do {
802 segments[segmentIndex].addRandomSample(factory, sampleSize, sampled, randomHash);
803 if (sampled.size() >= sampleSize) {
804 break;
805 }
806
807
808 segmentIndex = (segmentIndex + 1) & (segments.length - 1);
809 } while (segmentIndex != segmentStart);
810
811 return sampled;
812 }
813
814 private static int hash(int hash) {
815 int spread = hash;
816 spread += (spread << FIFTEEN ^ FFFFCD7D);
817 spread ^= spread >>> TEN;
818 spread += (spread << THREE);
819 spread ^= spread >>> SIX;
820 spread += (spread << 2) + (spread << FOURTEEN);
821 return (spread ^ spread >>> SIXTEEN);
822 }
823
824 private Segment segmentFor(int hash) {
825 return segments[hash >>> segmentShift];
826 }
827
828 /***
829 * Key set implementation for the DiskStore
830 */
831 final class KeySet extends AbstractSet<Object> {
832
833 /***
834 * {@inheritDoc}
835 */
836 @Override
837 public Iterator<Object> iterator() {
838 return new KeyIterator();
839 }
840
841 /***
842 * {@inheritDoc}
843 */
844 @Override
845 public int size() {
846 return DiskStore.this.getSize();
847 }
848
849 /***
850 * {@inheritDoc}
851 */
852 @Override
853 public boolean contains(Object o) {
854 return DiskStore.this.containsKey(o);
855 }
856
857 /***
858 * {@inheritDoc}
859 */
860 @Override
861 public boolean remove(Object o) {
862 return DiskStore.this.remove(o) != null;
863 }
864
865 /***
866 * {@inheritDoc}
867 */
868 @Override
869 public void clear() {
870 DiskStore.this.removeAll();
871 }
872
873 /***
874 * {@inheritDoc}
875 */
876 @Override
877 public Object[] toArray() {
878 Collection<Object> c = new ArrayList<Object>();
879 for (Object object : this) {
880 c.add(object);
881 }
882 return c.toArray();
883 }
884
885 /***
886 * {@inheritDoc}
887 */
888 @Override
889 public <T> T[] toArray(T[] a) {
890 Collection<Object> c = new ArrayList<Object>();
891 for (Object object : this) {
892 c.add(object);
893 }
894 return c.toArray(a);
895 }
896 }
897
898 /***
899 * LockProvider implementation that uses the segment locks.
900 */
901 private class LockProvider implements CacheLockProvider {
902
903 /***
904 * {@inheritDoc}
905 */
906 public Sync getSyncForKey(Object key) {
907 int hash = key == null ? 0 : hash(key.hashCode());
908 return new ReadWriteLockSync(segmentFor(hash));
909 }
910 }
911
912 /***
913 * Superclass for all store iterators.
914 */
915 abstract class HashIterator {
916 private int segmentIndex;
917 private Iterator<HashEntry> currentIterator;
918
919 /***
920 * Constructs a new HashIterator
921 */
922 HashIterator() {
923 segmentIndex = segments.length;
924
925 while (segmentIndex > 0) {
926 segmentIndex--;
927 currentIterator = segments[segmentIndex].hashIterator();
928 if (currentIterator.hasNext()) {
929 return;
930 }
931 }
932 }
933
934 /***
935 * {@inheritDoc}
936 */
937 public boolean hasNext() {
938 if (this.currentIterator == null) {
939 return false;
940 }
941
942 if (this.currentIterator.hasNext()) {
943 return true;
944 } else {
945 while (segmentIndex > 0) {
946 segmentIndex--;
947 currentIterator = segments[segmentIndex].hashIterator();
948 if (currentIterator.hasNext()) {
949 return true;
950 }
951 }
952 }
953 return false;
954 }
955
956 /***
957 * Returns the next hash-entry - called by subclasses
958 *
959 * @return next HashEntry
960 */
961 protected HashEntry nextEntry() {
962 if (currentIterator == null) {
963 return null;
964 }
965
966 if (currentIterator.hasNext()) {
967 return currentIterator.next();
968 } else {
969 while (segmentIndex > 0) {
970 segmentIndex--;
971 currentIterator = segments[segmentIndex].hashIterator();
972 if (currentIterator.hasNext()) {
973 return currentIterator.next();
974 }
975 }
976 }
977 return null;
978 }
979
980 /***
981 * {@inheritDoc}
982 */
983 public void remove() {
984 currentIterator.remove();
985 }
986
987 /***
988 * Get the current segment index of this iterator
989 *
990 * @return the current segment index
991 */
992 int getCurrentSegmentIndex() {
993 return segmentIndex;
994 }
995 }
996
997 /***
998 * Iterator over the store key set.
999 */
1000 private final class KeyIterator extends HashIterator implements Iterator<Object> {
1001 /***
1002 * {@inheritDoc}
1003 */
1004 public Object next() {
1005 return super.nextEntry().key;
1006 }
1007 }
1008
1009 /***
1010 * Sync implementation that wraps the segment locks
1011 */
1012 private static final class ReadWriteLockSync implements Sync {
1013
1014 private final ReentrantReadWriteLock lock;
1015
1016 private ReadWriteLockSync(ReentrantReadWriteLock lock) {
1017 this.lock = lock;
1018 }
1019
1020 /***
1021 * {@inheritDoc}
1022 */
1023 public void lock(LockType type) {
1024 switch (type) {
1025 case READ:
1026 lock.readLock().lock();
1027 break;
1028 case WRITE:
1029 lock.writeLock().lock();
1030 break;
1031 default:
1032 throw new IllegalArgumentException("We don't support any other lock type than READ or WRITE!");
1033 }
1034 }
1035
1036 /***
1037 * {@inheritDoc}
1038 */
1039 public boolean tryLock(LockType type, long msec) throws InterruptedException {
1040 switch (type) {
1041 case READ:
1042 return lock.readLock().tryLock(msec, TimeUnit.MILLISECONDS);
1043 case WRITE:
1044 return lock.writeLock().tryLock(msec, TimeUnit.MILLISECONDS);
1045 default:
1046 throw new IllegalArgumentException("We don't support any other lock type than READ or WRITE!");
1047 }
1048 }
1049
1050 /***
1051 * {@inheritDoc}
1052 */
1053 public void unlock(LockType type) {
1054 switch (type) {
1055 case READ:
1056 lock.readLock().unlock();
1057 break;
1058 case WRITE:
1059 lock.writeLock().unlock();
1060 break;
1061 default:
1062 throw new IllegalArgumentException("We don't support any other lock type than READ or WRITE!");
1063 }
1064 }
1065
1066 /***
1067 * {@inheritDoc}
1068 */
1069 public boolean isHeldByCurrentThread(LockType type) {
1070 switch (type) {
1071 case READ:
1072 throw new UnsupportedOperationException("Querying of read lock is not supported.");
1073 case WRITE:
1074 return lock.isWriteLockedByCurrentThread();
1075 default:
1076 throw new IllegalArgumentException("We don't support any other lock type than READ or WRITE!");
1077 }
1078 }
1079
1080 }
1081 }