/*
* 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.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
import org.novelang.outfit.DefaultCharset;
/**
* Generates multiple {@link org.novelang.novella.Novella}s incrementally.
*
* @author Laurent Caillette
*/
public class Novelist {
private static final Logger LOGGER = LoggerFactory.getLogger( Novelist.class );
private final File directory ;
private final String filenamePrototype ;
private final GeneratorSupplier< Level > generatorSupplier ;
private final List<Ghostwriter> ghostwriters = Lists.newArrayList() ;
public Novelist(
final File directory,
final String fileNamePrototype,
final GeneratorSupplier< Level > generatorSupplier,
final int ghostwriterCount
) throws IOException {
checkArgument( directory.isDirectory() || ! directory.exists() ) ;
this.directory = directory ;
checkArgument( ! StringUtils.isBlank( fileNamePrototype ) ) ;
this.filenamePrototype = fileNamePrototype ;
this.generatorSupplier = checkNotNull( generatorSupplier ) ;
createFreshDirectory( directory ) ;
for( int i = 1 ; i <= ghostwriterCount ; i ++ ) {
addGhostwriter() ;
}
createOpus( directory ) ;
}
public static final String BOOK_NAME_RADIX = "opus" ;
private static final String BOOK_FILE_NAME = BOOK_NAME_RADIX + ".nlb" ;
private static final String BOOK_CONTENT = "insert file:." ;
private static void createFreshDirectory( final File directory ) throws IOException {
if( directory.exists() ) {
FileUtils.deleteDirectory( directory ) ;
LOGGER.info( "Deleted directory '", directory.getAbsolutePath(), "' and all its contents." ) ;
}
if( directory.mkdirs() ) {
LOGGER.info( "Created directory '", directory.getAbsolutePath(), "'." ) ;
}
}
private static File createFreshNovellaFile(
final File directory,
final String prototype,
final int counter
)
throws IOException
{
final File targetFile = new File(
directory, prototype + "-" + String.format( "%04d", counter ) + ".nlp" ) ;
if( targetFile.exists() ) {
if( ! targetFile.delete() ) {
throw new IOException( "Could not delete file: '" + targetFile.getAbsolutePath() + "'" ) ;
}
if( ! targetFile.createNewFile() ) {
throw new IOException( "Could not create file: '" + targetFile.getAbsolutePath() + "'" ) ;
}
}
return targetFile ;
}
private static void createOpus( final File directory ) throws IOException {
final File bookFile = new File( directory, BOOK_FILE_NAME ) ;
FileUtils.writeStringToFile( bookFile, BOOK_CONTENT ) ;
LOGGER.info( "Created Opus: '", bookFile.getAbsolutePath(), "'" ) ;
}
public void addGhostwriter() throws IOException {
synchronized( ghostwriters ) {
final int ghostwriterIndex = ghostwriters.size() + 1 ;
final File file = createFreshNovellaFile( directory, filenamePrototype, ghostwriterIndex ) ;
ghostwriters.add( new Ghostwriter( file, generatorSupplier.get( ghostwriterIndex ) ) ) ;
}
}
public long addGhostwriter( final int iterationCountForNewGhostwriter ) throws IOException {
final Ghostwriter newGhostwriter ;
synchronized( ghostwriters ) {
final int ghostwriterIndex = ghostwriters.size() + 1 ;
final File file = createFreshNovellaFile( directory, filenamePrototype, ghostwriterIndex ) ;
newGhostwriter = new Ghostwriter( file, generatorSupplier.get( ghostwriterIndex ) );
ghostwriters.add( newGhostwriter ) ;
return newGhostwriter.write( iterationCountForNewGhostwriter ) ;
}
}
/**
* Calls {@link org.novelang.novelist.Novelist.Ghostwriter#write(int)} multiple times.
*
* @param iterationCount number of iterations.
* @return number of bytes written.
*/
public long write( final int iterationCount ) throws IOException {
long bytesWritten = 0 ;
synchronized( ghostwriters ) {
for( final Ghostwriter ghostwriter : ghostwriters ) {
bytesWritten += ghostwriter.write( iterationCount ) ;
}
}
LOGGER.debug( "Writing done, wrote ", bytesWritten, " bytes." ) ;
return bytesWritten ;
}
private static class Ghostwriter {
private final File file ;
private final Generator< ? extends TextElement > generator ;
// private static final Log WRITER_LOG = LogFactory.getLog( Ghostwriter.class ) ;
private Ghostwriter(
final File file,
final Generator< ? extends TextElement > generator
) {
this.file = file ;
this.generator = generator ;
}
public long write( final int iterationCount ) throws IOException {
Preconditions.checkArgument( iterationCount >= 0 ) ;
long bytesWritten = 0L ;
final OutputStream outputStream = new FileOutputStream( file, true ) ;
try {
for( int i = 0 ; i < iterationCount ; i ++ ) {
final String text = generator.generate().getLiteral() ;
bytesWritten += ( long ) text.length() ;
IOUtils.write( text, outputStream, DefaultCharset.SOURCE.name() ) ;
// WRITER_LOG.debug( "{" + name + "} wrote " + text.length() + " bytes." ) ;
}
} finally {
outputStream.close() ;
}
return bytesWritten ;
}
}
private static final int DEFAULT_GHOSTWRITER_COUNT = 5 ;
private static final int DEFAULT_ITERATION_COUNT = 3 ;
@SuppressWarnings( { "UseOfSystemOutOrSystemErr" } )
public static void main( final String[] args ) throws IOException {
if( args.length < 1 ) {
System.out.println( Novelist.class.getName() +
"<target-file-noprefix> [ ghostwriter-count [iteration-count] ]" ) ;
System.exit( 1 ) ;
}
final int ghostwriterCount ;
if( args.length < 2 ) {
ghostwriterCount = DEFAULT_GHOSTWRITER_COUNT ;
} else {
ghostwriterCount = Integer.parseInt( args[ 1 ] ) ;
}
final int iterationCount ;
if( args.length < 3 ) {
iterationCount = DEFAULT_ITERATION_COUNT ;
} else {
iterationCount = Integer.parseInt( args[ 2 ] ) ;
}
new Novelist(
new File( "_novelist" ),
args[ 0 ],
new LevelGeneratorSupplierWithDefaults(),
ghostwriterCount
).write( iterationCount ) ;
}
public interface GeneratorSupplier< T extends TextElement > {
Generator< ? extends T > get( final int number ) ;
}
public static class LevelGeneratorSupplierWithDefaults implements GeneratorSupplier< Level > {
@Override
public Generator< ? extends Level > get( final int number ) {
final LevelGenerator.Configuration configuration = GenerationDefaults.FOR_LEVELS
.withLevelCounterStart( number )
.withLockLevelCounterAtDepthOne( true )
;
return new LevelGenerator( configuration ) ;
}
}
}