001: /* Copyright (C) 2004 - 2007 db4objects Inc. http://www.db4o.com
002:
003: This file is part of the db4o open source object database.
004:
005: db4o is free software; you can redistribute it and/or modify it under
006: the terms of version 2 of the GNU General Public License as published
007: by the Free Software Foundation and as clarified by db4objects' GPL
008: interpretation policy, available at
009: http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
010: Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
011: Suite 350, San Mateo, CA 94403, USA.
012:
013: db4o is distributed in the hope that it will be useful, but WITHOUT ANY
014: WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: for more details.
017:
018: You should have received a copy of the GNU General Public License along
019: with this program; if not, write to the Free Software Foundation, Inc.,
020: 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
021: package com.db4o.io;
022:
023: import com.db4o.*;
024: import com.db4o.internal.fileheader.*;
025:
026: /**
027: * CachedIoAdapter is an IOAdapter for random access files, which caches data
028: * for IO access. Its functionality is similar to OS cache.<br>
029: * Example:<br>
030: * <code>delegateAdapter = new RandomAccessFileAdapter();</code><br>
031: * <code>Db4o.configure().io(new CachedIoAdapter(delegateAdapter));</code><br>
032: */
033: public class CachedIoAdapter extends IoAdapter {
034:
035: private Page _head;
036:
037: private Page _tail;
038:
039: private long _position;
040:
041: private int _pageSize;
042:
043: private int _pageCount;
044:
045: private long _fileLength;
046:
047: private long _filePointer;
048:
049: private IoAdapter _io;
050:
051: private boolean _readOnly;
052:
053: private static int DEFAULT_PAGE_SIZE = 1024;
054:
055: private static int DEFAULT_PAGE_COUNT = 64;
056:
057: // private Hashtable4 _posPageMap = new Hashtable4(PAGE_COUNT);
058:
059: /**
060: * Creates an instance of CachedIoAdapter with the default page size and
061: * page count.
062: *
063: * @param ioAdapter
064: * delegate IO adapter (RandomAccessFileAdapter by default)
065: */
066: public CachedIoAdapter(IoAdapter ioAdapter) {
067: this (ioAdapter, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
068: }
069:
070: /**
071: * Creates an instance of CachedIoAdapter with a custom page size and page
072: * count.<br>
073: *
074: * @param ioAdapter
075: * delegate IO adapter (RandomAccessFileAdapter by default)
076: * @param pageSize
077: * cache page size
078: * @param pageCount
079: * allocated amount of pages
080: */
081: public CachedIoAdapter(IoAdapter ioAdapter, int pageSize,
082: int pageCount) {
083: _io = ioAdapter;
084: _pageSize = pageSize;
085: _pageCount = pageCount;
086: }
087:
088: /**
089: * Creates an instance of CachedIoAdapter with extended parameters.<br>
090: *
091: * @param path
092: * database file path
093: * @param lockFile
094: * determines if the file should be locked
095: * @param initialLength
096: * initial file length, new writes will start from this point
097: * @param readOnly
098: * if the file should be used in read-onlyt mode.
099: * @param io
100: * delegate IO adapter (RandomAccessFileAdapter by default)
101: * @param pageSize
102: * cache page size
103: * @param pageCount
104: * allocated amount of pages
105: */
106: public CachedIoAdapter(String path, boolean lockFile,
107: long initialLength, boolean readOnly, IoAdapter io,
108: int pageSize, int pageCount) throws Db4oIOException {
109: _readOnly = readOnly;
110: _pageSize = pageSize;
111: _pageCount = pageCount;
112:
113: initCache();
114: initIOAdaptor(path, lockFile, initialLength, readOnly, io);
115:
116: _position = initialLength;
117: _filePointer = initialLength;
118: _fileLength = _io.getLength();
119: }
120:
121: /**
122: * Creates and returns a new CachedIoAdapter <br>
123: *
124: * @param path
125: * database file path
126: * @param lockFile
127: * determines if the file should be locked
128: * @param initialLength
129: * initial file length, new writes will start from this point
130: */
131: public IoAdapter open(String path, boolean lockFile,
132: long initialLength, boolean readOnly)
133: throws Db4oIOException {
134: return new CachedIoAdapter(path, lockFile, initialLength,
135: readOnly, _io, _pageSize, _pageCount);
136: }
137:
138: /**
139: * Deletes the database file
140: *
141: * @param path
142: * file path
143: */
144: public void delete(String path) {
145: _io.delete(path);
146: }
147:
148: /**
149: * Checks if the file exists
150: *
151: * @param path
152: * file path
153: */
154: public boolean exists(String path) {
155: return _io.exists(path);
156: }
157:
158: private void initIOAdaptor(String path, boolean lockFile,
159: long initialLength, boolean readOnly, IoAdapter io)
160: throws Db4oIOException {
161: _io = io.open(path, lockFile, initialLength, readOnly);
162: }
163:
164: private void initCache() {
165: _head = new Page(_pageSize);
166: _head._prev = null;
167: Page page = _head;
168: Page next = _head;
169: for (int i = 0; i < _pageCount - 1; ++i) {
170: next = new Page(_pageSize);
171: page._next = next;
172: next._prev = page;
173: page = next;
174: }
175: _tail = next;
176: }
177:
178: /**
179: * Reads the file into the buffer using pages from cache. If the next page
180: * is not cached it will be read from the file.
181: *
182: * @param buffer
183: * destination buffer
184: * @param length
185: * how many bytes to read
186: */
187: public int read(byte[] buffer, int length) throws Db4oIOException {
188: long startAddress = _position;
189: Page page;
190: int readBytes;
191: int bytesToRead = length;
192: int totalRead = 0;
193: while (bytesToRead > 0) {
194: page = getPage(startAddress, true);
195: readBytes = page.read(buffer, totalRead, startAddress,
196: bytesToRead);
197: movePageToHead(page);
198: if (readBytes <= 0) {
199: break;
200: }
201: bytesToRead -= readBytes;
202: startAddress += readBytes;
203: totalRead += readBytes;
204: }
205: _position = startAddress;
206: return totalRead == 0 ? -1 : totalRead;
207: }
208:
209: /**
210: * Writes the buffer to cache using pages
211: *
212: * @param buffer
213: * source buffer
214: * @param length
215: * how many bytes to write
216: */
217: public void write(byte[] buffer, int length) throws Db4oIOException {
218: validateReadOnly();
219: long startAddress = _position;
220: Page page = null;
221: int writtenBytes;
222: int bytesToWrite = length;
223: int bufferOffset = 0;
224: while (bytesToWrite > 0) {
225: // page doesn't need to loadFromDisk if the whole page is dirty
226: boolean loadFromDisk = (bytesToWrite < _pageSize)
227: || (startAddress % _pageSize != 0);
228: page = getPage(startAddress, loadFromDisk);
229: page.ensureEndAddress(getLength());
230: writtenBytes = page.write(buffer, bufferOffset,
231: startAddress, bytesToWrite);
232: if (containsHeaderBlock(page)) {
233: flushPage(page);
234: }
235: movePageToHead(page);
236: bytesToWrite -= writtenBytes;
237: startAddress += writtenBytes;
238: bufferOffset += writtenBytes;
239: }
240: long endAddress = startAddress;
241: _position = endAddress;
242: _fileLength = Math.max(endAddress, _fileLength);
243: }
244:
245: private void validateReadOnly() {
246: if (_readOnly) {
247: throw new Db4oIOException();
248: }
249: }
250:
251: /**
252: * Flushes cache to a physical storage
253: */
254: public void sync() throws Db4oIOException {
255: validateReadOnly();
256: flushAllPages();
257: _io.sync();
258: }
259:
260: /**
261: * Returns the file length
262: */
263: public long getLength() throws Db4oIOException {
264: return _fileLength;
265: }
266:
267: /**
268: * Flushes and closes the file
269: */
270: public void close() throws Db4oIOException {
271: flushAllPages();
272: _io.close();
273: }
274:
275: public IoAdapter delegatedIoAdapter() {
276: return _io.delegatedIoAdapter();
277: }
278:
279: private Page getPage(long startAddress, boolean loadFromDisk)
280: throws Db4oIOException {
281: Page page = getPageFromCache(startAddress);
282: if (page != null) {
283: if (containsHeaderBlock(page)) {
284: getPageFromDisk(page, startAddress);
285: }
286: page.ensureEndAddress(_fileLength);
287: return page;
288: }
289: // in case that page is not found in the cache
290: page = getFreePageFromCache();
291: if (loadFromDisk) {
292: getPageFromDisk(page, startAddress);
293: } else {
294: resetPageAddress(page, startAddress);
295: }
296:
297: return page;
298: }
299:
300: private boolean containsHeaderBlock(Page page) {
301: return page.startAddress() <= FileHeader1.LENGTH;
302: }
303:
304: private void resetPageAddress(Page page, long startAddress) {
305: page.startAddress(startAddress);
306: page.endAddress(startAddress + _pageSize);
307: }
308:
309: private Page getFreePageFromCache() throws Db4oIOException {
310: if (!_tail.isFree()) {
311: flushPage(_tail);
312: // _posPageMap.remove(new Long(tail.startPosition / PAGE_SIZE));
313: }
314: return _tail;
315: }
316:
317: private Page getPageFromCache(long pos) throws Db4oIOException {
318: Page page = _head;
319: while (page != null) {
320: if (page.contains(pos)) {
321: return page;
322: }
323: page = page._next;
324: }
325: return null;
326: // Page page = (Page) _posPageMap.get(new Long(pos/PAGE_SIZE));
327: // return page;
328: }
329:
330: private void flushAllPages() throws Db4oIOException {
331: Page node = _head;
332: while (node != null) {
333: flushPage(node);
334: node = node._next;
335: }
336: }
337:
338: private void flushPage(Page page) throws Db4oIOException {
339: if (!page._dirty) {
340: return;
341: }
342: ioSeek(page.startAddress());
343: writePageToDisk(page);
344: return;
345: }
346:
347: private void getPageFromDisk(Page page, long pos)
348: throws Db4oIOException {
349: long startAddress = pos - pos % _pageSize;
350: page.startAddress(startAddress);
351: ioSeek(page._startAddress);
352: int count = ioRead(page);
353: if (count > 0) {
354: page.endAddress(startAddress + count);
355: } else {
356: page.endAddress(startAddress);
357: }
358: // _posPageMap.put(new Long(page.startPosition / PAGE_SIZE), page);
359: }
360:
361: private int ioRead(Page page) throws Db4oIOException {
362: int count = _io.read(page._buffer);
363: if (count > 0) {
364: _filePointer = page._startAddress + count;
365: }
366: return count;
367: }
368:
369: private void movePageToHead(Page page) {
370: if (page == _head) {
371: return;
372: }
373: if (page == _tail) {
374: Page tempTail = _tail._prev;
375: tempTail._next = null;
376: _tail._next = _head;
377: _tail._prev = null;
378: _head._prev = page;
379: _head = _tail;
380: _tail = tempTail;
381: } else {
382: page._prev._next = page._next;
383: page._next._prev = page._prev;
384: page._next = _head;
385: _head._prev = page;
386: page._prev = null;
387: _head = page;
388: }
389: }
390:
391: private void writePageToDisk(Page page) throws Db4oIOException {
392: _io.write(page._buffer, page.size());
393: _filePointer = page.endAddress();
394: page._dirty = false;
395: }
396:
397: /**
398: * Moves the pointer to the specified file position
399: *
400: * @param pos
401: * position within the file
402: */
403: public void seek(long pos) throws Db4oIOException {
404: _position = pos;
405: }
406:
407: private void ioSeek(long pos) throws Db4oIOException {
408: if (_filePointer != pos) {
409: _io.seek(pos);
410: _filePointer = pos;
411: }
412: }
413:
414: private static class Page {
415:
416: byte[] _buffer;
417:
418: long _startAddress = -1;
419:
420: long _endAddress;
421:
422: int _bufferSize;
423:
424: boolean _dirty;
425:
426: Page _prev;
427:
428: Page _next;
429:
430: private byte[] zeroBytes;
431:
432: public Page(int size) {
433: _bufferSize = size;
434: _buffer = new byte[_bufferSize];
435: }
436:
437: /*
438: * This method must be invoked before page.write/read, because seek and
439: * write may write ahead the end of file.
440: */
441: void ensureEndAddress(long fileLength) {
442: long bufferEndAddress = _startAddress + _bufferSize;
443: if (_endAddress < bufferEndAddress
444: && fileLength > _endAddress) {
445: long newEndAddress = Math.min(fileLength,
446: bufferEndAddress);
447: if (zeroBytes == null) {
448: zeroBytes = new byte[_bufferSize];
449: }
450: System.arraycopy(zeroBytes, 0, _buffer,
451: (int) (_endAddress - _startAddress),
452: (int) (newEndAddress - _endAddress));
453: _endAddress = newEndAddress;
454: }
455: }
456:
457: long endAddress() {
458: return _endAddress;
459: }
460:
461: void startAddress(long address) {
462: _startAddress = address;
463: }
464:
465: long startAddress() {
466: return _startAddress;
467: }
468:
469: void endAddress(long address) {
470: _endAddress = address;
471: }
472:
473: int size() {
474: return (int) (_endAddress - _startAddress);
475: }
476:
477: int read(byte[] out, int outOffset, long startAddress,
478: int length) {
479: int bufferOffset = (int) (startAddress - _startAddress);
480: int pageAvailbeDataSize = (int) (_endAddress - startAddress);
481: int readBytes = Math.min(pageAvailbeDataSize, length);
482: if (readBytes <= 0) { // meaning reach EOF
483: return -1;
484: }
485: System.arraycopy(_buffer, bufferOffset, out, outOffset,
486: readBytes);
487: return readBytes;
488: }
489:
490: int write(byte[] data, int dataOffset, long startAddress,
491: int length) {
492: int bufferOffset = (int) (startAddress - _startAddress);
493: int pageAvailabeBufferSize = _bufferSize - bufferOffset;
494: int writtenBytes = Math.min(pageAvailabeBufferSize, length);
495: System.arraycopy(data, dataOffset, _buffer, bufferOffset,
496: writtenBytes);
497: long endAddress = startAddress + writtenBytes;
498: if (endAddress > _endAddress) {
499: _endAddress = endAddress;
500: }
501: _dirty = true;
502: return writtenBytes;
503: }
504:
505: boolean contains(long address) {
506: return (_startAddress != -1 && address >= _startAddress && address < _startAddress
507: + _bufferSize);
508: }
509:
510: boolean isFree() {
511: return _startAddress == -1;
512: }
513: }
514:
515: }
|