/* * [Reflow.java] * * Summary: Reflows a text file so each line in roughly the same length. * * Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2012-05-21 initial version. */ package com.mindprod.reflow; import com.mindprod.common18.EIO; import com.mindprod.hunkio.HunkIO; import java.io.BufferedReader; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import static java.lang.System.*; /** * Reflows a text file so each line in roughly the same length. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2012-05-21 initial version * @since 2012-05-21 */ public class Reflow { /** * Max line length of output. ideally would be * configurable. */ private static final int DEFAULT_LINE_LENGTH = 60; private static final int FIRST_COPYRIGHT_YEAR = 2012; @SuppressWarnings( "UnusedDeclaration" ) private static final String EmbeddedCopyright = "Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; @SuppressWarnings( { "UnusedDeclaration" } ) private static final String RELEASE_DATE = "2012-05-21"; /** * how to use the command line */ private static final String USAGE = "\nReflow needs a single filename.csv followed by optional [integer max line " + "length default 60] or the word [nowrap]"; /** * embedded version string. */ @SuppressWarnings( { "UnusedDeclaration" } ) private static final String VERSION_STRING = "1.0"; /** * input before file */ private static File inFile; /** * input "before" filename */ private static String inFilename; /** * input before reader */ private static BufferedReader inReader; /** * maximum length of the line */ private static int maxLineLength; /** * output after transformed file */ private static File tempOutFile; /** * output after transformed Writer */ private static PrintWriter tempOutWriter; /** * analyse the command line. It should have a filename * case-insensitive. */ private static void analyseCommandLine( String[] args ) { switch ( args.length ) { default: err.println( "Oops! " + USAGE ); die(); break; case 1: inFilename = args[ 0 ]; /* file to convert */ maxLineLength = DEFAULT_LINE_LENGTH; break; case 2: inFilename = args[ 0 ]; /* file to convert */ final String p = args[ 1 ]; if ( p.equals( "nowrap" ) ) { maxLineLength = ( int ) Math.min( new File( inFilename ).length(), 32 * 1024 ); } else { maxLineLength = Integer.parseInt( p ); } break; } } // end analyseCommandLine /** * abort the run, clean up as best as possible. */ private static void die() { honk(); try { if ( inReader != null ) { inReader.close(); } if ( tempOutWriter != null ) { tempOutWriter.close(); } } catch ( IOException e ) { } System.exit( 1 ); /* exit with errorlevel = 1 */ } // end die /** * emits paragraph followed by blank line. * * @param words Array list of words to output */ private static void emitParagraph( ArrayList words ) { /* if paragraph empty, nothing to do */ if ( words.size() == 0 ) { return; } int lineLength = 0; for ( String word : words ) { if ( lineLength + word.length() + 1 > maxLineLength ) { // won't fit. Start a new line. if ( lineLength != 0 ) { tempOutWriter.println(); lineLength = 0; } // no lead space } else { /* will fit */ if ( lineLength != 0 ) { // add lead space tempOutWriter.print( ' ' ); lineLength++; } } tempOutWriter.print( word ); lineLength += word.length(); } // end for // create blank line separating paragraphs in default encoding. tempOutWriter.println(); tempOutWriter.println(); } /** * make a noise */ private static void honk() { java.awt.Toolkit.getDefaultToolkit().beep(); } /** * open the input "before" file */ private static void openInReader() { try { inFile = new File( inFilename ); if ( !inFile.exists() ) { err.print( "Oops! Cannot find file " ); err.println( inFilename ); die(); } if ( !inFile.canRead() ) { err.print( "Oops! no permission to read (i.e. examine) the file " ); err.println( inFilename ); die(); } if ( !inFile.canWrite() ) { // canWrite true implies file exists. err.print( "Oops! no permission to write (i.e. change) the file " ); err.println( inFilename ); die(); } inReader = EIO.getBufferedReader( inFile, 1024 * 256 * 3, EIO.UTF8 ); } catch ( FileNotFoundException e ) { err.print( "Oops! Cannot open file " ); err.println( inFilename ); die(); } } // end openInReader /** * open the output "after" file */ private static void openOutWriter() { try { // get a temporary file in the same directory as inFile. tempOutFile = HunkIO.createTempFile( "reflow", "tmp", inFile ); tempOutWriter = EIO.getPrintWriter( tempOutFile, 256 * 1024, EIO.UTF8 ); } catch ( IOException e ) { err.println( "Oops! Cannot create the temporary work file\n" ); die(); } } // end OpenOutWriter /** * copy inReader to tempOutWriter, reflowing * Presume files already open. Does not close them. * * @throws IOException */ private static void processFiles() throws IOException { // list of words in paragraph ArrayList words = new ArrayList<>( 149 ); // have we just seen an new line. // blank lines separate paragraphs boolean recentNL = false; // the current word we are building up. StringBuilder word = new StringBuilder( 50 ); try { charReadLoop: while ( true ) { int c = inReader.read(); if ( c < 0 ) { break charReadLoop; } switch ( c ) { case 160: case ' ': case '\t': if ( word.length() != 0 ) { words.add( word.toString() ); word.setLength( 0 ); } break; case '\n': if ( word.length() != 0 ) { words.add( word.toString() ); word.setLength( 0 ); } if ( recentNL ) { emitParagraph( words ); words = new ArrayList<>( 149 ); recentNL = false; } else { recentNL = true; } break; case '\r': /* dos has \r\n, unix just \n */ /* we just ignore them here and generate them as needed on \n. */ break; default: /* ordinary non-blank char */ recentNL = false; word.append( ( char ) c ); break; } /* end switch */ } /* end while */ // dump possible last paragraph without trailing blank line. if ( words.size() != 0 ) { emitParagraph( words ); } } // end try catch ( EOFException e ) { } } // end processFiles /** * Command line utility to reflow the text. */ public static void main( String[] args ) { try { analyseCommandLine( args ); openInReader(); /* Open input "before" file. */ /* Make sure file exists before */ /* song and dance about extension. */ openOutWriter(); /* open output "after" file */ out.println( "Reflowing " + inFilename ); /* copy inReader to tempOutWriter reflowing the text */ processFiles(); inReader.close(); tempOutWriter.close(); HunkIO.deleteAndRename( tempOutFile, inFile ); } catch ( IOException e ) { err.println( "Oops! IO failure. e.g. out of disk space." ); die(); } } // end main } // end class Reflow