EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.store.fs]

COVERAGE SUMMARY FOR SOURCE FILE [FilePathNioMem.java]

nameclass, %method, %block, %line, %
FilePathNioMem.java100% (6/6)87%  (54/62)77%  (821/1061)79%  (199.5/251)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class FileNioMemData$Cache100% (1/1)50%  (1/2)33%  (9/27)38%  (3/8)
removeEldestEntry (Map$Entry): boolean 0%   (0/1)0%   (0/18)0%   (0/5)
FileNioMemData$Cache (int): void 100% (1/1)100% (9/9)100% (3/3)
     
class FilePathNioMemLZF100% (1/1)67%  (2/3)71%  (5/7)67%  (2/3)
compressed (): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
FilePathNioMemLZF (): void 100% (1/1)100% (3/3)100% (1/1)
getScheme (): String 100% (1/1)100% (2/2)100% (1/1)
     
class FilePathNioMem100% (1/1)92%  (23/25)75%  (300/400)80%  (62.5/78)
isAbsolute (): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
isDirectory (): boolean 0%   (0/1)0%   (0/25)0%   (0/5)
createDirectory (): void 100% (1/1)21%  (4/19)50%  (1.5/3)
moveTo (FilePath, boolean): void 100% (1/1)60%  (39/65)87%  (7.8/9)
createFile (): boolean 100% (1/1)62%  (15/24)76%  (4.5/6)
exists (): boolean 100% (1/1)72%  (18/25)60%  (3/5)
delete (): void 100% (1/1)73%  (16/22)78%  (4.7/6)
getMemoryFile (): FileNioMemData 100% (1/1)86%  (30/35)86%  (6/7)
newDirectoryStream (): List 100% (1/1)88%  (36/41)88%  (7/8)
<static initializer> 100% (1/1)100% (5/5)100% (1/1)
FilePathNioMem (): void 100% (1/1)100% (3/3)100% (1/1)
canWrite (): boolean 100% (1/1)100% (4/4)100% (1/1)
compressed (): boolean 100% (1/1)100% (2/2)100% (1/1)
getCanonicalPath (String): String 100% (1/1)100% (38/38)100% (5/5)
getParent (): FilePathNioMem 100% (1/1)100% (17/17)100% (2/2)
getPath (String): FilePathNioMem 100% (1/1)100% (10/10)100% (3/3)
getScheme (): String 100% (1/1)100% (2/2)100% (1/1)
isRoot (): boolean 100% (1/1)100% (6/6)100% (1/1)
lastModified (): long 100% (1/1)100% (4/4)100% (1/1)
newInputStream (): InputStream 100% (1/1)100% (15/15)100% (3/3)
newOutputStream (boolean): OutputStream 100% (1/1)100% (15/15)100% (3/3)
open (String): FileChannel 100% (1/1)100% (11/11)100% (2/2)
setReadOnly (): boolean 100% (1/1)100% (4/4)100% (1/1)
size (): long 100% (1/1)100% (4/4)100% (1/1)
toRealPath (): FilePathNioMem 100% (1/1)100% (2/2)100% (1/1)
     
class FileNioMemData100% (1/1)83%  (15/18)76%  (341/451)78%  (90/116)
compress (ByteBuffer [], int): void 0%   (0/1)0%   (0/36)0%   (0/8)
compressLater (ByteBuffer [], int): void 0%   (0/1)0%   (0/28)0%   (0/7)
lockShared (): boolean 0%   (0/1)0%   (0/13)0%   (0/4)
unlock (): void 100% (1/1)50%  (8/16)75%  (3/4)
expand (ByteBuffer [], int): void 100% (1/1)57%  (20/35)70%  (7/10)
lockExclusive (): boolean 100% (1/1)85%  (11/13)75%  (3/4)
truncate (long): void 100% (1/1)92%  (44/48)91%  (10/11)
readWrite (long, ByteBuffer, int, int, boolean): long 100% (1/1)97%  (122/126)97%  (31/32)
<static initializer> 100% (1/1)100% (32/32)100% (8/8)
FileNioMemData (String, boolean): void 100% (1/1)100% (16/16)100% (6/6)
canWrite (): boolean 100% (1/1)100% (7/7)100% (1/1)
changeLength (long): void 100% (1/1)100% (49/49)100% (10/10)
getLastModified (): long 100% (1/1)100% (3/3)100% (1/1)
getName (): String 100% (1/1)100% (3/3)100% (1/1)
length (): long 100% (1/1)100% (3/3)100% (1/1)
setName (String): void 100% (1/1)100% (4/4)100% (2/2)
setReadOnly (): boolean 100% (1/1)100% (5/5)100% (2/2)
touch (boolean): void 100% (1/1)100% (14/14)100% (4/4)
     
class FileNioMem$1100% (1/1)67%  (2/3)88%  (15/17)75%  (3/4)
isValid (): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
FileNioMem$1 (FileNioMem, FileChannel, long, long, boolean): void 100% (1/1)100% (10/10)100% (1/1)
release (): void 100% (1/1)100% (5/5)100% (2/2)
     
class FileNioMem100% (1/1)100% (11/11)95%  (151/159)93%  (40/43)
tryLock (long, long, boolean): FileLock 100% (1/1)69%  (18/26)57%  (4/7)
FileNioMem (FileNioMemData, boolean): void 100% (1/1)100% (9/9)100% (4/4)
force (boolean): void 100% (1/1)100% (1/1)100% (1/1)
implCloseChannel (): void 100% (1/1)100% (4/4)100% (2/2)
position (): long 100% (1/1)100% (3/3)100% (1/1)
position (long): FileChannel 100% (1/1)100% (7/7)100% (2/2)
read (ByteBuffer): int 100% (1/1)100% (40/40)100% (10/10)
size (): long 100% (1/1)100% (4/4)100% (1/1)
toString (): String 100% (1/1)100% (4/4)100% (1/1)
truncate (long): FileChannel 100% (1/1)100% (29/29)100% (7/7)
write (ByteBuffer): int 100% (1/1)100% (32/32)100% (7/7)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.store.fs;
7 
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.OutputStream;
11import java.nio.ByteBuffer;
12import java.nio.channels.FileChannel;
13import java.nio.channels.FileLock;
14import java.nio.channels.NonWritableChannelException;
15import java.util.ArrayList;
16import java.util.LinkedHashMap;
17import java.util.List;
18import java.util.Map;
19import java.util.TreeMap;
20 
21import org.h2.api.ErrorCode;
22import org.h2.compress.CompressLZF;
23import org.h2.message.DbException;
24import org.h2.util.MathUtils;
25import org.h2.util.New;
26 
27/**
28 * This file system keeps files fully in memory. There is an option to compress
29 * file blocks to safe memory.
30 */
31public class FilePathNioMem extends FilePath {
32 
33    private static final TreeMap<String, FileNioMemData> MEMORY_FILES =
34            new TreeMap<String, FileNioMemData>();
35 
36    @Override
37    public FilePathNioMem getPath(String path) {
38        FilePathNioMem p = new FilePathNioMem();
39        p.name = getCanonicalPath(path);
40        return p;
41    }
42 
43    @Override
44    public long size() {
45        return getMemoryFile().length();
46    }
47 
48    @Override
49    public void moveTo(FilePath newName, boolean atomicReplace) {
50        synchronized (MEMORY_FILES) {
51            if (!atomicReplace && !name.equals(newName.name) &&
52                    MEMORY_FILES.containsKey(newName.name)) {
53                throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2,
54                        new String[] { name, newName + " (exists)" });
55            }
56            FileNioMemData f = getMemoryFile();
57            f.setName(newName.name);
58            MEMORY_FILES.remove(name);
59            MEMORY_FILES.put(newName.name, f);
60        }
61    }
62 
63    @Override
64    public boolean createFile() {
65        synchronized (MEMORY_FILES) {
66            if (exists()) {
67                return false;
68            }
69            getMemoryFile();
70        }
71        return true;
72    }
73 
74    @Override
75    public boolean exists() {
76        if (isRoot()) {
77            return true;
78        }
79        synchronized (MEMORY_FILES) {
80            return MEMORY_FILES.get(name) != null;
81        }
82    }
83 
84    @Override
85    public void delete() {
86        if (isRoot()) {
87            return;
88        }
89        synchronized (MEMORY_FILES) {
90            MEMORY_FILES.remove(name);
91        }
92    }
93 
94    @Override
95    public List<FilePath> newDirectoryStream() {
96        ArrayList<FilePath> list = New.arrayList();
97        synchronized (MEMORY_FILES) {
98            for (String n : MEMORY_FILES.tailMap(name).keySet()) {
99                if (n.startsWith(name)) {
100                    list.add(getPath(n));
101                } else {
102                    break;
103                }
104            }
105            return list;
106        }
107    }
108 
109    @Override
110    public boolean setReadOnly() {
111        return getMemoryFile().setReadOnly();
112    }
113 
114    @Override
115    public boolean canWrite() {
116        return getMemoryFile().canWrite();
117    }
118 
119    @Override
120    public FilePathNioMem getParent() {
121        int idx = name.lastIndexOf('/');
122        return idx < 0 ? null : getPath(name.substring(0, idx));
123    }
124 
125    @Override
126    public boolean isDirectory() {
127        if (isRoot()) {
128            return true;
129        }
130        // TODO in memory file system currently
131        // does not really support directories
132        synchronized (MEMORY_FILES) {
133            return MEMORY_FILES.get(name) == null;
134        }
135    }
136 
137    @Override
138    public boolean isAbsolute() {
139        // TODO relative files are not supported
140        return true;
141    }
142 
143    @Override
144    public FilePathNioMem toRealPath() {
145        return this;
146    }
147 
148    @Override
149    public long lastModified() {
150        return getMemoryFile().getLastModified();
151    }
152 
153    @Override
154    public void createDirectory() {
155        if (exists() && isDirectory()) {
156            throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1,
157                    name + " (a file with this name already exists)");
158        }
159        // TODO directories are not really supported
160    }
161 
162    @Override
163    public OutputStream newOutputStream(boolean append) throws IOException {
164        FileNioMemData obj = getMemoryFile();
165        FileNioMem m = new FileNioMem(obj, false);
166        return new FileChannelOutputStream(m, append);
167    }
168 
169    @Override
170    public InputStream newInputStream() {
171        FileNioMemData obj = getMemoryFile();
172        FileNioMem m = new FileNioMem(obj, true);
173        return new FileChannelInputStream(m, true);
174    }
175 
176    @Override
177    public FileChannel open(String mode) {
178        FileNioMemData obj = getMemoryFile();
179        return new FileNioMem(obj, "r".equals(mode));
180    }
181 
182    private FileNioMemData getMemoryFile() {
183        synchronized (MEMORY_FILES) {
184            FileNioMemData m = MEMORY_FILES.get(name);
185            if (m == null) {
186                m = new FileNioMemData(name, compressed());
187                MEMORY_FILES.put(name, m);
188            }
189            return m;
190        }
191    }
192 
193    private boolean isRoot() {
194        return name.equals(getScheme());
195    }
196 
197    private static String getCanonicalPath(String fileName) {
198        fileName = fileName.replace('\\', '/');
199        int idx = fileName.indexOf(':') + 1;
200        if (fileName.length() > idx && fileName.charAt(idx) != '/') {
201            fileName = fileName.substring(0, idx) + "/" + fileName.substring(idx);
202        }
203        return fileName;
204    }
205 
206    @Override
207    public String getScheme() {
208        return "nioMemFS";
209    }
210 
211    /**
212     * Whether the file should be compressed.
213     *
214     * @return if it should be compressed.
215     */
216    boolean compressed() {
217        return false;
218    }
219 
220}
221 
222/**
223 * A memory file system that compresses blocks to conserve memory.
224 */
225class FilePathNioMemLZF extends FilePathNioMem {
226 
227    @Override
228    boolean compressed() {
229        return true;
230    }
231 
232    @Override
233    public String getScheme() {
234        return "nioMemLZF";
235    }
236 
237}
238 
239/**
240 * This class represents an in-memory file.
241 */
242class FileNioMem extends FileBase {
243 
244    /**
245     * The file data.
246     */
247    final FileNioMemData data;
248 
249    private final boolean readOnly;
250    private long pos;
251 
252    FileNioMem(FileNioMemData data, boolean readOnly) {
253        this.data = data;
254        this.readOnly = readOnly;
255    }
256 
257    @Override
258    public long size() {
259        return data.length();
260    }
261 
262    @Override
263    public FileChannel truncate(long newLength) throws IOException {
264        // compatibility with JDK FileChannel#truncate
265        if (readOnly) {
266            throw new NonWritableChannelException();
267        }
268        if (newLength < size()) {
269            data.touch(readOnly);
270            pos = Math.min(pos, newLength);
271            data.truncate(newLength);
272        }
273        return this;
274    }
275 
276    @Override
277    public FileChannel position(long newPos) {
278        this.pos = (int) newPos;
279        return this;
280    }
281 
282    @Override
283    public int write(ByteBuffer src) throws IOException {
284        int len = src.remaining();
285        if (len == 0) {
286            return 0;
287        }
288        data.touch(readOnly);
289        // offset is 0 because we start writing from src.position()
290        pos = data.readWrite(pos, src, 0, len, true);
291        src.position(src.position() + len);
292        return len;
293    }
294 
295    @Override
296    public int read(ByteBuffer dst) throws IOException {
297        int len = dst.remaining();
298        if (len == 0) {
299            return 0;
300        }
301        long newPos = data.readWrite(pos, dst, dst.position(), len, false);
302        len = (int) (newPos - pos);
303        if (len <= 0) {
304            return -1;
305        }
306        dst.position(dst.position() + len);
307        pos = newPos;
308        return len;
309    }
310 
311    @Override
312    public long position() {
313        return pos;
314    }
315 
316    @Override
317    public void implCloseChannel() throws IOException {
318        pos = 0;
319    }
320 
321    @Override
322    public void force(boolean metaData) throws IOException {
323        // do nothing
324    }
325 
326    @Override
327    public synchronized FileLock tryLock(long position, long size,
328            boolean shared) throws IOException {
329        if (shared) {
330            if (!data.lockShared()) {
331                return null;
332            }
333        } else {
334            if (!data.lockExclusive()) {
335                return null;
336            }
337        }
338 
339        // cast to FileChannel to avoid JDK 1.7 ambiguity
340        FileLock lock = new FileLock((FileChannel) null, position, size, shared) {
341 
342            @Override
343            public boolean isValid() {
344                return true;
345            }
346 
347            @Override
348            public void release() throws IOException {
349                data.unlock();
350            }
351        };
352        return lock;
353    }
354 
355    @Override
356    public String toString() {
357        return data.getName();
358    }
359 
360}
361 
362/**
363 * This class contains the data of an in-memory random access file.
364 * Data compression using the LZF algorithm is supported as well.
365 */
366class FileNioMemData {
367 
368    private static final int CACHE_SIZE = 8;
369    private static final int BLOCK_SIZE_SHIFT = 10;
370    private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT;
371    private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1;
372    private static final CompressLZF LZF = new CompressLZF();
373    private static final byte[] BUFFER = new byte[BLOCK_SIZE * 2];
374    private static final ByteBuffer COMPRESSED_EMPTY_BLOCK;
375 
376    private static final Cache<CompressItem, CompressItem> COMPRESS_LATER =
377        new Cache<CompressItem, CompressItem>(CACHE_SIZE);
378 
379    private String name;
380    private final boolean compress;
381    private long length;
382    private ByteBuffer[] data;
383    private long lastModified;
384    private boolean isReadOnly;
385    private boolean isLockedExclusive;
386    private int sharedLockCount;
387 
388    static {
389        byte[] n = new byte[BLOCK_SIZE];
390        int len = LZF.compress(n, BLOCK_SIZE, BUFFER, 0);
391        COMPRESSED_EMPTY_BLOCK = ByteBuffer.allocateDirect(len);
392        COMPRESSED_EMPTY_BLOCK.put(BUFFER, 0, len);
393    }
394 
395    FileNioMemData(String name, boolean compress) {
396        this.name = name;
397        this.compress = compress;
398        data = new ByteBuffer[0];
399        lastModified = System.currentTimeMillis();
400    }
401 
402    /**
403     * Lock the file in exclusive mode if possible.
404     *
405     * @return if locking was successful
406     */
407    synchronized boolean lockExclusive() {
408        if (sharedLockCount > 0 || isLockedExclusive) {
409            return false;
410        }
411        isLockedExclusive = true;
412        return true;
413    }
414 
415    /**
416     * Lock the file in shared mode if possible.
417     *
418     * @return if locking was successful
419     */
420    synchronized boolean lockShared() {
421        if (isLockedExclusive) {
422            return false;
423        }
424        sharedLockCount++;
425        return true;
426    }
427 
428    /**
429     * Unlock the file.
430     */
431    synchronized void unlock() {
432        if (isLockedExclusive) {
433            isLockedExclusive = false;
434        } else {
435            sharedLockCount = Math.max(0, sharedLockCount - 1);
436        }
437    }
438 
439    /**
440     * This small cache compresses the data if an element leaves the cache.
441     */
442    static class Cache<K, V> extends LinkedHashMap<K, V> {
443 
444        private static final long serialVersionUID = 1L;
445        private final int size;
446 
447        Cache(int size) {
448            super(size, (float) 0.75, true);
449            this.size = size;
450        }
451 
452        @Override
453        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
454            if (size() < size) {
455                return false;
456            }
457            CompressItem c = (CompressItem) eldest.getKey();
458            compress(c.data, c.page);
459            return true;
460        }
461    }
462 
463    /**
464     * Represents a compressed item.
465     */
466    static class CompressItem {
467 
468        /**
469         * The file data.
470         */
471        ByteBuffer[] data;
472 
473        /**
474         * The page to compress.
475         */
476        int page;
477 
478        @Override
479        public int hashCode() {
480            return page;
481        }
482 
483        @Override
484        public boolean equals(Object o) {
485            if (o instanceof CompressItem) {
486                CompressItem c = (CompressItem) o;
487                return c.data == data && c.page == page;
488            }
489            return false;
490        }
491 
492    }
493 
494    private static void compressLater(ByteBuffer[] data, int page) {
495        CompressItem c = new CompressItem();
496        c.data = data;
497        c.page = page;
498        synchronized (LZF) {
499            COMPRESS_LATER.put(c, c);
500        }
501    }
502 
503    private static void expand(ByteBuffer[] data, int page) {
504        ByteBuffer d = data[page];
505        if (d.capacity() == BLOCK_SIZE) {
506            return;
507        }
508        ByteBuffer out = ByteBuffer.allocateDirect(BLOCK_SIZE);
509        if (d != COMPRESSED_EMPTY_BLOCK) {
510            synchronized (LZF) {
511                CompressLZF.expand(d, out);
512            }
513        }
514        data[page] = out;
515    }
516 
517    /**
518     * Compress the data in a byte array.
519     *
520     * @param data the page array
521     * @param page which page to compress
522     */
523    static void compress(ByteBuffer[] data, int page) {
524        ByteBuffer d = data[page];
525        synchronized (LZF) {
526            int len = LZF.compress(d, BUFFER, 0);
527            d = ByteBuffer.allocateDirect(len);
528            d.put(BUFFER, 0, len);
529            data[page] = d;
530        }
531    }
532 
533    /**
534     * Update the last modified time.
535     *
536     * @param openReadOnly if the file was opened in read-only mode
537     */
538    void touch(boolean openReadOnly) throws IOException {
539        if (isReadOnly || openReadOnly) {
540            throw new IOException("Read only");
541        }
542        lastModified = System.currentTimeMillis();
543    }
544 
545    /**
546     * Get the file length.
547     *
548     * @return the length
549     */
550    long length() {
551        return length;
552    }
553 
554    /**
555     * Truncate the file.
556     *
557     * @param newLength the new length
558     */
559    void truncate(long newLength) {
560        changeLength(newLength);
561        long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE);
562        if (end != newLength) {
563            int lastPage = (int) (newLength >>> BLOCK_SIZE_SHIFT);
564            expand(data, lastPage);
565            ByteBuffer d = data[lastPage];
566            for (int i = (int) (newLength & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) {
567                d.put(i, (byte) 0);
568            }
569            if (compress) {
570                compressLater(data, lastPage);
571            }
572        }
573    }
574 
575    private void changeLength(long len) {
576        length = len;
577        len = MathUtils.roundUpLong(len, BLOCK_SIZE);
578        int blocks = (int) (len >>> BLOCK_SIZE_SHIFT);
579        if (blocks != data.length) {
580            ByteBuffer[] n = new ByteBuffer[blocks];
581            System.arraycopy(data, 0, n, 0, Math.min(data.length, n.length));
582            for (int i = data.length; i < blocks; i++) {
583                n[i] = COMPRESSED_EMPTY_BLOCK;
584            }
585            data = n;
586        }
587    }
588 
589    /**
590     * Read or write.
591     *
592     * @param pos the position
593     * @param b the byte array
594     * @param off the offset within the byte array
595     * @param len the number of bytes
596     * @param write true for writing
597     * @return the new position
598     */
599    long readWrite(long pos, ByteBuffer b, int off, int len, boolean write) {
600        long end = pos + len;
601        if (end > length) {
602            if (write) {
603                changeLength(end);
604            } else {
605                len = (int) (length - pos);
606            }
607        }
608        while (len > 0) {
609            int l = (int) Math.min(len, BLOCK_SIZE - (pos & BLOCK_SIZE_MASK));
610            int page = (int) (pos >>> BLOCK_SIZE_SHIFT);
611            expand(data, page);
612            ByteBuffer block = data[page];
613            int blockOffset = (int) (pos & BLOCK_SIZE_MASK);
614            if (write) {
615                ByteBuffer tmp = b.slice();
616                tmp.position(off);
617                tmp.limit(off + l);
618                block.position(blockOffset);
619                block.put(tmp);
620            } else {
621                block.position(blockOffset);
622                ByteBuffer tmp = block.slice();
623                tmp.limit(l);
624                int oldPosition = b.position();
625                b.position(off);
626                b.put(tmp);
627                // restore old position
628                b.position(oldPosition);
629            }
630            if (compress) {
631                compressLater(data, page);
632            }
633            off += l;
634            pos += l;
635            len -= l;
636        }
637        return pos;
638    }
639 
640    /**
641     * Set the file name.
642     *
643     * @param name the name
644     */
645    void setName(String name) {
646        this.name = name;
647    }
648 
649    /**
650     * Get the file name
651     *
652     * @return the name
653     */
654    String getName() {
655        return name;
656    }
657 
658    /**
659     * Get the last modified time.
660     *
661     * @return the time
662     */
663    long getLastModified() {
664        return lastModified;
665    }
666 
667    /**
668     * Check whether writing is allowed.
669     *
670     * @return true if it is
671     */
672    boolean canWrite() {
673        return !isReadOnly;
674    }
675 
676    /**
677     * Set the read-only flag.
678     *
679     * @return true
680     */
681    boolean setReadOnly() {
682        isReadOnly = true;
683        return true;
684    }
685 
686}
687 
688 

[all classes][org.h2.store.fs]
EMMA 2.0.5312 (C) Vladimir Roubtsov