0001: // kelondroTray.java
0002: // (C) 2003 - 2007 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
0003: // first published 2003 on http://yacy.net
0004: //
0005: // This is a part of YaCy, a peer-to-peer based web search engine
0006: //
0007: // $LastChangedDate: 2006-04-02 22:40:07 +0200 (So, 02 Apr 2006) $
0008: // $LastChangedRevision: 1986 $
0009: // $LastChangedBy: orbiter $
0010: //
0011: // LICENSE
0012: //
0013: // This program is free software; you can redistribute it and/or modify
0014: // it under the terms of the GNU General Public License as published by
0015: // the Free Software Foundation; either version 2 of the License, or
0016: // (at your option) any later version.
0017: //
0018: // This program is distributed in the hope that it will be useful,
0019: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0020: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0021: // GNU General Public License for more details.
0022: //
0023: // You should have received a copy of the GNU General Public License
0024: // along with this program; if not, write to the Free Software
0025: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0026:
0027: package de.anomic.kelondro;
0028:
0029: import java.io.File;
0030: import java.io.FileNotFoundException;
0031: import java.io.IOException;
0032: import java.util.HashSet;
0033: import java.util.Iterator;
0034: import java.util.Random;
0035: import java.util.Set;
0036: import java.util.StringTokenizer;
0037: import java.util.TreeSet;
0038: import java.util.logging.Level;
0039: import java.util.logging.Logger;
0040:
0041: import de.anomic.kelondro.kelondroRow.EntryIndex;
0042: import de.anomic.server.logging.serverLog;
0043:
0044: public abstract class kelondroAbstractRecords implements
0045: kelondroRecords {
0046:
0047: // static seek pointers
0048: private static int LEN_DESCR = 60;
0049: private static long POS_MAGIC = 0; // 1 byte, byte: file type magic
0050: private static long POS_BUSY = POS_MAGIC + 1; // 1 byte, byte: marker for synchronization
0051: private static long POS_PORT = POS_BUSY + 1; // 2 bytes, short: hint for remote db access
0052: private static long POS_DESCR = POS_PORT + 2; // 60 bytes, string: any description string
0053: private static long POS_COLUMNS = POS_DESCR + LEN_DESCR; // 2 bytes, short: number of columns in one entry
0054: private static long POS_OHBYTEC = POS_COLUMNS + 2; // 2 bytes, number of extra bytes on each Node
0055: private static long POS_OHHANDLEC = POS_OHBYTEC + 2; // 2 bytes, number of Handles on each Node
0056: private static long POS_USEDC = POS_OHHANDLEC + 2; // 4 bytes, int: used counter
0057: private static long POS_FREEC = POS_USEDC + 4; // 4 bytes, int: free counter
0058: private static long POS_FREEH = POS_FREEC + 4; // 4 bytes, int: free pointer (to free chain start)
0059: private static long POS_MD5PW = POS_FREEH + 4; // 16 bytes, string (encrypted password to this file)
0060: private static long POS_ENCRYPTION = POS_MD5PW + 16; // 16 bytes, string (method description)
0061: private static long POS_OFFSET = POS_ENCRYPTION + 16; // 8 bytes, long (seek position of first record)
0062: private static long POS_INTPROPC = POS_OFFSET + 8; // 4 bytes, int: number of INTPROP elements
0063: private static long POS_TXTPROPC = POS_INTPROPC + 4; // 4 bytes, int: number of TXTPROP elements
0064: private static long POS_TXTPROPW = POS_TXTPROPC + 4; // 4 bytes, int: width of TXTPROP elements
0065: private static long POS_COLWIDTHS = POS_TXTPROPW + 4; // array of 4 bytes, int[]: sizes of columns
0066: // after this configuration field comes:
0067: // POS_HANDLES: INTPROPC * 4 bytes : INTPROPC Integer properties, randomly accessible
0068: // POS_TXTPROPS: TXTPROPC * TXTPROPW : an array of TXTPROPC byte arrays of width TXTPROPW that can hold any string
0069: // POS_NODES : (USEDC + FREEC) * (overhead + sum(all: COLWIDTHS)) : Node Objects
0070:
0071: // values that are only present at run-time
0072: protected String filename; // the database's file name
0073: protected kelondroIOChunks entryFile; // the database file
0074: protected int overhead; // OHBYTEC + 4 * OHHANDLEC = size of additional control bytes
0075: protected int headchunksize;// overheadsize + key element column size
0076: protected int tailchunksize;// sum(all: COLWIDTHS) minus the size of the key element colum
0077: protected int recordsize; // (overhead + sum(all: COLWIDTHS)) = the overall size of a record
0078: private byte[] spaceChunk; // a chunk of data that is used to reserve space within the file
0079:
0080: // dynamic run-time seek pointers
0081: private long POS_HANDLES = 0; // starts after end of POS_COLWIDHS which is POS_COLWIDTHS + COLWIDTHS.length * 4
0082: private long POS_TXTPROPS = 0; // starts after end of POS_HANDLES which is POS_HANDLES + HANDLES.length * 4
0083: protected long POS_NODES = 0; // starts after end of POS_TXTPROPS which is POS_TXTPROPS + TXTPROPS.length * TXTPROPW
0084:
0085: // dynamic variables that are back-ups of stored values in file; read/defined on instantiation
0086: protected usageControl USAGE; // counter for used and re-use records and pointer to free-list
0087: protected short OHBYTEC; // number of extra bytes in each node
0088: protected short OHHANDLEC; // number of handles in each node
0089: protected kelondroRow ROW; // array with widths of columns
0090: private kelondroHandle HANDLES[]; // array with handles
0091: private byte[] TXTPROPS[]; // array with text properties
0092: private int TXTPROPW; // size of a single TXTPROPS element
0093:
0094: // optional logger
0095: protected Logger theLogger = Logger.getLogger("KELONDRO"); // default logger
0096:
0097: // tracking of file cration
0098: protected boolean fileExisted;
0099:
0100: // Random. This is used to shift flush-times of write-buffers to differrent time
0101: private static Random random = new Random(System
0102: .currentTimeMillis());
0103:
0104: // check for debug mode
0105: public static boolean debugmode = false;
0106: static {
0107: assert debugmode = true;
0108: }
0109:
0110: protected final class usageControl {
0111: protected int USEDC; // counter of used elements
0112: protected int FREEC; // counter of free elements in list of free Nodes
0113: protected kelondroHandle FREEH; // pointer to first element in list of free Nodes, empty = NUL
0114:
0115: protected usageControl(boolean init) throws IOException {
0116: if (init) {
0117: this .USEDC = 0;
0118: this .FREEC = 0;
0119: this .FREEH = new kelondroHandle(kelondroHandle.NUL);
0120: } else {
0121: readfree();
0122: readused();
0123: try {
0124: int rest = (int) ((entryFile.length() - POS_NODES) % recordsize);// == 0 : "rest = " + ((entryFile.length() - POS_NODES) % ((long) recordsize)) + ", USEDC = " + this.USEDC + ", FREEC = " + this.FREEC + ", recordsize = " + recordsize + ", file = " + filename;
0125: int calculated_used = (int) ((entryFile.length() - POS_NODES) / recordsize);
0126: if ((rest != 0)
0127: || (calculated_used != this .USEDC
0128: + this .FREEC)) {
0129: theLogger.log(Level.WARNING,
0130: "USEDC inconsistency at startup: calculated_used = "
0131: + calculated_used
0132: + ", USEDC = " + this .USEDC
0133: + ", FREEC = " + this .FREEC
0134: + ", recordsize = "
0135: + recordsize + ", file = "
0136: + filename);
0137: this .USEDC = calculated_used - this .FREEC;
0138: if (this .USEDC < 0) {
0139: theLogger.log(Level.WARNING,
0140: "USEDC inconsistency at startup: cannot recover "
0141: + filename);
0142: throw new kelondroException(
0143: "cannot recover inconsistency in "
0144: + filename);
0145: }
0146: writeused(true);
0147: }
0148: } catch (IOException e) {
0149: assert false;
0150: }
0151: }
0152: }
0153:
0154: private synchronized void writeused(boolean finalwrite)
0155: throws IOException {
0156: // we write only at close time, not in between. othervise, the read/write head
0157: // needs to run up and own all the way between the beginning and the end of the
0158: // file for each record. We check consistency beteen file size and
0159: if (finalwrite)
0160: synchronized (entryFile) {
0161: assert this .USEDC >= 0 : "this.USEDC = "
0162: + this .USEDC;
0163: entryFile.writeInt(POS_USEDC, this .USEDC);
0164: entryFile.commit();
0165: }
0166: }
0167:
0168: private synchronized void writefree() throws IOException {
0169: //synchronized (entryFile) {
0170: entryFile.writeInt(POS_FREEC, FREEC);
0171: entryFile.writeInt(POS_FREEH, FREEH.index);
0172: entryFile.commit();
0173: checkConsistency();
0174: //}
0175: }
0176:
0177: private synchronized void readused() throws IOException {
0178: //synchronized (entryFile) {
0179: this .USEDC = entryFile.readInt(POS_USEDC);
0180: assert this .USEDC >= 0 : "this.USEDC = " + this .USEDC
0181: + ", filename = " + filename;
0182: //}
0183: }
0184:
0185: private synchronized void readfree() throws IOException {
0186: //synchronized (entryFile) {
0187: this .FREEC = entryFile.readInt(POS_FREEC);
0188: this .FREEH = new kelondroHandle(entryFile
0189: .readInt(POS_FREEH));
0190: //}
0191: }
0192:
0193: protected synchronized int allCount() {
0194: checkConsistency();
0195: return this .USEDC + this .FREEC;
0196: }
0197:
0198: private synchronized int used() {
0199: checkConsistency();
0200: return this .USEDC;
0201: }
0202:
0203: protected synchronized void dispose(kelondroHandle h)
0204: throws IOException {
0205: // delete element with handle h
0206: // this element is then connected to the deleted-chain and can be
0207: // re-used change counter
0208: assert (h.index >= 0);
0209: assert (h.index != kelondroHandle.NUL);
0210: //synchronized (USAGE) {
0211: synchronized (entryFile) {
0212: assert (h.index < USEDC + FREEC) : "USEDC = " + USEDC
0213: + ", FREEC = " + FREEC + ", h.index = "
0214: + h.index;
0215: long sp = seekpos(h);
0216: assert (sp <= entryFile.length() + ROW.objectsize) : h.index
0217: + "/"
0218: + sp
0219: + " exceeds file size "
0220: + entryFile.length();
0221: USEDC--;
0222: FREEC++;
0223: // change pointer
0224: entryFile.writeInt(sp, FREEH.index); // extend free-list
0225: // write new FREEH Handle link
0226: FREEH = h;
0227: writefree();
0228: writeused(false);
0229: }
0230: //}
0231: }
0232:
0233: protected synchronized int allocatePayload(byte[] chunk)
0234: throws IOException {
0235: // reserves a new record and returns index of record
0236: // the return value is not a seek position
0237: // the seek position can be retrieved using the seekpos() function
0238: if (chunk == null) {
0239: chunk = spaceChunk;
0240: }
0241: assert (chunk.length == ROW.objectsize) : "chunk.length = "
0242: + chunk.length + ", ROW.objectsize() = "
0243: + ROW.objectsize;
0244: //synchronized (USAGE) {
0245: synchronized (entryFile) {
0246: if (USAGE.FREEC == 0) {
0247: // generate new entry
0248: int index = USAGE.allCount();
0249: entryFile.write(seekpos(index) + overhead, chunk,
0250: 0, ROW.objectsize); // occupy space, othervise the USAGE computaton does not work
0251: USAGE.USEDC++;
0252: writeused(false);
0253: return index;
0254: } else {
0255: // re-use record from free-list
0256: USAGE.USEDC++;
0257: USAGE.FREEC--;
0258: // take link
0259: int index = USAGE.FREEH.index;
0260: if (index == kelondroHandle.NUL) {
0261: serverLog.logSevere("kelondroTray/" + filename,
0262: "INTERNAL ERROR (DATA INCONSISTENCY): re-use of records failed, lost "
0263: + (USAGE.FREEC + 1)
0264: + " records.");
0265: // try to heal..
0266: USAGE.USEDC = USAGE.allCount() + 1;
0267: USAGE.FREEC = 0;
0268: index = USAGE.USEDC - 1;
0269: } else {
0270: //System.out.println("*DEBUG* ALLOCATED DELETED INDEX " + index);
0271: // check for valid seek position
0272: long seekp = seekpos(USAGE.FREEH);
0273: if (seekp >= entryFile.length()) {
0274: // this is a severe inconsistency. try to heal..
0275: serverLog
0276: .logSevere(
0277: "kelondroTray/" + filename,
0278: "new Handle: lost "
0279: + USAGE.FREEC
0280: + " marked nodes; seek position "
0281: + seekp
0282: + "/"
0283: + USAGE.FREEH.index
0284: + " out of file size "
0285: + entryFile
0286: .length()
0287: + "/"
0288: + ((entryFile
0289: .length() - POS_NODES) / recordsize));
0290: index = USAGE.allCount(); // a place at the end of the file
0291: USAGE.USEDC += USAGE.FREEC; // to avoid that non-empty records at the end are overwritten
0292: USAGE.FREEC = 0; // discard all possible empty nodes
0293: USAGE.FREEH.index = kelondroHandle.NUL;
0294: } else {
0295: // read link to next element of FREEH chain
0296: USAGE.FREEH.index = entryFile
0297: .readInt(seekp);
0298: assert ((USAGE.FREEH.index == kelondroHandle.NUL) && (USAGE.FREEC == 0))
0299: || seekpos(USAGE.FREEH) < entryFile
0300: .length() : "allocatePayload: USAGE.FREEH.index = "
0301: + USAGE.FREEH.index
0302: + ", seekp = "
0303: + seekp;
0304: }
0305: }
0306: USAGE.writeused(false);
0307: USAGE.writefree();
0308: entryFile.write(seekpos(index) + overhead, chunk,
0309: 0, ROW.objectsize); // overwrite space
0310: return index;
0311: }
0312: }
0313: //}
0314: }
0315:
0316: protected synchronized void allocateRecord(int index,
0317: byte[] bulkchunk, int offset) throws IOException {
0318: // in case that the handle index was created outside this class,
0319: // this method ensures that the USAGE counters are consistent with the
0320: // new handle index
0321: if (bulkchunk == null) {
0322: bulkchunk = new byte[recordsize];
0323: offset = 0;
0324: }
0325: //assert (chunk.length == ROW.objectsize()) : "chunk.length = " + chunk.length + ", ROW.objectsize() = " + ROW.objectsize();
0326: //synchronized (USAGE) {
0327: synchronized (entryFile) {
0328: if (index < USAGE.allCount()) {
0329: // write within the file
0330: // this can be critical, if we simply overwrite fields that are marked
0331: // as deleted. This case should be avoided. There is no other way to check
0332: // that the field is not occupied than looking at the FREEC counter
0333: assert (USAGE.FREEC == 0) : "FREEC = "
0334: + USAGE.FREEC;
0335: // simply overwrite the cell
0336: entryFile.write(seekpos(index), bulkchunk, offset,
0337: recordsize);
0338: // no changes of counter necessary
0339: } else {
0340: // write beyond the end of the file
0341: // records that are in between are marked as deleted
0342: kelondroHandle h;
0343: while (index > USAGE.allCount()) {
0344: h = new kelondroHandle(USAGE.allCount());
0345: USAGE.FREEC++;
0346: entryFile.write(seekpos(h), spaceChunk); // occupy space, othervise the USAGE computaton does not work
0347: entryFile.writeInt(seekpos(h),
0348: USAGE.FREEH.index);
0349: USAGE.FREEH = h;
0350: assert ((USAGE.FREEH.index == kelondroHandle.NUL) && (USAGE.FREEC == 0))
0351: || seekpos(USAGE.FREEH) < entryFile
0352: .length() : "allocateRecord: USAGE.FREEH.index = "
0353: + USAGE.FREEH.index;
0354: USAGE.writefree();
0355: entryFile.commit();
0356: }
0357: assert (index <= USAGE.allCount());
0358:
0359: // adopt USAGE.USEDC
0360: if (USAGE.allCount() == index) {
0361: entryFile.write(seekpos(index), bulkchunk,
0362: offset, recordsize); // write chunk and occupy space
0363: USAGE.USEDC++;
0364: USAGE.writeused(false);
0365: entryFile.commit();
0366: }
0367: }
0368: }
0369: //}
0370: }
0371:
0372: private synchronized void checkConsistency() {
0373: if ((debugmode) && (entryFile != null))
0374: try { // in debug mode
0375: long efl = entryFile.length();
0376: assert ((efl - POS_NODES) % ((long) recordsize)) == 0 : "rest = "
0377: + ((entryFile.length() - POS_NODES) % ((long) recordsize))
0378: + ", USEDC = "
0379: + this .USEDC
0380: + ", FREEC = "
0381: + this .FREEC
0382: + ", recordsize = "
0383: + recordsize + ", file = " + filename;
0384: long calculated_used = (efl - POS_NODES)
0385: / ((long) recordsize);
0386: if (calculated_used != this .USEDC + this .FREEC)
0387: logFailure("INCONSISTENCY in USED computation: calculated_used = "
0388: + calculated_used
0389: + ", USEDC = "
0390: + this .USEDC
0391: + ", FREEC = "
0392: + this .FREEC
0393: + ", recordsize = "
0394: + recordsize + ", file = " + filename);
0395: } catch (IOException e) {
0396: assert false;
0397: }
0398: }
0399: }
0400:
0401: public static int staticsize(File file) {
0402: if (!(file.exists()))
0403: return 0;
0404: try {
0405: kelondroRA ra = new kelondroFileRA(file.getCanonicalPath());
0406: kelondroIOChunks entryFile = new kelondroRAIOChunks(ra, ra
0407: .name());
0408:
0409: int used = entryFile.readInt(POS_USEDC); // works only if consistency with file size is given
0410: entryFile.close();
0411: ra.close();
0412: return used;
0413: } catch (FileNotFoundException e) {
0414: return 0;
0415: } catch (IOException e) {
0416: return 0;
0417: }
0418: }
0419:
0420: public kelondroAbstractRecords(File file, boolean useNodeCache,
0421: short ohbytec, short ohhandlec, kelondroRow rowdef,
0422: int FHandles, int txtProps, int txtPropWidth)
0423: throws IOException {
0424: // opens an existing file or creates a new file
0425: // file: the file that shall be created
0426: // oha : overhead size array of four bytes: oha[0]=# of bytes, oha[1]=# of shorts, oha[2]=# of ints, oha[3]=# of longs,
0427: // columns: array with size of column width; columns.length is number of columns
0428: // FHandles: number of integer properties
0429: // txtProps: number of text properties
0430:
0431: this .fileExisted = file.exists(); // can be used by extending class to track if this class created the file
0432: this .OHBYTEC = ohbytec;
0433: this .OHHANDLEC = ohhandlec;
0434: this .ROW = rowdef; // create row
0435: this .TXTPROPW = txtPropWidth;
0436:
0437: if (file.exists()) {
0438: // opens an existing tree
0439: this .filename = file.getCanonicalPath();
0440: kelondroRA raf = new kelondroFileRA(this .filename);
0441: //kelondroRA raf = new kelondroBufferedRA(new kelondroFileRA(this.filename), 1024, 100);
0442: //kelondroRA raf = new kelondroCachedRA(new kelondroFileRA(this.filename), 5000000, 1000);
0443: //kelondroRA raf = new kelondroNIOFileRA(this.filename, (file.length() < 4000000), 10000);
0444: initExistingFile(raf, useNodeCache);
0445: } else {
0446: this .filename = file.getCanonicalPath();
0447: kelondroRA raf = new kelondroFileRA(this .filename);
0448: // kelondroRA raf = new kelondroBufferedRA(new kelondroFileRA(this.filename), 1024, 100);
0449: // kelondroRA raf = new kelondroNIOFileRA(this.filename, false, 10000);
0450: initNewFile(raf, FHandles, txtProps);
0451: }
0452: assignRowdef(rowdef);
0453: if (fileExisted) {
0454: kelondroByteOrder oldOrder = readOrderType();
0455: if ((oldOrder != null)
0456: && (!(oldOrder.equals(rowdef.objectOrder)))) {
0457: writeOrderType(); // write new order type
0458: //throw new IOException("wrong object order upon initialization. new order is " + rowdef.objectOrder.toString() + ", old order was " + oldOrder.toString());
0459: }
0460: } else {
0461: // create new file structure
0462: writeOrderType();
0463: }
0464: }
0465:
0466: public kelondroAbstractRecords(kelondroRA ra, String filename,
0467: boolean useCache, short ohbytec, short ohhandlec,
0468: kelondroRow rowdef, int FHandles, int txtProps,
0469: int txtPropWidth, boolean exitOnFail) {
0470: // this always creates a new file
0471: this .fileExisted = false;
0472: this .filename = filename;
0473: this .OHBYTEC = ohbytec;
0474: this .OHHANDLEC = ohhandlec;
0475: this .ROW = rowdef; // create row
0476: this .TXTPROPW = txtPropWidth;
0477:
0478: try {
0479: initNewFile(ra, FHandles, txtProps);
0480: } catch (IOException e) {
0481: logFailure("cannot create / " + e.getMessage());
0482: if (exitOnFail)
0483: System.exit(-1);
0484: }
0485: assignRowdef(rowdef);
0486: writeOrderType();
0487: }
0488:
0489: public void reset() throws IOException {
0490: kelondroRA ra = this .entryFile.getRA();
0491: File f = new File(ra.name());
0492: this .entryFile.close();
0493: f.delete();
0494: ra = new kelondroFileRA(f);
0495: initNewFile(ra, this .HANDLES.length, this .TXTPROPS.length);
0496: }
0497:
0498: private void initNewFile(kelondroRA ra, int FHandles, int txtProps)
0499: throws IOException {
0500:
0501: // create new Chunked IO
0502: this .entryFile = new kelondroRAIOChunks(ra, ra.name());
0503:
0504: // store dynamic run-time data
0505: this .overhead = this .OHBYTEC + 4 * this .OHHANDLEC;
0506: this .recordsize = this .overhead + ROW.objectsize;
0507: this .headchunksize = overhead + ROW.width(0);
0508: this .tailchunksize = this .recordsize - this .headchunksize;
0509: this .spaceChunk = fillSpaceChunk(recordsize);
0510:
0511: // store dynamic run-time seek pointers
0512: POS_HANDLES = POS_COLWIDTHS + ROW.columns() * 4;
0513: POS_TXTPROPS = POS_HANDLES + FHandles * 4;
0514: POS_NODES = POS_TXTPROPS + txtProps * this .TXTPROPW;
0515: //System.out.println("*** DEBUG: POS_NODES = " + POS_NODES + " for " + filename);
0516:
0517: // store dynamic back-up variables
0518: USAGE = new usageControl(true);
0519: HANDLES = new kelondroHandle[FHandles];
0520: for (int i = 0; i < FHandles; i++)
0521: HANDLES[i] = new kelondroHandle(kelondroHandle.NUL);
0522: TXTPROPS = new byte[txtProps][];
0523: for (int i = 0; i < txtProps; i++)
0524: TXTPROPS[i] = new byte[0];
0525:
0526: // write data to file
0527: entryFile.writeByte(POS_MAGIC, 4); // magic marker for this file type
0528: entryFile.writeByte(POS_BUSY, 0); // unlock: default
0529: entryFile.writeShort(POS_PORT, 4444); // default port (not used yet)
0530: entryFile.write(POS_DESCR, "--AnomicRecords file structure--"
0531: .getBytes());
0532: entryFile.writeShort(POS_COLUMNS, this .ROW.columns());
0533: entryFile.writeShort(POS_OHBYTEC, OHBYTEC);
0534: entryFile.writeShort(POS_OHHANDLEC, OHHANDLEC);
0535: entryFile.writeInt(POS_USEDC, 0);
0536: entryFile.writeInt(POS_FREEC, 0);
0537: entryFile.writeInt(POS_FREEH, this .USAGE.FREEH.index);
0538: entryFile.write(POS_MD5PW, "PASSWORDPASSWORD".getBytes());
0539: entryFile.write(POS_ENCRYPTION, "ENCRYPTION!#$%&?".getBytes());
0540: entryFile.writeLong(POS_OFFSET, POS_NODES);
0541: entryFile.writeInt(POS_INTPROPC, FHandles);
0542: entryFile.writeInt(POS_TXTPROPC, txtProps);
0543: entryFile.writeInt(POS_TXTPROPW, this .TXTPROPW);
0544:
0545: // write configuration arrays
0546: for (int i = 0; i < this .ROW.columns(); i++) {
0547: entryFile
0548: .writeInt(POS_COLWIDTHS + 4 * i, this .ROW.width(i));
0549: }
0550: for (int i = 0; i < this .HANDLES.length; i++) {
0551: entryFile.writeInt(POS_HANDLES + 4 * i, kelondroHandle.NUL);
0552: HANDLES[i] = new kelondroHandle(kelondroHandle.NUL);
0553: }
0554: byte[] ea = new byte[TXTPROPW];
0555: for (int j = 0; j < TXTPROPW; j++)
0556: ea[j] = 0;
0557: for (int i = 0; i < this .TXTPROPS.length; i++) {
0558: entryFile.write(POS_TXTPROPS + TXTPROPW * i, ea);
0559: }
0560:
0561: this .entryFile.commit();
0562: }
0563:
0564: private static final byte[] fillSpaceChunk(int size) {
0565: byte[] chunk = new byte[size];
0566: while (--size >= 0)
0567: chunk[size] = (byte) 0xff;
0568: return chunk;
0569: }
0570:
0571: public void setDescription(byte[] description) throws IOException {
0572: if (description.length > LEN_DESCR)
0573: entryFile.write(POS_DESCR, description, 0, LEN_DESCR);
0574: else
0575: entryFile.write(POS_DESCR, description);
0576: }
0577:
0578: public byte[] getDescription() throws IOException {
0579: byte[] b = new byte[LEN_DESCR];
0580: entryFile.readFully(POS_DESCR, b, 0, LEN_DESCR);
0581: return b;
0582: }
0583:
0584: public void setLogger(Logger newLogger) {
0585: this .theLogger = newLogger;
0586: }
0587:
0588: public void logWarning(String message) {
0589: if (this .theLogger == null)
0590: System.err.println("KELONDRO WARNING " + this .filename
0591: + ": " + message);
0592: else
0593: this .theLogger.warning("KELONDRO WARNING " + this .filename
0594: + ": " + message);
0595: }
0596:
0597: public void logFailure(String message) {
0598: if (this .theLogger == null)
0599: System.err.println("KELONDRO FAILURE " + this .filename
0600: + ": " + message);
0601: else
0602: this .theLogger.severe("KELONDRO FAILURE " + this .filename
0603: + ": " + message);
0604: }
0605:
0606: public void logFine(String message) {
0607: if (this .theLogger == null)
0608: System.out.println("KELONDRO DEBUG " + this .filename + ": "
0609: + message);
0610: else
0611: this .theLogger.fine("KELONDRO DEBUG " + this .filename
0612: + ": " + message);
0613: }
0614:
0615: public kelondroAbstractRecords(kelondroRA ra, String filename,
0616: boolean useNodeCache) throws IOException {
0617: this .fileExisted = false;
0618: this .filename = filename;
0619: initExistingFile(ra, useNodeCache);
0620: readOrderType();
0621: }
0622:
0623: private void initExistingFile(kelondroRA ra, boolean useBuffer)
0624: throws IOException {
0625: // read from Chunked IO
0626: if (useBuffer) {
0627: this .entryFile = new kelondroBufferedIOChunks(ra,
0628: ra.name(), 0, 30000 + random.nextLong() % 30000);
0629: } else {
0630: this .entryFile = new kelondroRAIOChunks(ra, ra.name());
0631: }
0632:
0633: // read dynamic variables that are back-ups of stored values in file;
0634: // read/defined on instantiation
0635:
0636: this .OHBYTEC = entryFile.readShort(POS_OHBYTEC);
0637: this .OHHANDLEC = entryFile.readShort(POS_OHHANDLEC);
0638:
0639: kelondroColumn[] COLDEFS = new kelondroColumn[entryFile
0640: .readShort(POS_COLUMNS)];
0641: this .HANDLES = new kelondroHandle[entryFile
0642: .readInt(POS_INTPROPC)];
0643: this .TXTPROPS = new byte[entryFile.readInt(POS_TXTPROPC)][];
0644: this .TXTPROPW = entryFile.readInt(POS_TXTPROPW);
0645:
0646: if (COLDEFS.length == 0)
0647: throw new kelondroException(filename,
0648: "init: zero columns; strong failure");
0649:
0650: // calculate dynamic run-time seek pointers
0651: POS_HANDLES = POS_COLWIDTHS + COLDEFS.length * 4;
0652: POS_TXTPROPS = POS_HANDLES + HANDLES.length * 4;
0653: POS_NODES = POS_TXTPROPS + TXTPROPS.length * TXTPROPW;
0654: //System.out.println("*** DEBUG: POS_NODES = " + POS_NODES + " for " + filename);
0655:
0656: // read configuration arrays
0657: for (int i = 0; i < COLDEFS.length; i++) {
0658: COLDEFS[i] = new kelondroColumn("col-" + i,
0659: kelondroColumn.celltype_binary,
0660: kelondroColumn.encoder_bytes, entryFile
0661: .readInt(POS_COLWIDTHS + 4 * i), "");
0662: }
0663: for (int i = 0; i < HANDLES.length; i++) {
0664: HANDLES[i] = new kelondroHandle(entryFile
0665: .readInt(POS_HANDLES + 4 * i));
0666: }
0667: for (int i = 0; i < TXTPROPS.length; i++) {
0668: TXTPROPS[i] = new byte[TXTPROPW];
0669: entryFile.readFully(POS_TXTPROPS + TXTPROPW * i,
0670: TXTPROPS[i], 0, TXTPROPS[i].length);
0671: }
0672: this .ROW = new kelondroRow(COLDEFS, readOrderType(), 0);
0673:
0674: // assign remaining values that are only present at run-time
0675: this .overhead = OHBYTEC + 4 * OHHANDLEC;
0676: this .recordsize = this .overhead + ROW.objectsize;
0677: this .headchunksize = this .overhead + this .ROW.width(0);
0678: this .tailchunksize = this .recordsize - this .headchunksize;
0679: this .spaceChunk = fillSpaceChunk(recordsize);
0680:
0681: // init USAGE, must be done at the end because it needs the recordsize value
0682: this .USAGE = new usageControl(false);
0683: }
0684:
0685: private void writeOrderType() {
0686: try {
0687: setDescription((this .ROW.objectOrder == null) ? "__"
0688: .getBytes() : this .ROW.objectOrder.signature()
0689: .getBytes());
0690: } catch (IOException e) {
0691: }
0692: }
0693:
0694: private kelondroByteOrder readOrderType() {
0695: try {
0696: byte[] d = getDescription();
0697: String s = new String(d).substring(0, 2);
0698: return orderBySignature(s);
0699: } catch (IOException e) {
0700: return null;
0701: }
0702: }
0703:
0704: public static kelondroByteOrder orderBySignature(String signature) {
0705: kelondroByteOrder oo = null;
0706: if (oo == null)
0707: oo = kelondroNaturalOrder.bySignature(signature);
0708: if (oo == null)
0709: oo = kelondroBase64Order.bySignature(signature);
0710: if (oo == null)
0711: oo = new kelondroNaturalOrder(true);
0712: return oo;
0713: }
0714:
0715: public String filename() {
0716: return filename;
0717: }
0718:
0719: public synchronized final byte[] bulkRead(int start, int end)
0720: throws IOException {
0721: // a bulk read simply reads a piece of memory from the record file
0722: // this makes only sense if there are no overhead bytes or pointer
0723: // the end value is OUTSIDE the record interval
0724: assert OHBYTEC == 0;
0725: assert OHHANDLEC == 0;
0726: assert start >= 0;
0727: assert end <= USAGE.allCount();
0728: byte[] bulk = new byte[(end - start) * recordsize];
0729: long bulkstart = POS_NODES + ((long) recordsize * (long) start);
0730: entryFile.readFully(bulkstart, bulk, 0, bulk.length);
0731: return bulk;
0732: }
0733:
0734: protected synchronized void deleteNode(kelondroHandle handle)
0735: throws IOException {
0736: USAGE.dispose(handle);
0737: }
0738:
0739: protected void printChunk(kelondroRow.Entry chunk) {
0740: for (int j = 0; j < chunk.columns(); j++)
0741: System.out.print(new String(chunk.getColBytes(j)) + ", ");
0742: }
0743:
0744: public final kelondroRow row() {
0745: return this .ROW;
0746: }
0747:
0748: private final void assignRowdef(kelondroRow rowdef) {
0749: // overwrites a given rowdef
0750: // the new rowdef must be compatible
0751: /*
0752: if ((rowdef.columns() != ROW.columns()) &&
0753: ((rowdef.columns() + 1 != ROW.columns()) ||
0754: (rowdef.column(rowdef.columns() - 1).cellwidth() != (ROW.column(ROW.columns() - 1).cellwidth() + ROW.column(ROW.columns() - 2).cellwidth()))))
0755: throw new kelondroException(this.filename,
0756: "new rowdef '" + rowdef.toString() + "' is not compatible with old rowdef '" + ROW.toString() + "', they have a different number of columns");
0757: */
0758: // adopt encoder and cell type
0759: /*
0760: kelondroColumn col;
0761: for (int i = 0; i < ROW.columns(); i++) {
0762: col = rowdef.column(i);
0763: ROW.column(i).setAttributes(col.nickname(), col.celltype(), col.encoder());
0764: }
0765: */
0766: this .ROW = rowdef;
0767: }
0768:
0769: protected final long seekpos(kelondroHandle handle) {
0770: assert (handle.index >= 0) : "handle index too low: "
0771: + handle.index;
0772: return POS_NODES + ((long) recordsize * (long) handle.index);
0773: }
0774:
0775: protected final long seekpos(int index) {
0776: assert (index >= 0) : "handle index too low: " + index;
0777: return POS_NODES + ((long) recordsize * index);
0778: }
0779:
0780: // additional properties
0781: public final synchronized int handles() {
0782: return this .HANDLES.length;
0783: }
0784:
0785: protected final void setHandle(int pos, kelondroHandle handle)
0786: throws IOException {
0787: if (pos >= HANDLES.length)
0788: throw new IllegalArgumentException(
0789: "setHandle: handle array exceeded");
0790: if (handle == null)
0791: handle = new kelondroHandle(kelondroHandle.NUL);
0792: HANDLES[pos] = handle;
0793: entryFile.writeInt(POS_HANDLES + 4 * pos, handle.index);
0794: }
0795:
0796: protected final kelondroHandle getHandle(int pos) {
0797: if (pos >= HANDLES.length)
0798: throw new IllegalArgumentException(
0799: "getHandle: handle array exceeded");
0800: return (HANDLES[pos].index == kelondroHandle.NUL) ? null
0801: : HANDLES[pos];
0802: }
0803:
0804: // custom texts
0805: public final void setText(int pos, byte[] text) throws IOException {
0806: if (pos >= TXTPROPS.length)
0807: throw new IllegalArgumentException(
0808: "setText: text array exceeded");
0809: if (text.length > TXTPROPW)
0810: throw new IllegalArgumentException(
0811: "setText: text lemgth exceeded");
0812: if (text == null)
0813: text = new byte[0];
0814: TXTPROPS[pos] = text;
0815: entryFile.write(POS_TXTPROPS + TXTPROPW * pos, text);
0816: }
0817:
0818: public final byte[] getText(int pos) {
0819: if (pos >= TXTPROPS.length)
0820: throw new IllegalArgumentException(
0821: "getText: text array exceeded");
0822: return TXTPROPS[pos];
0823: }
0824:
0825: // Returns true if this map contains no key-value mappings.
0826: public final boolean isEmpty() {
0827: return (USAGE.used() == 0);
0828: }
0829:
0830: // Returns the number of key-value mappings in this map.
0831: public synchronized int size() {
0832: return USAGE.used();
0833: }
0834:
0835: protected synchronized final int free() {
0836: return USAGE.FREEC;
0837: }
0838:
0839: protected final Set<kelondroHandle> deletedHandles(long maxTime)
0840: throws kelondroException, IOException {
0841: // initialize set with deleted nodes; the set contains Handle-Objects
0842: // this may last only the given maxInitTime
0843: // if the initTime is exceeded, the method throws an kelondroException
0844: TreeSet<kelondroHandle> markedDeleted = new TreeSet<kelondroHandle>();
0845: long timeLimit = (maxTime < 0) ? Long.MAX_VALUE : System
0846: .currentTimeMillis()
0847: + maxTime;
0848: long seekp;
0849: synchronized (USAGE) {
0850: if (USAGE.FREEC != 0) {
0851: kelondroHandle h = USAGE.FREEH;
0852: long repair_position = POS_FREEH;
0853: while (h.index != kelondroHandle.NUL) {
0854: // check handle
0855: seekp = seekpos(h);
0856: if (seekp > entryFile.length()) {
0857: // repair last hande store position
0858: this .theLogger
0859: .severe("KELONDRO WARNING "
0860: + this .filename
0861: + ": seek position "
0862: + seekp
0863: + "/"
0864: + h.index
0865: + " out of file size "
0866: + entryFile.length()
0867: + "/"
0868: + ((entryFile.length() - POS_NODES) / recordsize)
0869: + " after "
0870: + markedDeleted.size()
0871: + " iterations; patched wrong node");
0872: entryFile.writeInt(repair_position,
0873: kelondroHandle.NUL);
0874: return markedDeleted;
0875: }
0876:
0877: // handle seems to be correct. store handle
0878: markedDeleted.add(h);
0879:
0880: // move to next handle
0881: repair_position = seekp;
0882: h = new kelondroHandle(entryFile.readInt(seekp));
0883: if (h.index == kelondroHandle.NUL)
0884: break;
0885:
0886: // double-check for already stored handles: detect loops
0887: if (markedDeleted.contains(h)) {
0888: // loop detection
0889: this .theLogger.severe("KELONDRO WARNING "
0890: + this .filename
0891: + ": FREE-Queue contains loops");
0892: entryFile.writeInt(repair_position,
0893: kelondroHandle.NUL);
0894: return markedDeleted;
0895: }
0896:
0897: // this appears to be correct. go on.
0898: if (System.currentTimeMillis() > timeLimit)
0899: throw new kelondroException(filename,
0900: "time limit of " + maxTime
0901: + " exceeded; > "
0902: + markedDeleted.size()
0903: + " deleted entries");
0904: }
0905: System.out.println("\nDEBUG: " + markedDeleted.size()
0906: + " deleted entries in " + entryFile.name());
0907: }
0908: }
0909: return markedDeleted;
0910: }
0911:
0912: public synchronized void close() {
0913: if (entryFile == null) {
0914: theLogger.severe("close(): file '" + this .filename
0915: + "' was closed before close was called.");
0916: } else
0917: try {
0918: USAGE.writeused(true);
0919: this .entryFile.close();
0920: theLogger.fine("file '" + this .filename + "' closed.");
0921: } catch (IOException e) {
0922: theLogger.severe("file '" + this .filename
0923: + "': failed to close.");
0924: e.printStackTrace();
0925: }
0926: this .entryFile = null;
0927: }
0928:
0929: public void finalize() {
0930: if (entryFile != null)
0931: close();
0932: this .entryFile = null;
0933: }
0934:
0935: protected final static String[] line2args(String line) {
0936: // parse the command line
0937: if ((line == null) || (line.length() == 0))
0938: return null;
0939: String args[];
0940: StringTokenizer st = new StringTokenizer(line);
0941:
0942: args = new String[st.countTokens()];
0943: for (int i = 0; st.hasMoreTokens(); i++) {
0944: args[i] = st.nextToken();
0945: }
0946: st = null;
0947: return args;
0948: }
0949:
0950: protected final static boolean equals(byte[] a, byte[] b) {
0951: if (a == b)
0952: return true;
0953: if ((a == null) || (b == null))
0954: return false;
0955: if (a.length != b.length)
0956: return false;
0957: for (int n = 0; n < a.length; n++)
0958: if (a[n] != b[n])
0959: return false;
0960: return true;
0961: }
0962:
0963: public final static void NUL2bytes(byte[] b, int offset) {
0964: b[offset] = (byte) (0XFF & (kelondroHandle.NUL >> 24));
0965: b[offset + 1] = (byte) (0XFF & (kelondroHandle.NUL >> 16));
0966: b[offset + 2] = (byte) (0XFF & (kelondroHandle.NUL >> 8));
0967: b[offset + 3] = (byte) (0XFF & kelondroHandle.NUL);
0968: }
0969:
0970: public final static void int2bytes(long i, byte[] b, int offset) {
0971: b[offset] = (byte) (0XFF & (i >> 24));
0972: b[offset + 1] = (byte) (0XFF & (i >> 16));
0973: b[offset + 2] = (byte) (0XFF & (i >> 8));
0974: b[offset + 3] = (byte) (0XFF & i);
0975: }
0976:
0977: public final static int bytes2int(byte[] b, int offset) {
0978: return (((b[offset] & 0xff) << 24)
0979: | ((b[offset + 1] & 0xff) << 16)
0980: | ((b[offset + 2] & 0xff) << 8) | (b[offset + 3] & 0xff));
0981: }
0982:
0983: public void print() throws IOException {
0984: System.out.println("REPORT FOR FILE '" + this .filename + "':");
0985: System.out.println("--");
0986: System.out.println("CONTROL DATA");
0987: System.out.print(" HANDLES : " + HANDLES.length
0988: + " int-values");
0989: if (HANDLES.length == 0)
0990: System.out.println();
0991: else {
0992: System.out.print(" {" + HANDLES[0].toString());
0993: for (int i = 1; i < HANDLES.length; i++)
0994: System.out.print(", " + HANDLES[i].toString());
0995: System.out.println("}");
0996: }
0997: System.out.print(" TXTPROPS : " + TXTPROPS.length
0998: + " strings, max length " + TXTPROPW + " bytes");
0999: if (TXTPROPS.length == 0)
1000: System.out.println();
1001: else {
1002: System.out.print(" {'" + (new String(TXTPROPS[0])).trim());
1003: System.out.print("'");
1004: for (int i = 1; i < TXTPROPS.length; i++)
1005: System.out.print(", '"
1006: + (new String(TXTPROPS[i])).trim() + "'");
1007: System.out.println("}");
1008: }
1009: System.out.println(" USEDC : " + USAGE.used());
1010: System.out.println(" FREEC : " + USAGE.FREEC);
1011: System.out.println(" FREEH : " + USAGE.FREEH.toString());
1012: System.out.println(" NUL repres.: 0x"
1013: + Integer.toHexString(kelondroHandle.NUL));
1014: System.out.println(" Data Offset: 0x"
1015: + Long.toHexString(POS_NODES));
1016: System.out.println("--");
1017: System.out.println("RECORDS");
1018: System.out.print(" Columns : " + row().columns()
1019: + " columns {" + ROW.width(0));
1020: for (int i = 1; i < row().columns(); i++)
1021: System.out.print(", " + ROW.width(i));
1022: System.out.println("}");
1023: System.out.println(" Overhead : " + this .overhead
1024: + " bytes (" + OHBYTEC + " OH bytes, " + OHHANDLEC
1025: + " OH Handles)");
1026: System.out.println(" Recordsize : " + this .recordsize
1027: + " bytes");
1028: System.out.println("--");
1029: System.out.println("DELETED HANDLES");
1030: Set<kelondroHandle> dh = deletedHandles(-1);
1031: Iterator<kelondroHandle> dhi = dh.iterator();
1032: kelondroHandle h;
1033: while (dhi.hasNext()) {
1034: h = (kelondroHandle) dhi.next();
1035: System.out.print(h.index + ", ");
1036: }
1037: System.out.println("\n--");
1038: }
1039:
1040: public String toString() {
1041: return size() + " RECORDS IN FILE " + filename;
1042: }
1043:
1044: public final Iterator<EntryIndex> contentRows(long maxInitTime)
1045: throws kelondroException {
1046: return new contentRowIterator(maxInitTime);
1047: }
1048:
1049: public final class contentRowIterator implements
1050: Iterator<EntryIndex> {
1051: // iterator that iterates all kelondroRow.Entry-objects in the file
1052: // all records that are marked as deleted are omitted
1053:
1054: private Iterator<kelondroNode> nodeIterator;
1055:
1056: public contentRowIterator(long maxInitTime) {
1057: nodeIterator = contentNodes(maxInitTime);
1058: }
1059:
1060: public boolean hasNext() {
1061: return nodeIterator.hasNext();
1062: }
1063:
1064: public EntryIndex next() {
1065: try {
1066: kelondroNode n = (kelondroNode) nodeIterator.next();
1067: return row().newEntryIndex(n.getValueRow(),
1068: n.handle().index);
1069: } catch (IOException e) {
1070: throw new kelondroException(filename, e.getMessage());
1071: }
1072: }
1073:
1074: public void remove() {
1075: throw new UnsupportedOperationException();
1076: }
1077:
1078: }
1079:
1080: protected final Iterator<kelondroNode> contentNodes(long maxInitTime)
1081: throws kelondroException {
1082: // returns an iterator of Node-objects that are not marked as 'deleted'
1083: try {
1084: return new contentNodeIterator(maxInitTime);
1085: } catch (IOException e) {
1086: return new HashSet<kelondroNode>().iterator();
1087: }
1088: }
1089:
1090: protected final class contentNodeIterator implements
1091: Iterator<kelondroNode> {
1092: // iterator that iterates all Node-objects in the file
1093: // all records that are marked as deleted are ommitted
1094: // this is probably also the fastest way to iterate all objects
1095:
1096: private Set<kelondroHandle> markedDeleted;
1097: private kelondroHandle pos;
1098: private byte[] bulk;
1099: private int bulksize;
1100: private int bulkstart; // the offset of the bulk array to the node position
1101: private boolean fullyMarked;
1102: private kelondroNode next;
1103:
1104: public contentNodeIterator(long maxInitTime)
1105: throws IOException, kelondroException {
1106: // initialize markedDeleted set of deleted Handles
1107: markedDeleted = deletedHandles(maxInitTime);
1108: fullyMarked = (maxInitTime < 0);
1109:
1110: // seek first position according the delete node set
1111: pos = new kelondroHandle(0);
1112: while ((markedDeleted.contains(pos))
1113: && (pos.index < USAGE.allCount()))
1114: pos.index++;
1115:
1116: // initialize bulk
1117: bulksize = Math.max(1, Math.min(65536 / recordsize, USAGE
1118: .allCount()));
1119: bulkstart = -bulksize;
1120: bulk = new byte[bulksize * recordsize];
1121: next = (hasNext0()) ? next0() : null;
1122: }
1123:
1124: public kelondroNode next() {
1125: kelondroNode n = next;
1126: next = next0();
1127: return n;
1128: }
1129:
1130: public boolean hasNext() {
1131: return next != null;
1132: }
1133:
1134: public boolean hasNext0() {
1135: return pos.index < USAGE.allCount();
1136: }
1137:
1138: public kelondroNode next0() {
1139: // read Objects until a non-deleted Node appears
1140: while (hasNext0()) {
1141: kelondroNode nn;
1142: try {
1143: nn = next00();
1144: } catch (IOException e) {
1145: serverLog
1146: .logSevere("kelondroCachedRecords",
1147: filename + " failed with "
1148: + e.getMessage(), e);
1149: return null;
1150: }
1151: byte[] key = nn.getKey();
1152: if ((key == null)
1153: || ((key.length == 1) && (key[0] == (byte) 0x80))
1154: || // the NUL pointer ('lost' chain terminator)
1155: (key.length < 3)
1156: || ((key.length > 3) && (key[2] == 0) && (key[3] == 0))
1157: || ((key.length > 3) && (key[0] == (byte) 0x80)
1158: && (key[1] == 0) && (key[2] == 0) && (key[3] == 0))
1159: || ((key.length > 0) && (key[0] == 0)) // a 'lost' pointer within a deleted-chain
1160: ) {
1161: // this is a deleted node; probably not commited with dispose
1162: if (fullyMarked)
1163: try {
1164: USAGE.dispose(nn.handle());
1165: } catch (IOException e) {
1166: } // mark this now as deleted
1167: continue;
1168: }
1169: return nn;
1170: }
1171: return null;
1172: }
1173:
1174: public kelondroNode next00() throws IOException {
1175: // see if the next record is in the bulk, and if not re-fill the bulk
1176: if (pos.index >= (bulkstart + bulksize)) {
1177: bulkstart = pos.index;
1178: int maxlength = Math.min(USAGE.allCount() - bulkstart,
1179: bulksize);
1180: if ((((long) POS_NODES) + ((long) bulkstart)
1181: * ((long) recordsize)) < 0)
1182: serverLog.logSevere("kelondroCachedRecords",
1183: "DEBUG: negative offset. POS_NODES = "
1184: + POS_NODES + ", bulkstart = "
1185: + bulkstart + ", recordsize = "
1186: + recordsize);
1187: if ((maxlength * recordsize) < 0)
1188: serverLog.logSevere("kelondroCachedRecords",
1189: "DEBUG: negative length. maxlength = "
1190: + maxlength + ", recordsize = "
1191: + recordsize);
1192: entryFile.readFully(((long) POS_NODES)
1193: + ((long) bulkstart) * ((long) recordsize),
1194: bulk, 0, maxlength * recordsize);
1195: }
1196: /* POS_NODES = 302, bulkstart = 3277, recordsize = 655386
1197: POS_NODES = 302, bulkstart = 820, recordsize = 2621466
1198: POS_NODES = 302, bulkstart = 13106, recordsize = 163866 */
1199: // read node from bulk
1200: kelondroNode n = newNode(new kelondroHandle(pos.index),
1201: bulk, (pos.index - bulkstart) * recordsize);
1202: pos.index++;
1203: while ((markedDeleted.contains(pos))
1204: && (pos.index < USAGE.allCount()))
1205: pos.index++;
1206: return n;
1207: }
1208:
1209: public void remove() {
1210: throw new UnsupportedOperationException();
1211: }
1212:
1213: }
1214:
1215: public static byte[] trimCopy(byte[] a, int offset, int length) {
1216: if (length > a.length - offset)
1217: length = a.length - offset;
1218: while ((length > 0) && (a[offset + length - 1] == 0))
1219: length--;
1220: if (length == 0)
1221: return null;
1222: byte[] b = new byte[length];
1223: System.arraycopy(a, offset, b, 0, length);
1224: return b;
1225: }
1226:
1227: public abstract kelondroNode newNode(kelondroHandle handle,
1228: byte[] bulk, int offset) throws IOException;
1229:
1230: }
|