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