/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2015, Geomatys * * 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. */ package org.geotoolkit.internal.tree; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.Channel; import java.nio.channels.SeekableByteChannel; import java.util.Arrays; import org.apache.sis.referencing.CRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.FactoryException; import org.geotoolkit.index.tree.Node; import org.geotoolkit.index.tree.basic.SplitCase; import static org.geotoolkit.internal.tree.TreeUtilities.intersects; /** * {@link TreeAccess} implementation.<br/> * Store all {@link Node} architecture use by {@link Tree} into a {@link SeekableByteChannel}. * * @author Remi Marechal (Geomatys). */ public abstract strictfp class ChannelTreeAccess extends TreeAccess { /** * Position in the tree file where CRS description should begin. */ private static final int CRS_POSITION = 34; /** * boundary table value length of each Node. */ protected final int boundLength; /** * Length in Byte unit of a Node in file on hard disk. */ protected final int nodeSize; /** * {@link FileChannel} position just after write or read file head.<br/> * Its also file position of first Node red or written. */ protected final int beginPosition; /** * ByteBuffer attributs use to read and write. */ protected int writeBufferLimit; protected long currentBufferPosition; protected int rwIndex; /** * {@link ByteBuffer} to read and write Node from file on hard disk. */ protected final ByteBuffer byteBuffer; /** * ByteBuffer Length. */ protected final int bufferLength; /** * {@link Channel} where {@link Node} architecture will be written. */ protected SeekableByteChannel inOutChannel; //------------------------- Reading mode ----------------------------------- /** * Build a {@link Tree} from a already filled {@link Channel}, in other words, open in reading mode.<br/><br/> * * @param byteChannel {@link SeekableByteChannel} to read already filled object. * @param magicNumber {@code Integer} single {@link Tree} code. * @param versionNumber tree version. * @param byteBufferLength length in Byte unit of the buffer which read and write on hard disk. * @param integerNumberPerNode integer number per Node which will be red/written during Node reading/writing process. * @throws IOException if problem during channel read / write action. */ protected ChannelTreeAccess(final SeekableByteChannel byteChannel, final int magicNumber, final double versionNumber, final int byteBufferLength, final int integerNumberPerNode) throws IOException, ClassNotFoundException { inOutChannel = byteChannel; final ByteBuffer magicOrderBuffer = ByteBuffer.allocate(5);//-- a stipuler en bigendian // magicOrderBuffer.order(ByteOrder.BIG_ENDIAN);//-- stand by byte order comportement inOutChannel.read(magicOrderBuffer); magicOrderBuffer.clear(); assert inOutChannel.position() == 5; /***************************** read head ******************************/ //-- read magicNumber final int mgNumber = magicOrderBuffer.getInt(); if (magicNumber != mgNumber) { final String createTreeType; final String redTreeType; switch (magicNumber) { case TreeUtilities.BASIC_NUMBER : createTreeType = " BasicRTree ";break; case TreeUtilities.STAR_NUMBER : createTreeType = " StarRTree ";break; case TreeUtilities.HILBERT_NUMBER : createTreeType = " hilbertRTree ";break; default : throw new IllegalArgumentException("Unknown tree type for magic number : "+magicNumber); }; switch (mgNumber) { case TreeUtilities.BASIC_NUMBER : redTreeType = " BasicRTree ";break; case TreeUtilities.STAR_NUMBER : redTreeType = " StarRTree ";break; case TreeUtilities.HILBERT_NUMBER : redTreeType = " hilbertRTree ";break; default : throw new IllegalArgumentException("Unknown tree type for magic number : "+mgNumber); }; final String messageError = "You try to create a "+createTreeType +"RTree from a file which has been filled by a "+redTreeType +"RTree implementation."; throw new IllegalArgumentException(messageError); } // read ByteOrder final boolean fbool = magicOrderBuffer.get() == 1; final ByteOrder bO = (fbool) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;//-- stand by byte order comportement //-- fin de l'entete dans le bon byteOrder final ByteBuffer headBuffer = ByteBuffer.allocate(33); // headBuffer.order(bO);//-- stand by byte order comportement inOutChannel.read(headBuffer); headBuffer.clear(); assert inOutChannel.position() == 38; // read version number final double vN = headBuffer.getDouble(); if (vN != versionNumber) throw new IllegalArgumentException("Wrong version number. Expected : "+versionNumber+". Version found in tree file : "+vN); // read maxElement maxElement = headBuffer.getInt(); // read hilbert Order hilbertOrder = headBuffer.getInt(); // read SplitCase final byte sm = headBuffer.get(); splitMade = ((sm & ((byte)1)) != 0) ? SplitCase.QUADRATIC : SplitCase.LINEAR; // read nodeID nodeId = headBuffer.getInt(); if (nodeId == 0) throw new IllegalStateException("User has not been invoked tree.close() method after insertions. You should build again RTree."); treeIdentifier = headBuffer.getInt(); // read element number within tree eltNumber = headBuffer.getInt(); // read CRS final int byteTabLength = headBuffer.getInt(); final byte[] crsByteArray = new byte[byteTabLength]; final ByteBuffer crsBuffer = ByteBuffer.wrap(crsByteArray); // crsBuffer.order(bO);//-- stand by byte order comportement inOutChannel.read(crsBuffer); String wktCRS = new String(crsByteArray); try { crs = CRS.fromWKT(wktCRS); } catch (FactoryException ex) { throw new IOException(ex); } assert inOutChannel.position() == 38 + crsByteArray.length; /***************************** end head ******************************/ this.boundLength = crs.getCoordinateSystem().getDimension() << 1; //-- nanbound nanBound = new double[boundLength]; Arrays.fill(nanBound, Double.NaN); /** * Node size : boundary weigth + Byte properties + n Integers.<br/><br/> * * see INT_NUMBER attribut. */ nodeSize = (boundLength * Double.SIZE + Integer.SIZE * integerNumberPerNode) / 8 + 1; // buffer attributs final int div = byteBufferLength / nodeSize; this.bufferLength = div * nodeSize; byteBuffer = ByteBuffer.allocate(bufferLength); // byteBuffer.order(bO);//-- stand by byte order comportement beginPosition = (int) inOutChannel.position(); currentBufferPosition = beginPosition; writeBufferLimit = 0; // root inOutChannel.position(currentBufferPosition); inOutChannel.read(byteBuffer); inOutChannel.position(beginPosition); root = this.readNode(1); if (root.isEmpty()) root = null; } //----------------------- writing constructors ----------------------------- /** * Build an empty {@link TreeAccess} and store {@link Node} architecture, * in other words, open in writing mode. * * @param byteChannel {@link SeekableByteChannel} to read already filled object. * @param magicNumber {@code Integer} single {@link Tree} code. * @param versionNumber tree version. * @param maxElements element number per cell. * @param hilbertOrder * @param splitMade define tree node split made. * @param crs * @param byteBufferLength length in Byte unit of the buffer which read and write on hard disk. * @param integerNumberPerNode integer number per Node which will be red/written during Node reading/writing process. * @throws IOException */ protected ChannelTreeAccess(final SeekableByteChannel byteChannel, final int magicNumber, final double versionNumber, final int maxElements, final int hilbertOrder, final SplitCase splitMade, final CoordinateReferenceSystem crs, final int byteBufferLength, final int integerNumberPerNode) throws IOException { this.crs = crs; this.maxElement = maxElements; this.boundLength = crs.getCoordinateSystem().getDimension() << 1; this.hilbertOrder = hilbertOrder; this.splitMade = splitMade; //nanbound nanBound = new double[boundLength]; Arrays.fill(nanBound, Double.NaN); /** * Node size : boundary weight + Byte properties + n Integers.<br/><br/> * * see this.INT_NUMBER attribute. */ nodeSize = (boundLength * Double.SIZE + Integer.SIZE * integerNumberPerNode) / 8 + 1; final int div = byteBufferLength / nodeSize; // 4096 this.bufferLength = div * nodeSize; inOutChannel = byteChannel; //-- current writing order final ByteOrder bO = ByteOrder.nativeOrder(); //-- magic number and byte order final ByteBuffer magicOrderBuffer = ByteBuffer.allocate(5); // magicOrderBuffer.order(ByteOrder.BIG_ENDIAN);//-- stand by byte order comportement // write magicNumber magicOrderBuffer.putInt(magicNumber); // write bytebuffer order magicOrderBuffer.put((byte)(bO == ByteOrder.LITTLE_ENDIAN ? 1 : 0)); magicOrderBuffer.flip(); inOutChannel.write(magicOrderBuffer); assert inOutChannel.position() == 5; //------------------------------------------------------- //--------------------------- head ending ------------------------------ final ByteBuffer headBuffer = ByteBuffer.allocate(33); // headBuffer.order(bO);//-- stand by byte order comportement /*************************** write head ******************************/ final ByteArrayOutputStream temp = new ByteArrayOutputStream(); try (final ObjectOutputStream objOutput = new ObjectOutputStream(temp)) { // write version number headBuffer.putDouble(versionNumber); // write element number per Node headBuffer.putInt(maxElements); // write hilbert order headBuffer.putInt(hilbertOrder); // write splitCase headBuffer.put((byte) ((splitMade == null || splitMade == SplitCase.LINEAR) ? 0 : 1)); // write nodeID headBuffer.putInt(0); // write treeIdentifier headBuffer.putInt(0); // write element number within tree headBuffer.putInt(0); // write CRS final String wktCRS = crs.toWKT(); final byte[] crsByteArray = wktCRS.getBytes(); headBuffer.putInt(crsByteArray.length); //-- write head headBuffer.flip(); inOutChannel.write(headBuffer); assert inOutChannel.position() == 38; final ByteBuffer crsbuff = ByteBuffer.allocate(crsByteArray.length); // crsbuff.order(bO);//-- stand by byte order comportement crsbuff.put(crsByteArray); crsbuff.flip(); inOutChannel.write(crsbuff);//-- write all the crs array assert inOutChannel.position() == (crsByteArray.length + 38); } /***************************** end head ******************************/ // ByteBuffer byteBuffer = ByteBuffer.allocate((int)bufferLength); // byteBuffer.order(bO);//-- stand by byte order comportement beginPosition = (int) inOutChannel.position(); currentBufferPosition = beginPosition; writeBufferLimit = 0; // root root = null; } /** * Adjust buffer position relative to filechanel which contain data, * and prepare bytebuffer position and limit for reading or writing action. * * @param treeIdentifier * @throws IOException */ protected void adjustBuffer(final int nodeID) throws IOException { assert inOutChannel.position() == currentBufferPosition; rwIndex = beginPosition + (nodeID - 1) * nodeSize; if (rwIndex < currentBufferPosition || (rwIndex + nodeSize) > currentBufferPosition + bufferLength) { //-- pense ici // write current data within bytebuffer in channel. byteBuffer.position(0); byteBuffer.limit(writeBufferLimit); int writtenByte = 0; while (writtenByte < writeBufferLimit) { writtenByte += inOutChannel.write(byteBuffer); } //-- define new appropriate window position final int div = (rwIndex - beginPosition) / bufferLength; currentBufferPosition = div * bufferLength + beginPosition; writeBufferLimit = 0; //-- read current data byteBuffer.clear(); inOutChannel.position(currentBufferPosition); inOutChannel.read(byteBuffer); byteBuffer.flip(); //-- get back to appropriate position after reading inOutChannel.position(currentBufferPosition); } rwIndex -= currentBufferPosition; byteBuffer.limit(rwIndex + nodeSize); byteBuffer.position(rwIndex); } /** * {@inheritDoc } */ @Override public void internalSearch(int nodeID) throws IOException { adjustBuffer(nodeID); final double[] boundary = new double[boundLength]; for (int i = 0; i < boundLength; i++) { boundary[i] = byteBuffer.getDouble(); } byteBuffer.position(byteBuffer.position() + 5);// step properties (1 byte) and step parent ID (int : 4 byte) final int sibling = byteBuffer.getInt(); final int child = byteBuffer.getInt(); byteBuffer.position(byteBuffer.position() + 4);// step child count if (sibling != 0) { internalSearch(sibling); } if (intersects(boundary, regionSearch, true)) { if (child > 0) { internalSearch(child); } else { if (child == 0) throw new IllegalStateException("child index should never be 0."); if (currentPosition == currentLength) { currentLength = currentLength << 1; final int[] tabTemp = tabSearch; tabSearch = new int[currentLength]; System.arraycopy(tabTemp, 0, tabSearch, 0, currentPosition); } tabSearch[currentPosition++] = -child; } } } /** * {@inheritDoc } */ @Override public Node readNode(int indexNode) throws IOException { adjustBuffer(indexNode); final double[] boundary = new double[boundLength]; for (int i = 0; i < boundLength; i++) { boundary[i] = byteBuffer.getDouble(); } final byte properties = byteBuffer.get(); final int parentId = byteBuffer.getInt(); final int siblingId = byteBuffer.getInt(); final int childId = byteBuffer.getInt(); final int childCount = byteBuffer.getInt(); final Node redNode = new Node(this, indexNode, boundary, properties, parentId, siblingId, childId); redNode.setChildCount(childCount); return redNode; } /** * {@inheritDoc } */ @Override public void writeNode(Node candidate) throws IOException { final int indexNode = candidate.getNodeId(); adjustBuffer(indexNode); writeBufferLimit = Math.max(writeBufferLimit, byteBuffer.limit()); double[] candidateBound = candidate.getBoundary(); if (candidateBound == null) candidateBound = nanBound; for (int i = 0; i < boundLength; i++) { byteBuffer.putDouble(candidateBound[i]); } byteBuffer.put(candidate.getProperties()); byteBuffer.putInt(candidate.getParentId()); byteBuffer.putInt(candidate.getSiblingId()); byteBuffer.putInt(candidate.getChildId()); byteBuffer.putInt(candidate.getChildCount()); } /** * {@inheritDoc } */ @Override public synchronized void removeNode(final Node candidate) { recycleID.add(candidate.getNodeId()); } /** * {@inheritDoc } */ @Override public synchronized void rewind() throws IOException { super.rewind(); byteBuffer.position(0); byteBuffer.limit(writeBufferLimit); inOutChannel.position(currentBufferPosition); int writtenByte = 0; while (writtenByte < writeBufferLimit) { writtenByte += inOutChannel.write(byteBuffer); } inOutChannel.position(beginPosition); currentBufferPosition = beginPosition; //-- fill buffer byteBuffer.clear(); inOutChannel.read(byteBuffer); byteBuffer.flip(); inOutChannel.position(beginPosition); //-- writeBufferLimit = 0; } /** * {@inheritDoc } * <br> * When you call this method the {@link #flush() } method is internaly invoked. */ @Override public void close() throws IOException { flush(); //close inOutChannel.close(); } /** * {@inheritDoc } */ @Override public void flush() throws IOException { byteBuffer.position(0); byteBuffer.limit(writeBufferLimit); inOutChannel.position(currentBufferPosition); int writtenByte = 0; while (writtenByte < writeBufferLimit) { writtenByte += inOutChannel.write(byteBuffer); } // write nodeID byteBuffer.clear(); inOutChannel.position(22); byteBuffer.putInt(nodeId); byteBuffer.putInt(treeIdentifier); byteBuffer.putInt(eltNumber); byteBuffer.flip(); inOutChannel.write(byteBuffer); //-- fill buffer inOutChannel.position(currentBufferPosition); byteBuffer.clear(); inOutChannel.read(byteBuffer); byteBuffer.flip(); inOutChannel.position(currentBufferPosition); writeBufferLimit = 0; //-- adjustBuffer(nodeId); } /** * {@inheritDoc } */ @Override public boolean isClose() { return !inOutChannel.isOpen(); } }