package plume; import java.io.*; // This solution only works for PrintWriter. // The class really ought to be reimplemented as a wrapper around an // arbitrary Writer. /** * <p> Prints formatted representations of objects to a text-output * stream counting the number of bytes and characters printed. </p> * * <p> Methods in this class never throw I/O exceptions. The client may * inquire as to whether any errors have occurred by invoking * checkError(). </p> */ public class CountingPrintWriter extends PrintWriter { // Field variables /** Number of written bytes using 'write' methods. */ private int writtenBytes; /** Number of printed bytes using 'print' and 'println' methods. */ private int printedBytes; /** Number of written chars using write methods. */ private int writtenChars; /** Number of printed chars using 'print' and 'println' methods. */ private int printedChars; // Constructors /** * Create a new PrintWriter, without automatic line flushing, from * an existing OutputStream. This convenience constructor creates * the necessary intermediate OutputStreamWriter, which will convert * characters into bytes using the default character encoding. * * @param out An output stream */ public CountingPrintWriter(OutputStream out) { super(out); printedBytes = 0; writtenChars = 0; } /** * Create a new PrintWriter from an existing OutputStream. This * convenience constructor creates the necessary intermediate * OutputStreamWriter, which will convert characters into bytes * using the default character encoding. * * @param out An output stream * @param autoFlush A boolean; if true, the println() methods will flush the output buffer */ public CountingPrintWriter(OutputStream out, boolean autoFlush) { super(out, autoFlush); printedBytes = 0; writtenChars = 0; } /** Create a new PrintWriter, without automatic line flushing. * @param out a Writer */ public CountingPrintWriter(Writer out) { super(out); printedBytes = 0; writtenChars = 0; } /** * Create a new PrintWriter, without automatic line flushing. * @param out A writer * @param autoFlush A boolean; if true, the println() methods will flush the output buffer */ public CountingPrintWriter(Writer out, boolean autoFlush) { super(out,autoFlush); printedBytes = 0; writtenChars = 0; } // Public utility methods /** * Returns the number of bytes contained in a string. * If s is null, returns -1 * @param s A String */ public int countBytes (String s) { if (s == null) return -1; int numchars = s.length(); int numbytes = 0; for (int i = 0 ; i < numchars ; i++) { char c = s.charAt(i); numbytes += countBytes(c); } return numbytes; } /** * Returns the number of bytes used to represent * a character. * @param c A character. */ public int countBytes (char c) { if ((c >= 0x0001) && (c <= 0x007F)) return 1; else if (c > 0x07FF) return 3; else return 2; } // Accessor Methods /** * Returns the total number of bytes printed using any of the * 'print' or 'println' methods of this CountingPrintBuffer. */ public int getNumberOfPrintedBytes() { return printedBytes; } /** * Returns the total number of bytes printed using any of the * 'write' methods of this CountingPrintBuffer. */ public int getNumberOfWrittenBytes() { return writtenBytes; } /** * Returns the total number of characters printed using any * of the 'print' or 'println' methods of this CountingPrintBuffer. */ public int getNumberOfPrintedChars() { return printedChars; } // PRINT METHODS // All these methods increment the byte and char // count, and call their super method. // It seems to me that in PrintWriter all 'print' methods convert // their input argument to String and call print(String s) on // that. However, I could not reach the source code of PrintWriter, // so I am not sure if this is the case. If it is, then the all // of the following methods are unnecessary except for print(String s). /** * Print a string. If the argument is null then the string "null" is * printed. Otherwise, the string's characters are converted into * bytes according to the platform's default character encoding, and * these bytes are written in exactly the manner of the write(int) * method. */ public void print(/*@Nullable*/ String s) { if (s == null) { printedBytes += countBytes("null"); printedChars += 4; } else { printedBytes += countBytes(s); printedChars += s.length(); } super.print(s); } /** * Print a boolean value. The string produced by * String.valueOf(boolean) is translated into bytes according to the * platform's default character encoding, and these bytes are * written in exactly the manner of the write(int) method. */ public void print(boolean b) { String s = String.valueOf(b); printedBytes += countBytes(s); printedChars += s.length(); super.print(b); } /** * Print a character. The character is translated into one or more * bytes according to the platform's default character encoding, and * these bytes are written in exactly the manner of the write(int) * method. */ public void print(char c) { printedBytes += countBytes(c); printedChars ++; } /** * Print an array of characters. The characters are converted into * bytes according to the platform's default character encoding, and * these bytes are written in exactly the manner of the write(int) * method. */ public void print(char[] s) { for (int i=0; i < s.length; i++) { printedBytes += countBytes(s[i]); } printedChars += s.length; super.print(s); } /** * Print a double-precision floating-point number. The string * produced by String.valueOf(double) is translated into bytes * according to the platform's default character encoding, and these * bytes are written in exactly the manner of the write(int) method. */ public void print(double d) { String s = String.valueOf(d); printedBytes += countBytes(s); printedChars += s.length(); super.print(d); } /** * Print a floating-point number. The string produced by * String.valueOf(float) is translated into bytes according to the * platform's default character encoding, and these bytes are * written in exactly the manner of the write(int) method. */ public void print(float f) { String s = String.valueOf(f); printedBytes += countBytes(s); printedChars += s.length(); super.print(f); } /** * Print an integer. The string produced by String.valueOf(int) is * translated into bytes according to the platform's default * character encoding, and these bytes are written in exactly the * manner of the write(int) method. */ public void print(int i) { String s = String.valueOf(i); printedBytes += countBytes(s); printedChars += s.length(); super.print(i); } /** Resets all the byte and char counters. */ public void resetAll() { resetPrintedByteCounter(); resetPrintedCharCounter(); resetWrittenByteCounter(); resetWrittenCharCounter(); } /** Resets printedByte counter. */ public void resetPrintedByteCounter() { printedBytes = 0; } /** Resets printedChar counter. */ public void resetPrintedCharCounter() { printedChars = 0; } /** Resets writtenByte counter. */ public void resetWrittenByteCounter() { writtenBytes = 0; } /** Resets writtenChar counter. */ public void resetWrittenCharCounter() { writtenChars = 0; } /** * Print a long integer. The string produced by * String.valueOf(long) is translated into bytes according to the * platform's default character encoding, and these bytes are * written in exactly the manner of the write(int) method. */ public void print(long l) { String s = String.valueOf(l); printedBytes += countBytes(s); printedChars += s.length(); super.print(l); } /** * Print an object. The string produced by the * String.valueOf(Object) method is translated into bytes according * to the platform's default character encoding, and these bytes are * written in exactly the manner of the write(int) method. */ public void print(/*@Nullable*/ Object obj) { String s = String.valueOf(obj); printedBytes += countBytes(s); printedChars += s.length(); super.print(obj); } @SuppressWarnings("nullness") // line.separator property always exists private static final String lineSep = System.getProperty("line.separator"); /** * Terminate the current line by writing the line separator * string. The line separator string is defined by the system * property line.separator, and is not necessarily a single newline * character ('\n'). * <p> * When incrementing the byte count of PrintWriter, also accounts for the * bytes needed to represent the line separator string. */ public void println() { printedBytes += countBytes(lineSep); printedChars += lineSep.length(); super.println(); } /** * Print a String and then terminate the line. This method behaves * as though it invokes print(String) and then println(). */ public void println(/*@Nullable*/ String x) { print(x); println(); } /** * Write an array of characters. This method cannot be inherited * from the Writer class because it must suppress I/O exceptions. */ public void write(char[] buf) { for (int i=0; i < buf.length; i++) { writtenBytes += countBytes(buf[i]); } writtenChars += buf.length; super.write(buf); } /** * Write a portion of a character array. * * @param buf character array * @param off Offset from which to start writing characters * @param len Number of characters to write */ public void write(char[] buf, int off, int len) { for (int i=off; i < off+len; i++) { writtenBytes += countBytes(buf[i]); } writtenChars += len; super.write(buf,off,len); } /** * Write a string. */ public void write(String s) { writtenBytes += countBytes(s); writtenChars += s.length(); super.write(s); } /** * Write a portion of a string. * * @param s string to be printed * @param off Offset from which to start writing characters * @param len Number of characters to write */ public void write(String s, int off, int len) { writtenBytes += countBytes(s.substring(off,len)); writtenChars += len; super.write(s,off,len); } }