/* * 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 java.util.List; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import org.apache.commons.lang.NullArgumentException; /** * Utility class for manipulating {@link Tree}s through copy-on-change. * * @author Laurent Caillette */ public class TreeTools { private TreeTools() { throw new Error( "Don't instantiate this class" ) ; } /** * Returns a copy of a {@code Tree} with the {@code newChild} added as first child. * * @param tree a non-null object that may implement {@link StorageTypeProvider}. * @param newChild a non-null object. * @return a non-null object. */ public static < T extends Tree< T > > T addFirst( final T tree, final T newChild ) { final List< T > newChildList = Lists.newArrayListWithCapacity( tree.getChildCount() + 1 ) ; newChildList.add( Preconditions.checkNotNull( newChild ) ) ; for( int i = 0 ; i < tree.getChildCount() ; i++ ) { newChildList.add( tree.getChildAt( i ) ) ; } return tree.adopt( newChildList ) ; } /** * Returns a copy of a {@code Tree} with the {@code newChild} added as first child. * * @param tree a non-null object that may implement {@link StorageTypeProvider}. * @param newChild a non-null object. * @param position a value between [0, {@link Tree#getChildCount()}[. * @return a non-null object. */ public static < T extends Tree< T > > T add( final T tree, final T newChild, final int position ) { Preconditions.checkNotNull( newChild ) ; if( position < 0 || position > tree.getChildCount() ) { throw new IllegalArgumentException( "Invalid position:" + position + " as childcount=" + tree.getChildCount() ) ; } final List< T > newChildList = Lists.newArrayListWithCapacity( tree.getChildCount() + 1 ) ; int oldArrayIndex = 0 ; for( int newArrayIndex = 0 ; newArrayIndex <= tree.getChildCount() ; newArrayIndex ++ ) { if( position == newArrayIndex ) { newChildList.add( newArrayIndex, newChild ) ; } else { newChildList.add( newArrayIndex, tree.getChildAt( oldArrayIndex ) ) ; oldArrayIndex++ ; } } return tree.adopt( newChildList ) ; } /** * Returns a copy of a {@code Tree} with the {@code newChild} added as last child. * * @param tree a non-null object that may implement {@link StorageTypeProvider}. * @param newChild a non-null object. * @return a non-null object. */ public static < T extends Tree< T > > T addLast( final T tree, final T newChild ) { final List< T > newChildList = Lists.newArrayListWithCapacity( tree.getChildCount() + 1 ) ; for( int i = 0 ; i < tree.getChildCount() ; i++ ) { newChildList.add( i, tree.getChildAt( i ) ) ; } newChildList.add( Preconditions.checkNotNull( newChild ) ) ; return tree.adopt( newChildList ) ; } /** * Returns a copy of a {@code Tree} with the {@code newChildren} added as last children. * * @param tree a non-null object that may implement {@link StorageTypeProvider}. * @param newChildren a non-null object iterating over non-null objects. * @return non-null object. */ public static < T extends Tree< T > > T addLast( final T tree, final Iterable< ? extends T > newChildren ) { final List< ? extends T > newChildrenList = Lists.newArrayList( newChildren ) ; if( 0 >= newChildrenList.size() ) { return tree ; } else { // We treat the first child in a special way because it is used to guess the // typeof the array. Without the check below, caller would get // an obscure NullPointerException when attempting to get the class of the null object. final T firstChild = newChildrenList.get( 0 ) ; if( null == firstChild ) { throw new NullArgumentException( "Null child at index 0" ) ; } final List< T > newChildList = Lists.newArrayListWithCapacity( tree.getChildCount() + 1 ) ; int i ; for( i = 0 ; i < tree.getChildCount() ; i++ ) { final T child = tree.getChildAt( i ) ; if( null == child ) { throw new NullArgumentException( "Null child at index " + i ) ; } newChildList.add( i, child ) ; } for( int j = 0 ; j < newChildrenList.size() ; j++ ) { newChildList.add( i + j, newChildrenList.get( j ) ) ; } return tree.adopt( newChildList ) ; } } /** * Returns a copy of this {@code Tree} minus the child of given index. * @param tree a non-null object that may implement {@link StorageTypeProvider}. * @param index a value between [0, {@link Tree#getChildCount()}[. * @return a non-null object. * @throws ArrayIndexOutOfBoundsException */ public static < T extends Tree< T > > T remove( final T tree, final int index ) throws ArrayIndexOutOfBoundsException { Preconditions.checkArgument( index >= 0 ) ; if( tree.getChildCount() < index ) { throw new ArrayIndexOutOfBoundsException( "Cannot remove child at index " + index + " (child count: " + tree.getChildCount() + ")" ) ; } final List< T > newChildList = Lists.newArrayListWithCapacity( tree.getChildCount() - 1 ) ; int keep = 0 ; for( int i = 0 ; i < tree.getChildCount() ; i++ ) { if( i != index ) { newChildList.add( keep ++, tree.getChildAt( i ) ) ; } } return tree.adopt( newChildList ) ; } /** * Returns a copy of this {@code Tree} minus the child of given index. * @param tree a non-null object that may implement {@link StorageTypeProvider}. * @param predicate a {@code Predicate} returning {@code true} for children to keep. * @return a non-null object. * @throws ArrayIndexOutOfBoundsException */ public static < T extends Tree< T > > T remove( final T tree, final Predicate< ? super T > predicate ) throws ArrayIndexOutOfBoundsException { final List< T > newChildList = Lists.newArrayListWithCapacity( tree.getChildCount() ) ; for( int i = 0 ; i < tree.getChildCount() ; i++ ) { final T child = tree.getChildAt( i ); if( ! predicate.apply( child ) ) { newChildList.add( child ) ; } } return tree.adopt( newChildList ) ; } /** * Returns a copy of this {@code Tree} minus the child of given index. * @param parent non-null object that may implement {@link StorageTypeProvider}. * @param index a value between [0, {@link Tree#getChildCount()}[. * @return a non-null object. * @throws ArrayIndexOutOfBoundsException */ public static < T extends Tree< T > > T replace( final T parent, final int index, final T newChild ) throws ArrayIndexOutOfBoundsException { if( index < 0 ) { throw new ArrayIndexOutOfBoundsException( "Negative index: " + index ) ; } if( parent.getChildCount() < index ) { throw new ArrayIndexOutOfBoundsException( "Cannot remove child at index " + index + " (child count: " + parent.getChildCount() + ")" ) ; } final List< T > newChildList = Lists.newArrayListWithCapacity( parent.getChildCount() ) ; for( int i = 0 ; i < parent.getChildCount() ; i++ ) { if( i == index ) { newChildList.add( i, newChild ) ; } else { newChildList.add( i, parent.getChildAt( i ) ) ; } } return parent.adopt( newChildList ) ; } }