import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; /** A class with static methods for capturing console output. * Useful for capturing the result of methods that do printlns. * * Example usage: * *
 *    OutputCapturer.start();
 *    // (code to capture)
 *    System.out.println("hello");
 *    System.out.println("world");
 *    String output = OutputCapturer.stop();  // "hello\nworld"
 *  
* * @author Marty Stepp * @version 2010-02-10 */ public class OutputCapturer { private static final PrintStream SYSTEM_OUT = java.lang.System.out; private static final PrintStream SYSTEM_ERR = java.lang.System.err; private static ByteArrayOutputStream outputStream = null; /** Returns true if output is currently being captured. */ public static boolean isCapturing() { return outputStream != null; } /** Begins capturing output with no limit as to its length. */ public static void start() { start(true); } /** * Begins capturing output. Will throw an exception if the student * printlns too much. Stops and discard any previously captured output. * @param limit if true, tells the print stream to throw an exception * if the client uses it too many times or prints too much. */ public static void start(boolean limit) { if (isCapturing()) { stop(); } outputStream = new ByteArrayOutputStream(16384); PrintStream out; if (limit) { out = new LimitedPrintStream(outputStream); } else { out = new PrintStream(outputStream); } System.setOut(out); System.setErr(out); } /** Stops capturing output and returns the string of captured output. */ public static String stop() { System.out.flush(); System.err.flush(); System.setOut(SYSTEM_OUT); System.setErr(SYSTEM_ERR); String actualOutput = ""; if (isCapturing()) { // fix line endings on Windows systems actualOutput = outputStream.toString().replace("\r", ""); } outputStream = null; return actualOutput; } /** A special output print stream that can constrain how many times it can be used * before it will throw an exception. */ private static class LimitedPrintStream extends PrintStream { private static final int MAX_CALLS = 5000; private static final int MAX_CHARS = 50000; private int calls = 0; private int chars = 0; private int maxCalls = 0; private int maxChars = 0; /** Constructs a new print stream to write to the given target, * using a default max allowed number of characters and calls. */ public LimitedPrintStream(java.io.OutputStream stream) { this(stream, MAX_CALLS, MAX_CHARS); } /** Constructs a new print stream to write to the given target, * using a default max allowed number of characters and calls. */ public LimitedPrintStream(File file) throws java.io.FileNotFoundException { this(file, MAX_CALLS, MAX_CHARS); } /** Constructs a new print stream to write to the given target, * using a default max allowed number of characters and calls. */ public LimitedPrintStream(String file) throws java.io.FileNotFoundException { this(file, MAX_CALLS, MAX_CHARS); } /** Constructs a new print stream to write to the given target, * using the given allowed number of characters and calls. * @throws IllegalArgumentException if either max is negative. */ public LimitedPrintStream(java.io.OutputStream stream, int maxCalls, int maxChars) { super(stream); setMax(maxCalls, maxChars); } /** Constructs a new print stream to write to the given target, * using the given allowed number of characters and calls. * @throws IllegalArgumentException if either max is negative. */ public LimitedPrintStream(File file, int maxCalls, int maxChars) throws java.io.FileNotFoundException { super(file); setMax(maxCalls, maxChars); } /** Constructs a new print stream to write to the given target, * using the given allowed number of characters and calls. * @throws IllegalArgumentException if either max is negative. */ public LimitedPrintStream(String file, int maxCalls, int maxChars) throws java.io.FileNotFoundException { super(file); setMax(maxCalls, maxChars); } // shouldn't close System.out anyway public void close() {} // all methods below wrap java.io.PrintStream methods; see those API docs public void print(int x) { print(String.valueOf(x)); } public void print(double x) { print(String.valueOf(x)); } public void print(float x) { print(String.valueOf(x)); } public void print(long x) { print(String.valueOf(x)); } public void print(short x) { print(String.valueOf(x)); } public void print(byte x) { print(String.valueOf(x)); } public void print(boolean x) { print(String.valueOf(x)); } public void print(char x) { print(String.valueOf(x)); } public void print(Object x) { print(String.valueOf(x)); } public void print(String x) { // count one call and the number of characters printed calls++; chars += (x == null) ? 4 : x.length(); if (calls < maxCalls && chars < maxChars) { super.print(x); } else { throw new ExcessiveOutputException(); } } public void println(int x) { println(String.valueOf(x)); } public void println(double x) { println(String.valueOf(x)); } public void println(float x) { println(String.valueOf(x)); } public void println(long x) { println(String.valueOf(x)); } public void println(short x) { println(String.valueOf(x)); } public void println(byte x) { println(String.valueOf(x)); } public void println(boolean x) { println(String.valueOf(x)); } public void println(char x) { println(String.valueOf(x)); } public void println(Object x) { println(String.valueOf(x)); } public void println(String x) { print(x); // funnel all calls to println through print method print("\n"); calls--; // called print twice; shouldn't count as 2 calls } /** Sets this stream to use the given allowed number of chars/calls. * @throws IllegalArgumentException if either max is negative. */ private void setMax(int maxCalls, int maxChars) { if (maxCalls < 0 || maxChars < 0) { throw new IllegalArgumentException("illegal max calls/chars: " + maxCalls + " " + maxChars); } this.maxCalls = maxCalls; this.maxChars = maxChars; } } /** Class representing an exception to throw if too much output is produced. */ public static class ExcessiveOutputException extends RuntimeException { private static final long serialVersionUID = 0; /** Constructs a new exception with a default message. */ public ExcessiveOutputException() { super(); } /** Constructs a new exception with the given message. */ public ExcessiveOutputException(String message) { super(message); } } }