0001: /*
0002: * This file is part of BORG.
0003: *
0004: * BORG is free software; you can redistribute it and/or modify it under the
0005: * terms of the GNU General Public License as published by the Free Software
0006: * Foundation; either version 2 of the License, or (at your option) any later
0007: * version.
0008: *
0009: * BORG is distributed in the hope that it will be useful, but WITHOUT ANY
0010: * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
0011: * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
0012: *
0013: * You should have received a copy of the GNU General Public License along with
0014: * BORG; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
0015: * Suite 330, Boston, MA 02111-1307 USA
0016: *
0017: * Copyright 2003 by Mike Berger
0018: */
0019: package net.sf.borg.model.db.file.mdb;
0020:
0021: import java.io.ByteArrayOutputStream;
0022: import java.io.EOFException;
0023: import java.io.File;
0024: import java.io.IOException;
0025: import java.io.RandomAccessFile;
0026: import java.nio.channels.FileChannel;
0027: import java.nio.channels.FileLock;
0028: import java.util.ArrayList;
0029: import java.util.HashMap;
0030: import java.util.Vector;
0031:
0032: import javax.swing.filechooser.FileFilter;
0033:
0034: import net.sf.borg.common.Errmsg;
0035:
0036: // MDB - a random access DB (or perhaps 1 table in a DB)
0037: // MDB stores text data of unlimited size using an integer key
0038: // So, the schema for MDB is simply: int key; short userflags; - some boolean
0039: // flags that are indexed text userdata
0040: // Supports true random access. Records are keyed using a
0041: // user supplied key - no duplicates allowed. When DB object
0042: // is constructed, the file is quickly scanned and an index (table of contents)
0043: // is created in memory. After that, insert, delete, update operations
0044: // are done at a record level.
0045: // Record size is fixed. Data larger than record size is stored
0046: // under a chain of records. Freed records are left in place
0047: // and marked as free for later reuse - file "holes" are not eliminated.
0048: // Data is ASCII strings of any size. DB is not sensitive
0049: // to the data content. DB is not sensitive to key values.
0050: // Each record can contain 2 bytes of user boolean flags. These flags
0051: // are read during the initial indexing scan - when no text is read
0052: // These flags can give the user info about record without the need to
0053: // perform the more expensive text read
0054: // File structure
0055: // |=========================================|
0056: // | Superblock - contains info about the DB |
0057: // | esp. size of the DB records |
0058: // |-----------------------------------------|
0059: // | Block |
0060: // |-----------------------------------------|
0061: // | Block |
0062: // |-----------------------------------------|
0063: // | Block |
0064: // |-----------------------------------------|
0065: //
0066: // Block structure
0067: // |=========================================|
0068: // | Header - contains the user KEY, optional|
0069: // | link to the next block if the record |
0070: // | is part of a chain, and flags |
0071: // |-----------------------------------------|
0072: // | Data |
0073: // |=========================================|
0074:
0075: public class MDB {
0076:
0077: /**
0078: * a file chooser filter that selects JDB files
0079: */
0080: public static class DBFilter extends FileFilter {
0081: public boolean accept(File f) {
0082: if (f.isDirectory()) {
0083: return true;
0084: }
0085: String ext = null;
0086: String s = f.getName();
0087: int i = s.lastIndexOf('.');
0088: if (i > 0 && i < s.length() - 1) {
0089: ext = s.substring(i + 1).toLowerCase();
0090: }
0091: if (ext != null) {
0092: if (ext.equals("jdb")) {
0093: return true;
0094: }
0095:
0096: return false;
0097: }
0098: return false;
0099: }
0100:
0101: // The description of this filter
0102: public String getDescription() {
0103: return "JDB Files";
0104: }
0105:
0106: }
0107:
0108: // Block Flags
0109: private static final int BH_SIZE = 4 + 4 + 4; // 3 integers
0110:
0111: private static final int F_DELETE = 0x01; // Block is unused (available)
0112:
0113: private static final int F_EXTEND = 0x02; // Block is part of a chain of
0114:
0115: // blocks - not the first
0116:
0117: private static final int F_CRYPT = 0x04; // Block is encrypted
0118:
0119: private static final int F_SYSTEM = 0x08; // system block
0120:
0121: private static final int F_MAX = 0x0F;
0122:
0123: // system keys - user cannot use keys this large - reserved for internal MDB
0124: // use
0125: // protected static final int LOG_KEY = 2000000001; // key to the record
0126: // that
0127: // holds the
0128:
0129: // log file name
0130:
0131: private static final int VERSION = 8; // 6 - add user flags, 7 = separate
0132:
0133: // SYS blocks
0134:
0135: // 8 - add update count to superblock for shared access
0136:
0137: // lock mode for constructor and for persistent lock flag
0138: public static final int UNLOCKED = 0; // no lock
0139:
0140: public static final int READ_ONLY = 1; // exclusive read lock, allow no
0141:
0142: // updates
0143:
0144: public static final int READ_WRITE = 2; // exclusive read/write
0145:
0146: public static final int READ_DIRTY = 3; // gets no lock, can read only, no
0147:
0148: // guarantee that
0149:
0150: public static final int ADMIN = 5; // unconditional access, used to remove
0151:
0152: // other locks
0153:
0154: // Header data contained in each block
0155: static private class BlockHeader {
0156: public int key_; // user key
0157:
0158: public int next_; // number of next "extension" block if
0159:
0160: // there is chaining
0161: public int flags_; // flags (deleted, extension, crypt, system)
0162:
0163: public int userflags_; // user defined flags
0164:
0165: static public BlockHeader read(RandomAccessFile fp)
0166: throws Exception, EOFException {
0167: BlockHeader bh = new BlockHeader();
0168: try {
0169:
0170: // read the 3 ints that make up the block header
0171: bh.key_ = fp.readInt();
0172: bh.next_ = fp.readInt();
0173: int allflags = fp.readInt();
0174:
0175: // the third int is half system flags and half user flags
0176: bh.flags_ = allflags & 0xFFFF;
0177: bh.userflags_ = (allflags & 0xFFFF0000) >>> 16;
0178: } catch (EOFException eo) {
0179: throw eo;
0180: } catch (Exception e) {
0181: throw e;
0182: }
0183:
0184: return (bh);
0185: }
0186:
0187: public void write(RandomAccessFile fp) throws Exception {
0188: try {
0189:
0190: // write out the 3 ints that make up the block header
0191: fp.writeInt(key_);
0192: fp.writeInt(next_);
0193:
0194: // the flags int must be combined from the system and user flags
0195: int allflags = (flags_ + ((userflags_ & 0xFFFF) << 16));
0196: fp.writeInt(allflags);
0197:
0198: }
0199:
0200: catch (Exception e2) {
0201: throw (new Exception("Cannot write superblock"));
0202: }
0203: }
0204: }
0205:
0206: // the superblock describes info about the entire DB file
0207: private static class SuperBlock {
0208: static final int NAME_SIZE = 20; // Size of DB name
0209:
0210: static final int FILLSIZE = 8; // filler space - not used yet
0211:
0212: // sizes of superblock is 3 ints (blocksize, version, lock) + name +
0213: // filler
0214: // Java int size is fixed to 4 bytes by definition!!
0215: static final int SB_SIZE = 4 + 4 + 4 + NAME_SIZE + 4 + FILLSIZE;
0216:
0217: public String dbname_;
0218:
0219: public int blocksize_;
0220:
0221: public int version_;
0222:
0223: public int locktype_;
0224:
0225: public int updateCount_;
0226:
0227: private SuperBlock(int blocksize, int version, int locktype) {
0228: dbname_ = "MDB Database"; // default name
0229: blocksize_ = blocksize;
0230: version_ = version;
0231: locktype_ = locktype;
0232: updateCount_ = 0;
0233: }
0234:
0235: // write superblock
0236: public void write(RandomAccessFile fp) throws Exception {
0237: if (dbname_.length() > NAME_SIZE)
0238: throw (new Exception("DB name too long"));
0239:
0240: // create byte array for name + padding to get to NAME_SIZE + filler
0241: byte b[] = dbname_.getBytes();
0242: byte pad[] = new byte[NAME_SIZE - b.length];
0243: byte fill[] = new byte[FILLSIZE];
0244:
0245: try {
0246: // super block always at file start
0247: fp.seek(0);
0248:
0249: // write it
0250: fp.writeInt(blocksize_);
0251: fp.writeInt(version_);
0252: fp.writeInt(locktype_);
0253: fp.write(b);
0254: fp.write(pad);
0255: fp.writeInt(updateCount_);
0256: fp.write(fill);
0257: }
0258:
0259: catch (Exception e2) {
0260: throw (new Exception("Cannot write superblock"));
0261: }
0262: }
0263:
0264: // read superblock
0265: static public SuperBlock read(RandomAccessFile fp)
0266: throws Exception {
0267: SuperBlock nsb = new SuperBlock(100, VERSION, READ_WRITE);
0268: try {
0269: // always at file start
0270: fp.seek(0);
0271: nsb.blocksize_ = fp.readInt();
0272: nsb.version_ = fp.readInt();
0273: nsb.locktype_ = fp.readInt();
0274: fp.seek(4 + 4 + 4 + NAME_SIZE);
0275: nsb.updateCount_ = fp.readInt();
0276:
0277: // don't need to read name for anything
0278: } catch (Exception e) {
0279: throw new Exception("Error reading superblock fields");
0280: }
0281:
0282: if (nsb.blocksize_ < BH_SIZE + 2)
0283: throw new Exception("Blocksize is too small");
0284:
0285: return (nsb);
0286: }
0287:
0288: public int readCounter(RandomAccessFile fp) throws Exception {
0289: try {
0290: fp.seek(4 + 4 + 4 + NAME_SIZE);
0291: updateCount_ = fp.readInt();
0292: return (updateCount_);
0293: } catch (Exception e) {
0294: throw new Exception("Error reading update counter");
0295: }
0296:
0297: }
0298:
0299: public void writeCounter(RandomAccessFile fp) throws Exception {
0300: try {
0301: // super block always at file start
0302: fp.seek(4 + 4 + 4 + NAME_SIZE);
0303:
0304: // write it
0305: fp.writeInt(updateCount_);
0306: } catch (Exception e2) {
0307: throw (new Exception("Cannot write update counter"));
0308: }
0309: }
0310: }
0311:
0312: //
0313: // fields
0314: //
0315:
0316: private String filename_; // the actual DB filename
0317:
0318: private SuperBlock sb; // the superblock read from the db
0319:
0320: private int locktype_; // locktype that we opened the db with
0321:
0322: private int maxkey_; // highest key in the db
0323:
0324: private boolean sharedDB_ = false;
0325:
0326: private FileLock rwlock_ = null;
0327:
0328: private HashMap index_;
0329:
0330: private HashMap sysindex_;
0331:
0332: // mapping of keys to user flags
0333: private HashMap flagmap_;
0334:
0335: private int cur_key_idx_; // currency pointer into cur_keys_ array to
0336:
0337: // hold current key across first/next calls
0338: private Object cur_keys_[]; // array of all keys to speed up first/next
0339:
0340: // traversal
0341:
0342: private boolean cur_crypt_; // flag to indicate if the current record is
0343:
0344: // encrypted
0345:
0346: // List of free blocks in the DB file
0347: private ArrayList freelist_;
0348:
0349: // the file itself
0350: private RandomAccessFile fp_;
0351:
0352: // last block in file - keeps track of where to add new blocks
0353: private int lastblock_;
0354:
0355: // key for weak-encryption for private records
0356: static private String ckey_ = "543216789192837465";
0357:
0358: // the encryption object - just does weak encryption - but prevents casual
0359: // string scan on db file for encrypted records.
0360: private Crypt mc_;
0361:
0362: //
0363: // end fields
0364: //
0365:
0366: // add a mapping of a key to a block into either the user or system
0367: // maps
0368: private void add_kpair(int key, int bnum, boolean system) {
0369:
0370: if (system)
0371: sysindex_.put(new Integer(key), new Integer(bnum));
0372: else
0373: index_.put(new Integer(key), new Integer(bnum));
0374: }
0375:
0376: // look up the block number corresponding to a key
0377: // in the system or user maps
0378: private int find_block(int key, boolean system) {
0379: Integer bnum;
0380: if (system) {
0381: bnum = (Integer) sysindex_.get(new Integer(key));
0382: } else {
0383: bnum = (Integer) index_.get(new Integer(key));
0384: }
0385:
0386: if (bnum != null)
0387: return (bnum.intValue());
0388:
0389: return (-1);
0390: }
0391:
0392: // find a free block in the DB or return the last block + 1 if the
0393: // DB needs to grow
0394: private int get_free_block() {
0395: Integer bnum;
0396: int i = freelist_.size();
0397: if (i > 0) {
0398: bnum = (Integer) freelist_.remove(i - 1);
0399: return (bnum.intValue());
0400: }
0401:
0402: lastblock_++;
0403: return (lastblock_);
0404: }
0405:
0406: // move a block from one of the maps to the freelist
0407: private int free_entry(int key, boolean system) throws Exception {
0408:
0409: // reset currency pointer when changing the DB
0410: cur_keys_ = null;
0411: Integer k = new Integer(key);
0412: Integer bnum;
0413: if (system)
0414: bnum = (Integer) sysindex_.remove(k);
0415: else
0416: bnum = (Integer) index_.remove(k);
0417:
0418: if (bnum != null) {
0419: freelist_.add(bnum);
0420: return (bnum.intValue());
0421: }
0422:
0423: throw new Exception("free_entry - block not found");
0424: }
0425:
0426: // add a new key to the DB and associate a block with it
0427: private int add_new_entry(int key, boolean system) throws Exception {
0428:
0429: // reset currency pointer when changing the DB
0430: cur_keys_ = null;
0431:
0432: int bnum = find_block(key, system);
0433: if (bnum != -1) {
0434: throw (new Exception(
0435: "add_new_entry - duplicate key already exists"));
0436: }
0437:
0438: int fb = get_free_block();
0439: add_kpair(key, fb, system);
0440: return (fb);
0441:
0442: }
0443:
0444: // create a new MDB database file
0445: static public void create(String dbname, String filename,
0446: int blocksize) throws Exception {
0447: RandomAccessFile fp;
0448:
0449: // blocksize is the size of a block - header + text space
0450: // make sure the user allocates 2 bytes text space for a block
0451: // this is way too small anyway - should be around 80-100 text bytes
0452: // per block - depends on the average text size for a DB
0453: if (blocksize < BH_SIZE + 2)
0454: throw (new Exception("Blocksize is too small"));
0455:
0456: // create the file
0457: File f = new File(filename);
0458: if (f.exists())
0459: throw (new Exception("File Already Exists"));
0460: fp = new RandomAccessFile(filename, "rw");
0461:
0462: // write out a superblock
0463: SuperBlock sb = new SuperBlock(blocksize, VERSION, UNLOCKED);
0464: sb.dbname_ = dbname;
0465: sb.write(fp);
0466:
0467: }
0468:
0469: // remove lock files when destroyed
0470: protected void finalize() throws Throwable {
0471: close();
0472: super .finalize();
0473: }
0474:
0475: // try to get a shared lock to lock for multi-user usage
0476: private void getSharedLock() throws Exception {
0477: try {
0478: FileChannel fc = fp_.getChannel();
0479:
0480: FileLock lock;
0481: try {
0482: lock = fc.tryLock(0, 1, true);
0483: } catch (IOException e) {
0484: throw new Exception("Cannot lock DB " + filename_);
0485: }
0486: if (lock == null) {
0487: throw new Exception("Cannot lock DB " + filename_);
0488: }
0489: } catch (NoSuchMethodError nsm) {
0490: // running 1.3 - from Jsync conduit
0491: return;
0492: }
0493:
0494: }
0495:
0496: // try to get an exclusive lock for single user usage
0497: private void getExclusiveLock() throws Exception {
0498: try {
0499: FileChannel fc = fp_.getChannel();
0500: FileLock lock;
0501: try {
0502: lock = fc.tryLock(0, 1, false);
0503: } catch (IOException e) {
0504: e.printStackTrace();
0505: throw new Exception("Cannot lock DB " + filename_);
0506: }
0507: if (lock == null) {
0508: throw new Exception("Cannot lock DB " + filename_);
0509: }
0510: } catch (NoSuchMethodError nsm) {
0511: // running 1.3 - from Jsync conduit
0512: return;
0513: }
0514:
0515: }
0516:
0517: // wait for a shared read lock
0518: public void getReadLock() throws Exception {
0519: if (!sharedDB_)
0520: return;
0521: FileChannel fc = fp_.getChannel();
0522: try {
0523: if (rwlock_ != null)
0524: return;
0525:
0526: rwlock_ = fc.lock(1, 1, true);
0527: } catch (IOException e) {
0528: throw new Exception("Cannot lock DB " + filename_);
0529: }
0530: if (rwlock_ == null) {
0531: throw new Exception("Cannot lock DB " + filename_);
0532: }
0533:
0534: }
0535:
0536: // wait for an exclusive write lock
0537: public void getWriteLock() throws Exception {
0538: if (!sharedDB_)
0539: return;
0540:
0541: FileChannel fc = fp_.getChannel();
0542: try {
0543: // check if we already have this lock
0544: if (rwlock_ != null && !rwlock_.isShared())
0545: return;
0546:
0547: // release any read lock to upgrade to write
0548: if (rwlock_ != null)
0549: rwlock_.release();
0550: rwlock_ = fc.lock(1, 1, false);
0551: } catch (IOException e) {
0552: throw new Exception("Cannot lock DB " + filename_);
0553: }
0554:
0555: if (rwlock_ == null) {
0556: throw new Exception("Cannot lock DB " + filename_);
0557: }
0558: }
0559:
0560: // release a read or write lock
0561: public void releaseLock() throws Exception {
0562: if (!sharedDB_)
0563: return;
0564: if (rwlock_ != null) {
0565: try {
0566: rwlock_.release();
0567: } catch (IOException e) {
0568: throw new Exception("Cannot lock DB " + filename_);
0569: } catch (NoSuchMethodError nsm) {
0570: // running 1.3 - from Jsync conduit
0571: return;
0572: }
0573: }
0574: rwlock_ = null;
0575: }
0576:
0577: public boolean haveLock() {
0578: if (rwlock_ != null)
0579: return (true);
0580:
0581: return (false);
0582: }
0583:
0584: // Are we out of sync?
0585: // if we're not locked before entering this method, we attempt to acquire
0586: // a read lock
0587: public boolean isDirty() throws Exception {
0588:
0589: // if we have exclusive access - don't bother doing anything
0590: if (!sharedDB_)
0591: return false;
0592:
0593: boolean gotlock = false;
0594:
0595: try {
0596: if (!haveLock()) {
0597: getReadLock();
0598: gotlock = true;
0599: }
0600:
0601: int count = sb.updateCount_;
0602: int newcount = sb.readCounter(fp_);
0603: if (count == newcount) {
0604: // if the last count for this object matches the count read
0605: // from the file - nothing is needed, db is in sync
0606: return false;
0607: }
0608: } finally {
0609: if (gotlock)
0610: releaseLock();
0611: }
0612: return true;
0613: }
0614:
0615: // flush all cached data and recreate
0616: // if we're not locked before entering this method, we attempt to acquire
0617: // a read lock
0618: public void sync() {
0619:
0620: // if we have exclusive access - don't bother doing anything
0621: if (!sharedDB_)
0622: return;
0623:
0624: boolean gotlock = false;
0625:
0626: try {
0627: if (!haveLock()) {
0628: getReadLock();
0629: gotlock = true;
0630: }
0631:
0632: int count = sb.updateCount_;
0633: int newcount = sb.readCounter(fp_);
0634: if (count == newcount) {
0635: // if the last count for this object matches the count read
0636: // from the file - nothing is needed, db is in sync
0637: return;
0638: }
0639:
0640: // flush all cached data and re-sync
0641: index_.clear();
0642: sysindex_.clear();
0643: flagmap_.clear();
0644: cur_key_idx_ = -1;
0645: cur_keys_ = null;
0646: freelist_.clear();
0647: lastblock_ = -1;
0648: build_index();
0649: } catch (Exception e) {
0650: System.out.println(e);
0651: } finally {
0652: if (gotlock)
0653: try {
0654: releaseLock();
0655: } catch (Exception e) {
0656:
0657: }
0658: }
0659: }
0660:
0661: /**
0662: * constructor - opens DB of given name with given lock type
0663: *
0664: * @param file
0665: * filename of DB
0666: * @param locktype
0667: * locking mode
0668: * @throws Exception
0669: * errors
0670: */
0671: public MDB(String file, int locktype, boolean shared)
0672: throws Exception {
0673: filename_ = file;
0674: lastblock_ = -1;
0675: sharedDB_ = shared;
0676:
0677: // allocate an encrpytion object
0678: try {
0679: mc_ = new Crypt(ckey_);
0680: } catch (Exception e) {
0681: throw new Exception(
0682: "failed to initialize encryption object");
0683: }
0684:
0685: // init some stuff
0686: locktype_ = locktype;
0687: maxkey_ = 0;
0688:
0689: index_ = new HashMap();
0690: sysindex_ = new HashMap(10);
0691: flagmap_ = new HashMap();
0692: freelist_ = new ArrayList();
0693: cur_keys_ = null;
0694: cur_key_idx_ = -1;
0695:
0696: // open the DB file
0697: File f = new File(filename_);
0698: if (!f.exists())
0699: throw new Exception("DB does not exist");
0700:
0701: // open mode based on lock type
0702: try {
0703: if (locktype == READ_ONLY || locktype == READ_DIRTY) {
0704: fp_ = new RandomAccessFile(filename_, "r");
0705: } else if (locktype == READ_WRITE || locktype == ADMIN) {
0706: fp_ = new RandomAccessFile(filename_, "rw");
0707: } else {
0708: throw new Exception("Invalid Locking Mode");
0709: }
0710: } catch (Exception e) {
0711: throw new Exception(e.toString());
0712: }
0713:
0714: // lock the database
0715: try {
0716: if (sharedDB_) {
0717: getSharedLock();
0718: getReadLock();
0719: } else
0720: getExclusiveLock();
0721: } catch (Exception e) {
0722: throw new Exception(e.toString());
0723: }
0724:
0725: // read the superblock
0726: sb = SuperBlock.read(fp_);
0727:
0728: // transition to version 8
0729: if (sb.version_ < 8) {
0730: sb.updateCount_ = 0;
0731: sb.version_ = VERSION;
0732: sb.write(fp_);
0733: }
0734:
0735: if (VERSION != sb.version_)
0736: throw new Exception("DB version != software version: "
0737: + sb.version_ + "!=" + VERSION);
0738:
0739: // check persistent SB lock - not really used
0740: // the persistent lock has nothing to do with the temporary lock files
0741: // it was used to lock a DB so that it could not be opened without
0742: // a special close process. it was meant to indicate that a DB was
0743: // probably
0744: // a secondary copy and perhaps should not be used unless the primary
0745: // was lost
0746: // didn't want to accidentally start making real changes to a throw-away
0747: // backup copy
0748: // obsolete
0749: if (((sb.locktype_ == READ_WRITE) && ((locktype == READ_WRITE) || (locktype == READ_ONLY)))
0750: || ((sb.locktype_ == READ_ONLY) && (locktype == READ_WRITE))) {
0751: throw new Exception(
0752: "Cannot lock DB. Superblock indicates a lock");
0753: }
0754:
0755: // scan the whole DB file jumping from block-header to block-header to
0756: // build
0757: // a map of keys vs. blocks. does not read throwse text fields
0758: build_index();
0759:
0760: releaseLock(); // release read lock
0761:
0762: }
0763:
0764: public void close() {
0765: try {
0766: fp_.close();
0767: } catch (Exception e) {
0768: System.out.println(e);
0769: }
0770:
0771: }
0772:
0773: // duh
0774: public int nextkey() {
0775: try {
0776: sync();
0777: } catch (Exception e) {
0778: Errmsg.errmsg(e);
0779: }
0780: return (++maxkey_);
0781: }
0782:
0783: /**
0784: * Add a new DB row
0785: *
0786: * @param key
0787: * integer key ( > 0 and < MAX_KEY)
0788: * @param st
0789: * data to store
0790: * @throws Exception
0791: * errors
0792: */
0793:
0794: // add a user row to the db - with optional encryption
0795: public void add(int key, int flags, String st, boolean crypt)
0796: throws Exception {
0797: add(key, flags, st, crypt, false);
0798: }
0799:
0800: // add a system row to the DB
0801: protected void addSys(int key, String st) throws Exception {
0802: add(key, 0, st, false, true);
0803: }
0804:
0805: // add an unencrypted user row
0806: public void add(int key, int flags, String st) throws Exception {
0807: add(key, flags, st, false, false);
0808: }
0809:
0810: // main add function
0811: // add will store a record with a key, flags, and text string
0812: // it will break up the string as needed and create extended blocks if the
0813: // string is too large to fit in a single block
0814: private void add(int key, int flags, String st, boolean crypt,
0815: boolean system) throws Exception {
0816: if (locktype_ != READ_WRITE && locktype_ != ADMIN)
0817: throw new Exception("add: Cannot lock DB for write");
0818:
0819: getWriteLock();
0820: sync();
0821:
0822: int bnum, main_bnum;
0823:
0824: // add the first block - get free block number
0825: main_bnum = add_new_entry(key, system);
0826:
0827: // figure out how much text can fit per block
0828: int text_per_block = sb.blocksize_ - BH_SIZE - 1;
0829:
0830: // allocate a new header
0831: BlockHeader header = new BlockHeader();
0832:
0833: // set the header flags
0834: header.flags_ = 0;
0835: header.userflags_ = flags;
0836:
0837: // encrypt the string if needed
0838: if (crypt) {
0839: st = mc_.encrypt(st);
0840: }
0841:
0842: byte[] utf;
0843:
0844: // UTF-8 encoding is used in the DB
0845: try {
0846: utf = st.getBytes("UTF-8");
0847: } catch (Exception e) {
0848: throw new Exception("UTF-8 not supported");
0849: }
0850:
0851: int ulen = utf.length;
0852:
0853: // build blocks as needed - IN REVERSE ORDER so that each can be stored
0854: // with the proper next value. So blocks are stored with the end of the
0855: // string
0856: // stored first (timewise). Each new block will point to the one stored
0857: // before it. The final linked chain will start at the beginning of the
0858: // string and end with block holding the end of the string
0859: int last_block = -1;
0860: for (int extnum = (ulen - 1) / text_per_block; extnum >= 0; extnum--) {
0861:
0862: // fill in key and next in header
0863: header.key_ = key;
0864: header.next_ = last_block;
0865:
0866: // Write an EXTENSION Block
0867: if (extnum > 0) {
0868: // get a free block - can't use main_bnum - thats for the first
0869: // block in the chain that holds the start of the text string
0870: bnum = get_free_block();
0871: header.flags_ = F_EXTEND;
0872: }
0873: // Write the first block using main_bnum
0874: else {
0875: header.flags_ = 0;
0876:
0877: // the SYSTEM and CRYPT flags are only set in the header of
0878: // the first block and aren't needed in any extension blocks
0879: if (crypt) {
0880: header.flags_ |= F_CRYPT;
0881: } else if (system) {
0882: header.flags_ |= F_SYSTEM;
0883: }
0884:
0885: bnum = main_bnum;
0886: }
0887:
0888: try {
0889: // seek to the beginning of the block in the file
0890: // which is sizeof(bnum blocks) after the superblock ends
0891: fp_.seek(SuperBlock.SB_SIZE + (sb.blocksize_ * bnum));
0892: } catch (Exception e) {
0893: throw new Exception("add: seek failed");
0894: }
0895:
0896: // write the record
0897: try {
0898:
0899: // write the header
0900: header.write(fp_);
0901:
0902: // figure out which portion of the string goes in this
0903: // block and write it
0904: int endindex = ((extnum + 1) * text_per_block);
0905:
0906: if (endindex > ulen) {
0907: endindex = ulen;
0908: }
0909:
0910: // String subs = st.substring(extnum * text_per_block, endindex
0911: // );
0912: // fp_.writeBytes(subs);
0913: int sidx = extnum * text_per_block;
0914: int sublen = endindex - sidx;
0915: // System.out.println( utf + " " + sidx + " " + sublen );
0916: fp_.write(utf, sidx, sublen);
0917:
0918: // if the string does not fill up all of the space in the block
0919: // -
0920: // write '\0' bytes to fill the block
0921: for (int i = 0; i <= (text_per_block - sublen); i++) {
0922: fp_.writeByte(0);
0923: }
0924:
0925: } catch (Exception e) {
0926: throw new Exception("add: write failed");
0927: }
0928:
0929: // set last_block in case we loop around and have to store
0930: // another that points to this one
0931: last_block = bnum;
0932:
0933: // if we have stored a user key larger that the max we already
0934: // have, then update the max.
0935: if (key > maxkey_ && !system)
0936: maxkey_ = key;
0937:
0938: }
0939:
0940: // write a log record and update the map of user flags with the user's
0941: // flags
0942: if (!system) {
0943: flagmap_.put(new Integer(key), new Integer(flags));
0944: }
0945:
0946: if (sharedDB_) {
0947: sb.updateCount_++;
0948: sb.writeCounter(fp_);
0949: }
0950: releaseLock();
0951: }
0952:
0953: /** delete a row */
0954: public void delete(int key) throws Exception {
0955: delete(key, false);
0956: }
0957:
0958: // delete a system row
0959: protected void deleteSys(int key) throws Exception {
0960: delete(key, true);
0961: }
0962:
0963: // main delete function
0964: private void delete(int key, boolean system) throws Exception {
0965:
0966: int bnum;
0967:
0968: // check if DB is open for R/W
0969: if (locktype_ != READ_WRITE && locktype_ != ADMIN)
0970: throw new Exception("delete: cannot lock DB");
0971:
0972: getWriteLock();
0973: sync();
0974:
0975: // remove the user flags data from the map
0976: if (!system)
0977: flagmap_.remove(new Integer(key));
0978:
0979: // free the entry from the map of keys/block nums
0980: bnum = free_entry(key, system);
0981:
0982: // build a new block header to mark the record as deleted
0983: BlockHeader inheader;
0984: BlockHeader delheader = new BlockHeader();
0985: delheader.key_ = -1;
0986: delheader.next_ = -1;
0987: delheader.flags_ = F_DELETE;
0988:
0989: // write deleted block headers to all records in the chain
0990: while (bnum >= 0) {
0991:
0992: try {
0993: // seek to block
0994: fp_.seek(SuperBlock.SB_SIZE + (sb.blocksize_ * bnum));
0995: } catch (Exception e) {
0996: throw new Exception("delete: seek failed");
0997: }
0998:
0999: try {
1000: // read existing header - to get next ptr
1001: inheader = BlockHeader.read(fp_);
1002: } catch (Exception e) {
1003: throw new Exception("delete: cannot read header");
1004: }
1005:
1006: try {
1007: // seek back to beginning of header
1008: fp_.seek(SuperBlock.SB_SIZE + (sb.blocksize_ * bnum));
1009: } catch (Exception e) {
1010: throw new Exception("delete: seek2 failed");
1011: }
1012:
1013: // overwrite the header with a deleted header
1014: try {
1015: delheader.write(fp_);
1016: } catch (Exception e) {
1017: throw new Exception("delete: write failed");
1018: }
1019:
1020: // add the block to the free list if it is an extended block
1021: // the main block is added to the free list in free_entry()
1022: if ((inheader.flags_ & F_EXTEND) != 0) {
1023: freelist_.add(new Integer(bnum));
1024: }
1025:
1026: // go to next block in chain
1027: bnum = inheader.next_;
1028:
1029: // sanity check - check if next block in chain has same key as
1030: // one before. if not, it may be different data so error, DB is
1031: // corrupted
1032: if (key != inheader.key_) {
1033: throw new Exception("delete: different key found");
1034: }
1035:
1036: }
1037:
1038: if (sharedDB_) {
1039: sb.updateCount_++;
1040: sb.writeCounter(fp_);
1041: }
1042: releaseLock();
1043: }
1044:
1045: public Vector keys() {
1046: Vector v = new Vector(index_.keySet());
1047: return (v);
1048: }
1049:
1050: protected Vector syskeys() {
1051: Vector v = new Vector(sysindex_.keySet());
1052: return (v);
1053: }
1054:
1055: // get the flags for the current record where current is the last record
1056: // accessed using first/next
1057: // in this way, the flags can be retrieved without accessing the DB text
1058: public int getFlags() throws Exception {
1059:
1060: if (cur_keys_ == null)
1061: throw new Exception("getFlags() called with no currency");
1062: Integer key = (Integer) cur_keys_[cur_key_idx_];
1063: Integer flags = (Integer) flagmap_.get(key);
1064: if (flags == null)
1065: throw new Exception("key not found in flagmap");
1066: return (flags.intValue());
1067: }
1068:
1069: // look up the flags for a certain record by key
1070: public int getFlags(int key) throws Exception {
1071:
1072: Integer flags = (Integer) flagmap_.get(new Integer(key));
1073: if (flags == null)
1074: throw new Exception("key not found in flagmap");
1075: return (flags.intValue());
1076: }
1077:
1078: /**
1079: * retrieve record text with given key
1080: *
1081: * @param key
1082: * key of record
1083: * @throws Exception
1084: * not found/lock error/system error
1085: * @return text of record if found
1086: */
1087: public String read(int key) throws Exception {
1088: return (read(key, false));
1089: }
1090:
1091: // get system record text
1092: protected String readSys(int key) throws Exception {
1093: return (read(key, true));
1094: }
1095:
1096: // main read function
1097: private String read(int key, boolean system) throws Exception {
1098:
1099: getReadLock();
1100: sync();
1101:
1102: // need to go to the DB - so get the block number
1103: int bnum = find_block(key, system);
1104: if (bnum == -1)
1105: return null;
1106:
1107: ByteArrayOutputStream bao = new ByteArrayOutputStream();
1108:
1109: cur_crypt_ = false;
1110: BlockHeader header = null;
1111: while (bnum >= 0) {
1112:
1113: // seek to the block
1114: fp_.seek(SuperBlock.SB_SIZE + (sb.blocksize_ * bnum));
1115:
1116: // read the header
1117: header = BlockHeader.read(fp_);
1118:
1119: // set flag if block is encrypted
1120: if ((header.flags_ & F_CRYPT) != 0)
1121: cur_crypt_ = true;
1122:
1123: // read the text bytes until we hit the max text in block or a 0
1124: // byte
1125: // and keep appending to the StringBuffer
1126:
1127: for (int i = 0; i < sb.blocksize_ - BH_SIZE; i++) {
1128: int c = fp_.readUnsignedByte();
1129:
1130: if (c == 0)
1131: break;
1132:
1133: bao.write(c);
1134: }
1135:
1136: // check if flags look valid
1137: if (header.flags_ < 0 || header.flags_ > F_MAX) {
1138: throw new Exception("read: invalid flags");
1139: }
1140:
1141: // check if key in block header matches the one we were looking for
1142: // never should fail unless DB file corrupted
1143: if (key != header.key_) {
1144: throw new Exception("read: key mismatch");
1145: }
1146:
1147: // go to next block in chain
1148: bnum = header.next_;
1149: }
1150:
1151: // get String from StringBuffer
1152: String s;
1153: try {
1154: s = new String(bao.toByteArray(), "UTF-8");
1155: // System.out.println("read: " + s );
1156: } catch (Exception e) {
1157: throw new Exception("UTF-8 not supported");
1158: }
1159:
1160: // decrypt if needed
1161: if (cur_crypt_) {
1162: s = mc_.decrypt(s);
1163: }
1164:
1165: releaseLock();
1166: return (s);
1167:
1168: }
1169:
1170: // update an unencrypted user record
1171: public void update(int key, int flags, String st) throws Exception {
1172: update(key, flags, st, false, false);
1173: }
1174:
1175: // update a user record with crypt option
1176: public void update(int key, int flags, String st, boolean crypt)
1177: throws Exception {
1178: update(key, flags, st, crypt, false);
1179: }
1180:
1181: // update a system record
1182: protected void updateSys(int key, String st) throws Exception {
1183: update(key, 0, st, false, true);
1184: }
1185:
1186: // main update function
1187: private void update(int key, int flags, String st, boolean crypt,
1188: boolean system) throws Exception {
1189:
1190: // check if DB opened for R/W
1191: if (locktype_ != READ_WRITE && locktype_ != ADMIN)
1192: throw new Exception("update: cannot lock db");
1193:
1194: // update is simply delete then add
1195: // update in place would be messy because the number of blocks in the
1196: // chain might change - so just delete and add
1197: delete(key, system);
1198: add(key, flags, st, crypt, system);
1199:
1200: }
1201:
1202: // scan all block headers and build the index of keys to blocks
1203: // done on startup
1204: private void build_index() throws Exception {
1205: BlockHeader header;
1206:
1207: int bnum = 0;
1208:
1209: // seek past superblock
1210: fp_.seek(SuperBlock.SB_SIZE);
1211:
1212: // read all block headers
1213: while (true) {
1214: try {
1215: // read a header
1216: header = BlockHeader.read(fp_);
1217: } catch (EOFException e) {
1218: break;
1219: }
1220:
1221: // validate that flags look ok
1222: if (header.flags_ < 0 || header.flags_ > F_MAX) {
1223: System.out
1224: .println("build_index(): Bad flags found for record ("
1225: + header.key_ + ") - ignoring record");
1226: }
1227: // if block is deleted - add to freelist
1228: else if ((header.flags_ & F_DELETE) != 0) {
1229: freelist_.add(new Integer(bnum));
1230: }
1231: // if block is not an extended block - i.e. it is a single block
1232: // or first in a chain, then save it to the user or system indexes
1233: else if ((header.flags_ & F_EXTEND) == 0) {
1234: if ((header.flags_ & F_SYSTEM) != 0) {
1235: add_kpair(header.key_, bnum, true);
1236: } else {
1237: add_kpair(header.key_, bnum, false);
1238:
1239: // for user blocks, add the user flags to an index
1240: flagmap_.put(new Integer(header.key_), new Integer(
1241: header.userflags_));
1242:
1243: // keep track of the highest key
1244: if (header.key_ > maxkey_)
1245: maxkey_ = header.key_;
1246: }
1247:
1248: }
1249:
1250: // go to next block and update total number of blocks
1251: lastblock_ = bnum;
1252: bnum++;
1253:
1254: try {
1255: // seek to next block
1256: fp_.seek(SuperBlock.SB_SIZE + bnum * sb.blocksize_);
1257: } catch (Exception e) {
1258: break;
1259: }
1260: }
1261:
1262: }
1263:
1264: }
|