/*==========================================================================*\ | $Id: PrintStreamWithHistory.java,v 1.4 2011/04/22 17:04:05 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2007-2010 Virginia Tech | | This file is part of the Student-Library. | | The Student-Library 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. | | The Student-Library 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 Lesser General Public License for more details. | | You should have received a copy of the GNU Lesser General Public License | along with the Student-Library; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package student.testingsupport; import java.io.File; import java.io.FileNotFoundException; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; //------------------------------------------------------------------------- /** * An enhanced version of {@link PrintStream} that provides for a history * recall function and some other features making I/O testing a bit * easier to perform. See the documentation for {@link PrintStream} for * more thorough details on what methods are provided. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.4 $, $Date: 2011/04/22 17:04:05 $ */ public class PrintStreamWithHistory extends PrintStream { //~ Instance/static variables ............................................. private StringWriter history = new StringWriter(); private final String LINE_SEPARATOR = java.security.AccessController .doPrivileged(new java.security.PrivilegedAction<String>() { public String run() { return System.getProperty("line.separator"); } }); private final byte[] LINE_SEPARATOR_BYTES = LINE_SEPARATOR.getBytes(); //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Create a new print stream. This stream will not flush automatically. * * @param out The output stream to which values and objects will be * printed * * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream) */ public PrintStreamWithHistory(OutputStream out) { super(out, false); } // ---------------------------------------------------------- /** * Create a new print stream. * * @param out The output stream to which values and objects will be * printed * @param autoFlush A boolean; if true, the output buffer will be flushed * whenever a byte array is written, one of the * <code>println</code> methods is invoked, or a newline * character or byte (<code>'\n'</code>) is written * * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean) */ public PrintStreamWithHistory(OutputStream out, boolean autoFlush) { super(out, autoFlush); } // ---------------------------------------------------------- /** * Create a new print stream. * * @param out The output stream to which values and objects will be * printed * @param autoFlush A boolean; if true, the output buffer will be flushed * whenever a byte array is written, one of the * <code>println</code> methods is invoked, or a newline * character or byte (<code>'\n'</code>) is written * @param encoding The name of a supported * <a href="../lang/package-summary.html#charenc"> * character encoding</a> * * @exception UnsupportedEncodingException * If the named encoding is not supported */ public PrintStreamWithHistory( OutputStream out, boolean autoFlush, String encoding) throws UnsupportedEncodingException { super(out, autoFlush, encoding); } // ---------------------------------------------------------- /** * Creates a new print stream, without automatic line flushing, with the * specified file name. This convenience constructor creates * the necessary intermediate {@link java.io.OutputStreamWriter * OutputStreamWriter}, which will encode characters using the * {@linkplain java.nio.charset.Charset#defaultCharset default charset} * for this instance of the Java virtual machine. * * @param fileName * The name of the file to use as the destination of this print * stream. If the file exists, then it will be truncated to * zero size; otherwise, a new file will be created. The output * will be written to the file and is buffered. * * @throws FileNotFoundException * If the given file object does not denote an existing, writable * regular file and a new regular file of that name cannot be * created, or if some other error occurs while opening or * creating the file * * @throws SecurityException * If a security manager is present and {@link * SecurityManager#checkWrite checkWrite(fileName)} denies write * access to the file * * @since 1.5 */ public PrintStreamWithHistory(String fileName) throws FileNotFoundException { super(fileName); } // ---------------------------------------------------------- /** * Creates a new print stream, without automatic line flushing, with the * specified file name and charset. This convenience constructor creates * the necessary intermediate {@link java.io.OutputStreamWriter * OutputStreamWriter}, which will encode characters using the provided * charset. * * @param fileName * The name of the file to use as the destination of this print * stream. If the file exists, then it will be truncated to * zero size; otherwise, a new file will be created. The output * will be written to the file and is buffered. * * @param csn * The name of a supported {@linkplain java.nio.charset.Charset * charset} * * @throws FileNotFoundException * If the given file object does not denote an existing, writable * regular file and a new regular file of that name cannot be * created, or if some other error occurs while opening or * creating the file * * @throws SecurityException * If a security manager is present and {@link * SecurityManager#checkWrite checkWrite(fileName)} denies write * access to the file * * @throws UnsupportedEncodingException * If the named charset is not supported * * @since 1.5 */ public PrintStreamWithHistory(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException { super(fileName, csn); } // ---------------------------------------------------------- /** * Creates a new print stream, without automatic line flushing, with the * specified file. This convenience constructor creates the necessary * intermediate {@link java.io.OutputStreamWriter OutputStreamWriter}, * which will encode characters using the {@linkplain * java.nio.charset.Charset#defaultCharset default charset} for this * instance of the Java virtual machine. * * @param file * The file to use as the destination of this print stream. If the * file exists, then it will be truncated to zero size; otherwise, * a new file will be created. The output will be written to the * file and is buffered. * * @throws FileNotFoundException * If the given file object does not denote an existing, writable * regular file and a new regular file of that name cannot be * created, or if some other error occurs while opening or * creating the file * * @throws SecurityException * If a security manager is present and {@link * SecurityManager#checkWrite checkWrite(file.getPath())} * denies write access to the file * * @since 1.5 */ public PrintStreamWithHistory(File file) throws FileNotFoundException { super(file); } // ---------------------------------------------------------- /** * Creates a new print stream, without automatic line flushing, with the * specified file and charset. This convenience constructor creates * the necessary intermediate {@link java.io.OutputStreamWriter * OutputStreamWriter}, which will encode characters using the provided * charset. * * @param file * The file to use as the destination of this print stream. If the * file exists, then it will be truncated to zero size; otherwise, * a new file will be created. The output will be written to the * file and is buffered. * * @param csn * The name of a supported {@linkplain java.nio.charset.Charset * charset} * * @throws FileNotFoundException * If the given file object does not denote an existing, writable * regular file and a new regular file of that name cannot be * created, or if some other error occurs while opening or * creating the file * * @throws SecurityException * If a security manager is presentand {@link * SecurityManager#checkWrite checkWrite(file.getPath())} * denies write access to the file * * @throws UnsupportedEncodingException * If the named charset is not supported * * @since 1.5 */ public PrintStreamWithHistory(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException { super(file, csn); } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Retrieve the text history of what has been sent to this PrintStream. * This will include all text printed through this object. The * {@link #clearHistory()} method resets the history to be empty, just * as when the object was first created. Note that newline characters * in the history are always represented by '\n', regardless of what * value the system <code>line.separator</code> property has. * @return all the text sent to this PrintStream */ public String getHistory() { return history.toString(); } // ---------------------------------------------------------- /** * Reset this object's history to be empty, just as when the object was * first created. You can access the history using {@link #getHistory()}. */ public void clearHistory() { getHistoryBuffer().setLength(0); } // ---------------------------------------------------------- /** * Retrieve the StringBuffer object used to store this object's text * history. * @return The history as a string buffer */ public StringBuffer getHistoryBuffer() { return history.getBuffer(); } // ---------------------------------------------------------- /** * Write the specified byte to this stream. If the byte is a newline and * automatic flushing is enabled then the <code>flush</code> method will be * invoked. * * <p> Note that the byte is written as given; to write a character that * will be translated according to the platform's default character * encoding, use the <code>print(char)</code> or <code>println(char)</code> * methods. * * @param b The byte to be written * @see #print(char) * @see #println(char) */ public void write(int b) { synchronized (history) { super.write(b); history.write(b); } } // ---------------------------------------------------------- /** * Write <code>len</code> bytes from the specified byte array starting at * offset <code>off</code> to this stream. If automatic flushing is * enabled then the <code>flush</code> method will be invoked. * * <p> Note that the bytes will be written as given; to write characters * that will be translated according to the platform's default character * encoding, use the <code>print(char)</code> or <code>println(char)</code> * methods. * * @param buf A byte array * @param off Offset from which to start taking bytes * @param len Number of bytes to write */ public void write(byte buf[], int off, int len) { synchronized (history) { super.write(buf, off, len); int startPos = off; int end = off + len; for (int i = off; i < end; i++) { if (matches(buf, i, end, LINE_SEPARATOR_BYTES)) { if (i > startPos) { history.write(new String(buf, startPos, i - startPos)); } startPos = i + LINE_SEPARATOR_BYTES.length; i = startPos - 1; history.write('\n'); } } if (startPos < end) { history.write(new String(buf, startPos, end - startPos)); } } } // ---------------------------------------------------------- /** * Returns true if the subsequence of elements in buf beginning at * index start matches the entire target array. If target has length * len, then this method compares the subranges buf[start..start+len-1] * and target[0..len-1], returning true iff they contain the same bytes. * If either range extends off the corresponding array, the result is * false. * @param buf The (possibly larger) array to look in. * @param start The starting index in buf to compare against. * @param target The target sequence of bytes to look for. */ private boolean matches(byte buf[], int start, int end, byte target[]) { int len = target.length; if (start + len > end) { return false; } for (int i = 0; i < len; i++) { if (buf[start + i] != target[i]) { return false; } } return true; } }