/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package com.db4o.io; import com.db4o.ext.*; import com.db4o.foundation.*; import com.db4o.internal.caching.*; /** * @exclude */ class CachingBin extends BinDecorator { private final int _pageSize; private final Cache4<Long, Page> _cache; private final ObjectPool<Page> _pagePool; private long _fileLength; private Procedure4<Page> _onDiscardPage = new Procedure4<Page>() { public void apply(Page discardedPage) { flushPage(discardedPage); _pagePool.returnObject(discardedPage); } }; public CachingBin(Bin bin, Cache4 cache, int pageCount, int pageSize) throws Db4oIOException { super(bin); _pageSize = pageSize; _pagePool = new SimpleObjectPool<Page>(newPagePool(pageCount)); _cache = cache; _fileLength = _bin.length(); } private Page[] newPagePool(int pageCount) { final Page[] pages = new Page[pageCount]; for (int i=0; i<pages.length; ++i) { pages[i] = new Page(_pageSize); } return pages; } /** * Reads the file into the buffer using pages from cache. If the next page * is not cached it will be read from the file. * * @param pos * start position to read * @param buffer * destination buffer * @param length * how many bytes to read */ public int read(final long pos, byte[] buffer, int length) throws Db4oIOException { return readInternal(pos, buffer, length, false); } private int readInternal(final long pos, byte[] buffer, int length, boolean syncRead) { long startAddress = pos; int bytesToRead = length; int totalRead = 0; while (bytesToRead > 0) { final Page page = syncRead ? syncReadPage(startAddress) : getPage(startAddress, _producerFromDisk); final int readBytes = page.read(buffer, totalRead, startAddress, bytesToRead); if (readBytes <= 0) { break; } bytesToRead -= readBytes; startAddress += readBytes; totalRead += readBytes; } return totalRead == 0 ? -1 : totalRead; } /** * Writes the buffer to cache using pages * * @param pos * start position to write * @param buffer * source buffer * @param length * how many bytes to write */ public void write(final long pos, byte[] buffer, int length) throws Db4oIOException { long startAddress = pos; int bytesToWrite = length; int bufferOffset = 0; while (bytesToWrite > 0) { // page doesn't need to loadFromDisk if the whole page is dirty boolean loadFromDisk = (bytesToWrite < _pageSize) || (startAddress % _pageSize != 0); final Page page = getPage(startAddress, loadFromDisk); final int writtenBytes = page.write(buffer, bufferOffset, startAddress, bytesToWrite); bytesToWrite -= writtenBytes; startAddress += writtenBytes; bufferOffset += writtenBytes; } long endAddress = startAddress; _fileLength = Math.max(endAddress, _fileLength); } /** * Flushes cache to a physical storage */ public void sync() throws Db4oIOException { flushAllPages(); super.sync(); } @Override public void sync(final Runnable runnable) { flushAllPages(); super.sync(new Runnable() { public void run() { runnable.run(); flushAllPages(); } }); } @Override public int syncRead(long position, byte[] bytes, int bytesToRead) { return readInternal(position, bytes, bytesToRead, true); } /** * Returns the file length */ public long length() throws Db4oIOException { return _fileLength; } final Function4<Long, Page> _producerFromDisk = new Function4<Long, Page>() { public Page apply(Long pageAddress) { // in case that page is not found in the cache final Page newPage = _pagePool.borrowObject(); loadPage(newPage, pageAddress.longValue()); return newPage; } }; final Function4<Long, Page> _producerFromPool = new Function4<Long, Page>() { public Page apply(Long pageAddress) { // in case that page is not found in the cache final Page newPage = _pagePool.borrowObject(); resetPageAddress(newPage, pageAddress.longValue()); return newPage; } }; private Page getPage(final long startAddress, final boolean loadFromDisk) throws Db4oIOException { final Function4<Long, Page> producer = loadFromDisk ? _producerFromDisk : _producerFromPool; return getPage(startAddress, producer); } private Page getPage(final long startAddress, final Function4<Long, Page> producer) { Page page = _cache.produce(pageAddressFor(startAddress), producer, _onDiscardPage); page.ensureEndAddress(_fileLength); return page; } private Page syncReadPage(final long startAddress) { Page page = new Page(_pageSize); loadPage(page, startAddress); page.ensureEndAddress(_fileLength); return page; } private Long pageAddressFor(long startAddress) { return (startAddress / _pageSize) * _pageSize; } private void resetPageAddress(Page page, long startAddress) { page._startAddress = startAddress; page._endAddress = startAddress + _pageSize; } protected void flushAllPages() throws Db4oIOException { for (Page p : _cache) { flushPage(p); } } private void flushPage(Page page) throws Db4oIOException { if (!page._dirty) { return; } writePageToDisk(page); } private void loadPage(Page page, long pos) throws Db4oIOException { long startAddress = pos - pos % _pageSize; page._startAddress = startAddress; int count = _bin.read(page._startAddress, page._buffer, page._bufferSize); if (count > 0) { page._endAddress = startAddress + count; } else { page._endAddress = startAddress; } } private void writePageToDisk(Page page) throws Db4oIOException { super.write(page._startAddress, page._buffer, page.size()); page._dirty = false; } private static class Page { public final byte[] _buffer; public long _startAddress = -1; public long _endAddress; public final int _bufferSize; public boolean _dirty; private byte[] zeroBytes; public Page(int size) { _bufferSize = size; _buffer = new byte[_bufferSize]; } /* * This method must be invoked before page.write/read, because seek and * write may write ahead the end of file. */ void ensureEndAddress(long fileLength) { long bufferEndAddress = _startAddress + _bufferSize; if (_endAddress < bufferEndAddress && fileLength > _endAddress) { long newEndAddress = Math.min(fileLength, bufferEndAddress); if (zeroBytes == null) { zeroBytes = new byte[_bufferSize]; } System.arraycopy(zeroBytes, 0, _buffer, (int) (_endAddress - _startAddress), (int) (newEndAddress - _endAddress)); _endAddress = newEndAddress; } } int size() { return (int) (_endAddress - _startAddress); } int read(byte[] out, int outOffset, long startAddress, int length) { int bufferOffset = (int) (startAddress - _startAddress); int pageAvailbeDataSize = (int) (_endAddress - startAddress); int readBytes = Math.min(pageAvailbeDataSize, length); if (readBytes <= 0) { // meaning reach EOF return -1; } System.arraycopy(_buffer, bufferOffset, out, outOffset, readBytes); return readBytes; } int write(byte[] data, int dataOffset, long startAddress, int length) { int bufferOffset = (int) (startAddress - _startAddress); int pageAvailabeBufferSize = _bufferSize - bufferOffset; int writtenBytes = Math.min(pageAvailabeBufferSize, length); System.arraycopy(data, dataOffset, _buffer, bufferOffset, writtenBytes); long endAddress = startAddress + writtenBytes; if (endAddress > _endAddress) { _endAddress = endAddress; } _dirty = true; return writtenBytes; } } }