001: /**
002: * JDBM LICENSE v1.00
003: *
004: * Redistribution and use of this software and associated documentation
005: * ("Software"), with or without modification, are permitted provided
006: * that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain copyright
009: * statements and notices. Redistributions must also contain a
010: * copy of this document.
011: *
012: * 2. Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and the
014: * following disclaimer in the documentation and/or other
015: * materials provided with the distribution.
016: *
017: * 3. The name "JDBM" must not be used to endorse or promote
018: * products derived from this Software without prior written
019: * permission of Cees de Groot. For written permission,
020: * please contact cg@cdegroot.com.
021: *
022: * 4. Products derived from this Software may not be called "JDBM"
023: * nor may "JDBM" appear in their names without prior written
024: * permission of Cees de Groot.
025: *
026: * 5. Due credit should be given to the JDBM Project
027: * (http://jdbm.sourceforge.net/).
028: *
029: * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030: * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
033: * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040: * OF THE POSSIBILITY OF SUCH DAMAGE.
041: *
042: * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043: * Contributions are Copyright (C) 2000 by their associated contributors.
044: *
045: * $Id: PhysicalRowIdManager.java,v 1.3 2003/03/21 03:00:09 boisvert Exp $
046: */package jdbm.recman;
047:
048: import java.io.IOException;
049:
050: /**
051: * This class manages physical row ids, and their data.
052: */
053: final class PhysicalRowIdManager {
054:
055: // The file we're talking to and the associated page manager.
056: private RecordFile file;
057: private PageManager pageman;
058: private FreePhysicalRowIdPageManager freeman;
059:
060: /**
061: * Creates a new rowid manager using the indicated record file.
062: * and page manager.
063: */
064: PhysicalRowIdManager(RecordFile file, PageManager pageManager)
065: throws IOException {
066: this .file = file;
067: this .pageman = pageManager;
068: this .freeman = new FreePhysicalRowIdPageManager(file, pageman);
069: }
070:
071: /**
072: * Inserts a new record. Returns the new physical rowid.
073: */
074: Location insert(byte[] data, int start, int length)
075: throws IOException {
076: Location retval = alloc(length);
077: write(retval, data, start, length);
078: return retval;
079: }
080:
081: /**
082: * Updates an existing record. Returns the possibly changed
083: * physical rowid.
084: */
085: Location update(Location rowid, byte[] data, int start, int length)
086: throws IOException {
087: // fetch the record header
088: BlockIo block = file.get(rowid.getBlock());
089: RecordHeader head = new RecordHeader(block, rowid.getOffset());
090: if (length > head.getAvailableSize()) {
091: // not enough space - we need to copy to a new rowid.
092: file.release(block);
093: free(rowid);
094: rowid = alloc(length);
095: } else {
096: file.release(block);
097: }
098:
099: // 'nuff space, write it in and return the rowid.
100: write(rowid, data, start, length);
101: return rowid;
102: }
103:
104: /**
105: * Deletes a record.
106: */
107: void delete(Location rowid) throws IOException {
108: free(rowid);
109: }
110:
111: /**
112: * Retrieves a record.
113: */
114: byte[] fetch(Location rowid) throws IOException {
115: // fetch the record header
116: PageCursor curs = new PageCursor(pageman, rowid.getBlock());
117: BlockIo block = file.get(curs.getCurrent());
118: RecordHeader head = new RecordHeader(block, rowid.getOffset());
119:
120: // allocate a return buffer
121: byte[] retval = new byte[head.getCurrentSize()];
122: if (retval.length == 0) {
123: file.release(curs.getCurrent(), false);
124: return retval;
125: }
126:
127: // copy bytes in
128: int offsetInBuffer = 0;
129: int leftToRead = retval.length;
130: short dataOffset = (short) (rowid.getOffset() + RecordHeader.SIZE);
131: while (leftToRead > 0) {
132: // copy current page's data to return buffer
133: int toCopy = RecordFile.BLOCK_SIZE - dataOffset;
134: if (leftToRead < toCopy) {
135: toCopy = leftToRead;
136: }
137: System.arraycopy(block.getData(), dataOffset, retval,
138: offsetInBuffer, toCopy);
139:
140: // Go to the next block
141: leftToRead -= toCopy;
142: offsetInBuffer += toCopy;
143:
144: file.release(block);
145:
146: if (leftToRead > 0) {
147: block = file.get(curs.next());
148: dataOffset = DataPage.O_DATA;
149: }
150:
151: }
152:
153: return retval;
154: }
155:
156: /**
157: * Allocate a new rowid with the indicated size.
158: */
159: private Location alloc(int size) throws IOException {
160: Location retval = freeman.get(size);
161: if (retval == null) {
162: retval = allocNew(size, pageman.getLast(Magic.USED_PAGE));
163: }
164: return retval;
165: }
166:
167: /**
168: * Allocates a new rowid. The second parameter is there to
169: * allow for a recursive call - it indicates where the search
170: * should start.
171: */
172: private Location allocNew(int size, long start) throws IOException {
173: BlockIo curBlock;
174: DataPage curPage;
175: if (start == 0) {
176: // we need to create a new page.
177: start = pageman.allocate(Magic.USED_PAGE);
178: curBlock = file.get(start);
179: curPage = DataPage.getDataPageView(curBlock);
180: curPage.setFirst(DataPage.O_DATA);
181: RecordHeader hdr = new RecordHeader(curBlock,
182: DataPage.O_DATA);
183: hdr.setAvailableSize(0);
184: hdr.setCurrentSize(0);
185: } else {
186: curBlock = file.get(start);
187: curPage = DataPage.getDataPageView(curBlock);
188: }
189:
190: // follow the rowids on this page to get to the last one. We don't
191: // fall off, because this is the last page, remember?
192: short pos = curPage.getFirst();
193: if (pos == 0) {
194: // page is exactly filled by the last block of a record
195: file.release(curBlock);
196: return allocNew(size, 0);
197: }
198:
199: RecordHeader hdr = new RecordHeader(curBlock, pos);
200: while (hdr.getAvailableSize() != 0
201: && pos < RecordFile.BLOCK_SIZE) {
202: pos += hdr.getAvailableSize() + RecordHeader.SIZE;
203: if (pos == RecordFile.BLOCK_SIZE) {
204: // Again, a filled page.
205: file.release(curBlock);
206: return allocNew(size, 0);
207: }
208:
209: hdr = new RecordHeader(curBlock, pos);
210: }
211:
212: if (pos == RecordHeader.SIZE) {
213: // the last record exactly filled the page. Restart forcing
214: // a new page.
215: file.release(curBlock);
216: }
217:
218: // we have the position, now tack on extra pages until we've got
219: // enough space.
220: Location retval = new Location(start, pos);
221: int freeHere = RecordFile.BLOCK_SIZE - pos - RecordHeader.SIZE;
222: if (freeHere < size) {
223: // check whether the last page would have only a small bit left.
224: // if yes, increase the allocation. A small bit is a record
225: // header plus 16 bytes.
226: int lastSize = (size - freeHere) % DataPage.DATA_PER_PAGE;
227: if ((DataPage.DATA_PER_PAGE - lastSize) < (RecordHeader.SIZE + 16)) {
228: size += (DataPage.DATA_PER_PAGE - lastSize);
229: }
230:
231: // write out the header now so we don't have to come back.
232: hdr.setAvailableSize(size);
233: file.release(start, true);
234:
235: int neededLeft = size - freeHere;
236: // Refactor these two blocks!
237: while (neededLeft >= DataPage.DATA_PER_PAGE) {
238: start = pageman.allocate(Magic.USED_PAGE);
239: curBlock = file.get(start);
240: curPage = DataPage.getDataPageView(curBlock);
241: curPage.setFirst((short) 0); // no rowids, just data
242: file.release(start, true);
243: neededLeft -= DataPage.DATA_PER_PAGE;
244: }
245: if (neededLeft > 0) {
246: // done with whole chunks, allocate last fragment.
247: start = pageman.allocate(Magic.USED_PAGE);
248: curBlock = file.get(start);
249: curPage = DataPage.getDataPageView(curBlock);
250: curPage
251: .setFirst((short) (DataPage.O_DATA + neededLeft));
252: file.release(start, true);
253: }
254: } else {
255: // just update the current page. If there's less than 16 bytes
256: // left, we increase the allocation (16 bytes is an arbitrary
257: // number).
258: if (freeHere - size <= (16 + RecordHeader.SIZE)) {
259: size = freeHere;
260: }
261: hdr.setAvailableSize(size);
262: file.release(start, true);
263: }
264: return retval;
265:
266: }
267:
268: private void free(Location id) throws IOException {
269: // get the rowid, and write a zero current size into it.
270: BlockIo curBlock = file.get(id.getBlock());
271: DataPage curPage = DataPage.getDataPageView(curBlock);
272: RecordHeader hdr = new RecordHeader(curBlock, id.getOffset());
273: hdr.setCurrentSize(0);
274: file.release(id.getBlock(), true);
275:
276: // write the rowid to the free list
277: freeman.put(id, hdr.getAvailableSize());
278: }
279:
280: /**
281: * Writes out data to a rowid. Assumes that any resizing has been
282: * done.
283: */
284: private void write(Location rowid, byte[] data, int start,
285: int length) throws IOException {
286: PageCursor curs = new PageCursor(pageman, rowid.getBlock());
287: BlockIo block = file.get(curs.getCurrent());
288: RecordHeader hdr = new RecordHeader(block, rowid.getOffset());
289: hdr.setCurrentSize(length);
290: if (length == 0) {
291: file.release(curs.getCurrent(), true);
292: return;
293: }
294:
295: // copy bytes in
296: int offsetInBuffer = start;
297: int leftToWrite = length;
298: short dataOffset = (short) (rowid.getOffset() + RecordHeader.SIZE);
299: while (leftToWrite > 0) {
300: // copy current page's data to return buffer
301: int toCopy = RecordFile.BLOCK_SIZE - dataOffset;
302:
303: if (leftToWrite < toCopy) {
304: toCopy = leftToWrite;
305: }
306: System.arraycopy(data, offsetInBuffer, block.getData(),
307: dataOffset, toCopy);
308:
309: // Go to the next block
310: leftToWrite -= toCopy;
311: offsetInBuffer += toCopy;
312:
313: file.release(curs.getCurrent(), true);
314:
315: if (leftToWrite > 0) {
316: block = file.get(curs.next());
317: dataOffset = DataPage.O_DATA;
318: }
319: }
320: }
321: }
|