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: RecordFile.java,v 1.6 2005/06/25 23:12:32 doomdark Exp $
046: */package jdbm.recman;
047:
048: import java.io.*;
049: import java.util.*;
050:
051: /**
052: * This class represents a random access file as a set of fixed size
053: * records. Each record has a physical record number, and records are
054: * cached in order to improve access.
055: *<p>
056: * The set of dirty records on the in-use list constitutes a transaction.
057: * Later on, we will send these records to some recovery thingy.
058: */
059: public final class RecordFile {
060: final TransactionManager txnMgr;
061:
062: // Todo: reorganize in hashes and fifos as necessary.
063: // free -> inUse -> dirty -> inTxn -> free
064: // free is a cache, thus a FIFO. The rest are hashes.
065: private final LinkedList free = new LinkedList();
066: private final HashMap inUse = new HashMap();
067: private final HashMap dirty = new HashMap();
068: private final HashMap inTxn = new HashMap();
069:
070: // transactions disabled?
071: private boolean transactionsDisabled = false;
072:
073: /** The length of a single block. */
074: public final static int BLOCK_SIZE = 8192;//4096;
075:
076: /** The extension of a record file */
077: final static String extension = ".db";
078:
079: /** A block of clean data to wipe clean pages. */
080: final static byte[] cleanData = new byte[BLOCK_SIZE];
081:
082: private RandomAccessFile file;
083: private final String fileName;
084:
085: /**
086: * Creates a new object on the indicated filename. The file is
087: * opened in read/write mode.
088: *
089: * @param fileName the name of the file to open or create, without
090: * an extension.
091: * @throws IOException whenever the creation of the underlying
092: * RandomAccessFile throws it.
093: */
094: RecordFile(String fileName) throws IOException {
095: this .fileName = fileName;
096: file = new RandomAccessFile(fileName + extension, "rw");
097: txnMgr = new TransactionManager(this );
098: }
099:
100: /**
101: * Returns the file name.
102: */
103: String getFileName() {
104: return fileName;
105: }
106:
107: /**
108: * Disables transactions: doesn't sync and doesn't use the
109: * transaction manager.
110: */
111: void disableTransactions() {
112: transactionsDisabled = true;
113: }
114:
115: /**
116: * Gets a block from the file. The returned byte array is
117: * the in-memory copy of the record, and thus can be written
118: * (and subsequently released with a dirty flag in order to
119: * write the block back).
120: *
121: * @param blockid The record number to retrieve.
122: */
123: BlockIo get(long blockid) throws IOException {
124: Long key = new Long(blockid);
125:
126: // try in transaction list, dirty list, free list
127: BlockIo node = (BlockIo) inTxn.get(key);
128: if (node != null) {
129: inTxn.remove(key);
130: inUse.put(key, node);
131: return node;
132: }
133: node = (BlockIo) dirty.get(key);
134: if (node != null) {
135: dirty.remove(key);
136: inUse.put(key, node);
137: return node;
138: }
139: for (Iterator i = free.iterator(); i.hasNext();) {
140: BlockIo cur = (BlockIo) i.next();
141: if (cur.getBlockId() == blockid) {
142: node = cur;
143: i.remove();
144: inUse.put(key, node);
145: return node;
146: }
147: }
148:
149: // sanity check: can't be on in use list
150: if (inUse.get(key) != null) {
151: throw new Error("double get for block " + blockid);
152: }
153:
154: // get a new node and read it from the file
155: node = getNewNode(blockid);
156: long offset = blockid * BLOCK_SIZE;
157: if (file.length() > 0 && offset <= file.length()) {
158: read(file, offset, node.getData(), BLOCK_SIZE);
159: } else {
160: System.arraycopy(cleanData, 0, node.getData(), 0,
161: BLOCK_SIZE);
162: }
163: inUse.put(key, node);
164: node.setClean();
165: return node;
166: }
167:
168: /**
169: * Releases a block.
170: *
171: * @param blockid The record number to release.
172: * @param isDirty If true, the block was modified since the get().
173: */
174: void release(long blockid, boolean isDirty) throws IOException {
175: BlockIo node = (BlockIo) inUse.get(new Long(blockid));
176: if (node == null)
177: throw new IOException("bad blockid " + blockid
178: + " on release");
179: if (!node.isDirty() && isDirty)
180: node.setDirty();
181: release(node);
182: }
183:
184: /**
185: * Releases a block.
186: *
187: * @param block The block to release.
188: */
189: void release(BlockIo block) {
190: Long key = new Long(block.getBlockId());
191: inUse.remove(key);
192: if (block.isDirty()) {
193: // System.out.println( "Dirty: " + key + block );
194: dirty.put(key, block);
195: } else {
196: if (!transactionsDisabled && block.isInTransaction()) {
197: inTxn.put(key, block);
198: } else {
199: free.add(block);
200: }
201: }
202: }
203:
204: /**
205: * Discards a block (will not write the block even if it's dirty)
206: *
207: * @param block The block to discard.
208: */
209: void discard(BlockIo block) {
210: Long key = new Long(block.getBlockId());
211: inUse.remove(key);
212:
213: // note: block not added to free list on purpose, because
214: // it's considered invalid
215: }
216:
217: /**
218: * Commits the current transaction by flushing all dirty buffers
219: * to disk.
220: */
221: void commit() throws IOException {
222: // debugging...
223: if (!inUse.isEmpty() && inUse.size() > 1) {
224: showList(inUse.values().iterator());
225: throw new Error("in use list not empty at commit time ("
226: + inUse.size() + ")");
227: }
228:
229: // System.out.println("committing...");
230:
231: if (dirty.size() == 0) {
232: // if no dirty blocks, skip commit process
233: return;
234: }
235:
236: if (!transactionsDisabled) {
237: txnMgr.start();
238: }
239:
240: for (Iterator i = dirty.values().iterator(); i.hasNext();) {
241: BlockIo node = (BlockIo) i.next();
242: i.remove();
243: // System.out.println("node " + node + " map size now " + dirty.size());
244: if (transactionsDisabled) {
245: long offset = node.getBlockId() * BLOCK_SIZE;
246: file.seek(offset);
247: file.write(node.getData());
248: node.setClean();
249: free.add(node);
250: } else {
251: txnMgr.add(node);
252: inTxn.put(new Long(node.getBlockId()), node);
253: }
254: }
255: if (!transactionsDisabled) {
256: txnMgr.commit();
257: }
258: }
259:
260: /**
261: * Rollback the current transaction by discarding all dirty buffers
262: */
263: void rollback() throws IOException {
264: // debugging...
265: if (!inUse.isEmpty()) {
266: showList(inUse.values().iterator());
267: throw new Error("in use list not empty at rollback time ("
268: + inUse.size() + ")");
269: }
270: // System.out.println("rollback...");
271: dirty.clear();
272:
273: txnMgr.synchronizeLogFromDisk();
274:
275: if (!inTxn.isEmpty()) {
276: showList(inTxn.values().iterator());
277: throw new Error("in txn list not empty at rollback time ("
278: + inTxn.size() + ")");
279: }
280: ;
281: }
282:
283: /**
284: * Commits and closes file.
285: */
286: void close() throws IOException {
287: if (!dirty.isEmpty()) {
288: commit();
289: }
290: txnMgr.shutdown();
291:
292: if (!inTxn.isEmpty()) {
293: showList(inTxn.values().iterator());
294: throw new Error("In transaction not empty");
295: }
296:
297: // these actually ain't that bad in a production release
298: if (!dirty.isEmpty()) {
299: System.out.println("ERROR: dirty blocks at close time");
300: showList(dirty.values().iterator());
301: throw new Error("Dirty blocks at close time");
302: }
303: if (!inUse.isEmpty()) {
304: System.out.println("ERROR: inUse blocks at close time");
305: showList(inUse.values().iterator());
306: throw new Error("inUse blocks at close time");
307: }
308:
309: // debugging stuff to keep an eye on the free list
310: // System.out.println("Free list size:" + free.size());
311: file.close();
312: file = null;
313: }
314:
315: /**
316: * Force closing the file and underlying transaction manager.
317: * Used for testing purposed only.
318: */
319: void forceClose() throws IOException {
320: txnMgr.forceClose();
321: file.close();
322: }
323:
324: /**
325: * Prints contents of a list
326: */
327: private void showList(Iterator i) {
328: int cnt = 0;
329: while (i.hasNext()) {
330: System.out.println("elem " + cnt + ": " + i.next());
331: cnt++;
332: }
333: }
334:
335: /**
336: * Returns a new node. The node is retrieved (and removed)
337: * from the released list or created new.
338: */
339: private BlockIo getNewNode(long blockid) throws IOException {
340:
341: BlockIo retval = null;
342: if (!free.isEmpty()) {
343: retval = (BlockIo) free.removeFirst();
344: }
345: if (retval == null)
346: retval = new BlockIo(0, new byte[BLOCK_SIZE]);
347:
348: retval.setBlockId(blockid);
349: retval.setView(null);
350: return retval;
351: }
352:
353: /**
354: * Synchs a node to disk. This is called by the transaction manager's
355: * synchronization code.
356: */
357: void synch(BlockIo node) throws IOException {
358: byte[] data = node.getData();
359: if (data != null) {
360: long offset = node.getBlockId() * BLOCK_SIZE;
361: file.seek(offset);
362: file.write(data);
363: }
364: }
365:
366: /**
367: * Releases a node from the transaction list, if it was sitting
368: * there.
369: *
370: * @param recycle true if block data can be reused
371: */
372: void releaseFromTransaction(BlockIo node, boolean recycle)
373: throws IOException {
374: Long key = new Long(node.getBlockId());
375: if ((inTxn.remove(key) != null) && recycle) {
376: free.add(node);
377: }
378: }
379:
380: /**
381: * Synchronizes the file.
382: */
383: void sync() throws IOException {
384: file.getFD().sync();
385: }
386:
387: /**
388: * Utility method: Read a block from a RandomAccessFile
389: */
390: private static void read(RandomAccessFile file, long offset,
391: byte[] buffer, int nBytes) throws IOException {
392: file.seek(offset);
393: int remaining = nBytes;
394: int pos = 0;
395: while (remaining > 0) {
396: int read = file.read(buffer, pos, remaining);
397: if (read == -1) {
398: System.arraycopy(cleanData, 0, buffer, pos, remaining);
399: break;
400: }
401: remaining -= read;
402: pos += read;
403: }
404: }
405:
406: }
|