kitaev@213: /* kitaev@213: * Copyright (c) 2010-2011 TMate Software Ltd kitaev@213: * kitaev@213: * This program is free software; you can redistribute it and/or modify kitaev@213: * it under the terms of the GNU General Public License as published by kitaev@213: * the Free Software Foundation; version 2 of the License. kitaev@213: * kitaev@213: * This program is distributed in the hope that it will be useful, kitaev@213: * but WITHOUT ANY WARRANTY; without even the implied warranty of kitaev@213: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the kitaev@213: * GNU General Public License for more details. kitaev@213: * kitaev@213: * For information on how to redistribute this software under kitaev@213: * the terms of a license other than GNU General Public License kitaev@213: * contact TMate Software at support@hg4j.com kitaev@213: */ kitaev@213: package org.tmatesoft.hg.internal; kitaev@213: kitaev@213: import java.io.File; kitaev@213: import java.io.FileInputStream; kitaev@213: import java.io.IOException; kitaev@213: import java.nio.ByteBuffer; kitaev@213: import java.nio.MappedByteBuffer; kitaev@213: import java.nio.channels.FileChannel; kitaev@213: kitaev@213: import org.tmatesoft.hg.core.HgBadStateException; kitaev@213: kitaev@213: /** kitaev@213: * kitaev@213: * @author Artem Tikhomirov kitaev@213: * @author TMate Software Ltd. kitaev@213: */ kitaev@213: public class DataAccessProvider { kitaev@213: kitaev@213: private final int mapioMagicBoundary; kitaev@213: private final int bufferSize; kitaev@213: kitaev@213: public DataAccessProvider() { kitaev@213: this(100 * 1024, 8 * 1024); kitaev@213: } kitaev@213: kitaev@213: public DataAccessProvider(int mapioBoundary, int regularBufferSize) { kitaev@213: mapioMagicBoundary = mapioBoundary; kitaev@213: bufferSize = regularBufferSize; kitaev@213: } kitaev@213: kitaev@213: public DataAccess create(File f) { kitaev@213: if (!f.exists()) { kitaev@213: return new DataAccess(); kitaev@213: } kitaev@213: try { kitaev@213: FileChannel fc = new FileInputStream(f).getChannel(); kitaev@213: int flen = (int) fc.size(); kitaev@213: if (fc.size() - flen != 0) { kitaev@213: throw new HgBadStateException("Files greater than 2Gb are not yet supported"); kitaev@213: } kitaev@213: if (flen > mapioMagicBoundary) { kitaev@213: // TESTS: bufLen of 1024 was used to test MemMapFileAccess kitaev@213: return new MemoryMapFileAccess(fc, flen, mapioMagicBoundary); kitaev@213: } else { kitaev@213: // XXX once implementation is more or less stable, kitaev@213: // may want to try ByteBuffer.allocateDirect() to see kitaev@213: // if there's any performance gain. kitaev@213: boolean useDirectBuffer = false; kitaev@213: // TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize kitaev@213: return new FileAccess(fc, flen, bufferSize, useDirectBuffer); kitaev@213: } kitaev@213: } catch (IOException ex) { kitaev@213: // unlikely to happen, we've made sure file exists. kitaev@213: ex.printStackTrace(); // FIXME log error kitaev@213: } kitaev@213: return new DataAccess(); // non-null, empty. kitaev@213: } kitaev@213: kitaev@213: // DOESN'T WORK YET kitaev@213: private static class MemoryMapFileAccess extends DataAccess { kitaev@213: private FileChannel fileChannel; kitaev@213: private final int size; kitaev@213: private long position = 0; // always points to buffer's absolute position in the file kitaev@213: private final int memBufferSize; kitaev@213: private MappedByteBuffer buffer; kitaev@213: kitaev@213: public MemoryMapFileAccess(FileChannel fc, int channelSize, int /*long?*/ bufferSize) { kitaev@213: fileChannel = fc; kitaev@213: size = channelSize; kitaev@213: memBufferSize = bufferSize; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public boolean isEmpty() { kitaev@213: return position + (buffer == null ? 0 : buffer.position()) >= size; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public int length() { kitaev@213: return size; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public DataAccess reset() throws IOException { kitaev@213: seek(0); kitaev@213: return this; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void seek(int offset) { kitaev@213: assert offset >= 0; kitaev@213: // offset may not necessarily be further than current position in the file (e.g. rewind) kitaev@213: if (buffer != null && /*offset is within buffer*/ offset >= position && (offset - position) < buffer.limit()) { kitaev@213: buffer.position((int) (offset - position)); kitaev@213: } else { kitaev@213: position = offset; kitaev@213: buffer = null; kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void skip(int bytes) throws IOException { kitaev@213: assert bytes >= 0; kitaev@213: if (buffer == null) { kitaev@213: position += bytes; kitaev@213: return; kitaev@213: } kitaev@213: if (buffer.remaining() > bytes) { kitaev@213: buffer.position(buffer.position() + bytes); kitaev@213: } else { kitaev@213: position += buffer.position() + bytes; kitaev@213: buffer = null; kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: private void fill() throws IOException { kitaev@213: if (buffer != null) { kitaev@213: position += buffer.position(); kitaev@213: } kitaev@213: long left = size - position; kitaev@213: buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < memBufferSize ? left : memBufferSize); kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void readBytes(byte[] buf, int offset, int length) throws IOException { kitaev@213: if (buffer == null || !buffer.hasRemaining()) { kitaev@213: fill(); kitaev@213: } kitaev@213: // XXX in fact, we may try to create a MappedByteBuffer of exactly length size here, and read right away kitaev@213: while (length > 0) { kitaev@213: int tail = buffer.remaining(); kitaev@213: if (tail == 0) { kitaev@213: throw new IOException(); kitaev@213: } kitaev@213: if (tail >= length) { kitaev@213: buffer.get(buf, offset, length); kitaev@213: } else { kitaev@213: buffer.get(buf, offset, tail); kitaev@213: fill(); kitaev@213: } kitaev@213: offset += tail; kitaev@213: length -= tail; kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public byte readByte() throws IOException { kitaev@213: if (buffer == null || !buffer.hasRemaining()) { kitaev@213: fill(); kitaev@213: } kitaev@213: if (buffer.hasRemaining()) { kitaev@213: return buffer.get(); kitaev@213: } kitaev@213: throw new IOException(); kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void done() { kitaev@213: buffer = null; kitaev@213: if (fileChannel != null) { kitaev@213: try { kitaev@213: fileChannel.close(); kitaev@213: } catch (IOException ex) { kitaev@213: ex.printStackTrace(); // log debug kitaev@213: } kitaev@213: fileChannel = null; kitaev@213: } kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: // (almost) regular file access - FileChannel and buffers. kitaev@213: private static class FileAccess extends DataAccess { kitaev@213: private FileChannel fileChannel; kitaev@213: private final int size; kitaev@213: private ByteBuffer buffer; kitaev@213: private int bufferStartInFile = 0; // offset of this.buffer in the file. kitaev@213: kitaev@213: public FileAccess(FileChannel fc, int channelSize, int bufferSizeHint, boolean useDirect) { kitaev@213: fileChannel = fc; kitaev@213: size = channelSize; kitaev@213: final int capacity = size < bufferSizeHint ? size : bufferSizeHint; kitaev@213: buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity); kitaev@213: buffer.flip(); // or .limit(0) to indicate it's empty kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public boolean isEmpty() { kitaev@213: return bufferStartInFile + buffer.position() >= size; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public int length() { kitaev@213: return size; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public DataAccess reset() throws IOException { kitaev@213: seek(0); kitaev@213: return this; kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void seek(int offset) throws IOException { kitaev@213: if (offset > size) { kitaev@213: throw new IllegalArgumentException(); kitaev@213: } kitaev@213: if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) { kitaev@213: buffer.position((int) (offset - bufferStartInFile)); kitaev@213: } else { kitaev@213: // out of current buffer, invalidate it (force re-read) kitaev@213: // XXX or ever re-read it right away? kitaev@213: bufferStartInFile = offset; kitaev@213: buffer.clear(); kitaev@213: buffer.limit(0); // or .flip() to indicate we switch to reading kitaev@213: fileChannel.position(offset); kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void skip(int bytes) throws IOException { kitaev@213: final int newPos = buffer.position() + bytes; kitaev@213: if (newPos >= 0 && newPos < buffer.limit()) { kitaev@213: // no need to move file pointer, just rewind/seek buffer kitaev@213: buffer.position(newPos); kitaev@213: } else { kitaev@213: // kitaev@213: seek(bufferStartInFile + newPos); kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: private boolean fill() throws IOException { kitaev@213: if (!buffer.hasRemaining()) { kitaev@213: bufferStartInFile += buffer.limit(); kitaev@213: buffer.clear(); kitaev@213: if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1 kitaev@213: fileChannel.read(buffer); kitaev@213: // may return -1 when EOF, but empty will reflect this, hence no explicit support here kitaev@213: } kitaev@213: buffer.flip(); kitaev@213: } kitaev@213: return buffer.hasRemaining(); kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void readBytes(byte[] buf, int offset, int length) throws IOException { kitaev@213: if (!buffer.hasRemaining()) { kitaev@213: fill(); kitaev@213: } kitaev@213: while (length > 0) { kitaev@213: int tail = buffer.remaining(); kitaev@213: if (tail == 0) { kitaev@213: throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past isEmpty() == true are made. kitaev@213: } kitaev@213: if (tail >= length) { kitaev@213: buffer.get(buf, offset, length); kitaev@213: } else { kitaev@213: buffer.get(buf, offset, tail); kitaev@213: fill(); kitaev@213: } kitaev@213: offset += tail; kitaev@213: length -= tail; kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public byte readByte() throws IOException { kitaev@213: if (buffer.hasRemaining()) { kitaev@213: return buffer.get(); kitaev@213: } kitaev@213: if (fill()) { kitaev@213: return buffer.get(); kitaev@213: } kitaev@213: throw new IOException(); kitaev@213: } kitaev@213: kitaev@213: @Override kitaev@213: public void done() { kitaev@213: if (buffer != null) { kitaev@213: buffer = null; kitaev@213: } kitaev@213: if (fileChannel != null) { kitaev@213: try { kitaev@213: fileChannel.close(); kitaev@213: } catch (IOException ex) { kitaev@213: ex.printStackTrace(); // log debug kitaev@213: } kitaev@213: fileChannel = null; kitaev@213: } kitaev@213: } kitaev@213: } kitaev@213: }