/* -*- 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);
}
}
}