001: // kelondroEcoRecords.java
002: // (C) 2007 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
003: // first published 03.07.2007 on http://yacy.net
004: //
005: // This is a part of YaCy, a peer-to-peer based web search engine
006: //
007: // $LastChangedDate: 2006-04-02 22:40:07 +0200 (So, 02 Apr 2006) $
008: // $LastChangedRevision: 1986 $
009: // $LastChangedBy: orbiter $
010: //
011: // LICENSE
012: //
013: // This program is free software; you can redistribute it and/or modify
014: // it under the terms of the GNU General Public License as published by
015: // the Free Software Foundation; either version 2 of the License, or
016: // (at your option) any later version.
017: //
018: // This program is distributed in the hope that it will be useful,
019: // but WITHOUT ANY WARRANTY; without even the implied warranty of
020: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
021: // GNU General Public License for more details.
022: //
023: // You should have received a copy of the GNU General Public License
024: // along with this program; if not, write to the Free Software
025: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
026:
027: package de.anomic.kelondro;
028:
029: import java.io.File;
030: import java.io.IOException;
031: import java.util.Iterator;
032: import java.util.TreeMap;
033:
034: public class kelondroFullRecords extends kelondroAbstractRecords {
035:
036: // static supervision objects: recognize and coordinate all activities
037: private static TreeMap<String, kelondroFullRecords> recordTracker = new TreeMap<String, kelondroFullRecords>();
038:
039: public kelondroFullRecords(File file, short ohbytec,
040: short ohhandlec, kelondroRow rowdef, int FHandles,
041: int txtProps, int txtPropWidth) throws IOException {
042: super (file, true, ohbytec, ohhandlec, rowdef, FHandles,
043: txtProps, txtPropWidth);
044: recordTracker.put(this .filename, this );
045: }
046:
047: public kelondroFullRecords(kelondroRA ra, String filename,
048: short ohbytec, short ohhandlec, kelondroRow rowdef,
049: int FHandles, int txtProps, int txtPropWidth,
050: boolean exitOnFail) {
051: super (ra, filename, true, ohbytec, ohhandlec, rowdef, FHandles,
052: txtProps, txtPropWidth, exitOnFail);
053: recordTracker.put(this .filename, this );
054: }
055:
056: public kelondroFullRecords(kelondroRA ra, String filename)
057: throws IOException {
058: super (ra, filename, true);
059: recordTracker.put(this .filename, this );
060: }
061:
062: public static final Iterator<String> filenames() {
063: // iterates string objects; all file names from record tracker
064: return recordTracker.keySet().iterator();
065: }
066:
067: protected synchronized void deleteNode(kelondroHandle handle)
068: throws IOException {
069: super .deleteNode(handle);
070: }
071:
072: public synchronized void close() {
073: recordTracker.remove(this .filename);
074: super .close();
075: }
076:
077: public kelondroNode newNode(kelondroHandle handle, byte[] bulk,
078: int offset) throws IOException {
079: return new EcoNode(handle, bulk, offset);
080: }
081:
082: public final class EcoNode implements kelondroNode {
083: private kelondroHandle handle = null; // index of the entry, by default NUL means undefined
084: private byte[] ohChunk = null; // contains overhead values
085: private byte[] bodyChunk = null; // contains all row values
086: private boolean ohChanged = false;
087: private boolean bodyChanged = false;
088:
089: public EcoNode(byte[] rowinstance) throws IOException {
090: // this initializer is used to create nodes from bulk-read byte arrays
091: assert ((rowinstance == null) || (rowinstance.length == ROW.objectsize)) : "bulkchunk.length = "
092: + rowinstance.length
093: + ", ROW.width(0) = "
094: + ROW.width(0);
095: this .handle = new kelondroHandle(USAGE
096: .allocatePayload(rowinstance));
097:
098: // create chunks
099: this .ohChunk = new byte[overhead];
100: this .bodyChunk = new byte[ROW.objectsize];
101: for (int i = this .ohChunk.length - 1; i >= 0; i--)
102: this .ohChunk[i] = (byte) 0xff;
103: if (rowinstance == null) {
104: for (int i = this .bodyChunk.length - 1; i >= 0; i--)
105: this .bodyChunk[i] = (byte) 0xff;
106: } else {
107: System.arraycopy(rowinstance, 0, this .bodyChunk, 0,
108: this .bodyChunk.length);
109: }
110:
111: // mark chunks as not changed, we wrote that already during allocatePayload
112: this .ohChanged = false;
113: this .bodyChanged = false;
114: }
115:
116: public EcoNode(kelondroHandle handle, byte[] bulkchunk,
117: int offset) throws IOException {
118: // this initializer is used to create nodes from bulk-read byte arrays
119: // if write is true, then the chunk in bulkchunk is written to the file
120: // othervise it is considered equal to what is stored in the file
121: // (that is ensured during pre-loaded enumeration)
122: this .handle = handle;
123: boolean changed;
124: if (handle.index >= USAGE.allCount()) {
125: // this causes only a write action if we create a node beyond the end of the file
126: USAGE.allocateRecord(handle.index, bulkchunk, offset);
127: changed = false; // we have already wrote the record, so it is considered as unchanged
128: } else {
129: changed = true;
130: }
131: assert ((bulkchunk == null) || (bulkchunk.length - offset >= recordsize)) : "bulkchunk.length = "
132: + bulkchunk.length
133: + ", offset = "
134: + offset
135: + ", recordsize = " + recordsize;
136:
137: /*if ((offset == 0) && (overhead == 0) && ((bulkchunk == null) || (bulkchunk.length == ROW.objectsize()))) {
138: this.ohChunk = new byte[0];
139: if (bulkchunk == null) {
140: this.bodyChunk = new byte[ROW.objectsize()];
141: } else {
142: this.bodyChunk = bulkchunk;
143: }
144: } else { */
145: // create empty chunks
146: this .ohChunk = new byte[overhead];
147: this .bodyChunk = new byte[ROW.objectsize];
148:
149: // write content to chunks
150: if (bulkchunk != null) {
151: if (overhead > 0)
152: System.arraycopy(bulkchunk, offset, this .ohChunk,
153: 0, overhead);
154: System.arraycopy(bulkchunk, offset + overhead,
155: this .bodyChunk, 0, ROW.objectsize);
156: }
157: //}
158:
159: // mark chunks as changed
160: this .ohChanged = changed;
161: this .bodyChanged = changed;
162: }
163:
164: public EcoNode(kelondroHandle handle) throws IOException {
165: // this creates an entry with an pre-reserved entry position.
166: // values can be written using the setValues() method,
167: // but we expect that values are already there in the file.
168: assert (handle != null) : "node handle is null";
169: assert (handle.index >= 0) : "node handle too low: "
170: + handle.index;
171:
172: if (handle == null)
173: throw new kelondroException(filename,
174: "INTERNAL ERROR: node handle is null.");
175: if (handle.index >= USAGE.allCount()) {
176: throw new kelondroException(
177: filename,
178: "INTERNAL ERROR, Node/init: node handle index "
179: + handle.index
180: + " exceeds size. No auto-fix node was submitted. This is a serious failure.");
181: }
182:
183: // use given handle
184: this .handle = new kelondroHandle(handle.index);
185:
186: // read record
187: this .ohChunk = new byte[overhead];
188: this .bodyChunk = new byte[ROW.objectsize];
189: if (overhead > 0)
190: entryFile.readFully(seekpos(this .handle), this .ohChunk,
191: 0, overhead);
192: entryFile.readFully(seekpos(this .handle) + overhead,
193: this .bodyChunk, 0, this .bodyChunk.length);
194:
195: // mark chunks as not changed
196: this .ohChanged = false;
197: this .bodyChanged = false;
198: }
199:
200: public kelondroHandle handle() {
201: // if this entry has an index, return it
202: if (this .handle.index == kelondroHandle.NUL)
203: throw new kelondroException(filename,
204: "the entry has no index assigned");
205: return this .handle;
206: }
207:
208: public void setOHByte(int i, byte b) {
209: if (i >= OHBYTEC)
210: throw new IllegalArgumentException(
211: "setOHByte: wrong index " + i);
212: if (this .handle.index == kelondroHandle.NUL)
213: throw new kelondroException(filename,
214: "setOHByte: no handle assigned");
215: this .ohChunk[i] = b;
216: this .ohChanged = true;
217: }
218:
219: public void setOHHandle(int i, kelondroHandle otherhandle) {
220: assert (i < OHHANDLEC) : "setOHHandle: wrong array size "
221: + i;
222: assert (this .handle.index != kelondroHandle.NUL) : "setOHHandle: no handle assigned ind file"
223: + filename;
224: if (otherhandle == null) {
225: NUL2bytes(this .ohChunk, OHBYTEC + 4 * i);
226: } else {
227: if (otherhandle.index >= USAGE.allCount())
228: throw new kelondroException(filename,
229: "INTERNAL ERROR, setOHHandles: handle " + i
230: + " exceeds file size ("
231: + handle.index + " >= "
232: + USAGE.allCount() + ")");
233: int2bytes(otherhandle.index, this .ohChunk, OHBYTEC + 4
234: * i);
235: }
236: this .ohChanged = true;
237: }
238:
239: public byte getOHByte(int i) {
240: if (i >= OHBYTEC)
241: throw new IllegalArgumentException(
242: "getOHByte: wrong index " + i);
243: if (this .handle.index == kelondroHandle.NUL)
244: throw new kelondroException(filename,
245: "Cannot load OH values");
246: return this .ohChunk[i];
247: }
248:
249: public kelondroHandle getOHHandle(int i) {
250: if (this .handle.index == kelondroHandle.NUL)
251: throw new kelondroException(filename,
252: "Cannot load OH values");
253: assert (i < OHHANDLEC) : "handle index out of bounds: " + i
254: + " in file " + filename;
255: int h = bytes2int(this .ohChunk, OHBYTEC + 4 * i);
256: return (h == kelondroHandle.NUL) ? null
257: : new kelondroHandle(h);
258: }
259:
260: public synchronized void setValueRow(byte[] row)
261: throws IOException {
262: // if the index is defined, then write values directly to the file, else only to the object
263: if ((row != null) && (row.length != ROW.objectsize))
264: throw new IOException("setValueRow with wrong ("
265: + row.length + ") row length instead correct: "
266: + ROW.objectsize);
267:
268: // set values
269: if (this .handle.index != kelondroHandle.NUL) {
270: this .bodyChunk = row;
271: this .bodyChanged = true;
272: }
273: }
274:
275: public synchronized boolean valid() {
276: // returns true if the key starts with non-zero byte
277: // this may help to detect deleted entries
278: return (this .bodyChunk[0] != 0)
279: && ((this .bodyChunk[0] != -128) || (this .bodyChunk[1] != 0));
280: }
281:
282: public synchronized byte[] getKey() {
283: // read key
284: return trimCopy(this .bodyChunk, 0, ROW.width(0));
285: }
286:
287: public synchronized byte[] getValueRow() throws IOException {
288:
289: if (this .bodyChunk == null) {
290: // load all values from the database file
291: this .bodyChunk = new byte[ROW.objectsize];
292: // read values
293: entryFile.readFully(seekpos(this .handle)
294: + (long) overhead, this .bodyChunk, 0,
295: this .bodyChunk.length);
296: }
297:
298: return this .bodyChunk;
299: }
300:
301: public synchronized void commit() throws IOException {
302: // this must be called after all write operations to the node are finished
303:
304: // place the data to the file
305:
306: boolean doCommit = this .ohChanged || this .bodyChanged;
307:
308: // save head
309: synchronized (entryFile) {
310: if (this .ohChanged) {
311: //System.out.println("WRITEH(" + filename + ", " + seekpos(this.handle) + ", " + this.headChunk.length + ")");
312: assert (ohChunk == null)
313: || (ohChunk.length == overhead);
314: entryFile.write(seekpos(this .handle),
315: (this .ohChunk == null) ? new byte[overhead]
316: : this .ohChunk);
317: this .ohChanged = false;
318: }
319:
320: // save tail
321: if ((this .bodyChunk != null) && (this .bodyChanged)) {
322: //System.out.println("WRITET(" + filename + ", " + (seekpos(this.handle) + headchunksize) + ", " + this.tailChunk.length + ")");
323: assert (this .bodyChunk == null)
324: || (this .bodyChunk.length == ROW.objectsize);
325: entryFile
326: .write(
327: seekpos(this .handle) + overhead,
328: (this .bodyChunk == null) ? new byte[ROW.objectsize]
329: : this .bodyChunk);
330: this .bodyChanged = false;
331: }
332:
333: if (doCommit)
334: entryFile.commit();
335: }
336: }
337:
338: }
339:
340: }
|