/* * Geotools2 - OpenSource mapping toolkit * http://geotools.org * (C) 2002, Geotools Project Managment Committee (PMC) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * */ /* * Geotools - OpenSource mapping toolkit * (C) 2002, Centre for Computational Geography * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package org.geotools.index.rtree.fs; import com.vividsolutions.jts.geom.Envelope; import org.geotools.index.DataDefinition; import org.geotools.index.DataDefinition.Field; import org.geotools.index.TreeException; import org.geotools.index.rtree.Entry; import org.geotools.index.rtree.Node; import org.geotools.index.rtree.PageStore; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; /** * DOCUMENT ME! * * @author Tommaso Nolli * @source $URL$ */ public class FileSystemPageStore extends PageStore { protected static final byte B_SHORT = (byte) 1; protected static final byte B_INTEGER = (byte) 2; protected static final byte B_LONG = (byte) 3; protected static final byte B_FLOAT = (byte) 4; protected static final byte B_DOUBLE = (byte) 5; protected static final byte B_STRING = (byte) 6; private static final int DEF_MAX = 50; private static final int DEF_MIN = 25; private static final short DEF_SPLIT = SPLIT_QUADRATIC; private static final int FILE_VERSION = 19730103; private Parameters params = new Parameters(); private RandomAccessFile raFile; private FileChannel channel; private ByteBuffer header; private FileSystemNode root; /** * Loads an index from the specified File, if the file doesn't * exists or is 0 length, a new index will be created with default values * for maxNodeEntries, minNodeEntries and splitAlgorithm * * @param file The file that stores the index * * @throws TreeException */ public FileSystemPageStore(File file) throws TreeException { super(); if (file.isDirectory()) { throw new TreeException("Cannot use a directory as index!"); } this.params.setMaxNodeEntries(DEF_MAX); this.params.setMinNodeEntries(DEF_MIN); this.params.setSplitAlg(DEF_SPLIT); try { this.init(file); } catch (IOException e) { throw new TreeException(e); } } /** * DOCUMENT ME! * * @param file * @param def * * @throws TreeException */ public FileSystemPageStore(File file, DataDefinition def) throws TreeException { this(file, def, DEF_MAX, DEF_MIN, DEF_SPLIT); } /** * Create and index with the specified values, if the file exists then an * RTreeException will be thrown. * * @param file The file to store the index * @param def DOCUMENT ME! * @param maxNodeEntries * @param minNodeEntries * @param splitAlg * * @throws TreeException */ public FileSystemPageStore(File file, DataDefinition def, int maxNodeEntries, int minNodeEntries, short splitAlg) throws TreeException { super(def, maxNodeEntries, minNodeEntries, splitAlg); if (file.exists() && (file.length() != 0)) { throw new TreeException("Cannot set dataDefinition, " + "maxNodesEntries and " + "minNodeEntries to the existing index " + file); } if (file.isDirectory()) { throw new TreeException("Cannot use a directory as index!"); } this.params.setDataDef(def); this.params.setMaxNodeEntries(maxNodeEntries); this.params.setMinNodeEntries(minNodeEntries); this.params.setSplitAlg(splitAlg); try { this.init(file); } catch (IOException e) { throw new TreeException(e); } } /** * DOCUMENT ME! * * @param file * * @throws IOException * @throws TreeException */ private void init(File file) throws IOException, TreeException { this.raFile = new RandomAccessFile(file, "rw"); this.channel = raFile.getChannel(); this.params.setChannel(this.channel); try { if (file.length() > 0) { this.loadIndex(); } else { this.prepareIndex(); } } catch (TreeException e) { throw new TreeException(e); } } /** * DOCUMENT ME! * * @throws IOException * @throws TreeException */ private void loadIndex() throws IOException, TreeException { Charset charset = Charset.forName("US-ASCII"); ByteBuffer buf = ByteBuffer.allocate(8); this.channel.read(buf); buf.position(0); if (buf.getInt() != FILE_VERSION) { throw new TreeException("Wrong file version, shoud be " + FILE_VERSION); } // Get the header size int headerSize = buf.getInt(); buf.position(0); this.header = ByteBuffer.allocate(headerSize); this.header.put(buf); this.header.mark(); this.channel.read(this.header); this.header.reset(); this.params.setMaxNodeEntries(this.header.getInt()); this.params.setMinNodeEntries(this.header.getInt()); this.params.setSplitAlg(this.header.getShort()); // Get the charset used for string encoding int chLen = this.header.getInt(); byte[] bytes = new byte[chLen]; this.header.get(bytes); CharBuffer dummy = charset.decode(ByteBuffer.wrap(bytes)); dummy.position(0); String defCharset = dummy.toString(); DataDefinition def = new DataDefinition(defCharset); byte type = 0; int remaining = this.header.remaining(); for (int i = 0; i < (remaining - 8); i += 5) { type = this.header.get(); if (type == B_STRING) { def.addField(this.header.getInt()); } else { if (type == B_SHORT) { def.addField(Short.class); } else if (type == B_INTEGER) { def.addField(Integer.class); } else if (type == B_LONG) { def.addField(Long.class); } else if (type == B_FLOAT) { def.addField(Float.class); } else if (type == B_DOUBLE) { def.addField(Double.class); } this.header.getInt(); // Not used } } this.params.setDataDef(def); long rootOffset = this.header.getLong(); this.root = new FileSystemNode(this.params, rootOffset); } /** * DOCUMENT ME! * * @throws IOException * @throws TreeException */ private void prepareIndex() throws IOException, TreeException { if (this.params.getDataDef() == null) { throw new TreeException("Data definition cannot be null " + "when creating a new index."); } Charset charset = Charset.forName("US-ASCII"); ByteBuffer chBuf = charset.encode(this.params.getDataDef().getCharset() .name()); chBuf.position(0); int headerSize = 22 + chBuf.capacity() + (5 * this.params.getDataDef().getFieldsCount()) + 8; this.header = ByteBuffer.allocate(headerSize); this.header.putInt(FILE_VERSION); this.header.putInt(headerSize); this.header.putInt(this.params.getMaxNodeEntries()); this.header.putInt(this.params.getMinNodeEntries()); this.header.putShort(this.params.getSplitAlg()); // Store data definition this.header.putInt(chBuf.capacity()); this.header.put(chBuf); Field field = null; Class clazz = null; for (int i = 0; i < this.params.getDataDef().getFieldsCount(); i++) { field = this.params.getDataDef().getField(i); clazz = field.getFieldClass(); if (clazz.isAssignableFrom(Short.class)) { this.header.put(B_SHORT); } else if (clazz.isAssignableFrom(Integer.class)) { this.header.put(B_INTEGER); } else if (clazz.isAssignableFrom(Long.class)) { this.header.put(B_LONG); } else if (clazz.isAssignableFrom(Float.class)) { this.header.put(B_FLOAT); } else if (clazz.isAssignableFrom(Double.class)) { this.header.put(B_DOUBLE); } else if (clazz.isAssignableFrom(String.class)) { this.header.put(B_STRING); } this.header.putInt(field.getLen()); } this.header.putLong(headerSize); // The initial root offset this.header.position(0); this.channel.write(this.header); if (this.params.getForceChannel()) { this.channel.force(true); } // Create the root this.root = new FileSystemNode(this.params); this.root.setLeaf(true); this.root.save(); } /** * @see org.geotools.index.rtree.PageStore#getRoot() */ public Node getRoot() { return this.root; } /** * @see org.geotools.index.rtree.PageStore#setRoot(org.geotools.index.rtree.Node) */ public void setRoot(Node node) throws TreeException { try { FileSystemNode n = (FileSystemNode) node; n.setParent(null); this.root = n; this.header.position(this.header.limit() - 8); this.header.putLong(n.getOffset()); this.header.position(0); synchronized (this.channel) { this.channel.position(0); this.channel.write(this.header); if (this.params.getForceChannel()) { this.channel.force(true); } } } catch (IOException e) { throw new TreeException(e); } } /** * @see org.geotools.index.rtree.PageStore#getEmptyNode(boolean) */ public Node getEmptyNode(boolean isLeaf) { FileSystemNode node = new FileSystemNode(params); node.setLeaf(isLeaf); return node; } /** * @see org.geotools.index.rtree.PageStore#getNode(long, * org.geotools.index.rtree.Node) */ public Node getNode(Entry parentEntry, Node parent) throws TreeException { Node node = null; long offset = ((Long) parentEntry.getData()).longValue(); try { node = new FileSystemNode(this.params, offset); node.setParent(parent); } catch (IOException e) { throw new TreeException(e); } return node; } /** * @see org.geotools.index.rtree.PageStore#createEntryPointingNode(org.geotools.index.rtree.Node) */ public Entry createEntryPointingNode(Node node) { FileSystemNode fn = (FileSystemNode) node; return new Entry(new Envelope(fn.getBounds()), new Long(fn.getOffset())); } /** * @see org.geotools.index.rtree.PageStore#free(org.geotools.index.rtree.Node) */ public void free(Node node) { try { FileSystemNode fn = (FileSystemNode) node; fn.free(); } catch (IOException e) { // Ignore } } /** * @see org.geotools.index.rtree.PageStore#close() */ public void close() throws TreeException { try { this.header.position(0); synchronized (this.channel) { this.channel.position(0); this.channel.write(this.header); this.channel.force(true); } this.raFile.close(); } catch (IOException e) { e.printStackTrace(); throw new TreeException(e); } } /** * @see org.geotools.index.rtree.PageStore#getMaxNodeEntries() */ public int getMaxNodeEntries() { return this.params.getMaxNodeEntries(); } /** * @see org.geotools.index.rtree.PageStore#getMinNodeEntries() */ public int getMinNodeEntries() { return this.params.getMinNodeEntries(); } /** * @see org.geotools.index.rtree.PageStore#getSplitAlgorithm() */ public short getSplitAlgorithm() { return this.params.getSplitAlg(); } /** * @see org.geotools.index.rtree.PageStore#getKeyDefinition() */ public DataDefinition getDataDefinition() { return this.params.getDataDef(); } /** * If this is set to true, then every write to the index will * call a force() on the associated channel * * @param b true or false */ public void setForceChannel(boolean b) { this.params.setForceChannel(b); } /** * DOCUMENT ME! * * @return The state of Force channel parameter */ public boolean getForceChannel() { return this.params.getForceChannel(); } }