/*
This file is part of jpcsp.
Jpcsp 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 3 of the License, or
(at your option) any later version.
Jpcsp 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 Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jpcsp.log;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
/**
* An OutputStream that flushes out to a Category.
* <p>
*
* Note that no data is written out to the Category until the stream is flushed
* or closed.
* <p>
*
* Example:
*
* <pre>
* // make sure everything sent to System.err is logged
* System.setErr(new PrintStream(new LoggingOutputStream(Category.getRoot(),
* Priority.WARN), true));
*
* // make sure everything sent to System.out is also logged
* System.setOut(new PrintStream(new LoggingOutputStream(Category.getRoot(),
* Priority.INFO), true));
* </pre>
*
* @author <a href="mailto://Jim.Moore@rocketmail.com">Jim Moore</a>
* @see Category
*/
public class LoggingOutputStream extends OutputStream {
protected static final String LINE_SEPERATOR = System.getProperty("line.separator");
/**
* Used to maintain the contract of {@link #close()}.
*/
protected boolean hasBeenClosed = false;
/**
* The internal buffer where data is stored.
*/
protected byte[] buf;
/**
* The number of valid bytes in the buffer. This value is always in the
* range <tt>0</tt> through <tt>buf.length</tt>; elements <tt>buf[0]</tt>
* through <tt>buf[count-1]</tt> contain valid byte data.
*/
protected int count;
/**
* Remembers the size of the buffer for speed.
*/
private int bufLength;
/**
* The default number of bytes in the buffer. =2048
*/
public static final int DEFAULT_BUFFER_LENGTH = 2048;
/**
* The category to write to.
*/
protected Category category;
/**
* The priority to use when writing to the Category.
*/
protected Priority priority;
/**
* Creates the LoggingOutputStream to flush to the given Category.
*
* @param cat
* the Category to write to
*
* @param priority
* the Priority to use when writing to the Category
*
* @exception IllegalArgumentException
* if cat == null or priority == null
*/
public LoggingOutputStream(Logger cat, Level priority)
throws IllegalArgumentException {
if (cat == null) {
throw new IllegalArgumentException("cat == null");
}
if (priority == null) {
throw new IllegalArgumentException("priority == null");
}
this.priority = priority;
category = cat;
bufLength = DEFAULT_BUFFER_LENGTH;
buf = new byte[DEFAULT_BUFFER_LENGTH];
count = 0;
}
/**
* Closes this output stream and releases any system resources associated
* with this stream. The general contract of <code>close</code> is that it
* closes the output stream. A closed stream cannot perform output
* operations and cannot be reopened.
*/
@Override
public void close() {
flush();
hasBeenClosed = true;
}
/**
* Writes the specified byte to this output stream. The general contract for
* <code>write</code> is that one byte is written to the output stream. The
* byte to be written is the eight low-order bits of the argument
* <code>b</code>. The 24 high-order bits of <code>b</code> are ignored.
*
* @param b
* the <code>byte</code> to write
*
* @exception IOException
* if an I/O error occurs. In particular, an
* <code>IOException</code> may be thrown if the output
* stream has been closed.
*/
@Override
public void write(final int b) throws IOException {
if (hasBeenClosed) {
throw new IOException("The stream has been closed.");
}
// don't log nulls
if (b == 0) {
return;
}
if(b == '\r' || b == '\n') {
flush();
return;
}
// would this be writing past the buffer?
if (count == bufLength) {
// grow the buffer
final int newBufLength = bufLength + DEFAULT_BUFFER_LENGTH;
final byte[] newBuf = new byte[newBufLength];
System.arraycopy(buf, 0, newBuf, 0, bufLength);
buf = newBuf;
bufLength = newBufLength;
}
buf[count] = (byte) b;
count++;
}
/**
* Flushes this output stream and forces any buffered output bytes to be
* written out. The general contract of <code>flush</code> is that calling
* it is an indication that, if any bytes previously written have been
* buffered by the implementation of the output stream, such bytes should
* immediately be written to their intended destination.
*/
@Override
public void flush() {
if (count == 0) {
return;
}
// don't print out blank lines; flushing from PrintStream puts out these
if (count == LINE_SEPERATOR.length()) {
if (((char) buf[0]) == LINE_SEPERATOR.charAt(0) && ((count == 1) || // <- Unix & Mac
((count == 2) && ((char) buf[1]) == LINE_SEPERATOR.charAt(1)))) { // <- Windows
reset();
return;
}
}
final byte[] theBytes = new byte[count];
System.arraycopy(buf, 0, theBytes, 0, count);
category.log(priority, new String(theBytes));
reset();
}
private void reset() {
// not resetting the buffer -- assuming that if it grew that it
// will likely grow similarly again
count = 0;
}
}