View Javadoc

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             // no-op
189         }
190 
191         /***
192          * {@inheritDoc}
193          */
194         public void timeToLiveChanged(long oldTimeToLive, long newTimeToLive) {
195             // no-op
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             // no-op
210         }
211 
212         /***
213          * {@inheritDoc}
214          */
215         public void loggingChanged(boolean oldValue, boolean newValue) {
216             // no-op
217         }
218 
219         /***
220          * {@inheritDoc}
221          */
222         public void registered(CacheConfiguration config) {
223             // no-op
224         }
225 
226         /***
227          * {@inheritDoc}
228          */
229         public void deregistered(CacheConfiguration config) {
230             // no-op
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         // Try a few times to get accurate count. On failure due to
535         // continuous async changes in table, resort to locking.
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             // Resort to locking all segments
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         // evicting from disk also frees up heap
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         // pick a random starting point in the map
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             // move to next segment
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 }