/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.dev.store; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Map; import org.h2.store.fs.FileBase; import org.h2.store.fs.FilePathWrapper; import org.h2.util.SmallLRUCache; /** * A file with a read cache. */ public class FilePathCache extends FilePathWrapper { public static FileChannel wrap(FileChannel f) throws IOException { return new FileCache(f); } public FileChannel open(String mode) throws IOException { return new FileCache(getBase().open(mode)); } public String getScheme() { return "cache"; } /** * A file with a read cache. */ public static class FileCache extends FileBase { private static final int CACHE_BLOCK_SIZE = 4 * 1024; private final FileChannel base; private long pos, posBase, size; private final Map<Long, ByteBuffer> cache = SmallLRUCache.newInstance(16); FileCache(FileChannel base) throws IOException { this.base = base; this.size = base.size(); } protected void implCloseChannel() throws IOException { base.close(); } public FileChannel position(long newPosition) throws IOException { this.pos = newPosition; return this; } public long position() throws IOException { return pos; } private void positionBase(long pos) throws IOException { if (posBase != pos) { base.position(pos); posBase = pos; } } public int read(ByteBuffer dst) throws IOException { long cachePos = getCachePos(pos); int off = (int) (pos - cachePos); int len = CACHE_BLOCK_SIZE - off; ByteBuffer buff = cache.get(cachePos); if (buff == null) { buff = ByteBuffer.allocate(CACHE_BLOCK_SIZE); positionBase(cachePos); int read = base.read(buff); posBase += read; if (read == CACHE_BLOCK_SIZE) { cache.put(cachePos, buff); } else { if (read < 0) { return -1; } len = Math.min(len, read); } } len = Math.min(len, dst.remaining()); System.arraycopy(buff.array(), off, dst.array(), dst.position(), len); dst.position(dst.position() + len); pos += len; return len; } private static long getCachePos(long pos) { return (pos / CACHE_BLOCK_SIZE) * CACHE_BLOCK_SIZE; } public long size() throws IOException { return size; } public FileChannel truncate(long newSize) throws IOException { cache.clear(); base.truncate(newSize); size = Math.min(size, newSize); pos = Math.min(pos, newSize); posBase = pos; return this; } public int write(ByteBuffer src) throws IOException { if (cache.size() > 0) { for (long p = getCachePos(pos), len = src.remaining(); len > 0; p += CACHE_BLOCK_SIZE, len -= CACHE_BLOCK_SIZE) { cache.remove(p); } } positionBase(pos); int len = base.write(src); posBase += len; pos += len; size = Math.max(size, pos); return len; } public void force(boolean metaData) throws IOException { base.force(metaData); } public FileLock tryLock(long position, long size, boolean shared) throws IOException { return base.tryLock(position, size, shared); } public String toString() { return "cache:" + base.toString(); } } }