123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- /*
- Copyright (c) 2012 James Ahlborn
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
-
- package com.healthmarketscience.jackcess.util;
-
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.RandomAccessFile;
- import java.nio.ByteBuffer;
- import java.nio.MappedByteBuffer;
- import java.nio.channels.Channels;
- import java.nio.channels.FileChannel;
- import java.nio.channels.FileLock;
- import java.nio.channels.NonWritableChannelException;
- import java.nio.channels.ReadableByteChannel;
- import java.nio.channels.WritableByteChannel;
- import java.nio.file.OpenOption;
- import java.nio.file.Path;
- import java.nio.file.StandardOpenOption;
-
- import com.healthmarketscience.jackcess.Database;
- import com.healthmarketscience.jackcess.DatabaseBuilder;
- import com.healthmarketscience.jackcess.impl.ByteUtil;
- import com.healthmarketscience.jackcess.impl.DatabaseImpl;
-
- /**
- * FileChannel implementation which maintains the entire "file" in memory.
- * This enables working with a Database entirely in memory (for situations
- * where disk usage may not be possible or desirable). Obviously, this
- * requires enough jvm heap space to fit the file data. Use one of the
- * {@code newChannel()} methods to construct an instance of this class.
- * <p>
- * In order to use this class with a Database, you <i>must</i> use the {@link
- * DatabaseBuilder} to open/create the Database instance, passing an instance
- * of this class to the {@link DatabaseBuilder#setChannel} method.
- * <p>
- * Implementation note: this class is optimized for use with {@link Database}.
- * Therefore not all methods may be implemented and individual read/write
- * operations are only supported within page boundaries.
- *
- * @author James Ahlborn
- * @usage _advanced_class_
- */
- public class MemFileChannel extends FileChannel
- {
- /** read-only channel access mode */
- public static final String RO_CHANNEL_MODE = "r";
- /** read/write channel access mode */
- public static final String RW_CHANNEL_MODE = "rw";
-
- private static final byte[][] EMPTY_DATA = new byte[0][];
-
- // use largest possible Jet "page size" to ensure that reads/writes will
- // always be within a single chunk
- private static final int CHUNK_SIZE = 4096;
- // this ensures that an "empty" mdb will fit in the initial chunk table
- private static final int INIT_CHUNKS = 128;
-
- /** current read/write position */
- private long _position;
- /** current amount of actual data in the file */
- private long _size;
- /** chunks containing the file data. the length of the chunk array is
- always a power of 2 and the chunks are always CHUNK_SIZE. */
- private byte[][] _data;
-
- private MemFileChannel()
- {
- this(0L, 0L, EMPTY_DATA);
- }
-
- private MemFileChannel(long position, long size, byte[][] data) {
- _position = position;
- _size = size;
- _data = data;
- }
-
- /**
- * Creates a new read/write, empty MemFileChannel.
- */
- public static MemFileChannel newChannel() {
- return new MemFileChannel();
- }
-
- /**
- * Creates a new read/write MemFileChannel containing the contents of the
- * given File. Note, modifications to the returned channel will <i>not</i>
- * affect the original File source.
- */
- public static MemFileChannel newChannel(File file) throws IOException {
- return newChannel(file, RW_CHANNEL_MODE);
- }
-
- /**
- * Creates a new MemFileChannel containing the contents of the
- * given File with the given mode (for mode details see
- * {@link RandomAccessFile#RandomAccessFile(File,String)}). Note,
- * modifications to the returned channel will <i>not</i> affect the original
- * File source.
- */
- public static MemFileChannel newChannel(File file, String mode)
- throws IOException
- {
- RandomAccessFile raf = null;
- FileChannel in = null;
- try {
- raf = new RandomAccessFile(file, RO_CHANNEL_MODE);
- return newChannel(in = raf.getChannel(), mode);
- } finally {
- ByteUtil.closeQuietly(in);
- ByteUtil.closeQuietly(raf);
- }
- }
-
- /**
- * Creates a new MemFileChannel containing the contents of the
- * given Path with the given mode (for mode details see
- * {@link RandomAccessFile#RandomAccessFile(File,String)}). Note,
- * modifications to the returned channel will <i>not</i> affect the original
- * File source.
- */
- public static MemFileChannel newChannel(Path file, OpenOption... opts)
- throws IOException
- {
- FileChannel in = null;
- try {
- String mode = RO_CHANNEL_MODE;
- if(opts != null) {
- for(OpenOption opt : opts) {
- if(opt == StandardOpenOption.WRITE) {
- mode = RW_CHANNEL_MODE;
- break;
- }
- }
- }
- return newChannel(in = FileChannel.open(file, StandardOpenOption.READ),
- mode);
- } finally {
- ByteUtil.closeQuietly(in);
- }
- }
-
- /**
- * Creates a new read/write MemFileChannel containing the contents of the
- * given Path. Note, modifications to the returned channel will <i>not</i>
- * affect the original File source.
- */
- public static MemFileChannel newChannel(Path file) throws IOException {
- return newChannel(file, DatabaseImpl.RW_CHANNEL_OPTS);
- }
-
- /**
- * Creates a new read/write MemFileChannel containing the contents of the
- * given InputStream.
- */
- public static MemFileChannel newChannel(InputStream in) throws IOException {
- return newChannel(in, RW_CHANNEL_MODE);
- }
-
- /**
- * Creates a new MemFileChannel containing the contents of the
- * given InputStream with the given mode (for mode details see
- * {@link RandomAccessFile#RandomAccessFile(File,String)}).
- */
- public static MemFileChannel newChannel(InputStream in, String mode)
- throws IOException
- {
- return newChannel(Channels.newChannel(in), mode);
- }
-
- /**
- * Creates a new read/write MemFileChannel containing the contents of the
- * given ReadableByteChannel.
- */
- public static MemFileChannel newChannel(ReadableByteChannel in)
- throws IOException
- {
- return newChannel(in, RW_CHANNEL_MODE);
- }
-
- /**
- * Creates a new MemFileChannel containing the contents of the
- * given ReadableByteChannel with the given mode (for mode details see
- * {@link RandomAccessFile#RandomAccessFile(File,String)}).
- */
- public static MemFileChannel newChannel(ReadableByteChannel in, String mode)
- throws IOException
- {
- MemFileChannel channel = new MemFileChannel();
- channel.transferFrom(in, 0L, Long.MAX_VALUE);
- if(!mode.contains("w")) {
- channel = new ReadOnlyChannel(channel);
- }
- return channel;
- }
-
- @Override
- public int read(ByteBuffer dst) throws IOException {
- int bytesRead = read(dst, _position);
- if(bytesRead > 0) {
- _position += bytesRead;
- }
- return bytesRead;
- }
-
- @Override
- public int read(ByteBuffer dst, long position) throws IOException {
- if(position >= _size) {
- return -1;
- }
-
- int numBytes = (int)Math.min(dst.remaining(), _size - position);
- int rem = numBytes;
-
- while(rem > 0) {
- byte[] chunk = _data[getChunkIndex(position)];
- int chunkOffset = getChunkOffset(position);
- int bytesRead = Math.min(rem, CHUNK_SIZE - chunkOffset);
- dst.put(chunk, chunkOffset, bytesRead);
- rem -= bytesRead;
- position += bytesRead;
- }
-
- return numBytes;
- }
-
- @Override
- public int write(ByteBuffer src) throws IOException {
- int bytesWritten = write(src, _position);
- _position += bytesWritten;
- return bytesWritten;
- }
-
- @Override
- public int write(ByteBuffer src, long position) throws IOException {
- int numBytes = src.remaining();
- long newSize = position + numBytes;
- ensureCapacity(newSize);
-
- int rem = numBytes;
- while(rem > 0) {
- byte[] chunk = _data[getChunkIndex(position)];
- int chunkOffset = getChunkOffset(position);
- int bytesWritten = Math.min(rem, CHUNK_SIZE - chunkOffset);
- src.get(chunk, chunkOffset, bytesWritten);
- rem -= bytesWritten;
- position += bytesWritten;
- }
-
- if(newSize > _size) {
- _size = newSize;
- }
-
- return numBytes;
- }
-
- @Override
- public long position() throws IOException {
- return _position;
- }
-
- @Override
- public FileChannel position(long newPosition) throws IOException {
- if(newPosition < 0L) {
- throw new IllegalArgumentException("negative position");
- }
- _position = newPosition;
- return this;
- }
-
- @Override
- public long size() throws IOException {
- return _size;
- }
-
- @Override
- public FileChannel truncate(long newSize) throws IOException {
- if(newSize < 0L) {
- throw new IllegalArgumentException("negative size");
- }
- if(newSize < _size) {
- // we'll optimize for memory over speed and aggressively free unused
- // chunks
- for(int i = getNumChunks(newSize); i < getNumChunks(_size); ++i) {
- _data[i] = null;
- }
- _size = newSize;
- }
- _position = Math.min(newSize, _position);
- return this;
- }
-
- @Override
- public void force(boolean metaData) throws IOException {
- // nothing to do
- }
-
- /**
- * Convenience method for writing the entire contents of this channel to the
- * given destination channel.
- * @see #transferTo(long,long,WritableByteChannel)
- */
- public long transferTo(WritableByteChannel dst)
- throws IOException
- {
- return transferTo(0L, _size, dst);
- }
-
- @Override
- public long transferTo(long position, long count, WritableByteChannel dst)
- throws IOException
- {
- if(position >= _size) {
- return 0L;
- }
-
- count = Math.min(count, _size - position);
-
- int chunkIndex = getChunkIndex(position);
- int chunkOffset = getChunkOffset(position);
-
- long numBytes = 0L;
- while(count > 0L) {
-
- int chunkBytes = (int)Math.min(count, CHUNK_SIZE - chunkOffset);
- ByteBuffer src = ByteBuffer.wrap(_data[chunkIndex], chunkOffset,
- chunkBytes);
-
- do {
- int bytesWritten = dst.write(src);
- if(bytesWritten == 0L) {
- // dst full
- return numBytes;
- }
- numBytes += bytesWritten;
- count -= bytesWritten;
- } while(src.hasRemaining());
-
- ++chunkIndex;
- chunkOffset = 0;
- }
-
- return numBytes;
- }
-
- /**
- * Convenience method for writing the entire contents of this channel to the
- * given destination stream.
- * @see #transferTo(long,long,WritableByteChannel)
- */
- public long transferTo(OutputStream dst)
- throws IOException
- {
- return transferTo(0L, _size, dst);
- }
-
- /**
- * Convenience method for writing the selected portion of this channel to
- * the given destination stream.
- * @see #transferTo(long,long,WritableByteChannel)
- */
- public long transferTo(long position, long count, OutputStream dst)
- throws IOException
- {
- return transferTo(position, count, Channels.newChannel(dst));
- }
-
- @Override
- public long transferFrom(ReadableByteChannel src,
- long position, long count)
- throws IOException
- {
- int chunkIndex = getChunkIndex(position);
- int chunkOffset = getChunkOffset(position);
-
- long numBytes = 0L;
- while(count > 0L) {
-
- ensureCapacity(position + numBytes + 1);
-
- int chunkBytes = (int)Math.min(count, CHUNK_SIZE - chunkOffset);
- ByteBuffer dst = ByteBuffer.wrap(_data[chunkIndex], chunkOffset,
- chunkBytes);
- do {
- int bytesRead = src.read(dst);
- if(bytesRead <= 0) {
- // src empty
- return numBytes;
- }
- numBytes += bytesRead;
- count -= bytesRead;
- _size = Math.max(_size, position + numBytes);
- } while(dst.hasRemaining());
-
- ++chunkIndex;
- chunkOffset = 0;
- }
-
- return numBytes;
- }
-
- @Override
- protected void implCloseChannel() throws IOException {
- // release data
- _data = EMPTY_DATA;
- _size = _position = 0L;
- }
-
- private void ensureCapacity(long newSize)
- {
- if(newSize <= _size) {
- // nothing to do
- return;
- }
-
- int newNumChunks = getNumChunks(newSize);
- int numChunks = getNumChunks(_size);
-
- if(newNumChunks > _data.length) {
-
- // need to extend chunk array (use powers of 2)
- int newDataLen = Math.max(_data.length, INIT_CHUNKS);
- while(newDataLen < newNumChunks) {
- newDataLen <<= 1;
- }
-
- byte[][] newData = new byte[newDataLen][];
-
- // copy existing chunks
- System.arraycopy(_data, 0, newData, 0, numChunks);
-
- _data = newData;
- }
-
- // allocate new chunks
- for(int i = numChunks; i < newNumChunks; ++i) {
- _data[i] = new byte[CHUNK_SIZE];
- }
- }
-
- private static int getChunkIndex(long pos) {
- return (int)(pos / CHUNK_SIZE);
- }
-
- private static int getChunkOffset(long pos) {
- return (int)(pos % CHUNK_SIZE);
- }
-
- private static int getNumChunks(long size) {
- return getChunkIndex(size + CHUNK_SIZE - 1);
- }
-
- @Override
- public long write(ByteBuffer[] srcs, int offset, int length)
- throws IOException
- {
- long numBytes = 0L;
- for(int i = offset; i < offset + length; ++i) {
- numBytes += write(srcs[i]);
- }
- return numBytes;
- }
-
- @Override
- public long read(ByteBuffer[] dsts, int offset, int length)
- throws IOException
- {
- long numBytes = 0L;
- for(int i = offset; i < offset + length; ++i) {
- if(_position >= _size) {
- return ((numBytes > 0L) ? numBytes : -1L);
- }
- numBytes += read(dsts[i]);
- }
- return numBytes;
- }
-
- @Override
- public MappedByteBuffer map(MapMode mode, long position, long size)
- throws IOException
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public FileLock lock(long position, long size, boolean shared)
- throws IOException
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public FileLock tryLock(long position, long size, boolean shared)
- throws IOException
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Subclass of MemFileChannel which is read-only.
- */
- private static final class ReadOnlyChannel extends MemFileChannel
- {
- private ReadOnlyChannel(MemFileChannel channel)
- {
- super(channel._position, channel._size, channel._data);
- }
-
- @Override
- public int write(ByteBuffer src, long position) throws IOException {
- throw new NonWritableChannelException();
- }
-
- @Override
- public FileChannel truncate(long newSize) throws IOException {
- throw new NonWritableChannelException();
- }
-
- @Override
- public long transferFrom(ReadableByteChannel src,
- long position, long count)
- throws IOException
- {
- throw new NonWritableChannelException();
- }
- }
- }
|