001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2002, Centre for Computational Geography
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.index.rtree.cachefs;
018:
019: import com.vividsolutions.jts.geom.Envelope;
020: import org.geotools.index.Data;
021: import org.geotools.index.DataDefinition;
022: import org.geotools.index.DataDefinition.Field;
023: import org.geotools.index.TreeException;
024: import org.geotools.index.rtree.Entry;
025: import org.geotools.index.rtree.Node;
026: import java.io.IOException;
027: import java.nio.ByteBuffer;
028: import java.nio.CharBuffer;
029: import java.nio.channels.FileChannel;
030: import java.util.EmptyStackException;
031:
032: /**
033: * DOCUMENT ME!
034: *
035: * @author Tommaso Nolli
036: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/shapefile/src/main/java/org/geotools/index/rtree/cachefs/FileSystemNode.java $
037: */
038: public class FileSystemNode extends Node {
039: static final int ENTRY_SIZE = 40;
040: private static int pageLen = 0;
041: private Parameters params = null;
042: private boolean flushNeeded;
043: private long parentOffset = -1;
044: private long offset = -1;
045:
046: private FileSystemNode(Parameters params, boolean getFromFree) {
047: super (params.getMaxNodeEntries());
048:
049: this .params = params;
050:
051: if (pageLen == 0) {
052: pageLen = (params.getMaxNodeEntries() * ENTRY_SIZE) + 9; // Flag (leaf or not)
053: }
054:
055: Long oOffset = null;
056:
057: if (getFromFree) {
058: try {
059: oOffset = (Long) this .params.getFreePages().pop();
060: } catch (EmptyStackException e) {
061: // The stack is empty
062: }
063: }
064:
065: this .offset = (oOffset == null) ? (-1) : oOffset.longValue();
066: this .flushNeeded = false;
067: }
068:
069: /**
070: * DOCUMENT ME!
071: *
072: * @param params
073: */
074: public FileSystemNode(Parameters params) {
075: this (params, true);
076: }
077:
078: /**
079: * DOCUMENT ME!
080: *
081: * @param params
082: * @param offset DOCUMENT ME!
083: *
084: * @throws IOException DOCUMENT ME!
085: * @throws TreeException DOCUMENT ME!
086: */
087: public FileSystemNode(Parameters params, long offset)
088: throws IOException, TreeException {
089: this (params, false);
090: this .offset = offset;
091:
092: FileChannel channel = this .params.getChannel();
093: ByteBuffer buf = this .getEmptyByteBuffer();
094: ByteBuffer dataBuf = null;
095:
096: synchronized (channel) {
097: channel.position(offset);
098: channel.read(buf);
099:
100: // Check if I'm a leaf
101: buf.position(0);
102: this .setLeaf(buf.get() == (byte) 1);
103:
104: // If I'm a leaf, read the data
105: if (this .isLeaf()) {
106: dataBuf = this .getEmptyByteBuffer(this .params
107: .getDataDef());
108: channel.read(dataBuf);
109: dataBuf.position(0);
110: }
111: }
112:
113: this .parentOffset = buf.getLong();
114:
115: double x1;
116: double x2;
117: double y1;
118: double y2;
119: long p;
120: Entry entry = null;
121:
122: for (int i = 0; i < this .params.getMaxNodeEntries(); i++) {
123: x1 = buf.getDouble();
124: x2 = buf.getDouble();
125: y1 = buf.getDouble();
126: y2 = buf.getDouble();
127: p = buf.getLong();
128:
129: if ((x1 == 0) && (x2 == 0) && (y1 == 0) && (y2 == 0)
130: && (p == 0)) {
131: // This is an empty entry
132: break;
133: }
134:
135: if (this .isLeaf()) {
136: entry = new Entry(new Envelope(x1, x2, y1, y2), this
137: .loadData(dataBuf, this .params.getDataDef()));
138: } else {
139: entry = new Entry(new Envelope(x1, x2, y1, y2),
140: new Long(p));
141: }
142:
143: this .addEntry(entry);
144: }
145: }
146:
147: /**
148: * DOCUMENT ME!
149: *
150: * @param buf
151: * @param def
152: *
153: *
154: * @throws TreeException
155: */
156: private Data loadData(ByteBuffer buf, DataDefinition def)
157: throws TreeException {
158: Data data = new Data(def);
159:
160: Field field = null;
161:
162: for (int i = 0; i < def.getFieldsCount(); i++) {
163: field = def.getField(i);
164:
165: if (field.getFieldClass().equals(Short.class)) {
166: data.addValue(new Short(buf.getShort()));
167: } else if (field.getFieldClass().equals(Integer.class)) {
168: data.addValue(new Integer(buf.getInt()));
169: } else if (field.getFieldClass().equals(Long.class)) {
170: data.addValue(new Long(buf.getLong()));
171: } else if (field.getFieldClass().equals(Float.class)) {
172: data.addValue(new Float(buf.getFloat()));
173: } else if (field.getFieldClass().equals(Double.class)) {
174: data.addValue(new Double(buf.getDouble()));
175: } else if (field.getFieldClass().equals(String.class)) {
176: byte[] bytes = new byte[field.getEncodedLen()];
177: buf.get(bytes);
178:
179: CharBuffer cb = def.getCharset().decode(
180: ByteBuffer.wrap(bytes));
181: cb.position(0);
182: data.addValue(cb.toString().trim());
183: }
184: }
185:
186: return data;
187: }
188:
189: /**
190: * DOCUMENT ME!
191: *
192: */
193: private ByteBuffer getEmptyByteBuffer() {
194: return ByteBuffer.allocate(pageLen);
195: }
196:
197: /**
198: * DOCUMENT ME!
199: *
200: * @param dataDef
201: *
202: */
203: private ByteBuffer getEmptyByteBuffer(DataDefinition dataDef) {
204: int bufLen = dataDef.getEncodedLen()
205: * this .params.getMaxNodeEntries();
206:
207: return ByteBuffer.allocate(bufLen);
208: }
209:
210: /**
211: * DOCUMENT ME!
212: *
213: */
214: long getOffset() {
215: return this .offset;
216: }
217:
218: /**
219: * @see org.geotools.rtree.Node#getParent()
220: */
221: public Node getParent() throws TreeException {
222: FileSystemNode node = null;
223:
224: try {
225: node = this .params.getFromCache(this .parentOffset);
226: } catch (IOException e) {
227: throw new TreeException(e);
228: }
229:
230: return node;
231: }
232:
233: /**
234: * Flushes this node to disk
235: * <pre>
236: * Node page structure:
237: * 1 * byte --> 1 = leaf, 2 = non leaf
238: * 1 * long --> parent offset
239: * entries len * 40 --> the entries
240: *
241: * each entry is as follow
242: * 4 * double --> the bounding box (x1, x2, y1, y2)
243: * 1 * long --> the pointer (-1 if leaf)
244: *
245: * Data pages are immediatly after leaf Node pages.
246: *
247: * </pre>
248: *
249: * @throws TreeException DOCUMENT ME!
250: */
251: public void flush() throws TreeException {
252: if (!this .flushNeeded) {
253: return;
254: }
255:
256: FileChannel channel = this .params.getChannel();
257:
258: try {
259: // Prepare buffers...
260: ByteBuffer buf = this .getEmptyByteBuffer();
261: ByteBuffer dataBuf = null;
262:
263: if (this .isLeaf()) {
264: dataBuf = this .getEmptyByteBuffer(this .params
265: .getDataDef());
266: }
267:
268: buf.put(this .isLeaf() ? (byte) 1 : (byte) 2);
269: buf.putLong(this .parentOffset);
270:
271: long pointOffset = 0;
272:
273: if (this .getEntriesCount() > 0) {
274: Envelope env = null;
275:
276: for (int i = 0; i < this .getEntriesCount(); i++) {
277: env = this .entries[i].getBounds();
278: buf.putDouble(env.getMinX());
279: buf.putDouble(env.getMaxX());
280: buf.putDouble(env.getMinY());
281: buf.putDouble(env.getMaxY());
282:
283: Object objData = this .entries[i].getData();
284:
285: if (this .isLeaf()) {
286: this .storeKeyData(dataBuf, (Data) objData);
287: pointOffset = -1;
288: } else {
289: pointOffset = ((Long) objData).longValue();
290: }
291:
292: buf.putLong(pointOffset);
293: }
294: }
295:
296: synchronized (channel) {
297: if (this .offset == -1) {
298: throw new TreeException("Cannot flush a new node!");
299: }
300:
301: buf.position(0);
302: channel.position(this .offset);
303: channel.write(buf);
304:
305: // If I'm a leaf, then store my Data
306: if (this .isLeaf()) {
307: dataBuf.position(0);
308: channel.write(dataBuf);
309: }
310:
311: if (this .params.getForceChannel()) {
312: channel.force(false);
313: }
314: }
315:
316: this .flushNeeded = false;
317: } catch (IOException e) {
318: throw new TreeException(e);
319: }
320: }
321:
322: /**
323: * @see org.geotools.rtree.Node#save()
324: */
325: protected void doSave() throws TreeException {
326: FileChannel channel = this .params.getChannel();
327:
328: try {
329: // Allocate needed space for this node
330: if (this .offset == -1) {
331: /*
332: synchronized (channel) {
333: this.offset = channel.size();
334: channel.position(this.offset);
335:
336: ByteBuffer buf = this.getEmptyByteBuffer();
337: buf.position(0);
338: channel.write(buf);
339:
340: if (this.isLeaf()) {
341: buf = this.getEmptyByteBuffer(this.params.getDataDef());
342: buf.position(0);
343: channel.write(buf);
344: }
345: if (this.params.getForceChannel()) {
346: channel.force(false);
347: }
348: }
349: */
350: int len = pageLen;
351:
352: if (this .isLeaf()) {
353: len += (this .params.getDataDef().getEncodedLen() * this .params
354: .getMaxNodeEntries());
355: }
356:
357: this .offset = this .params.getNewNodeOffset(len);
358:
359: this .flushNeeded = true;
360: }
361:
362: // Change parentOffset of my childrens
363: if (this .isChanged && !this .isLeaf()) {
364: FileSystemNode child = null;
365:
366: for (int i = 0; i < this .entriesCount; i++) {
367: child = this .params
368: .getFromCache(((Long) this .entries[i]
369: .getData()).longValue());
370:
371: child.setParent(this );
372: }
373: }
374:
375: this .params.putToCache(this );
376: } catch (IOException e) {
377: throw new TreeException(e);
378: }
379:
380: /*
381: try {
382: // Prepare buffers...
383: ByteBuffer buf = this.getEmptyByteBuffer();
384: ByteBuffer dataBuf = null;
385: if (this.isLeaf()) {
386: dataBuf = this.getEmptyByteBuffer(this.params.getDataDef());
387: }
388:
389: buf.put(this.isLeaf() ? (byte)1 : (byte)2);
390: buf.putLong(this.parentOffset);
391:
392: long pointOffset = 0;
393: if (this.getEntriesCount() > 0) {
394: Envelope env = null;
395: for (int i = 0; i < this.getEntriesCount(); i++) {
396: env = this.entries[i].getBounds();
397: buf.putDouble(env.getMinX());
398: buf.putDouble(env.getMaxX());
399: buf.putDouble(env.getMinY());
400: buf.putDouble(env.getMaxY());
401:
402: Object objData = this.entries[i].getData();
403: if (this.isLeaf()) {
404: this.storeKeyData(dataBuf, (Data)objData);
405: pointOffset = -1;
406: } else {
407: pointOffset = ((Long)objData).longValue();
408: }
409:
410: buf.putLong(pointOffset);
411: }
412: }
413: synchronized (channel) {
414: if (this.offset == -1) {
415: // I'm a new Node
416: this.offset = channel.size();
417: }
418:
419: buf.position(0);
420: channel.position(this.offset);
421: channel.write(buf);
422:
423: // If I'm a leaf, then store my Data
424: if (this.isLeaf()) {
425: dataBuf.position(0);
426: channel.write(dataBuf);
427: }
428:
429: // Change parentOffset of my childrens
430: if (this.isChanged && !this.isLeaf()) {
431: ByteBuffer childBuf = ByteBuffer.allocate(8);
432: childBuf.putLong(this.offset);
433: Long pos = null;
434: for (int i = 0; i < this.entriesCount; i++) {
435: pos = (Long)this.entries[i].getData();
436: childBuf.flip();
437: channel.position(pos.longValue() + 1);
438: channel.write(childBuf);
439: }
440: }
441:
442: if (this.params.getForceChannel()) {
443: channel.force(false);
444: }
445: }
446:
447: } catch (IOException e) {
448: throw new TreeException(e);
449: }
450: */
451: }
452:
453: /**
454: * Force this node flush
455: *
456: * @throws Throwable DOCUMENT ME!
457: */
458: protected void finalize() throws Throwable {
459: this .flush();
460: }
461:
462: /**
463: * DOCUMENT ME!
464: *
465: * @param buf
466: * @param data
467: *
468: * @throws IOException
469: */
470: private void storeKeyData(ByteBuffer buf, Data data)
471: throws IOException {
472: Object val = null;
473: Field field = null;
474:
475: for (int i = 0; i < data.getValuesCount(); i++) {
476: val = data.getValue(i);
477: field = data.getDefinition().getField(i);
478:
479: if (val instanceof Short) {
480: buf.putShort(((Short) val).shortValue());
481: } else if (val instanceof Integer) {
482: buf.putInt(((Integer) val).intValue());
483: } else if (val instanceof Long) {
484: buf.putLong(((Long) val).longValue());
485: } else if (val instanceof Float) {
486: buf.putFloat(((Float) val).floatValue());
487: } else if (val instanceof Double) {
488: buf.putDouble(((Double) val).doubleValue());
489: } else if (val instanceof String) {
490: ByteBuffer strBuffer = ByteBuffer.allocate(field
491: .getEncodedLen());
492:
493: ByteBuffer enc = data.getDefinition().getCharset()
494: .encode(val.toString());
495:
496: enc.position(0);
497: strBuffer.put(enc);
498: strBuffer.position(0);
499: buf.put(strBuffer);
500: }
501: }
502: }
503:
504: /**
505: * DOCUMENT ME!
506: *
507: * @throws IOException
508: */
509: void free() throws IOException {
510: if (this .offset < 0) {
511: return;
512: }
513:
514: /*
515: FileChannel channel = this.params.getChannel();
516:
517: ByteBuffer buf = this.getEmptyByteBuffer();
518:
519: synchronized (channel) {
520: channel.position(this.offset);
521: channel.write(buf);
522:
523: if (this.isLeaf()) {
524: buf = this.getEmptyByteBuffer(this.params.getDataDef());
525: channel.write(buf);
526: }
527:
528: if (this.params.getForceChannel()) {
529: channel.force(false);
530: }
531: }
532: */
533: this .flushNeeded = false;
534: this .params.removeFromCache(this );
535: this .params.getFreePages().push(new Long(this .offset));
536: }
537:
538: /**
539: * @see org.geotools.rtree.Node#getEntry(org.geotools.rtree.Node)
540: */
541: protected Entry getEntry(Node node) {
542: FileSystemNode fn = (FileSystemNode) node;
543:
544: Entry ret = null;
545: Long l = null;
546:
547: for (int i = 0; i < this .getEntriesCount(); i++) {
548: l = (Long) this .entries[i].getData();
549:
550: if (l.longValue() == fn.getOffset()) {
551: ret = this .entries[i];
552:
553: break;
554: }
555: }
556:
557: return ret;
558: }
559:
560: /**
561: * @see org.geotools.rtree.Node#setParent(org.geotools.rtree.Node)
562: */
563: public void setParent(Node node) {
564: if (node == null) {
565: this .parentOffset = -1;
566: } else {
567: FileSystemNode fn = (FileSystemNode) node;
568: this .parentOffset = fn.getOffset();
569: }
570:
571: this .flushNeeded = true;
572: }
573:
574: /**
575: * @see java.lang.Object#equals(java.lang.Object)
576: */
577: public boolean equals(Object obj) {
578: FileSystemNode comp = (FileSystemNode) obj;
579:
580: return this.getOffset() == comp.getOffset();
581: }
582: }
|