/*
* 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 ) ;
}
}