/* * Copyright (C) 2011 Laurent Caillette * * This program 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, either * version 3 of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.novelang.common.tree; import com.google.common.base.Preconditions; import org.apache.commons.lang.NullArgumentException; /** * An immutable structure representing where a {@link Tree} lies inside a bigger owning * {@link Tree}. * <p> * A {@code Treepath} object is made of: * <ul> * <li>A reference to the {@code Tree} itself. * <li>a reference to another {@code Treepath} which references the parent of our * {@code Tree} and so on. * </ul> * <p> * Each reference to a parent tree is like a previous step on the path from the top to the bottom. * The top tree is the <strong>start</strong> of the path and the lowest-level tree * is the <strong>end</strong> (the terms "start" and "end" are parts of the API). * <p> * Each previous step includes the reference to the owning tree and the <strong>index</strong> * of the child inside its parent. This is required in case one {@code Tree} having * multiple references on the same child object. * <p> * The backward chaining makes possible to have immutable {@code Treepath} objects. * <p> * Given this tree below: * <pre> * t0 * / \ * t1 t2 * / \ * t3 t7 * / | \ * t4 t5 t6 * </pre> * <p> * The {@code Treepath} starting on {@code t0} and ending on {@code t6} can be represented * like this: * <pre> * t0 <-[1]- t2 <-[0]- t3 <-[2]- t6 * ^ start ^ end * </pre> * <p> * While the backward chaining may seem confusing, most of {@code Treepath}-related methods * allow to thing "from start to end". * * @see javax.swing.tree.TreePath Swing's {@code TreePath} from which this class was inspired from. * * @author Laurent Caillette */ public final class Treepath< T extends Tree< T > > implements Comparable< Treepath< T > > { private final Treepath< T > previous; private final int indexInPrevious; private final T treeAtEnd; private Treepath( final Treepath< T > previous, final int indexInPrevious ) { if( null == previous ) { throw new NullArgumentException( "Cannot define null previous path with this constructor" ) ; } this.previous = previous; this.treeAtEnd = previous.getTreeAtEnd().getChildAt( indexInPrevious ); this.indexInPrevious = indexInPrevious; } private Treepath( final T tree ) { previous = null ; indexInPrevious = -1 ; treeAtEnd = tree ; } /** * Returns the reference to the start of the path, corresponding to the root tree. * @return a non-null object. */ public T getTreeAtStart() { if( null == previous ) { return treeAtEnd; } else { return previous.getTreeAtStart() ; } } /** * Returns the reference to the start of the path, corresponding to the root tree. * @return a non-null object. */ public Treepath< T > getStart() { if( null == previous ) { return this ; } else { return previous.getStart() ; } } /** * Returns the {@code Tree} at the end of this path. * @return a non-null object. */ public T getTreeAtEnd() { return treeAtEnd; } /** * Returns the length of the path, corresponding to the number of chained parent {@code Treepath} * instances plus one. * * @return an integer equal to or greater than 1. */ public int getLength() { if( null == previous ) { return 1 ; } else { return previous.getLength() + 1 ; } } /** * Returns a reference to the {@code Treepath} object representing the whole path to the * previous step. * @return a possibly null object when this {@code Treepath} represents the start of the path. */ public Treepath< T > getPrevious() { return previous; } /** * Returns the index inside the parent tree which corresponds to the previous step of the path. * * @return -1 if this {@code Treepath} represents the start of the path, the index of the tree * in its parent otherwise. */ public int getIndexInPrevious() { return indexInPrevious; } /** * Returns the indices in parent tree, from the second treepath to the end. * * @return null if this {@code Treepath} has a {@link #getLength() length} of 1, or an array * of {@code int}s of {@link #getLength() length - 1} elements, corresponding to the * index in parent tree of each referenced tree. * * @see Treepath#getIndexInPrevious() * @see Treepath#create(Tree, int...) * @see Treepath#create(Treepath, int...) */ public int[] getIndicesInParent() { if( getLength() == 1 ) { return null ; } final int[] indices = new int[ getLength() - 1 ] ; for( int distance = indices.length - 1 ; distance >= 0 ; distance -- ) { indices[ indices.length - distance - 1 ] = getTreepathAtDistance( distance ).getIndexInPrevious() ; } return indices ; } /** * Returns the {@code Tree} at a given distance, which is the n<sup>th</sup> * from the end. * Invariant: {@code getTreeAtHeight( 0 ) == getEnd()}. * * @param distance 0 or more. * @return a non-null object. * @throws IllegalArgumentException if negative distance or distance greater than or * or equal to {@link #getLength()}. */ public T getTreeAtDistance( final int distance ) throws IllegalDistanceException { return getTreepathAtDistance( distance ).getTreeAtEnd() ; } /** * Returns the {@code Tree} at a given distance, which is the n<sup>th</sup> * from the end. * Invariant: {@code getTreeAtHeight( 0 ) == getEnd()}. * * @param distance 0 or more. * @return a non-null object. * @throws IllegalArgumentException if negative distance or distance greater than or * or equal to {@link #getLength()}. */ public Treepath< T > getTreepathAtDistance( final int distance ) throws IllegalDistanceException { if( 0 > distance ) { throw new IllegalDistanceException( distance ) ; } if( 0 == distance ) { return this ; } else { if( null == previous ) { throw new IllegalDistanceException( distance ) ; } else { try { return getPrevious().getTreepathAtDistance( distance - 1 ) ; } catch( IllegalDistanceException e ) { throw new IllegalDistanceException( distance + 1 ) ; } } } } /** * Returns the {@code Tree} at a given distance, which is the n<sup>th</sup> * from the start. * Invariant: {@code getTreeAtHeight( 0 ) == getStart()}. * * @param distance 0 or more. * @return a non-null object. * @throws IllegalArgumentException if negative distance or distance greater than or * or equal to {@link #getLength()}. */ public Treepath< T > getTreepathAtDistanceFromStart( final int distance ) throws IllegalDistanceException { return getTreepathAtDistance( getLength() - distance - 1 ) ; } @Override public String toString() { boolean first = true ; final StringBuilder buffer = new StringBuilder(); for( int i = 0 ; i < this.getLength() ; i++ ) { if( first ) { first = false ; } else { buffer.append( " -> " ) ; } buffer.append( "{" ).append( this.getTreeAtDistance( i ) ).append( "}" ) ; } return buffer.toString() ; } // =============== // Factory methods // =============== /** * Creates a {@code Treepath} from a parent {@code Treepath}, * adding the {@code indexInParent}<sup>th</sup> child from parent's end. * * @param root non-null object. * @param indexInParent positive integer, must be a valid index. * @return a non-null object. */ public static< T extends Tree< T > > Treepath< T > create( final Treepath< T > root, final int indexInParent ) { return new Treepath< T >( root, indexInParent ) ; } /** * Creates a {@code Treepath} from a root {@code Tree}, adding a child, a * grandchild and so on in order, given their respective position. * * @param root non-null object * @param indexes must be a valid index in each of their respective tree. * @return a non-null object. * @see #create(Treepath, int...) */ public static< T extends Tree< T > > Treepath< T > create( final T root, final int... indexes ) { return create( create( root ), indexes ) ; } /** * Creates a {@code Treepath} extending another {@code Treepath}, adding a child, a * grandchild and so on in order, given their respective position. * <p> * Given this tree below: * <pre> * *t0 * / \ * t1 *t2 * / \ * *t3 t7 * / | \ * t4 t5 *t6 * </pre> * The {@code Treepath} indicated with asterisks ({@code t0<-t2<-t3<-t6}) is formed * by calling: * <pre> * Treepath< T >.create( t0, 1, 0, 2) ; * </pre> * * @param parent non-null object * @param indexes must be a valid index in each of their respective tree. * @return a non-null object. * @see #create(Treepath, int...) */ public static< T extends Tree< T > > Treepath< T > create( final Treepath< T > parent, final int... indexes ) { if( null == indexes || 0 == indexes.length ) { return parent ; } else { final int newLength = indexes.length - 1 ; final int[] newIndexes = new int[ newLength ] ; System.arraycopy( indexes, 1, newIndexes, 0, newLength ) ; return create( create( parent, indexes[ 0 ] ), newIndexes ) ; } } /** * Creates a {@code Treepath} out from a single {@code Tree}. * @param tree a non-null object. * @return a non-null object. */ public static< T extends Tree< T > > Treepath< T > create( final T tree ) { return new Treepath< T >( tree ) ; } private static class IllegalDistanceException extends IllegalArgumentException { public IllegalDistanceException( final int distance ) { super( "distance=" + distance ) ; } } // ========== // Comparable // ========== /** * Compares with another {@code Treepath}. * For a whole tree, sorting the {@code Treepath} objects with one for each node gives the * same node order as for pre-order traversal. * <p> * Implementation note: comparison occurs on indexes; node equality is not good because * a tree may reference the same child object more than once. * * @param other a non-null object with the same {@link #getTreeAtStart()} reference. * @return 0 if both {@code Treepath} objects have the same length and same indices in * parent {@code Treepath}s; * <1 if this {@code Treepath} is "on the left" of the other; * >1 if this {@code Treepath} is "on the right" of the other. * * @throws NullPointerException if {@code other} is null. * @throws IllegalArgumentException if {@code other} doesn't refer to the same * {@link #getTreeAtStart() start tree} . */ @Override public int compareTo( final Treepath< T > other ) { Preconditions.checkNotNull( other ) ; Preconditions.checkArgument( other.getTreeAtStart() == this.getTreeAtStart() ) ; final int shortestLength = Math.min( this.getLength(), other.getLength() ) ; for( int distance = 1 ; distance < shortestLength ; distance ++ ) { final Treepath< T > thisIntermediateTreepath = this.getTreepathAtDistanceFromStart( distance ) ; final Treepath< T > otherIntermediateTreepath = other.getTreepathAtDistanceFromStart( distance ) ; final int localDifference = thisIntermediateTreepath.getIndexInPrevious() - otherIntermediateTreepath.getIndexInPrevious() ; if( localDifference != 0 ) { return localDifference ; } } if( this.getLength() == other.getLength() ) { // Same length at this point with every indexes equal means we're on the same node. if( this.getTreeAtEnd() != other.getTreeAtEnd() ) { throw new Error( "Implementation problem!" ) ; } return 0 ; } else { return this.getLength() - other.getLength() ; } } }