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.fs;
018:
019: import java.io.IOException;
020: import java.nio.ByteBuffer;
021: import java.nio.CharBuffer;
022: import java.nio.channels.FileChannel;
023: import java.util.EmptyStackException;
024:
025: import org.geotools.index.Data;
026: import org.geotools.index.DataDefinition;
027: import org.geotools.index.TreeException;
028: import org.geotools.index.DataDefinition.Field;
029: import org.geotools.index.rtree.Entry;
030: import org.geotools.index.rtree.Node;
031:
032: import com.vividsolutions.jts.geom.Envelope;
033:
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/fs/FileSystemNode.java $
037: */
038: public class FileSystemNode extends Node {
039:
040: static final int ENTRY_SIZE = 40;
041:
042: private static int pageLen = 0;
043:
044: private Parameters params = null;
045: private long parentOffset = -1;
046: private long offset = -1;
047:
048: private FileSystemNode(Parameters params, boolean getFromFree) {
049: super (params.getMaxNodeEntries());
050:
051: this .params = params;
052:
053: if (pageLen == 0) {
054: pageLen = params.getMaxNodeEntries() * ENTRY_SIZE + 9; // Flag (leaf or not)
055: }
056:
057: Long oOffset = null;
058: if (getFromFree) {
059: try {
060: oOffset = (Long) this .params.getFreePages().pop();
061: } catch (EmptyStackException e) {
062: // The stack is empty
063: }
064: }
065: this .offset = oOffset == null ? -1 : oOffset.longValue();
066: }
067:
068: /**
069: * @param params
070: */
071: public FileSystemNode(Parameters params) {
072: this (params, true);
073: }
074:
075: /**
076: * @param params
077: * @param offset
078: */
079: public FileSystemNode(Parameters params, long offset)
080: throws IOException, TreeException {
081: this (params, false);
082: this .offset = offset;
083:
084: FileChannel channel = this .params.getChannel();
085: ByteBuffer buf = this .getEmptyByteBuffer();
086: ByteBuffer dataBuf = null;
087:
088: synchronized (channel) {
089: channel.position(offset);
090: channel.read(buf);
091:
092: // Check if I'm a leaf
093: buf.position(0);
094: this .setLeaf(buf.get() == (byte) 1);
095:
096: // If I'm a leaf, read the data
097: if (this .isLeaf()) {
098: dataBuf = this .getEmptyByteBuffer(this .params
099: .getDataDef());
100: channel.read(dataBuf);
101: dataBuf.position(0);
102: }
103: }
104:
105: this .parentOffset = buf.getLong();
106:
107: double x1, x2, y1, y2;
108: long p;
109: Entry entry = null;
110: for (int i = 0; i < this .params.getMaxNodeEntries(); i++) {
111: x1 = buf.getDouble();
112: x2 = buf.getDouble();
113: y1 = buf.getDouble();
114: y2 = buf.getDouble();
115: p = buf.getLong();
116:
117: if (x1 == 0 && x2 == 0 && y1 == 0 && y2 == 0 && p == 0) {
118: // This is an empty entry
119: break;
120: }
121:
122: if (this .isLeaf()) {
123: entry = new Entry(new Envelope(x1, x2, y1, y2), this
124: .loadData(dataBuf, this .params.getDataDef()));
125:
126: } else {
127: entry = new Entry(new Envelope(x1, x2, y1, y2),
128: new Long(p));
129: }
130:
131: this .addEntry(entry);
132: }
133: }
134:
135: /**
136: *
137: * @param buf
138: * @param def
139: * @throws TreeException
140: */
141: private Data loadData(ByteBuffer buf, DataDefinition def)
142: throws TreeException {
143: Data data = new Data(def);
144:
145: Field field = null;
146: for (int i = 0; i < def.getFieldsCount(); i++) {
147: field = def.getField(i);
148:
149: if (field.getFieldClass().equals(Short.class)) {
150: data.addValue(new Short(buf.getShort()));
151: } else if (field.getFieldClass().equals(Integer.class)) {
152: data.addValue(new Integer(buf.getInt()));
153: } else if (field.getFieldClass().equals(Long.class)) {
154: data.addValue(new Long(buf.getLong()));
155: } else if (field.getFieldClass().equals(Float.class)) {
156: data.addValue(new Float(buf.getFloat()));
157: } else if (field.getFieldClass().equals(Double.class)) {
158: data.addValue(new Double(buf.getDouble()));
159: } else if (field.getFieldClass().equals(String.class)) {
160: byte[] bytes = new byte[field.getEncodedLen()];
161: buf.get(bytes);
162: CharBuffer cb = def.getCharset().decode(
163: ByteBuffer.wrap(bytes));
164: cb.position(0);
165: data.addValue(cb.toString().trim());
166: }
167: }
168:
169: return data;
170: }
171:
172: /**
173: *
174: */
175: private ByteBuffer getEmptyByteBuffer() {
176: return ByteBuffer.allocate(pageLen);
177: }
178:
179: /**
180: *
181: * @param dataDef
182: */
183: private ByteBuffer getEmptyByteBuffer(DataDefinition dataDef) {
184: int bufLen = dataDef.getEncodedLen()
185: * this .params.getMaxNodeEntries();
186: return ByteBuffer.allocate(bufLen);
187: }
188:
189: /**
190: *
191: */
192: long getOffset() {
193: return this .offset;
194: }
195:
196: /**
197: * @see org.geotools.rtree.Node#getParent()
198: */
199: public Node getParent() throws TreeException {
200: FileSystemNode node = null;
201: try {
202: node = new FileSystemNode(this .params, this .parentOffset);
203: } catch (IOException e) {
204: throw new TreeException(e);
205: }
206: return node;
207: }
208:
209: /**
210: * @see org.geotools.rtree.Node#save()
211: *
212: * <pre>
213: * Node page structure:
214: * 1 * byte --> 1 = leaf, 2 = non leaf
215: * 1 * long --> parent offset
216: * entries len * 40 --> the entries
217: *
218: * each entry is as follow
219: * 4 * double --> the bounding box (x1, x2, y1, y2)
220: * 1 * long --> the pointer (-1 if leaf)
221: *
222: * Data pages are immediatly after leaf Node pages.
223: *
224: * </pre>
225: *
226: */
227: protected void doSave() throws TreeException {
228: FileChannel channel = this .params.getChannel();
229: try {
230: // Prepare buffers...
231: ByteBuffer buf = this .getEmptyByteBuffer();
232: ByteBuffer dataBuf = null;
233: if (this .isLeaf()) {
234: dataBuf = this .getEmptyByteBuffer(this .params
235: .getDataDef());
236: }
237:
238: buf.put(this .isLeaf() ? (byte) 1 : (byte) 2);
239: buf.putLong(this .parentOffset);
240:
241: long pointOffset = 0;
242: if (this .getEntriesCount() > 0) {
243: Envelope env = null;
244: for (int i = 0; i < this .getEntriesCount(); i++) {
245: env = this .entries[i].getBounds();
246: buf.putDouble(env.getMinX());
247: buf.putDouble(env.getMaxX());
248: buf.putDouble(env.getMinY());
249: buf.putDouble(env.getMaxY());
250:
251: Object objData = this .entries[i].getData();
252: if (this .isLeaf()) {
253: this .storeKeyData(dataBuf, (Data) objData);
254: pointOffset = -1;
255: } else {
256: pointOffset = ((Long) objData).longValue();
257: }
258:
259: buf.putLong(pointOffset);
260: }
261: }
262:
263: synchronized (channel) {
264: if (this .offset == -1) {
265: // I'm a new Node
266: this .offset = channel.size();
267: }
268:
269: buf.position(0);
270: channel.position(this .offset);
271: channel.write(buf);
272:
273: // If I'm a leaf, then store my Data
274: if (this .isLeaf()) {
275: dataBuf.position(0);
276: channel.write(dataBuf);
277: }
278:
279: // Change parentOffset of my childrens
280: if (this .isChanged && !this .isLeaf()) {
281: ByteBuffer childBuf = ByteBuffer.allocate(8);
282: childBuf.putLong(this .offset);
283: Long pos = null;
284: for (int i = 0; i < this .entriesCount; i++) {
285: pos = (Long) this .entries[i].getData();
286: childBuf.flip();
287: channel.position(pos.longValue() + 1);
288: channel.write(childBuf);
289: }
290: }
291:
292: if (this .params.getForceChannel()) {
293: channel.force(false);
294: }
295: }
296:
297: } catch (IOException e) {
298: throw new TreeException(e);
299: }
300: }
301:
302: /**
303: *
304: * @param buf
305: * @param data
306: * @throws IOException
307: */
308: private void storeKeyData(ByteBuffer buf, Data data)
309: throws IOException {
310:
311: Object val = null;
312: Field field = null;
313: for (int i = 0; i < data.getValuesCount(); i++) {
314: val = data.getValue(i);
315: field = data.getDefinition().getField(i);
316:
317: if (val instanceof Short) {
318: buf.putShort(((Short) val).shortValue());
319: } else if (val instanceof Integer) {
320: buf.putInt(((Integer) val).intValue());
321: } else if (val instanceof Long) {
322: buf.putLong(((Long) val).longValue());
323: } else if (val instanceof Float) {
324: buf.putFloat(((Float) val).floatValue());
325: } else if (val instanceof Double) {
326: buf.putDouble(((Double) val).doubleValue());
327: } else if (val instanceof String) {
328: ByteBuffer strBuffer = ByteBuffer.allocate(field
329: .getEncodedLen());
330:
331: ByteBuffer enc = data.getDefinition().getCharset()
332: .encode(val.toString());
333:
334: enc.position(0);
335: strBuffer.put(enc);
336: strBuffer.position(0);
337: buf.put(strBuffer);
338: }
339: }
340: }
341:
342: /**
343: *
344: * @throws IOException
345: */
346: void free() throws IOException {
347: if (this .offset < 0) {
348: return;
349: }
350:
351: FileChannel channel = this .params.getChannel();
352:
353: ByteBuffer buf = this .getEmptyByteBuffer();
354:
355: synchronized (channel) {
356: channel.position(this .offset);
357: channel.write(buf);
358:
359: if (this .isLeaf()) {
360: buf = this .getEmptyByteBuffer(this .params.getDataDef());
361: channel.write(buf);
362: }
363:
364: if (this .params.getForceChannel()) {
365: channel.force(false);
366: }
367: }
368:
369: this .params.getFreePages().push(new Long(this .offset));
370: }
371:
372: /**
373: * @see org.geotools.rtree.Node#getEntry(org.geotools.rtree.Node)
374: */
375: protected Entry getEntry(Node node) {
376: FileSystemNode fn = (FileSystemNode) node;
377:
378: Entry ret = null;
379: Long l = null;
380: for (int i = 0; i < this .getEntriesCount(); i++) {
381: l = (Long) this .entries[i].getData();
382: if (l.longValue() == fn.getOffset()) {
383: ret = this .entries[i];
384: break;
385: }
386: }
387:
388: return ret;
389: }
390:
391: /**
392: * @see org.geotools.rtree.Node#setParent(org.geotools.rtree.Node)
393: */
394: public void setParent(Node node) {
395: if (node == null) {
396: this .parentOffset = -1;
397: } else {
398: FileSystemNode fn = (FileSystemNode) node;
399: this .parentOffset = fn.getOffset();
400: }
401: }
402:
403: /**
404: * @see java.lang.Object#equals(java.lang.Object)
405: */
406: public boolean equals(Object obj) {
407: FileSystemNode comp = (FileSystemNode) obj;
408: return this.getOffset() == comp.getOffset();
409: }
410:
411: }
|