bb.io
Class FileRollingWriter

java.lang.Object
  extended by java.io.Writer
      extended by bb.io.FileRollingWriter
All Implemented Interfaces:
Closeable, Flushable, Appendable

public class FileRollingWriter
extends Writer

Similar to FileWriter except that:

  1. writes to a rolling series of files instead of just one file
  2. buffers its output
  3. is not multithread safe
At any moment in time, there is a single output file. When checkForRollover detects that it is time to rollover to a new file, the current file is transparently closed and the next one in sequence is opened and used for subsequent writing.

All the chars supplied to a given append/write call always appear in the same file (i.e. they will never be split across two files). However, this means that the lengths of the rollover files are not necessarily exactly equal.

Note that checkForRollover is only automatically called if autoCheck is true. In this case, it is the first action performed (after arg checking) by every one of the append/write methods. But if autoCheck is false, the user has to manually call checkForRollover.

Using autoCheck set to true is most safe and convenient, but it has a significant drawback: if many append operations are being used to, say, print out the components of a line of text, then it could happen that one of the appends causes the first part of the line to be in one file and the remaining part of the line to be in the next file. This may complicate subsequent parsing of the files.

There are at least two ways to prevent this. The first method, if want to retain autoCheck set to true, is to use but a single append/write for the entire line. For example, call


        fileRollingWriter.write( linePart1 + linePart2 + '\n' );        // linePart1 and linePart2 are String variables
 
The only problem with this approach is that it creates a temp StringBuilder instance and does extra char copying for each line of output, which causes suboptimal performance. The second method is to use autoCheck set to false and manually call checkForRollover after the line end char sequence has been written. For example, call

        fileRollingWriter.append( linePart1 ).append( linePart2 ).append( '\n' ).checkForRollover();
 
The only problem with this approach is that you must remember to always call checkForRollover.

The names of the files all share the same prefix and suffix, but in the middle of their name is a series number. The minimum number of digits of this series number may be specified.

For file sorting purposes, it is crucial that the series numbers are represented with exactly the same number of digits. For example, suppose that there will 0-99 rollover files with prefix "log" and suffix ".txt". Then the filenames should be log_#00.txt, log_#01.txt, ..., log_#99.txt to allow typical operating systems to sort them properly.

Note that the logic inside nextFileCount should ensure that previously existing rollover files are never overwritten.

Because this class does its own internal buffering, instances should not be wrapped inside a BufferedWriter.

One use of this class is to handle really large data sets. If a process only writes to a single big file, the application may hold a file lock for too long. Furthermore, one single big file may be too big to open in, say, a text editor.

This class is not multithread safe.

Author:
Brent Boyer

Nested Class Summary
static class FileRollingWriter.UnitTest
          See the Overview page of the project's javadocs for a general description of this unit test class.
 
Field Summary
private  boolean autoCheck
          If true, then every char writing method (e.g. append, write) will first call checkForRollover before doing any output.
private  File directory
          Parent directory of the rollover files.
private  String filePrefix
          Prefix to append to the begining of all rollover file names.
private  String fileSuffix
          Suffix to append to the end of all rollover file names.
private  int minDigitsInSeriesNumber
          If > 0, specifies the minimum number of digits that must appear in each filename's series number (leading zeroes will be appended to a given number if necessary to make it achieve this length).
private  long nCharsTillRollover
          Maximum number of chars already written to the current rollover file that will allow before closing this file and start writing to a new one.
private  long nCharsWritten
           
private  boolean open
           
private  Writer writer
           
 
Fields inherited from class java.io.Writer
lock
 
Constructor Summary
FileRollingWriter(File directory, String filePrefix, int minDigitsInSeriesNumber, String fileSuffix, long nCharsTillRollover)
          Calls this(directory, filePrefix, minDigitsInSeriesNumber, fileSuffix, nCharsTillRollover, true).
FileRollingWriter(File directory, String filePrefix, int minDigitsInSeriesNumber, String fileSuffix, long nCharsTillRollover, boolean autoCheck)
          Fundamental constructor.
 
Method Summary
 FileRollingWriter append(char c)
          
 FileRollingWriter append(CharSequence csq)
          
 FileRollingWriter append(CharSequence csq, int start, int end)
          
 void checkForRollover()
          Determines if need to close the current writer and create a new one that writes to the next rollover file.
 void close()
           
private  File createRolloverFile(int n)
          Creates the nth rollover file.
private  int extractNumberFromFilename(File file)
          Parses and returns the series number from file.
 void flush()
           
private  void makeWriter()
           
private  int nextFileCount()
          Finds any previously written files inside directory that both start with filePrefix and end with fileSuffix, and then returns the next available file count.
 void write(char[] cBuffer)
          Calls write( cBuffer, 0, cBuffer.length ).
 void write(char[] cBuffer, int offset, int length)
          
 void write(int c)
          
 void write(String s)
          Calls write( s, 0, s.length() ).
 void write(String s, int offset, int length)
          
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

directory

private final File directory
Parent directory of the rollover files.


filePrefix

private final String filePrefix
Prefix to append to the begining of all rollover file names. May be null (in which case "" is substituted).


minDigitsInSeriesNumber

private final int minDigitsInSeriesNumber
If > 0, specifies the minimum number of digits that must appear in each filename's series number (leading zeroes will be appended to a given number if necessary to make it achieve this length). Otherwise, must have the special value -1, which specifies that the series numbers are to be represented in the usual way, that is, each number is written using precisely the minimum number of digits that are required.


fileSuffix

private final String fileSuffix
Suffix to append to the end of all rollover file names. May be null (in which case "" is substituted).


nCharsTillRollover

private final long nCharsTillRollover
Maximum number of chars already written to the current rollover file that will allow before closing this file and start writing to a new one.


autoCheck

private final boolean autoCheck
If true, then every char writing method (e.g. append, write) will first call checkForRollover before doing any output. Otherwise, if false, then it is the responsibility of the user to explicitly call checkForRollover.


open

private boolean open

writer

private Writer writer

nCharsWritten

private long nCharsWritten
Constructor Detail

FileRollingWriter

public FileRollingWriter(File directory,
                         String filePrefix,
                         int minDigitsInSeriesNumber,
                         String fileSuffix,
                         long nCharsTillRollover)
                  throws IllegalArgumentException
Calls this(directory, filePrefix, minDigitsInSeriesNumber, fileSuffix, nCharsTillRollover, true).

Throws:
IllegalArgumentException

FileRollingWriter

public FileRollingWriter(File directory,
                         String filePrefix,
                         int minDigitsInSeriesNumber,
                         String fileSuffix,
                         long nCharsTillRollover,
                         boolean autoCheck)
                  throws IllegalArgumentException
Fundamental constructor.

The filenames that will be produced by this instance will have the form filePrefix_#nnnfileSuffix where filePrefix and fileSuffix are the args to this method, while nnn are the series numbers represented according to minDigitsInSeriesNumber.

Parameters:
directory - assigned to directory
filePrefix - assigned to filePrefix
fileSuffix - assigned to fileSuffix
nCharsTillRollover - assigned to nCharsTillRollover
autoCheck - assigned to autoCheck
Throws:
IllegalArgumentException - if directory is not valid; minDigitsInSeriesNumber < -1; nCharsTillRollover <= 0
Method Detail

append

public FileRollingWriter append(char c)
                         throws SecurityException,
                                IOException

Specified by:
append in interface Appendable
Overrides:
append in class Writer
Throws:
SecurityException - if a security manager exists and denies access to some resource
IOException

append

public FileRollingWriter append(CharSequence csq)
                         throws SecurityException,
                                IOException

Specified by:
append in interface Appendable
Overrides:
append in class Writer
Throws:
SecurityException - if a security manager exists and denies access to some resource
IOException

append

public FileRollingWriter append(CharSequence csq,
                                int start,
                                int end)
                         throws SecurityException,
                                IOException

Specified by:
append in interface Appendable
Overrides:
append in class Writer
Throws:
SecurityException - if a security manager exists and denies access to some resource
IOException

close

public void close()
Specified by:
close in interface Closeable
Specified by:
close in class Writer

flush

public void flush()
           throws IOException
Specified by:
flush in interface Flushable
Specified by:
flush in class Writer
Throws:
IOException

write

public void write(char[] cBuffer)
           throws IllegalArgumentException,
                  SecurityException,
                  IOException
Calls write( cBuffer, 0, cBuffer.length ).

Overrides:
write in class Writer
Throws:
IllegalArgumentException
SecurityException
IOException

write

public void write(char[] cBuffer,
                  int offset,
                  int length)
           throws IllegalArgumentException,
                  SecurityException,
                  IOException

Specified by:
write in class Writer
Throws:
IllegalArgumentException - if cBuffer == null
SecurityException - if a security manager exists and denies access to some resource
IOException

write

public void write(int c)
           throws SecurityException,
                  IOException

Overrides:
write in class Writer
Throws:
SecurityException - if a security manager exists and denies access to some resource
IOException

write

public void write(String s)
           throws IllegalArgumentException,
                  SecurityException,
                  IOException
Calls write( s, 0, s.length() ).

Overrides:
write in class Writer
Throws:
IllegalArgumentException
SecurityException
IOException

write

public void write(String s,
                  int offset,
                  int length)
           throws IllegalArgumentException,
                  SecurityException,
                  IOException

Overrides:
write in class Writer
Throws:
IllegalArgumentException - if s == null
SecurityException - if a security manager exists and denies access to some resource
IOException

checkForRollover

public void checkForRollover()
                      throws SecurityException,
                             IOException
Determines if need to close the current writer and create a new one that writes to the next rollover file. This happens when the number of chars written to the current file equals or exceeds specified limit.

Throws:
SecurityException
IOException

makeWriter

private void makeWriter()
                 throws SecurityException,
                        IOException
Throws:
SecurityException
IOException

createRolloverFile

private File createRolloverFile(int n)
Creates the nth rollover file.


nextFileCount

private int nextFileCount()
                   throws SecurityException,
                          IOException
Finds any previously written files inside directory that both start with filePrefix and end with fileSuffix, and then returns the next available file count.

This directory search is done, as opposed to tracking the next file number as a field of this class, in order to support correct file numbering in the event that this class is run in multiple JVMs (e.g. if a program had to be restarted for some reason).

Throws:
SecurityException
IOException

extractNumberFromFilename

private int extractNumberFromFilename(File file)
                               throws IllegalArgumentException
Parses and returns the series number from file. Returns -1 if the series number is unparsable. Warning: does no arg or state checking, since is meant only be called by nextFileCount.

Throws:
IllegalArgumentException - if file == null