/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008-2012, Open Source Geospatial Foundation (OSGeo) * (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.image.io.mosaic; import java.util.List; import java.util.LinkedList; import java.util.Collection; import java.util.Iterator; import java.util.Enumeration; import java.util.NoSuchElementException; import java.util.Objects; import java.awt.Rectangle; import java.io.IOException; import org.geotoolkit.gui.swing.tree.Trees; /** * Base class for tree node wrapping a {@link Tile}. Children are managed as a linked list. * <p> * This class extends {@link Rectangle} for pure opportunist reasons, in order to reduce the amount * of object created (we will have thousands of {@code TreeNode}s) and for direct (without the cost * of an additional pointer) invocation of {@link Rectangle} methods. We do this unrecommendable * practice only because this class is not public. The inherited {@link Rectangle} should * <string>never</strong> be modified by anyone outside this class. * <p> * The inherited {@linkplain #x x}, {@linkplain #y y}, {@linkplain #width width} and * {@linkplain #height height} fields are tile bounds in <cite>absolute</cite> coordinates, * as returned by {@link Tile#getAbsoluteRegion}. They must be wide enough for including * all children bounds. * * @author Martin Desruisseaux (Geomatys) * @version 3.04 * * @since 2.5 * @module */ class TreeNode extends Rectangle implements Iterable<TreeNode>, org.geotoolkit.gui.swing.tree.TreeNode { /** * For cross-version compatibility. */ private static final long serialVersionUID = 1669511478839242773L; /** * The tile wrapped by this node, or {@code null} if we should read only the children, * not the tile itself. */ protected Tile tile; /** * The parent, or {@code null} if none. */ private TreeNode parent; /** * The first and last children {@linkplain #addChild added} to this node, * or {@code null} if none. */ private TreeNode firstChildren, lastChildren; /** * The previous and next sibling, or {@code null} if none. Used in order to created * a linked list of children. */ private TreeNode previousSibling, nextSibling; /** * Creates an initially empty rectangle. Width and height are set to -1, which * stands for non-existent rectangle according {@link Rectangle} documentation. * Other fields (including subsamplings) are set to 0 or {@code null}. */ protected TreeNode() { super(-1, -1); } /** * Creates a node for the specified bounds with no tile. * * @param bounds The tile bounds in absolute coordinates. */ protected TreeNode(final Rectangle bounds) { super(bounds); } /** * Adds the given tile as a child of this tile. This method do nothing if the given child * is null (which is typically the case when it doesn't intercept the region of interest). * <p> * Note that this method has pre-conditions checked by {@code assert} statements. If the * {@link TreeNode} class become public, we would need to performs inconditional checks. * * @throws ClassCastException If a subclass has some restrictions about the kind * of child accepted, and the given child doesn't meet those restrictions. */ public void addChild(final TreeNode child) throws ClassCastException { if (child != null) { assert child.isRoot() && (tile == null || contains(child)) : child; child.parent = this; if (lastChildren == null) { lastChildren = firstChildren = child; } else { child.previousSibling = lastChildren; lastChildren.nextSibling = child; lastChildren = child; } } } /** * Removes all children. */ public void removeChildren() { TreeNode child = firstChildren; while (child != null) { assert child.parent == this; final TreeNode next = child.nextSibling; child.previousSibling = null; child.nextSibling = null; child.parent = null; child = next; } firstChildren = lastChildren = null; } /** * Removes this tile and all its children from the tree. */ public void remove() { if (previousSibling != null) { previousSibling.nextSibling = nextSibling; } if (nextSibling != null) { nextSibling.previousSibling = previousSibling; } if (parent != null) { if (parent.firstChildren == this) { parent.firstChildren = nextSibling; } if (parent.lastChildren == this) { parent.lastChildren = previousSibling; } parent = null; } previousSibling = null; nextSibling = null; } /** * Returns the number of non-null tiles, including in children. */ final int getTileCount() { int count = (tile != null) ? 1 : 0; TreeNode child = firstChildren; while (child != null) { count += child.getTileCount(); child = child.nextSibling; } return count; } /** * Returns the tile in this node, or {@code null} if none. This is also the <cite>Swing</cite> * user object which is formatted (when available) by the {@link #toString} method, as required * by {@link javax.swing.JTree}. */ @Override public final Tile getUserObject() { return tile; } /** * Returns the parent node that contains this child node, or {@code null} if this node * is the root node. */ @Override public final TreeNode getParent() { return parent; } /** * Returns {@code true} if this node has no parent. Note that having no parent * implies having no sibling neither (otherwise the tree would be malformed). */ public final boolean isRoot() { return parent == null && previousSibling == null && nextSibling == null; } /** * Returns {@code true} if this node has no children. Note that this is slightly different from * {@link #getAllowsChildren} which returns {@code false} if this node <strong>can not</strong> * have children. */ @Override public final boolean isLeaf() { assert (firstChildren == null) == (lastChildren == null); return firstChildren == null; } /** * Return {@code true} in almost every case since there is no reason to prevent a tile from * containing smaller tiles, unless the tile bounds {@linkplain #isEmpty is empty}. */ @Override public final boolean getAllowsChildren() { return !super.isEmpty(); } /** * Returns {@code true} if this node has more than one tile and some of them overlap. * This method iterates over the direct children of this node, but does not scan down * the tree. */ public final boolean hasOverlaps() { for (TreeNode child = firstChildren; child != null; child = child.nextSibling) { for (TreeNode next = child; (next = next.nextSibling) != null;) { if (child.intersects(next)) { return true; } } } return false; } /** * If this node has exactly one child, returns that child. Otherwise returns {@code null}. */ public final TreeNode getChild() { return (firstChildren == lastChildren) ? firstChildren : null; } /** * Returns the number of children. This method is provided for <cite>Swing</cite> usage. * When using {@link TreeNode} directly, consider using {@link #iterator} instead. */ @Override public final int getChildCount() { // Don't invoke assertValid() directly or indirectly since it could cause never-ending loop. int count = 0; TreeNode child = firstChildren; while (child != null) { child = child.nextSibling; count++; } return count; } /** * Returns the children at the given index. This method is provided for <cite>Swing</cite> * usage. When using {@link TreeNode} directly, consider using {@link #iterator} instead. */ @Override public final TreeNode getChildAt(int index) { TreeNode child = firstChildren; while (child != null) { if (index == 0) { return child; } child = child.nextSibling; index--; } throw new IndexOutOfBoundsException(); } /** * Returns the index of given node. If this node does not contain the given one, * -1 will be returned. This method is provided for <cite>Swing</cite> usage. */ @Override public final int getIndex(final javax.swing.tree.TreeNode node) { int index = 0; TreeNode child = firstChildren; while (child != null) { if (child == node) { assert getChildAt(index) == node : index; return index; } child = child.nextSibling; index++; } return -1; } /** * Returns the first children, or {@code null} if none. Used as an alternative to the * iterator, since profiling suggests that it has a perceptible cost. */ final TreeNode firstChildren() { return firstChildren; } /** * Returns the next sibling, or {@code null} if none. Used as an alternative to the * iterator, since profiling suggests that it has a perceptible cost. */ final TreeNode nextSibling() { return nextSibling; } /** * Returns an enumeration over the children. This method is provided for <cite>Swing</cite> * usage. When using {@link TreeNode} directly, consider using {@link #iterator} instead. */ @Override public final Enumeration<TreeNode> children() { return new Iter(firstChildren); } /** * Returns an iterator over the children. */ @Override public final Iterator<TreeNode> iterator() { return new Iter(firstChildren); } /** * The iterator over the children. */ private static final class Iter implements Iterator<TreeNode>, Enumeration<TreeNode> { /** * The next children to return, or {@code null} if we have reached * the end of iteration. */ private TreeNode children; /** * The last children returned, or {@code null} if none or removed. * Used for {@link #remove} implementation. */ private TreeNode last; /** * Creates an iterator starting at the given element. */ public Iter(final TreeNode first) { children = first; } /** * Returns {@code true} if there is more elements to return. */ @Override public boolean hasMoreElements() { return hasNext(); } /** * Returns {@code true} if there is more elements to return. */ @Override public boolean hasNext() { return children != null; } /** * Returns the next element. */ @Override public TreeNode next() { if (children != null) { last = children; children = children.nextSibling; return last; } throw new NoSuchElementException(); } /** * Returns the next element. */ @Override public TreeNode nextElement() { return next(); } /** * Removes the last children returned by this iterator. */ @Override public void remove() { if (last == null) { throw new IllegalStateException(); } last.remove(); last = null; } } /** * Removes the given tile. * * @param tile The tile to remove. * @return {@code true} if the given tile was found and removed. * @throws IOException if an I/O operation was required and failed. */ public final boolean remove(final Tile tile) throws IOException { return contains(tile.getAbsoluteRegion(), tile, true); } /** * Returns {@code true} if this tree contains all the given tile. * This method is insensitive to the order of elements in the given array. * * @param tiles The tiles to test for presence in this tree. * @return {@code true} if every given tiles are present in this tree. * @throws IOException if an I/O operation was required and failed. */ public final boolean containsAll(final Collection<Tile> tiles) throws IOException { for (final Tile tile : tiles) { if (!contains(tile.getAbsoluteRegion(), tile, false)) { return false; } } return true; } /** * Returns {@code true} if this tree contains the given tile. This method * invokes itself recursively for scanning through the subtrees. * * @param region The result of {@link Tile#getAbsoluteRegion}. * @param candidate The tile to test for presence in this tree. * @param remove {@code true} if the node containing the tile should be removed. * @return {@code true} if the given tile is presents in this tree. */ private boolean contains(final Rectangle region, final Tile candidate, final boolean remove) { if (equals(region)) { if (remove) { tile = null; } return true; } if (super.contains(region)) { TreeNode child = firstChildren; while (child != null) { if (child.contains(region, candidate, remove)) { if (remove && child.tile == null && child.isLeaf()) { child.remove(); } return true; } child = child.nextSibling; } } return false; } /** * Returns the tiles entirely contained in the given region of interest (ROI). * The returned collection is a copy that can be modified without altering the tree. */ public final Collection<Tile> containedIn(final Rectangle roi) { final List<Tile> tiles = new LinkedList<>(); containedIn(roi, tiles); return tiles; } /** * Returns the tiles entirely contained in the given region of interest (ROI). * This method invokes itself recursively down the tree. */ private void containedIn(final Rectangle roi, final Collection<Tile> tiles) { if (roi.contains(this)) { copy(tiles); } else if (roi.intersects(this)) { TreeNode child = firstChildren; while (child != null) { child.containedIn(roi, tiles); child = child.nextSibling; } } } /** * Copies unconditionally every tiles (including children) to the given list. * This method invokes itself recursively down the tree. */ private void copy(final Collection<Tile> tiles) { if (tile != null) { tiles.add(tile); } TreeNode child = firstChildren; while (child != null) { child.copy(tiles); child = child.nextSibling; } } /** * Returns the tiles intersecting the given region of interest (ROI). * The returned collection is a copy that can be modified without altering the tree. */ public final Collection<Tile> intersecting(final Rectangle roi) { final List<Tile> tiles = new LinkedList<>(); intersecting(roi, tiles); return tiles; } /** * Adds the tiles intersecting the given region of interest (ROI) to the given array. * This method invokes itself recursively down the tree. */ private void intersecting(final Rectangle roi, final Collection<Tile> tiles) { if (intersects(roi)) { if (tile != null) { tiles.add(tile); } TreeNode child = firstChildren; while (child != null) { child.intersecting(roi, tiles); child = child.nextSibling; } } } /** * Copies to the specified list the tiles in this node and every children nodes. */ final void getTiles(final List<Tile> tiles) { if (tile != null) { tiles.add(tile); } TreeNode child = firstChildren; while (child != null) { child.getTiles(tiles); child = child.nextSibling; } } /** * Compares this rectangle with the specified one for equality. This method * <strong>must</strong> be semantically identical to {@link Rectangle#equals} * and the inherited {@link Rectangle#hashCode} must be unchanged. This is required * for proper working of {@link #filter}. */ @Override public final boolean equals(final Object other) { return (other == this) || super.equals(other); } /** * Compares this tree and all its chilren with the specified one for equality. */ public final boolean deepEquals(final TreeNode other) { if (other == this) { return true; } if (!equals(other) || !Objects.equals(tile, other.tile)) { return false; } final Iterator<TreeNode> it1 = this .iterator(); final Iterator<TreeNode> it2 = other.iterator(); while (it1.hasNext()) { if (!it2.hasNext()) { return false; } final TreeNode t1 = it1.next(); final TreeNode t2 = it2.next(); return (t1 == t2) || (t1 != null && t1.deepEquals(t2)); } return !it2.hasNext(); } /** * Returns a string representation of this node as a tree. This is for debugging purpose only. */ public final String toTree() { return Trees.toString(this); } /** * Returns the string representation of this node. This string should holds in a single line * since it may be displayed in a {@link javax.swing.JTree}. The content may change in any * future version. It is provided mostly for debugging purpose. */ @Override public final String toString() { // Don't invoke assertValid() directly or indirectly since it could cause never-ending loop. String text; if (tile != null) { text = tile.toString(); } else { text = super.toString(); text = text.substring(text.lastIndexOf('.') + 1); } if (!isLeaf()) { final StringBuilder buffer = new StringBuilder(text). append(" (").append(getChildCount()).append(" childs"); if (hasOverlaps()) { buffer.append(", overlaps"); } text = buffer.append(')').toString(); } return text; } /** * Invoked in assertion for checking the validity of the whole tree. Checks in this method body * are performed unconditionally though explicit {@code if ... throw new AssertionError(...)} * statements rather than {@code assert} statements because the whole methods is expected to be * invoked in an {@code assert} statement. * * @return {@code true} on success. */ boolean checkValidity() { TreeNode child = firstChildren; if (child != null) { if (child.previousSibling != null) { throw new AssertionError(this); } if (isLeaf() || !getAllowsChildren()) { throw new AssertionError(this); } for (int index=0; ; index++) { if (child.parent != this) { throw new AssertionError(child); } if (getIndex(child) != index) { throw new AssertionError(child); } if (!child.checkValidity()) { return false; } final TreeNode next = child.nextSibling; if (next == null) { break; } if (next.previousSibling != child) { throw new AssertionError(child); } if (!contains(child) && width >= 0 && height >= 0) { throw new AssertionError(child); } child = next; } } if (child != lastChildren) { throw new AssertionError(this); } if (tile != null) { if (isEmpty()) { throw new AssertionError(this); } final Rectangle bounds; try { bounds = tile.getAbsoluteRegion(); } catch (IOException e) { throw new AssertionError(e); } if (!bounds.contains(this)) { throw new AssertionError(this); } } return true; } }