/* * 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.outfit.xml; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import static com.google.common.base.Preconditions.checkNotNull; import org.novelang.outfit.CollectionTools; /** * A stack of "buildup" objects (representing objects being built during XML parsing), * associating a "segment" to each stacked buildup, enforcing that stacked segments respect * a known order known as a "path". * * @param< SEGMENT > * @param< BUILDUP > * * @author Laurent Caillette */ /*package*/ class BuildupStack< SEGMENT, BUILDUP > { private final ImmutableSet< ImmutableList< SEGMENT > > legalPaths ; private final Function< SEGMENT, String > pathElementToString ; public BuildupStack( final ImmutableSet< ImmutableList< SEGMENT > > legalPaths, final Function< SEGMENT, String > pathElementToString ) { this.legalPaths = checkNotNull( legalPaths ) ; this.pathElementToString = checkNotNull( pathElementToString ) ; } private ImmutableList< Cell< SEGMENT, BUILDUP> > cells = ImmutableList.of() ; public void push( final SEGMENT segment, final BUILDUP buildup ) throws IllegalPathException { final ImmutableList< Cell< SEGMENT, BUILDUP> > newCells = CollectionTools.append( cells, new Cell< SEGMENT, BUILDUP>( segment, buildup ) ) ; final ImmutableList< SEGMENT > newPath = CollectionTools.append( getPath(), segment ) ; if( ! newPath.isEmpty() && legalPaths.contains( newPath ) ) { cells = newCells ; } else { throw new IllegalPathException( newPath, pathElementToString ) ; } } public void pop() { checkNotEmpty() ; cells = CollectionTools.removeLast( cells ) ; } public SEGMENT topSegment() { checkNotEmpty() ; final int lastIndex = cells.size() - 1; return cells.get( lastIndex ).segment ; } public BUILDUP getBuildupOnTop() { return getBuildupAtDepth( 0 ) ; } public BUILDUP getBuildupUnderTop() { return getBuildupAtDepth( 1 ) ; } public BUILDUP getBuildupAtDepth( final int depth ) { checkNotEmpty() ; final int index = cells.size() - 1 - depth ; return cells.get( index ).buildup ; } public void setTopBuildup( final BUILDUP buildup ) { checkNotEmpty() ; final ImmutableList.Builder< Cell< SEGMENT, BUILDUP > > newStackBuilder = ImmutableList.builder() ; for( int i = 0 ; i < cells.size() - 1 ; i ++ ) { newStackBuilder.add( cells.get( i ) ) ; } newStackBuilder.add( new Cell< SEGMENT, BUILDUP >( topSegment(), buildup ) ) ; cells = newStackBuilder.build() ; } private void checkNotEmpty() { if( isEmpty() ) { throw new IllegalStateException( "Empty stack" ) ; } } public boolean isEmpty() { return cells.isEmpty() ; } public ImmutableList< SEGMENT > getPath() { final ImmutableList.Builder< SEGMENT > builder = ImmutableList.builder() ; for( final Cell< SEGMENT, ? > cell : cells ) { builder.add( cell.segment ) ; } return builder.build() ; } public String getPathAsString() { return pathElementsAsString( getPath(), pathElementToString ) ; } private static< T > String pathElementsAsString( final ImmutableList< T > path, final Function< T, String > pathElementToString ) { return Joiner.on( "/" ).join( Iterables.transform( path, pathElementToString ) ) ; } /** * @author Laurent Caillette */ public static class IllegalPathException extends Exception { public < T > IllegalPathException( final ImmutableList< T > path, final Function< T , String> pathElementToString ) { super( "Not a legal path: " + "'" + pathElementsAsString( path, pathElementToString ) + "'" ) ; } } private static final class Cell< SEGMENT, BUILDUP > { private final SEGMENT segment ; private final BUILDUP buildup ; private Cell( final SEGMENT segment, final BUILDUP buildup ) { this.segment = checkNotNull( segment ) ; this.buildup = buildup ; } } }