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 com.vividsolutions.jts.geom.Envelope;
020: import org.geotools.index.DataDefinition;
021: import org.geotools.index.DataDefinition.Field;
022: import org.geotools.index.TreeException;
023: import org.geotools.index.rtree.Entry;
024: import org.geotools.index.rtree.Node;
025: import org.geotools.index.rtree.PageStore;
026: import java.io.File;
027: import java.io.IOException;
028: import java.io.RandomAccessFile;
029: import java.nio.ByteBuffer;
030: import java.nio.CharBuffer;
031: import java.nio.channels.FileChannel;
032: import java.nio.charset.Charset;
033:
034: /**
035: * DOCUMENT ME!
036: *
037: * @author Tommaso Nolli
038: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/shapefile/src/main/java/org/geotools/index/rtree/fs/FileSystemPageStore.java $
039: */
040: public class FileSystemPageStore extends PageStore {
041: protected static final byte B_SHORT = (byte) 1;
042: protected static final byte B_INTEGER = (byte) 2;
043: protected static final byte B_LONG = (byte) 3;
044: protected static final byte B_FLOAT = (byte) 4;
045: protected static final byte B_DOUBLE = (byte) 5;
046: protected static final byte B_STRING = (byte) 6;
047: private static final int DEF_MAX = 50;
048: private static final int DEF_MIN = 25;
049: private static final short DEF_SPLIT = SPLIT_QUADRATIC;
050: private static final int FILE_VERSION = 19730103;
051: private Parameters params = new Parameters();
052: private RandomAccessFile raFile;
053: private FileChannel channel;
054: private ByteBuffer header;
055: private FileSystemNode root;
056:
057: /**
058: * Loads an index from the specified <code>File</code>, if the file doesn't
059: * exists or is 0 length, a new index will be created with default values
060: * for maxNodeEntries, minNodeEntries and splitAlgorithm
061: *
062: * @param file The file that stores the index
063: *
064: * @throws TreeException
065: */
066: public FileSystemPageStore(File file) throws TreeException {
067: super ();
068:
069: if (file.isDirectory()) {
070: throw new TreeException("Cannot use a directory as index!");
071: }
072:
073: this .params.setMaxNodeEntries(DEF_MAX);
074: this .params.setMinNodeEntries(DEF_MIN);
075: this .params.setSplitAlg(DEF_SPLIT);
076:
077: try {
078: this .init(file);
079: } catch (IOException e) {
080: throw new TreeException(e);
081: }
082: }
083:
084: /**
085: * DOCUMENT ME!
086: *
087: * @param file
088: * @param def
089: *
090: * @throws TreeException
091: */
092: public FileSystemPageStore(File file, DataDefinition def)
093: throws TreeException {
094: this (file, def, DEF_MAX, DEF_MIN, DEF_SPLIT);
095: }
096:
097: /**
098: * Create and index with the specified values, if the file exists then an
099: * <code>RTreeException</code> will be thrown.
100: *
101: * @param file The file to store the index
102: * @param def DOCUMENT ME!
103: * @param maxNodeEntries
104: * @param minNodeEntries
105: * @param splitAlg
106: *
107: * @throws TreeException
108: */
109: public FileSystemPageStore(File file, DataDefinition def,
110: int maxNodeEntries, int minNodeEntries, short splitAlg)
111: throws TreeException {
112: super (def, maxNodeEntries, minNodeEntries, splitAlg);
113:
114: if (file.exists() && (file.length() != 0)) {
115: throw new TreeException("Cannot set dataDefinition, "
116: + "maxNodesEntries and "
117: + "minNodeEntries to the existing index " + file);
118: }
119:
120: if (file.isDirectory()) {
121: throw new TreeException("Cannot use a directory as index!");
122: }
123:
124: this .params.setDataDef(def);
125: this .params.setMaxNodeEntries(maxNodeEntries);
126: this .params.setMinNodeEntries(minNodeEntries);
127: this .params.setSplitAlg(splitAlg);
128:
129: try {
130: this .init(file);
131: } catch (IOException e) {
132: throw new TreeException(e);
133: }
134: }
135:
136: /**
137: * DOCUMENT ME!
138: *
139: * @param file
140: *
141: * @throws IOException
142: * @throws TreeException
143: */
144: private void init(File file) throws IOException, TreeException {
145: this .raFile = new RandomAccessFile(file, "rw");
146: this .channel = raFile.getChannel();
147:
148: this .params.setChannel(this .channel);
149:
150: try {
151: if (file.length() > 0) {
152: this .loadIndex();
153: } else {
154: this .prepareIndex();
155: }
156: } catch (TreeException e) {
157: // close the channel, or we won't be able to delete the grx file
158: channel.close();
159: throw new TreeException(e);
160: }
161: }
162:
163: /**
164: * DOCUMENT ME!
165: *
166: * @throws IOException
167: * @throws TreeException
168: */
169: private void loadIndex() throws IOException, TreeException {
170: Charset charset = Charset.forName("US-ASCII");
171:
172: ByteBuffer buf = ByteBuffer.allocate(8);
173: this .channel.read(buf);
174: buf.position(0);
175:
176: if (buf.getInt() != FILE_VERSION) {
177: throw new TreeException("Wrong file version, shoud be "
178: + FILE_VERSION);
179: }
180:
181: // Get the header size
182: int headerSize = buf.getInt();
183: buf.position(0);
184:
185: this .header = ByteBuffer.allocate(headerSize);
186: this .header.put(buf);
187: this .header.mark();
188: this .channel.read(this .header);
189: this .header.reset();
190:
191: this .params.setMaxNodeEntries(this .header.getInt());
192: this .params.setMinNodeEntries(this .header.getInt());
193: this .params.setSplitAlg(this .header.getShort());
194:
195: // Get the charset used for string encoding
196: int chLen = this .header.getInt();
197: byte[] bytes = new byte[chLen];
198: this .header.get(bytes);
199:
200: CharBuffer dummy = charset.decode(ByteBuffer.wrap(bytes));
201: dummy.position(0);
202:
203: String defCharset = dummy.toString();
204:
205: DataDefinition def = new DataDefinition(defCharset);
206: byte type = 0;
207: int remaining = this .header.remaining();
208:
209: for (int i = 0; i < (remaining - 8); i += 5) {
210: type = this .header.get();
211:
212: if (type == B_STRING) {
213: def.addField(this .header.getInt());
214: } else {
215: if (type == B_SHORT) {
216: def.addField(Short.class);
217: } else if (type == B_INTEGER) {
218: def.addField(Integer.class);
219: } else if (type == B_LONG) {
220: def.addField(Long.class);
221: } else if (type == B_FLOAT) {
222: def.addField(Float.class);
223: } else if (type == B_DOUBLE) {
224: def.addField(Double.class);
225: }
226:
227: this .header.getInt(); // Not used
228: }
229: }
230:
231: this .params.setDataDef(def);
232:
233: long rootOffset = this .header.getLong();
234:
235: this .root = new FileSystemNode(this .params, rootOffset);
236: }
237:
238: /**
239: * DOCUMENT ME!
240: *
241: * @throws IOException
242: * @throws TreeException
243: */
244: private void prepareIndex() throws IOException, TreeException {
245: if (this .params.getDataDef() == null) {
246: throw new TreeException("Data definition cannot be null "
247: + "when creating a new index.");
248: }
249:
250: Charset charset = Charset.forName("US-ASCII");
251: ByteBuffer chBuf = charset.encode(this .params.getDataDef()
252: .getCharset().name());
253: chBuf.position(0);
254:
255: int headerSize = 22 + chBuf.capacity()
256: + (5 * this .params.getDataDef().getFieldsCount()) + 8;
257:
258: this .header = ByteBuffer.allocate(headerSize);
259:
260: this .header.putInt(FILE_VERSION);
261: this .header.putInt(headerSize);
262: this .header.putInt(this .params.getMaxNodeEntries());
263: this .header.putInt(this .params.getMinNodeEntries());
264: this .header.putShort(this .params.getSplitAlg());
265:
266: // Store data definition
267: this .header.putInt(chBuf.capacity());
268: this .header.put(chBuf);
269:
270: Field field = null;
271: Class clazz = null;
272:
273: for (int i = 0; i < this .params.getDataDef().getFieldsCount(); i++) {
274: field = this .params.getDataDef().getField(i);
275: clazz = field.getFieldClass();
276:
277: if (clazz.isAssignableFrom(Short.class)) {
278: this .header.put(B_SHORT);
279: } else if (clazz.isAssignableFrom(Integer.class)) {
280: this .header.put(B_INTEGER);
281: } else if (clazz.isAssignableFrom(Long.class)) {
282: this .header.put(B_LONG);
283: } else if (clazz.isAssignableFrom(Float.class)) {
284: this .header.put(B_FLOAT);
285: } else if (clazz.isAssignableFrom(Double.class)) {
286: this .header.put(B_DOUBLE);
287: } else if (clazz.isAssignableFrom(String.class)) {
288: this .header.put(B_STRING);
289: }
290:
291: this .header.putInt(field.getLen());
292: }
293:
294: this .header.putLong(headerSize); // The initial root offset
295:
296: this .header.position(0);
297: this .channel.write(this .header);
298:
299: if (this .params.getForceChannel()) {
300: this .channel.force(true);
301: }
302:
303: // Create the root
304: this .root = new FileSystemNode(this .params);
305: this .root.setLeaf(true);
306: this .root.save();
307: }
308:
309: /**
310: * @see org.geotools.index.rtree.PageStore#getRoot()
311: */
312: public Node getRoot() {
313: return this .root;
314: }
315:
316: /**
317: * @see org.geotools.index.rtree.PageStore#setRoot(org.geotools.index.rtree.Node)
318: */
319: public void setRoot(Node node) throws TreeException {
320: try {
321: FileSystemNode n = (FileSystemNode) node;
322: n.setParent(null);
323: this .root = n;
324:
325: this .header.position(this .header.limit() - 8);
326: this .header.putLong(n.getOffset());
327:
328: this .header.position(0);
329:
330: synchronized (this .channel) {
331: this .channel.position(0);
332: this .channel.write(this .header);
333:
334: if (this .params.getForceChannel()) {
335: this .channel.force(true);
336: }
337: }
338: } catch (IOException e) {
339: throw new TreeException(e);
340: }
341: }
342:
343: /**
344: * @see org.geotools.index.rtree.PageStore#getEmptyNode(boolean)
345: */
346: public Node getEmptyNode(boolean isLeaf) {
347: FileSystemNode node = new FileSystemNode(params);
348: node.setLeaf(isLeaf);
349:
350: return node;
351: }
352:
353: /**
354: * @see org.geotools.index.rtree.PageStore#getNode(long,
355: * org.geotools.index.rtree.Node)
356: */
357: public Node getNode(Entry parentEntry, Node parent)
358: throws TreeException {
359: Node node = null;
360: long offset = ((Long) parentEntry.getData()).longValue();
361:
362: try {
363: node = new FileSystemNode(this .params, offset);
364: node.setParent(parent);
365: } catch (IOException e) {
366: throw new TreeException(e);
367: }
368:
369: return node;
370: }
371:
372: /**
373: * @see org.geotools.index.rtree.PageStore#createEntryPointingNode(org.geotools.index.rtree.Node)
374: */
375: public Entry createEntryPointingNode(Node node) {
376: FileSystemNode fn = (FileSystemNode) node;
377:
378: return new Entry(new Envelope(fn.getBounds()), new Long(fn
379: .getOffset()));
380: }
381:
382: /**
383: * @see org.geotools.index.rtree.PageStore#free(org.geotools.index.rtree.Node)
384: */
385: public void free(Node node) {
386: try {
387: FileSystemNode fn = (FileSystemNode) node;
388: fn.free();
389: } catch (IOException e) {
390: // Ignore
391: }
392: }
393:
394: /**
395: * @see org.geotools.index.rtree.PageStore#close()
396: */
397: public void close() throws TreeException {
398: try {
399: this .header.position(0);
400:
401: synchronized (this .channel) {
402: this .channel.position(0);
403: this .channel.write(this .header);
404: this .channel.force(true);
405: }
406:
407: this .raFile.close();
408: } catch (IOException e) {
409: e.printStackTrace();
410: throw new TreeException(e);
411: }
412: }
413:
414: /**
415: * @see org.geotools.index.rtree.PageStore#getMaxNodeEntries()
416: */
417: public int getMaxNodeEntries() {
418: return this .params.getMaxNodeEntries();
419: }
420:
421: /**
422: * @see org.geotools.index.rtree.PageStore#getMinNodeEntries()
423: */
424: public int getMinNodeEntries() {
425: return this .params.getMinNodeEntries();
426: }
427:
428: /**
429: * @see org.geotools.index.rtree.PageStore#getSplitAlgorithm()
430: */
431: public short getSplitAlgorithm() {
432: return this .params.getSplitAlg();
433: }
434:
435: /**
436: * @see org.geotools.index.rtree.PageStore#getKeyDefinition()
437: */
438: public DataDefinition getDataDefinition() {
439: return this .params.getDataDef();
440: }
441:
442: /**
443: * If this is set to <code>true</code>, then every write to the index will
444: * call a force() on the associated channel
445: *
446: * @param b true or false
447: */
448: public void setForceChannel(boolean b) {
449: this .params.setForceChannel(b);
450: }
451:
452: /**
453: * DOCUMENT ME!
454: *
455: * @return The state of Force channel parameter
456: */
457: public boolean getForceChannel() {
458: return this.params.getForceChannel();
459: }
460: }
|