/* * 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.novelist; import java.util.Random; import java.util.Set; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import static com.google.common.base.Preconditions.checkNotNull; import org.novelang.designator.Tag; /** * @author Laurent Caillette */ public class LevelGenerator implements Generator< Level > { private final Random random ; private final Generator< Sentence > titleGenerator ; private final Generator< ? extends TextElement > bodyGenerator ; private final Bounded.Percentage prelevelProbability ; private final Bounded.Percentage sublevelProbability ; private final Bounded.IntegerInclusiveExclusive sublevelCountRange; private final boolean lockLevelCounterAtDepthOne ; private final int maximumStackHeight ; private final Set< Tag > availableTags ; private final Bounded.Percentage tagAppearanceProbability ; public LevelGenerator( final Configuration configuration ) { this.random = checkNotNull( configuration.getRandom() ) ; this.prelevelProbability = checkNotNull( configuration.getPrelevelProbability() ) ; this.sublevelProbability = checkNotNull( configuration.getSublevelProbability() ) ; this.sublevelCountRange = checkNotNull( configuration.getSublevelCountRange() ) ; this.lockLevelCounterAtDepthOne = configuration.getLockLevelCounterAtDepthOne() ; this.titleGenerator = checkNotNull( configuration.getTitleGenerator() ) ; this.bodyGenerator = checkNotNull( configuration.getBodyGenerator() ) ; this.maximumStackHeight = configuration.getMaximumDepth() ; Preconditions.checkArgument( maximumStackHeight > 0 ) ; this.tagAppearanceProbability = checkNotNull( configuration.getTagAppearanceProbability() ) ; this.availableTags = checkNotNull( configuration.getTags() ) ; final int levelCounterStart = configuration.getLevelCounterStart(); Preconditions.checkArgument( levelCounterStart >= 0 ) ; stack = new Stack( levelCounterStart ) ; } private Stack stack ; @Override public Level generate() { if( stack.getLevelCounter() == 0 && stack.getHeight() == 1 ) { stack.incrementLevelCounter() ; if( prelevelProbability.hit( random ) ) { // Pre-title body. return new Level( bodyGenerator.generate() ) ; } } // If we get here we didn't return pre-title body so we continue with a plain, titled level. final Markup markup = createMarkup( stack.getHeight() ) ; final Sentence title = new Sentence( new Word( "`" + stack.countersHierarchyAsString() + "` " ), titleGenerator.generate() ) ; final Set< Tag > effectiveTags = Sets.newHashSet() ; for( final Tag tag : availableTags ) { if( tagAppearanceProbability.hit( random ) ) { effectiveTags.add( tag ) ; } } if( stack.maximumLevelReached() ) { stack = stack.pop() ; stack.incrementLevelCounter() ; } else { if( lockLevelCounterAtDepthOne && stack.getHeight() == 1 ) { stack = new Stack( stack, null /* No maximum level. */ ) ; } else if( sublevelProbability.hit( random ) && stack.getHeight() < maximumStackHeight ) { final int sublevelCount = this.sublevelCountRange.boundInteger( random ) ; stack = new Stack( stack, sublevelCount ) ; } else { stack.incrementLevelCounter() ; } } final Level level = new Level( markup, title, bodyGenerator.generate(), effectiveTags ) ; return level ; } private static Markup createMarkup( final int depth ) { final StringBuilder text = new StringBuilder( "=" ) ; for( int i = 0 ; i < depth ; i++ ) { text.append( "=" ) ; } return new Markup( text.toString() ) ; } /** * Keeps track of the counter at given depth. */ private final static class Stack { private final Stack previous ; private final Integer maximumLevel; private int levelCounter ; public Stack( final int initialLevelCounter ) { this( null, null, initialLevelCounter ) ; } public Stack( final Stack previous, final Integer maximumLevel ) { this( previous, maximumLevel, 1 ) ; } public Stack( final Stack previous, final Integer maximumLevel, final int initialLevelCounter ) { this.previous = previous ; Preconditions.checkArgument( maximumLevel == null || maximumLevel >= 0, maximumLevel ) ; this.maximumLevel = maximumLevel ; levelCounter = initialLevelCounter ; } public boolean isBottom() { return previous == null ; } public Stack pop() { if( isBottom() ) { throw new IllegalStateException( "Can't pop the bottom, should not happen" ) ; } return previous ; } public int getLevelCounter() { return levelCounter; } private int getHeight() { return isBottom() ? 1 : 1 + previous.getHeight() ; } public void incrementLevelCounter() { if( maximumLevelReached() ) { throw new IllegalStateException( "Don't increment when maximum reached" ) ; } levelCounter++ ; } public boolean maximumLevelReached() { return maximumLevel != null && levelCounter > maximumLevel ; } public String countersHierarchyAsString() { if( isBottom() ) { return Integer.toString( levelCounter ) + "." ; } else { return previous.countersHierarchyAsString() + levelCounter + "." ; } } } @org.novelang.outfit.Husk.Converter( converterClass = Bounded.class ) public interface Configuration { Configuration withRandom( Random random ) ; Random getRandom() ; Configuration withPrelevelProbability( float percentage ) ; Bounded.Percentage getPrelevelProbability() ; Configuration withLevelCounterStart( int counter ) ; /** * Default value of 0 suitable for the first levels in a document. * When generating one document with several {@link org.novelang.novelist.Novelist.Ghostwriter}s, * each one should have its own counter value. */ int getLevelCounterStart() ; Configuration withSublevelProbability( float percentage ) ; Bounded.Percentage getSublevelProbability() ; Configuration withMaximumDepth( int depth ) ; int getMaximumDepth() ; Configuration withSublevelCountRange( int lowerBound, int upperBound ) ; Bounded.IntegerInclusiveExclusive getSublevelCountRange() ; Configuration withLockLevelCounterAtDepthOne( boolean lock ) ; boolean getLockLevelCounterAtDepthOne() ; Configuration withTitleGenerator( Generator< Sentence > generator ) ; Generator< Sentence > getTitleGenerator() ; Configuration withBodyGenerator( final Generator< ? extends TextElement > generator ) ; Generator< ? extends TextElement > getBodyGenerator() ; Configuration withTagAppearanceProbability( float percentage ) ; Bounded.Percentage getTagAppearanceProbability() ; Configuration withTags( Set< Tag > tags ) ; Set< Tag > getTags() ; } }