tikhomirov@10: /* tikhomirov@10: * Copyright (c) 2010 Artem Tikhomirov tikhomirov@10: */ tikhomirov@10: package com.tmate.hgkit.fs; tikhomirov@10: tikhomirov@10: import java.io.File; tikhomirov@10: import java.io.FileInputStream; tikhomirov@10: import java.io.IOException; tikhomirov@10: import java.nio.ByteBuffer; tikhomirov@10: import java.nio.MappedByteBuffer; tikhomirov@10: import java.nio.channels.FileChannel; tikhomirov@10: tikhomirov@10: /** tikhomirov@10: * tikhomirov@10: * @author artem tikhomirov@10: */ tikhomirov@10: public class DataAccessProvider { tikhomirov@10: tikhomirov@10: private final int mapioMagicBoundary; tikhomirov@10: private final int bufferSize; tikhomirov@10: tikhomirov@10: public DataAccessProvider() { tikhomirov@10: mapioMagicBoundary = 100 * 1024; tikhomirov@10: bufferSize = 8 * 1024; tikhomirov@10: } tikhomirov@10: tikhomirov@10: public DataAccess create(File f) { tikhomirov@10: if (!f.exists()) { tikhomirov@10: return new DataAccess(); tikhomirov@10: } tikhomirov@10: try { tikhomirov@10: FileChannel fc = new FileInputStream(f).getChannel(); tikhomirov@10: if (fc.size() > mapioMagicBoundary) { tikhomirov@10: return new MemoryMapFileAccess(fc, fc.size()); tikhomirov@10: } else { tikhomirov@10: // XXX once implementation is more or less stable, tikhomirov@10: // may want to try ByteBuffer.allocateDirect() to see tikhomirov@10: // if there's any performance gain. tikhomirov@10: boolean useDirectBuffer = false; tikhomirov@10: return new FileAccess(fc, fc.size(), bufferSize, useDirectBuffer); tikhomirov@10: } tikhomirov@10: } catch (IOException ex) { tikhomirov@10: // unlikely to happen, we've made sure file exists. tikhomirov@10: ex.printStackTrace(); // FIXME log error tikhomirov@10: } tikhomirov@10: return new DataAccess(); // non-null, empty. tikhomirov@10: } tikhomirov@10: tikhomirov@10: // DOESN'T WORK YET tikhomirov@10: private static class MemoryMapFileAccess extends DataAccess { tikhomirov@10: private FileChannel fileChannel; tikhomirov@10: private final long size; tikhomirov@10: private long position = 0; tikhomirov@10: tikhomirov@10: public MemoryMapFileAccess(FileChannel fc, long channelSize) { tikhomirov@10: fileChannel = fc; tikhomirov@10: size = channelSize; tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void seek(long offset) { tikhomirov@10: position = offset; tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void skip(int bytes) throws IOException { tikhomirov@10: position += bytes; tikhomirov@10: } tikhomirov@10: tikhomirov@10: private boolean fill() throws IOException { tikhomirov@10: final int BUFFER_SIZE = 8 * 1024; tikhomirov@10: long left = size - position; tikhomirov@10: MappedByteBuffer rv = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < BUFFER_SIZE ? left : BUFFER_SIZE); tikhomirov@10: position += rv.capacity(); tikhomirov@10: return rv.hasRemaining(); tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void done() { tikhomirov@10: if (fileChannel != null) { tikhomirov@10: try { tikhomirov@10: fileChannel.close(); tikhomirov@10: } catch (IOException ex) { tikhomirov@10: ex.printStackTrace(); // log debug tikhomirov@10: } tikhomirov@10: fileChannel = null; tikhomirov@10: } tikhomirov@10: } tikhomirov@10: } tikhomirov@10: tikhomirov@10: // (almost) regular file access - FileChannel and buffers. tikhomirov@10: private static class FileAccess extends DataAccess { tikhomirov@10: private FileChannel fileChannel; tikhomirov@10: private final long size; tikhomirov@10: private ByteBuffer buffer; tikhomirov@10: private long bufferStartInFile = 0; // offset of this.buffer in the file. tikhomirov@10: tikhomirov@10: public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect) { tikhomirov@10: fileChannel = fc; tikhomirov@10: size = channelSize; tikhomirov@10: final int capacity = size < bufferSizeHint ? (int) size : bufferSizeHint; tikhomirov@10: buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity); tikhomirov@10: buffer.flip(); // or .limit(0) to indicate it's empty tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public boolean isEmpty() { tikhomirov@10: return bufferStartInFile + buffer.position() >= size; tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void seek(long offset) throws IOException { tikhomirov@10: if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) { tikhomirov@10: buffer.position((int) (offset - bufferStartInFile)); tikhomirov@10: } else { tikhomirov@10: // out of current buffer, invalidate it (force re-read) tikhomirov@10: // XXX or ever re-read it right away? tikhomirov@10: bufferStartInFile = offset; tikhomirov@10: buffer.clear(); tikhomirov@10: buffer.limit(0); // or .flip() to indicate we switch to reading tikhomirov@10: fileChannel.position(offset); tikhomirov@10: } tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void skip(int bytes) throws IOException { tikhomirov@10: final int newPos = buffer.position() + bytes; tikhomirov@10: if (newPos >= 0 && newPos < buffer.limit()) { tikhomirov@10: // no need to move file pointer, just rewind/seek buffer tikhomirov@10: buffer.position(newPos); tikhomirov@10: } else { tikhomirov@10: // tikhomirov@10: seek(fileChannel.position()+ bytes); tikhomirov@10: } tikhomirov@10: } tikhomirov@10: tikhomirov@10: private boolean fill() throws IOException { tikhomirov@10: if (!buffer.hasRemaining()) { tikhomirov@10: bufferStartInFile += buffer.limit(); tikhomirov@10: buffer.clear(); tikhomirov@10: if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1 tikhomirov@10: fileChannel.read(buffer); tikhomirov@10: // may return -1 when EOF, but empty will reflect this, hence no explicit support here tikhomirov@10: } tikhomirov@10: buffer.flip(); tikhomirov@10: } tikhomirov@10: return buffer.hasRemaining(); tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void readBytes(byte[] buf, int offset, int length) throws IOException { tikhomirov@10: final int tail = buffer.remaining(); tikhomirov@10: if (tail >= length) { tikhomirov@10: buffer.get(buf, offset, length); tikhomirov@10: } else { tikhomirov@10: buffer.get(buf, offset, tail); tikhomirov@10: if (fill()) { tikhomirov@10: buffer.get(buf, offset + tail, length - tail); tikhomirov@10: } else { tikhomirov@10: throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past nonEmpty() == false are made. tikhomirov@10: } tikhomirov@10: } tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public byte readByte() throws IOException { tikhomirov@10: if (buffer.hasRemaining()) { tikhomirov@10: return buffer.get(); tikhomirov@10: } tikhomirov@10: if (fill()) { tikhomirov@10: return buffer.get(); tikhomirov@10: } tikhomirov@10: throw new IOException(); tikhomirov@10: } tikhomirov@10: tikhomirov@10: @Override tikhomirov@10: public void done() { tikhomirov@10: if (buffer != null) { tikhomirov@10: buffer = null; tikhomirov@10: } tikhomirov@10: if (fileChannel != null) { tikhomirov@10: try { tikhomirov@10: fileChannel.close(); tikhomirov@10: } catch (IOException ex) { tikhomirov@10: ex.printStackTrace(); // log debug tikhomirov@10: } tikhomirov@10: fileChannel = null; tikhomirov@10: } tikhomirov@10: } tikhomirov@10: } tikhomirov@10: }