/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2012, 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.index.tree; import org.geotoolkit.internal.tree.TreeAccess; import java.io.IOException; import static org.geotoolkit.internal.tree.TreeUtilities.*; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Classes; import org.geotoolkit.gui.swing.tree.Trees; import org.geotoolkit.internal.tree.TreeAccessFile; import org.geotoolkit.internal.tree.TreeAccessMemory; import org.apache.sis.util.logging.Logging; /** * Default implementation Node use in Tree.<br/><br/> * * In Tree, Node architecture is organize like a chained list.<br/><br/> * *  N1 (root)<br/> *   /->N2--------------------------->N3---------------------------->N4<br/> *        /-->N5-->N6-->N7   *    /-->N8-->N9-->N10     *  /-->N11-->N12-->N13<br/><br/> * * Note : Neither Node has an identifier equal to 0.<br/> * zero is reserved to significate end of chained list or no parent like tree root Node. * * @author Martin Desruisseaux (Geomatys). * @author Remi Marechal (Geomatys). */ public class Node { /** * Single Node identifier. */ protected final int nodeId; /** * Node boundary. */ protected double[] boundary; /** * Identifier of parent Node.<br/> * If Node have no parent like tree trunk (see {@link AbstractTree#root) the value is 0. */ protected int parentId; /** * Identifier of sibling Node.<br/> * Note : all sibling Node have same parent identifiers.<br/> * When it s the last sibling Node of the current chained list Tree level the value is zero. */ protected int siblingId; /** * There are 2 cases : <br/> * if Node is a data (see {@link Node#isData()) childID is the tree identifier of a data.<br/> * else childID is the identifier of the first children from Node chained list architecture. */ protected int childId;// < 0 if it is a data. /** * Number of children. */ protected int childCount; /** * Object which store Node attributs on a file define by user (see {@link TreeAccessFile}) * or in memory (see{@link TreeAccessMemory). */ protected TreeAccess tAF; /** * {@code Byte} which use to test some properties.<br/> * first bit is at 1 if Node is a leaf.(see {@link TreeUtilities#IS_LEAF).<br/> * second bit is at 2 if Node is a data.(see {@link TreeUtilities#IS_DATA).<br/> * third bit is at 1 if Node is a cell.(see {@link TreeUtilities#IS_CELL).<br/> * fourth bit is at 1 if Node is other.(see {@link TreeUtilities#IS_OTHER). */ protected byte properties; /** * Create a Node adapted for standard Tree implementation. * * @param tAF Object which store Node attributs. * @param nodeId invariable single integer Node identifier. * @param boundary double table which represent boundary Node coordinates. * @param properties define type of Node. see ({@link Node#properties}). * @param parentId identifier of parent Node Tree architecture. * @param siblingId identifier of sibling Node. * @param childId if Node is a data it is the identifier of the data which is * store in this tree (see {@link Node#childId}) else it is the first child of this Node. * @see TreeAccess */ public Node(final TreeAccess tAF, final int nodeId, final double[] boundary, final byte properties, final int parentId, final int siblingId, final int childId) { this.tAF = tAF; this.nodeId = nodeId; this.boundary = boundary; this.parentId = parentId; this.siblingId = siblingId; this.childId = childId; this.childCount = (childId != 0) ? 1 : 0; this.properties = properties; } /** * Return invariable single Node identifier. * * @return invariable single Node identifier. * @see Node#nodeId. */ public int getNodeId() { return nodeId; } /** * Return invariable single Node identifier from its parent Node. * * @return invariable single Node identifier from its parent Node. * @see Node#parentId. */ public int getParentId() { return parentId; } /** * Affect a new parent identifier. * * @param parentId identifier of new parent. */ public void setParentId(final int parentId) { this.parentId = parentId; } /** * Return invariable single Node identifier of its sibling Node. * * @return invariable single Node identifier from its sibling Node. * @see Node#siblingId. */ public int getSiblingId() { return siblingId; } /** * Affect a new sibling identifier. * * @param siblingId identifier of new sibling. */ public void setSiblingId(final int siblingId) { this.siblingId = siblingId; } /** * Return invariable single Node identifier of its first children or data tree identifier value. * * @return invariable single Node identifier of its first children or data tree identifier value. * @see Node#childId. */ public int getChildId() { return childId; } /** * Affect a new child identifier. * * @param childId identifier of new child. */ public void setChildId(final int childId) { this.childId = childId; } /** * Return {@code Byte} which contains type of Node. * * @return {@code Byte} which contains type of Node. * @see Node#properties */ public byte getProperties() { return properties; } /** * Affect a new type on this Node. * * @param properties newest type. */ public void setProperties(final byte properties) { this.properties = properties; } /** * Return boundary of this Node. * * @return boundary of this Node. */ public double[] getBoundary() { return boundary; } /** * Affect a new boundary on this Node. * * @param boundary newest boundary. */ public void setBoundary(final double[] boundary) { this.boundary = boundary; } /** * Return TreeAccess pointer. * * @return TreeAccess pointer. * @see TreeAccess */ public TreeAccess getTreeAccess() { return tAF; } /** * A leaf is a {@code Node} which contains only some data Node. * * @return true if it is a leaf else false (branch). * @see Node#properties */ public boolean isLeaf() { return (properties & IS_LEAF) != 0; } /** * A leaf is a {@code Node} at extremity of {@code Tree} * which contains a single data tree identifier. * * @return true if it is a leaf else false (branch). * @see Node#properties */ public boolean isData() { return (properties & IS_DATA) != 0; } /** * Return true if Node don't contains children else false. * * @return true if Node don't contains children else false. */ public boolean isEmpty() { return childCount == 0; } /** * Add some children Node. * * @param nodes children Node which will be added. * @throws IOException if problem during Node writing from {@link TreeAccessFile}. * @see TreeAccessFile#writeNode(org.geotoolkit.index.tree.Node) */ public void addChildren(final Node[] nodes) throws IOException { for(Node fnod : nodes) { // all elements should be distinct. fnod.setSiblingId(0); // connect child at other children (its sibling). childCount++; final int nextSibling = getChildId(); setChildId(fnod.getNodeId()); fnod.setParentId(getNodeId()); fnod.setSiblingId(nextSibling); if (boundary == null) { boundary = fnod.getBoundary().clone(); } else { add(boundary, fnod.getBoundary()); } tAF.writeNode(fnod); } tAF.writeNode(this); } /** * Return all children from this Node.<br/><br/> * * Note : if is leaf all children returned are data type else other type. * * @return all children from this Node. * @throws IOException if problem during Node reading from {@link TreeAccessFile}. * @see TreeAccessFile#readNode(int) */ public Node[] getChildren() throws IOException { final Node[] children = new Node[childCount]; int sibl = getChildId(); int id = 0; while (sibl != 0) { final Node cuN = tAF.readNode(sibl); children[id++] = cuN; sibl = cuN.getSiblingId(); } assert id == childCount : "FileNode : getChildren : childCound and child number doesn't match."; return children; } /** * Remove specified child Node.<br/><br/> * * Return true if child Node was found and should be removed else false. * * @param node Node which will be removed. * @return true if child Node was found and should be removed else false. * @throws IOException if problem during Node writing from {@link TreeAccessFile}. * @see TreeAccessFile#writeNode(org.geotoolkit.index.tree.Node) */ public boolean removeChild(final Node node) throws IOException { boolean found = false; if (childCount == 1) { if (node.getNodeId() == getChildId()) { childCount--; setChildId(0); tAF.writeNode(this); found = true; } } else { if (getChildId() == node.getNodeId()) { setChildId(node.getSiblingId()); childCount--; final Node[] children = getChildren(); boundary = children[0].getBoundary().clone(); for (int i = 1, l = children.length; i < l; i++) { add(boundary, children[i].getBoundary()); } tAF.writeNode(this); found = true; } else { // connect sibling with its next sibling. Node precChild = tAF.readNode(getChildId()); boundary = precChild.getBoundary().clone(); int sibl = precChild.getSiblingId(); while (sibl != 0) { if (sibl == node.getNodeId()) { sibl = node.getSiblingId(); found = true; // accrocher les voisins precChild.setSiblingId(sibl); tAF.writeNode(precChild); } else { precChild = tAF.readNode(sibl); sibl = precChild.getSiblingId(); add(boundary, precChild.getBoundary()); } } if (found) { childCount--; tAF.writeNode(this); } } } if (found) tAF.removeNode(node); return found; } /** * Remove specified data.<br/><br/> * * Return true if data was found and should be removed else false. * * @param identifier tree identifier. * @param coordinates data boundary * @return true if data was found and should be removed else false. * @throws IOException if problem during Node writing from {@link TreeAccessFile}. * @see TreeAccessFile#writeNode(org.geotoolkit.index.tree.Node) */ public boolean removeData(final int identifier, final double ...coordinates) throws IOException { if (!((properties & 5) != 0))// test isleaf or iscell throw new IllegalStateException("You should not call removeData() method on a no leaf or cell Node."); if (isEmpty()) return false; final Node[] children = getChildren(); final int l = children.length; assert childCount == l; int index = -1; for (int i = 0; i < l; i++) { final Node currentData = children[i]; if (identifier == -currentData.getChildId() && Arrays.equals(currentData.getBoundary(), coordinates)) { index = i; break; } } if (index == -1) return false; childCount--; tAF.removeNode(children[index]); if (index == 0) { if (l == 1) { setChildId(0); boundary = null; } else { setChildId(children[1].getNodeId()); boundary = children[1].getBoundary().clone(); for (int i = 2; i < l; i++) { add(boundary, children[i].getBoundary()); } } } else { children[index-1].setSiblingId(children[index].getSiblingId()); tAF.writeNode(children[index-1]); boundary = children[0].getBoundary().clone(); for (int i = 1; i < l; i++) { if (i != index) add(boundary, children[i].getBoundary()); } } tAF.writeNode(this); return true; } /** * Initialize Node. */ public void clear() { boundary = null; childId = 0; childCount = 0; } /** * Return children number. * * @return children number. */ public int getChildCount() { return childCount; } /** * Affect a new children number value. * * @param value new children number value. * @see TreeAccessFile#readNode(int) */ public void setChildCount(final int value) { childCount = value; } /** * Add a new child in this Node.<br/><br/> * * Added child own a sibling id equal to last child Node identifier. * * @param node added child. * @throws IOException if problem during Node writing from {@link TreeAccessFile}. * @see TreeAccessFile#writeNode(org.geotoolkit.index.tree.Node) */ public void addChild(final Node node) throws IOException { final double[] nodeBoundary = node.getBoundary(); assert node.getSiblingId() == 0 : "future added element should be distinct from others."; // connect child at other children (its sibling). childCount++; final int nextSibling = getChildId(); setChildId(node.getNodeId()); node.setParentId(getNodeId()); node.setSiblingId(nextSibling); if (nodeBoundary != null) { if (boundary == null || ArraysExt.hasNaN(boundary)) { boundary = nodeBoundary.clone(); } else { add(boundary, nodeBoundary); } } tAF.writeNode(this); tAF.writeNode(node); } /** * Verify some internal Node properties.<br/><br/> * * Return false if a Node properties doesn't match with an expected results else true. * * @return false if a Node properties doesn't match with an expected results else true. * @throws IOException if problem during Node reading from {@link TreeAccessFile}. * @see TreeAccessFile#readNode(int) */ public boolean checkInternal() throws IOException { if (isEmpty()) return true; if (getChildId() < 0) { if (!isData()) { throw new IllegalStateException("with childID < 0 isData() should return true."); } if (getChildCount() != 1) { throw new IllegalStateException("in data childcount always equals to 1."); } } else { int verifChildCount = 0; double[] boundTemp = null; int sibl = getChildId(); while (sibl != 0) { final Node cuChild = tAF.readNode(sibl); if (cuChild.getParentId() != getNodeId()) { throw new IllegalStateException("Child sibling should have parent ID equals to this.nodeID. Expected : "+getNodeId()+". Found : "+cuChild.getParentId()); } if (boundTemp == null) { boundTemp = cuChild.getBoundary().clone(); } else { add(boundTemp, cuChild.getBoundary()); } verifChildCount++; sibl = cuChild.getSiblingId(); } if (isData()) { throw new IllegalStateException("with childID > 0 isData() should return false."); } if (verifChildCount != childCount) { throw new IllegalStateException("sibling number and child count should have same value. Expected : "+childCount+". Found : "+verifChildCount); } if (!Arrays.equals(getBoundary(), boundTemp)) { throw new IllegalStateException("children boundary adding should be same as this.boundary.\n"+Arrays.toString(boundTemp)+"\n"+Arrays.toString(getBoundary())); } } return true; } /** * Return true if children number is higher than maximum elements permit * per Node else false. * * @return true if children number is higher than maximum elements permit per Node else false. * @throws IOException if problem during reading Node in {@link TreeAccessFile}. * But exception only return from {@link HilbertNode} sub-implementation. * @see HilbertNode#isFull() */ public boolean isFull() throws IOException { return getChildCount() >= tAF.getMaxElementPerCells(); } /** * {@inheritDoc}. */ @Override public String toString() { final List toString; try { if (!isData()) { toString = Arrays.asList(this.getChildren()); String strparent = (getParentId() == 0) ? "null" : (""+getParentId()); return Trees.toString(Classes.getShortClassName(this)+" parent : "+strparent+" : ID : "+getNodeId() + " leaf : "+isLeaf()+" sibling : "+getSiblingId()+" child "+getChildId()+" children number : "+getChildCount()+Arrays.toString(getBoundary()), toString); } else { return Classes.getShortClassName(this)+"Data : parent : "+getParentId()+" ID : "+getNodeId()+" sibling : "+getSiblingId()+" value : "+(-getChildId())+" bound : "+Arrays.toString(getBoundary()); } } catch (IOException ex) { Logging.getLogger("org.geotoolkit.index.tree").log(Level.SEVERE, null, ex); } return null; } /** * {@inheritDoc }. */ @Override public boolean equals(final Object obj) { if (!(obj instanceof Node)) return false; final Node objNode = (Node) obj; boolean boundBool; final double[] boundThis = getBoundary(); final double[] objBound = objNode.getBoundary(); if (boundThis == null || ArraysExt.allEquals(boundThis, Double.NaN)) { boundBool = (objBound == null || ArraysExt.allEquals(objBound, Double.NaN)); } else { boundBool = Arrays.equals(objBound, boundThis); } return objNode.getNodeId() == getNodeId() && boundBool && objNode.getParentId() == getParentId() && objNode.getSiblingId() == getSiblingId() && objNode.getChildId() == getChildId() && objNode.getChildCount() == getChildCount(); } }