/* * 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.Iterator; import java.util.List; import java.util.NoSuchElementException; import com.google.common.collect.Lists; import com.google.common.collect.ObjectArrays; import org.apache.commons.lang.NullArgumentException; /** * Immutable base class for homogeneous n-ary trees. * <p> * This class is generic for strong-typing the {@link Tree#adopt(Iterable)}} method. * * @author Laurent Caillette */ public abstract class ImmutableTree< T extends Tree< T > > implements Tree< T > { /** * Don't be stupid using reflection to make this mutable! */ private final T[] children ; /** * Constructor. * @param children may be null but may not contain nulls. * @throws NullArgumentException */ protected ImmutableTree( final T... children ) throws NullArgumentException { this( Lists.newArrayList( children ) ) ; } /** * Constructor. * @param children a non-null iterable returning non-null iterators iterating over * non-null objects. * @throws NullArgumentException */ protected ImmutableTree( final Iterable< ? extends T > children ) throws NullArgumentException { final List< T > childList = Lists.newArrayList( children ) ; if( childList.isEmpty() ) { this.children = null ; } else { this.children = ( T[] ) createArray( this, childList.get( 0 ), childList.size() ) ; for( int i = 0 ; i < childList.size() ; i++ ) { final T child = childList.get( i ) ; if( null == child ) { throw new NullArgumentException( "Null child at index " + i ) ; } this.children[ i ] = childList.get( i ) ; } } } @Override public final int getChildCount() { return null == children ? 0 : children.length ; } @Override public final T getChildAt( final int index ) { if( index >= getChildCount() ) { throw new ArrayIndexOutOfBoundsException( "Unsupported index: " + index + " (child count: " + getChildCount() + ")" ) ; } return children[ index ] ; } /** * Convenience method (not a part of {@link Tree} contract). * * @return a non-null iterable returning non-null iterators, which iterate on non-null objects. */ public Iterable< ? extends T > getChildren() { return new Iterable() { @Override public Iterator< T > iterator() { return new ChildrenIterator() ; } } ; } private class ChildrenIterator implements Iterator< T > { private int current = 0 ; @Override public boolean hasNext() { return current < getChildCount() ; } @Override public T next() throws NoSuchElementException { if( hasNext() ) { return ImmutableTree.this.getChildAt( current++ ) ; } else { throw new NoSuchElementException( "No more children (child count: " + getChildCount() + ")" ) ; } } @Override public void remove() { throw new UnsupportedOperationException( "remove" ) ; } } /** * Creates an array for storing children. * If the concrete instance ({@code this}) is an instance of * {@link org.novelang.common.tree.StorageTypeProvider}, then returned array is of type * returned by {@link StorageTypeProvider#getStorageType()}. * Otherwise, it is of the type of the {@code fallback} parameter. * <p> * This method is used internally. * It is made a member of {@code ImmutableTree} in order to avoid a cyclic depedency * with {@code TreeTools} (which would be the logical place). * * @param fallback a non-null object. * @param arraySize a non-negative number. * @return a non-null array of the given size. * * @see org.novelang.common.tree.StorageTypeProvider */ protected static< T extends Tree > T[] createArray( final T tree, final T fallback, final int arraySize ) { final Class< T > concreteClass ; if( tree instanceof StorageTypeProvider ) { concreteClass = ( ( StorageTypeProvider ) tree ).getStorageType() ; } else { concreteClass = ( Class< T > ) fallback.getClass() ; } return ObjectArrays.newArray( concreteClass, arraySize ) ; } }