001: /*
002: Copyright (c) 2005 Health Market Science, Inc.
003:
004: This library is free software; you can redistribute it and/or
005: modify it under the terms of the GNU Lesser General Public
006: License as published by the Free Software Foundation; either
007: version 2.1 of the License, or (at your option) any later version.
008:
009: This library is distributed in the hope that it will be useful,
010: but WITHOUT ANY WARRANTY; without even the implied warranty of
011: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: Lesser General Public License for more details.
013:
014: You should have received a copy of the GNU Lesser General Public
015: License along with this library; if not, write to the Free Software
016: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
017: USA
018:
019: You can contact Health Market Science at info@healthmarketscience.com
020: or at the following address:
021:
022: Health Market Science
023: 2700 Horizon Drive
024: Suite 200
025: King of Prussia, PA 19406
026: */
027:
028: package com.healthmarketscience.jackcess;
029:
030: import java.io.Flushable;
031: import java.io.IOException;
032: import java.nio.ByteBuffer;
033: import java.nio.ByteOrder;
034: import java.nio.channels.Channel;
035: import java.nio.channels.FileChannel;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039:
040: /**
041: * Reads and writes individual pages in a database file
042: * @author Tim McCune
043: */
044: public class PageChannel implements Channel, Flushable {
045:
046: private static final Log LOG = LogFactory.getLog(PageChannel.class);
047:
048: static final int INVALID_PAGE_NUMBER = -1;
049:
050: static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
051:
052: /** dummy buffer used when allocating new pages */
053: private static final ByteBuffer FORCE_BYTES = ByteBuffer
054: .allocate(1);
055:
056: /** Global usage map always lives on page 1 */
057: private static final int PAGE_GLOBAL_USAGE_MAP = 1;
058: /** Global usage map always lives at row 0 */
059: private static final int ROW_GLOBAL_USAGE_MAP = 0;
060:
061: /** Channel containing the database */
062: private final FileChannel _channel;
063: /** Format of the database in the channel */
064: private final JetFormat _format;
065: /** whether or not to force all writes to disk immediately */
066: private final boolean _autoSync;
067: /** Tracks free pages in the database. */
068: private UsageMap _globalUsageMap;
069:
070: /**
071: * @param channel Channel containing the database
072: * @param format Format of the database in the channel
073: */
074: public PageChannel(FileChannel channel, JetFormat format,
075: boolean autoSync) throws IOException {
076: _channel = channel;
077: _format = format;
078: _autoSync = autoSync;
079: }
080:
081: /**
082: * Does second-stage initialization, must be called after construction.
083: */
084: public void initialize(Database database) throws IOException {
085: // note the global usage map is a special map where any page outside of
086: // the current range is assumed to be "on"
087: _globalUsageMap = UsageMap.read(database,
088: PAGE_GLOBAL_USAGE_MAP, ROW_GLOBAL_USAGE_MAP, true);
089: }
090:
091: /**
092: * Only used by unit tests
093: */
094: PageChannel(boolean testing) {
095: if (!testing) {
096: throw new IllegalArgumentException();
097: }
098: _channel = null;
099: _format = JetFormat.VERSION_4;
100: _autoSync = false;
101: }
102:
103: public JetFormat getFormat() {
104: return _format;
105: }
106:
107: /**
108: * @param buffer Buffer to read the page into
109: * @param pageNumber Number of the page to read in (starting at 0)
110: */
111: public void readPage(ByteBuffer buffer, int pageNumber)
112: throws IOException {
113: if (pageNumber == INVALID_PAGE_NUMBER) {
114: throw new IllegalStateException("invalid page number");
115: }
116: if (LOG.isDebugEnabled()) {
117: LOG.debug("Reading in page "
118: + Integer.toHexString(pageNumber));
119: }
120: buffer.clear();
121: int bytesRead = _channel.read(buffer, (long) pageNumber
122: * (long) getFormat().PAGE_SIZE);
123: buffer.flip();
124: if (bytesRead != getFormat().PAGE_SIZE) {
125: throw new IOException("Failed attempting to read "
126: + getFormat().PAGE_SIZE + " bytes from page "
127: + pageNumber + ", only read " + bytesRead);
128: }
129: }
130:
131: /**
132: * Write a page to disk
133: * @param page Page to write
134: * @param pageNumber Page number to write the page to
135: */
136: public void writePage(ByteBuffer page, int pageNumber)
137: throws IOException {
138: writePage(page, pageNumber, 0);
139: }
140:
141: /**
142: * Write a page (or part of a page) to disk
143: * @param page Page to write
144: * @param pageNumber Page number to write the page to
145: * @param pageOffset offset within the page at which to start writing the
146: * page data
147: */
148: public void writePage(ByteBuffer page, int pageNumber,
149: int pageOffset) throws IOException {
150: page.rewind();
151: page.position(pageOffset);
152: _channel
153: .write(
154: page,
155: (((long) pageNumber * (long) getFormat().PAGE_SIZE) + pageOffset));
156: if (_autoSync) {
157: flush();
158: }
159: }
160:
161: /**
162: * Write a page to disk as a new page, appending it to the database
163: * @param page Page to write
164: * @return Page number at which the page was written
165: */
166: public int writeNewPage(ByteBuffer page) throws IOException {
167: long size = _channel.size();
168: if (size >= getFormat().MAX_DATABASE_SIZE) {
169: throw new IOException("Database is at maximum size "
170: + getFormat().MAX_DATABASE_SIZE);
171: }
172: if ((size % getFormat().PAGE_SIZE) != 0L) {
173: throw new IOException("Database corrupted, file size "
174: + size + " is not multiple of page size "
175: + getFormat().PAGE_SIZE);
176: }
177:
178: page.rewind();
179: // push the buffer to the end of the page, so that a full page's worth of
180: // data is written regardless of the incoming buffer size (we use a tiny
181: // buffer in allocateNewPage)
182: long offset = size + (getFormat().PAGE_SIZE - page.remaining());
183: _channel.write(page, offset);
184: int pageNumber = (int) (size / getFormat().PAGE_SIZE);
185: _globalUsageMap.removePageNumber(pageNumber); //force is done here
186: return pageNumber;
187: }
188:
189: /**
190: * Allocates a new page in the database. Data in the page is undefined
191: * until it is written in a call to {@link #writePage(ByteBuffer,int)}.
192: */
193: public int allocateNewPage() throws IOException {
194: // this will force the file to be extended with mostly undefined bytes
195: return writeNewPage(FORCE_BYTES);
196: }
197:
198: /**
199: * @return A newly-allocated buffer that can be passed to readPage
200: */
201: public ByteBuffer createPageBuffer() {
202: return createBuffer(getFormat().PAGE_SIZE);
203: }
204:
205: /**
206: * @return A newly-allocated buffer of the given size and LITTLE_ENDIAN byte
207: * order
208: */
209: public ByteBuffer createBuffer(int size) {
210: return createBuffer(size, ByteOrder.LITTLE_ENDIAN);
211: }
212:
213: /**
214: * @return A newly-allocated buffer of the given size and byte order
215: */
216: public ByteBuffer createBuffer(int size, ByteOrder order) {
217: ByteBuffer rtn = ByteBuffer.allocate(size);
218: rtn.order(order);
219: return rtn;
220: }
221:
222: public void flush() throws IOException {
223: _channel.force(true);
224: }
225:
226: public void close() throws IOException {
227: flush();
228: _channel.close();
229: }
230:
231: public boolean isOpen() {
232: return _channel.isOpen();
233: }
234:
235: /**
236: * @return a duplicate of the current buffer narrowed to the given position
237: * and limit. mark will be set at the current position.
238: */
239: public static ByteBuffer narrowBuffer(ByteBuffer buffer,
240: int position, int limit) {
241: return (ByteBuffer) buffer.duplicate().order(buffer.order())
242: .clear().limit(limit).position(position).mark();
243: }
244:
245: }
|