/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2012-16 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.app; import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; /** * Non-GUI handling of System.out and System.err redirection. * <p /> * Be careful when debugging this class, because if it's throwing exceptions, * don't take over System.err, and debug while watching just System.out * or just call println() or whatever directly to systemOut or systemErr. * <p /> * Also note that encodings will not work properly when run from Eclipse. This * means that if you use non-ASCII characters in a println() or some such, * the characters won't print properly in the Processing and/or Eclipse console. * It seems that Eclipse's console-grabbing and that of Processing don't * get along with one another. Use 'ant run' to work on encoding-related issues. */ public class Console { // Single static instance shared because there's only one real System.out. // Within the input handlers, the currentConsole variable will be used to // echo things to the correct location. /** The original System.out */ static PrintStream systemOut; /** The original System.err */ static PrintStream systemErr; /** Our replacement System.out */ static PrintStream consoleOut; /** Our replacement System.err */ static PrintStream consoleErr; /** All stdout also written to a file */ static OutputStream stdoutFile; /** All stderr also written to a file */ static OutputStream stderrFile; /** stdout listener for the currently active Editor */ static OutputStream editorOut; /** stderr listener for the currently active Editor */ static OutputStream editorErr; static public void startup() { if (systemOut != null) { // TODO fix this dreadful style choice in how the Console is initialized // (This is not good code.. startup() should gracefully deal with this. // It's just a low priority relative to the likelihood of trouble.) new Exception("startup() called more than once").printStackTrace(systemErr); return; } systemOut = System.out; systemErr = System.err; // placing everything inside a try block because this can be a dangerous // time for the lights to blink out and crash for and obscure reason. try { SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd_HHmmss"); // Moving away from a random string in 0256 (and adding hms) because // the random digits looked like times anyway, causing confusion. //String randy = String.format("%04d", (int) (1000 * Math.random())); //final String stamp = formatter.format(new Date()) + "_" + randy; final String stamp = formatter.format(new Date()); File consoleDir = Base.getSettingsFile("console"); if (consoleDir.exists()) { // clear old debug files File[] stdFiles = consoleDir.listFiles(new FileFilter() { final String todayPrefix = stamp.substring(0, 4); public boolean accept(File file) { if (!file.isDirectory()) { String name = file.getName(); if (name.endsWith(".err") || name.endsWith(".out")) { // don't delete any of today's debug messages return !name.startsWith(todayPrefix); } } return false; } }); // Remove any files that aren't from today for (File file : stdFiles) { file.delete(); } } else { consoleDir.mkdirs(); consoleDir.setWritable(true, false); } File outFile = new File(consoleDir, stamp + ".out"); outFile.setWritable(true, false); stdoutFile = new FileOutputStream(outFile); File errFile = new File(consoleDir, stamp + ".err"); errFile.setWritable(true, false); stderrFile = new FileOutputStream(errFile); consoleOut = new PrintStream(new ConsoleStream(false)); consoleErr = new PrintStream(new ConsoleStream(true)); System.setOut(consoleOut); System.setErr(consoleErr); } catch (Exception e) { stdoutFile = null; stderrFile = null; consoleOut = null; consoleErr = null; System.setOut(systemOut); System.setErr(systemErr); e.printStackTrace(); } } static public void setEditor(OutputStream out, OutputStream err) { editorOut = out; editorErr = err; } static public void systemOut(String what) { systemOut.println(what); } static public void systemErr(String what) { systemErr.println(what); } /** * Close the streams so that the temporary files can be deleted. * <p/> * File.deleteOnExit() cannot be used because the stdout and stderr * files are inside a folder, and have to be deleted before the * folder itself is deleted, which can't be guaranteed when using * the deleteOnExit() method. */ static public void shutdown() { // replace original streams to remove references to console's streams System.setOut(systemOut); System.setErr(systemErr); cleanup(consoleOut); cleanup(consoleErr); // also have to close the original FileOutputStream // otherwise it won't be shut down completely cleanup(stdoutFile); cleanup(stderrFile); } static private void cleanup(OutputStream output) { try { if (output != null) { output.flush(); output.close(); } } catch (IOException e) { e.printStackTrace(); } } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . static class ConsoleStream extends OutputStream { boolean err; // whether stderr or stdout byte single[] = new byte[1]; public ConsoleStream(boolean err) { this.err = err; } public void close() { } public void flush() { } public void write(byte b[]) { // appears never to be used write(b, 0, b.length); } public void write(byte b[], int offset, int length) { // First write to the original stdout/stderr if (err) { systemErr.write(b, offset, length); } else { systemOut.write(b, offset, length); } // Write to the files that are storing this information writeFile(b, offset, length); // Write to the console of the current Editor, if any try { if (err) { if (editorErr != null) { editorErr.write(b, offset, length); } } else { if (editorOut != null) { editorOut.write(b, offset, length); } } } catch (IOException e) { // Avoid this function being called in a recursive, infinite loop e.printStackTrace(systemErr); } } public void writeFile(byte b[], int offset, int length) { final OutputStream echo = err ? stderrFile : stdoutFile; if (echo != null) { try { echo.write(b, offset, length); echo.flush(); } catch (IOException e) { e.printStackTrace(); } } } public void write(int b) { single[0] = (byte) b; write(single, 0, 1); } } }