File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\store\fs\FilePathMem.java.html

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