You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MemFileChannel.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /*
  2. Copyright (c) 2012 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.util;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.OutputStream;
  18. import java.io.RandomAccessFile;
  19. import java.nio.ByteBuffer;
  20. import java.nio.MappedByteBuffer;
  21. import java.nio.channels.Channels;
  22. import java.nio.channels.FileChannel;
  23. import java.nio.channels.FileLock;
  24. import java.nio.channels.NonWritableChannelException;
  25. import java.nio.channels.ReadableByteChannel;
  26. import java.nio.channels.WritableByteChannel;
  27. import java.nio.file.OpenOption;
  28. import java.nio.file.Path;
  29. import java.nio.file.StandardOpenOption;
  30. import com.healthmarketscience.jackcess.Database;
  31. import com.healthmarketscience.jackcess.DatabaseBuilder;
  32. import com.healthmarketscience.jackcess.impl.ByteUtil;
  33. import com.healthmarketscience.jackcess.impl.DatabaseImpl;
  34. /**
  35. * FileChannel implementation which maintains the entire "file" in memory.
  36. * This enables working with a Database entirely in memory (for situations
  37. * where disk usage may not be possible or desirable). Obviously, this
  38. * requires enough jvm heap space to fit the file data. Use one of the
  39. * {@code newChannel()} methods to construct an instance of this class.
  40. * <p>
  41. * In order to use this class with a Database, you <i>must</i> use the {@link
  42. * DatabaseBuilder} to open/create the Database instance, passing an instance
  43. * of this class to the {@link DatabaseBuilder#setChannel} method.
  44. * <p>
  45. * Implementation note: this class is optimized for use with {@link Database}.
  46. * Therefore not all methods may be implemented and individual read/write
  47. * operations are only supported within page boundaries.
  48. *
  49. * @author James Ahlborn
  50. * @usage _advanced_class_
  51. */
  52. public class MemFileChannel extends FileChannel
  53. {
  54. /** read-only channel access mode */
  55. public static final String RO_CHANNEL_MODE = "r";
  56. /** read/write channel access mode */
  57. public static final String RW_CHANNEL_MODE = "rw";
  58. private static final byte[][] EMPTY_DATA = new byte[0][];
  59. // use largest possible Jet "page size" to ensure that reads/writes will
  60. // always be within a single chunk
  61. private static final int CHUNK_SIZE = 4096;
  62. // this ensures that an "empty" mdb will fit in the initial chunk table
  63. private static final int INIT_CHUNKS = 128;
  64. /** current read/write position */
  65. private long _position;
  66. /** current amount of actual data in the file */
  67. private long _size;
  68. /** chunks containing the file data. the length of the chunk array is
  69. always a power of 2 and the chunks are always CHUNK_SIZE. */
  70. private byte[][] _data;
  71. private MemFileChannel()
  72. {
  73. this(0L, 0L, EMPTY_DATA);
  74. }
  75. private MemFileChannel(long position, long size, byte[][] data) {
  76. _position = position;
  77. _size = size;
  78. _data = data;
  79. }
  80. /**
  81. * Creates a new read/write, empty MemFileChannel.
  82. */
  83. public static MemFileChannel newChannel() {
  84. return new MemFileChannel();
  85. }
  86. /**
  87. * Creates a new read/write MemFileChannel containing the contents of the
  88. * given File. Note, modifications to the returned channel will <i>not</i>
  89. * affect the original File source.
  90. */
  91. public static MemFileChannel newChannel(File file) throws IOException {
  92. return newChannel(file, RW_CHANNEL_MODE);
  93. }
  94. /**
  95. * Creates a new MemFileChannel containing the contents of the
  96. * given File with the given mode (for mode details see
  97. * {@link RandomAccessFile#RandomAccessFile(File,String)}). Note,
  98. * modifications to the returned channel will <i>not</i> affect the original
  99. * File source.
  100. */
  101. public static MemFileChannel newChannel(File file, String mode)
  102. throws IOException
  103. {
  104. RandomAccessFile raf = null;
  105. FileChannel in = null;
  106. try {
  107. raf = new RandomAccessFile(file, RO_CHANNEL_MODE);
  108. return newChannel(in = raf.getChannel(), mode);
  109. } finally {
  110. ByteUtil.closeQuietly(in);
  111. ByteUtil.closeQuietly(raf);
  112. }
  113. }
  114. /**
  115. * Creates a new MemFileChannel containing the contents of the
  116. * given Path with the given mode (for mode details see
  117. * {@link RandomAccessFile#RandomAccessFile(File,String)}). Note,
  118. * modifications to the returned channel will <i>not</i> affect the original
  119. * File source.
  120. */
  121. public static MemFileChannel newChannel(Path file, OpenOption... opts)
  122. throws IOException
  123. {
  124. FileChannel in = null;
  125. try {
  126. String mode = RO_CHANNEL_MODE;
  127. if(opts != null) {
  128. for(OpenOption opt : opts) {
  129. if(opt == StandardOpenOption.WRITE) {
  130. mode = RW_CHANNEL_MODE;
  131. break;
  132. }
  133. }
  134. }
  135. return newChannel(in = FileChannel.open(file, StandardOpenOption.READ),
  136. mode);
  137. } finally {
  138. ByteUtil.closeQuietly(in);
  139. }
  140. }
  141. /**
  142. * Creates a new read/write MemFileChannel containing the contents of the
  143. * given Path. Note, modifications to the returned channel will <i>not</i>
  144. * affect the original File source.
  145. */
  146. public static MemFileChannel newChannel(Path file) throws IOException {
  147. return newChannel(file, DatabaseImpl.RW_CHANNEL_OPTS);
  148. }
  149. /**
  150. * Creates a new read/write MemFileChannel containing the contents of the
  151. * given InputStream.
  152. */
  153. public static MemFileChannel newChannel(InputStream in) throws IOException {
  154. return newChannel(in, RW_CHANNEL_MODE);
  155. }
  156. /**
  157. * Creates a new MemFileChannel containing the contents of the
  158. * given InputStream with the given mode (for mode details see
  159. * {@link RandomAccessFile#RandomAccessFile(File,String)}).
  160. */
  161. public static MemFileChannel newChannel(InputStream in, String mode)
  162. throws IOException
  163. {
  164. return newChannel(Channels.newChannel(in), mode);
  165. }
  166. /**
  167. * Creates a new read/write MemFileChannel containing the contents of the
  168. * given ReadableByteChannel.
  169. */
  170. public static MemFileChannel newChannel(ReadableByteChannel in)
  171. throws IOException
  172. {
  173. return newChannel(in, RW_CHANNEL_MODE);
  174. }
  175. /**
  176. * Creates a new MemFileChannel containing the contents of the
  177. * given ReadableByteChannel with the given mode (for mode details see
  178. * {@link RandomAccessFile#RandomAccessFile(File,String)}).
  179. */
  180. public static MemFileChannel newChannel(ReadableByteChannel in, String mode)
  181. throws IOException
  182. {
  183. MemFileChannel channel = new MemFileChannel();
  184. channel.transferFrom(in, 0L, Long.MAX_VALUE);
  185. if(!mode.contains("w")) {
  186. channel = new ReadOnlyChannel(channel);
  187. }
  188. return channel;
  189. }
  190. @Override
  191. public int read(ByteBuffer dst) throws IOException {
  192. int bytesRead = read(dst, _position);
  193. if(bytesRead > 0) {
  194. _position += bytesRead;
  195. }
  196. return bytesRead;
  197. }
  198. @Override
  199. public int read(ByteBuffer dst, long position) throws IOException {
  200. if(position >= _size) {
  201. return -1;
  202. }
  203. int numBytes = (int)Math.min(dst.remaining(), _size - position);
  204. int rem = numBytes;
  205. while(rem > 0) {
  206. byte[] chunk = _data[getChunkIndex(position)];
  207. int chunkOffset = getChunkOffset(position);
  208. int bytesRead = Math.min(rem, CHUNK_SIZE - chunkOffset);
  209. dst.put(chunk, chunkOffset, bytesRead);
  210. rem -= bytesRead;
  211. position += bytesRead;
  212. }
  213. return numBytes;
  214. }
  215. @Override
  216. public int write(ByteBuffer src) throws IOException {
  217. int bytesWritten = write(src, _position);
  218. _position += bytesWritten;
  219. return bytesWritten;
  220. }
  221. @Override
  222. public int write(ByteBuffer src, long position) throws IOException {
  223. int numBytes = src.remaining();
  224. long newSize = position + numBytes;
  225. ensureCapacity(newSize);
  226. int rem = numBytes;
  227. while(rem > 0) {
  228. byte[] chunk = _data[getChunkIndex(position)];
  229. int chunkOffset = getChunkOffset(position);
  230. int bytesWritten = Math.min(rem, CHUNK_SIZE - chunkOffset);
  231. src.get(chunk, chunkOffset, bytesWritten);
  232. rem -= bytesWritten;
  233. position += bytesWritten;
  234. }
  235. if(newSize > _size) {
  236. _size = newSize;
  237. }
  238. return numBytes;
  239. }
  240. @Override
  241. public long position() throws IOException {
  242. return _position;
  243. }
  244. @Override
  245. public FileChannel position(long newPosition) throws IOException {
  246. if(newPosition < 0L) {
  247. throw new IllegalArgumentException("negative position");
  248. }
  249. _position = newPosition;
  250. return this;
  251. }
  252. @Override
  253. public long size() throws IOException {
  254. return _size;
  255. }
  256. @Override
  257. public FileChannel truncate(long newSize) throws IOException {
  258. if(newSize < 0L) {
  259. throw new IllegalArgumentException("negative size");
  260. }
  261. if(newSize < _size) {
  262. // we'll optimize for memory over speed and aggressively free unused
  263. // chunks
  264. for(int i = getNumChunks(newSize); i < getNumChunks(_size); ++i) {
  265. _data[i] = null;
  266. }
  267. _size = newSize;
  268. }
  269. _position = Math.min(newSize, _position);
  270. return this;
  271. }
  272. @Override
  273. public void force(boolean metaData) throws IOException {
  274. // nothing to do
  275. }
  276. /**
  277. * Convenience method for writing the entire contents of this channel to the
  278. * given destination channel.
  279. * @see #transferTo(long,long,WritableByteChannel)
  280. */
  281. public long transferTo(WritableByteChannel dst)
  282. throws IOException
  283. {
  284. return transferTo(0L, _size, dst);
  285. }
  286. @Override
  287. public long transferTo(long position, long count, WritableByteChannel dst)
  288. throws IOException
  289. {
  290. if(position >= _size) {
  291. return 0L;
  292. }
  293. count = Math.min(count, _size - position);
  294. int chunkIndex = getChunkIndex(position);
  295. int chunkOffset = getChunkOffset(position);
  296. long numBytes = 0L;
  297. while(count > 0L) {
  298. int chunkBytes = (int)Math.min(count, CHUNK_SIZE - chunkOffset);
  299. ByteBuffer src = ByteBuffer.wrap(_data[chunkIndex], chunkOffset,
  300. chunkBytes);
  301. do {
  302. int bytesWritten = dst.write(src);
  303. if(bytesWritten == 0L) {
  304. // dst full
  305. return numBytes;
  306. }
  307. numBytes += bytesWritten;
  308. count -= bytesWritten;
  309. } while(src.hasRemaining());
  310. ++chunkIndex;
  311. chunkOffset = 0;
  312. }
  313. return numBytes;
  314. }
  315. /**
  316. * Convenience method for writing the entire contents of this channel to the
  317. * given destination stream.
  318. * @see #transferTo(long,long,WritableByteChannel)
  319. */
  320. public long transferTo(OutputStream dst)
  321. throws IOException
  322. {
  323. return transferTo(0L, _size, dst);
  324. }
  325. /**
  326. * Convenience method for writing the selected portion of this channel to
  327. * the given destination stream.
  328. * @see #transferTo(long,long,WritableByteChannel)
  329. */
  330. public long transferTo(long position, long count, OutputStream dst)
  331. throws IOException
  332. {
  333. return transferTo(position, count, Channels.newChannel(dst));
  334. }
  335. @Override
  336. public long transferFrom(ReadableByteChannel src,
  337. long position, long count)
  338. throws IOException
  339. {
  340. int chunkIndex = getChunkIndex(position);
  341. int chunkOffset = getChunkOffset(position);
  342. long numBytes = 0L;
  343. while(count > 0L) {
  344. ensureCapacity(position + numBytes + 1);
  345. int chunkBytes = (int)Math.min(count, CHUNK_SIZE - chunkOffset);
  346. ByteBuffer dst = ByteBuffer.wrap(_data[chunkIndex], chunkOffset,
  347. chunkBytes);
  348. do {
  349. int bytesRead = src.read(dst);
  350. if(bytesRead <= 0) {
  351. // src empty
  352. return numBytes;
  353. }
  354. numBytes += bytesRead;
  355. count -= bytesRead;
  356. _size = Math.max(_size, position + numBytes);
  357. } while(dst.hasRemaining());
  358. ++chunkIndex;
  359. chunkOffset = 0;
  360. }
  361. return numBytes;
  362. }
  363. @Override
  364. protected void implCloseChannel() throws IOException {
  365. // release data
  366. _data = EMPTY_DATA;
  367. _size = _position = 0L;
  368. }
  369. private void ensureCapacity(long newSize)
  370. {
  371. if(newSize <= _size) {
  372. // nothing to do
  373. return;
  374. }
  375. int newNumChunks = getNumChunks(newSize);
  376. int numChunks = getNumChunks(_size);
  377. if(newNumChunks > _data.length) {
  378. // need to extend chunk array (use powers of 2)
  379. int newDataLen = Math.max(_data.length, INIT_CHUNKS);
  380. while(newDataLen < newNumChunks) {
  381. newDataLen <<= 1;
  382. }
  383. byte[][] newData = new byte[newDataLen][];
  384. // copy existing chunks
  385. System.arraycopy(_data, 0, newData, 0, numChunks);
  386. _data = newData;
  387. }
  388. // allocate new chunks
  389. for(int i = numChunks; i < newNumChunks; ++i) {
  390. _data[i] = new byte[CHUNK_SIZE];
  391. }
  392. }
  393. private static int getChunkIndex(long pos) {
  394. return (int)(pos / CHUNK_SIZE);
  395. }
  396. private static int getChunkOffset(long pos) {
  397. return (int)(pos % CHUNK_SIZE);
  398. }
  399. private static int getNumChunks(long size) {
  400. return getChunkIndex(size + CHUNK_SIZE - 1);
  401. }
  402. @Override
  403. public long write(ByteBuffer[] srcs, int offset, int length)
  404. throws IOException
  405. {
  406. long numBytes = 0L;
  407. for(int i = offset; i < offset + length; ++i) {
  408. numBytes += write(srcs[i]);
  409. }
  410. return numBytes;
  411. }
  412. @Override
  413. public long read(ByteBuffer[] dsts, int offset, int length)
  414. throws IOException
  415. {
  416. long numBytes = 0L;
  417. for(int i = offset; i < offset + length; ++i) {
  418. if(_position >= _size) {
  419. return ((numBytes > 0L) ? numBytes : -1L);
  420. }
  421. numBytes += read(dsts[i]);
  422. }
  423. return numBytes;
  424. }
  425. @Override
  426. public MappedByteBuffer map(MapMode mode, long position, long size)
  427. throws IOException
  428. {
  429. throw new UnsupportedOperationException();
  430. }
  431. @Override
  432. public FileLock lock(long position, long size, boolean shared)
  433. throws IOException
  434. {
  435. throw new UnsupportedOperationException();
  436. }
  437. @Override
  438. public FileLock tryLock(long position, long size, boolean shared)
  439. throws IOException
  440. {
  441. throw new UnsupportedOperationException();
  442. }
  443. /**
  444. * Subclass of MemFileChannel which is read-only.
  445. */
  446. private static final class ReadOnlyChannel extends MemFileChannel
  447. {
  448. private ReadOnlyChannel(MemFileChannel channel)
  449. {
  450. super(channel._position, channel._size, channel._data);
  451. }
  452. @Override
  453. public int write(ByteBuffer src, long position) throws IOException {
  454. throw new NonWritableChannelException();
  455. }
  456. @Override
  457. public FileChannel truncate(long newSize) throws IOException {
  458. throw new NonWritableChannelException();
  459. }
  460. @Override
  461. public long transferFrom(ReadableByteChannel src,
  462. long position, long count)
  463. throws IOException
  464. {
  465. throw new NonWritableChannelException();
  466. }
  467. }
  468. }