/*
* 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 java.util.logging;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
/**
* A {@code StreamHandler} object writes log messages to an output stream, that
* is, objects of the class {@link java.io.OutputStream}.
* <p>
* A {@code StreamHandler} object reads the following properties from the log
* manager to initialize itself. A default value will be used if a property is
* not found or has an invalid value.
* <ul>
* <li>java.util.logging.StreamHandler.encoding specifies the encoding this
* handler will use to encode log messages. Default is the encoding used by the
* current platform.
* <li>java.util.logging.StreamHandler.filter specifies the name of the filter
* class to be associated with this handler. No <code>Filter</code> is used by
* default.
* <li>java.util.logging.StreamHandler.formatter specifies the name of the
* formatter class to be associated with this handler. Default is
* {@code java.util.logging.SimpleFormatter}.
* <li>java.util.logging.StreamHandler.level specifies the logging level.
* Defaults is {@code Level.INFO}.
* </ul>
* <p>
* This class is not thread-safe.
*/
public class StreamHandler extends Handler {
// the output stream this handler writes to
private OutputStream os;
// the writer that writes to the output stream
private Writer writer;
// the flag indicating whether the writer has been initialized
private boolean writerNotInitialized;
/**
* Constructs a {@code StreamHandler} object. The new stream handler
* does not have an associated output stream.
*/
public StreamHandler() {
initProperties("INFO", null, "java.util.logging.SimpleFormatter", null);
this.os = null;
this.writer = null;
this.writerNotInitialized = true;
}
/**
* Constructs a {@code StreamHandler} object with the supplied output
* stream. Default properties are read.
*
* @param os
* the output stream this handler writes to.
*/
StreamHandler(OutputStream os) {
this();
this.os = os;
}
/**
* Constructs a {@code StreamHandler} object. The specified default values
* will be used if the corresponding properties are not found in the log
* manager's properties.
*/
StreamHandler(String defaultLevel, String defaultFilter,
String defaultFormatter, String defaultEncoding) {
initProperties(defaultLevel, defaultFilter, defaultFormatter,
defaultEncoding);
this.os = null;
this.writer = null;
this.writerNotInitialized = true;
}
/**
* Constructs a {@code StreamHandler} object with the supplied output stream
* and formatter.
*
* @param os
* the output stream this handler writes to.
* @param formatter
* the formatter this handler uses to format the output.
* @throws NullPointerException
* if {@code os} or {@code formatter} is {@code null}.
*/
public StreamHandler(OutputStream os, Formatter formatter) {
this();
if (os == null) {
throw new NullPointerException("os == null");
}
if (formatter == null) {
throw new NullPointerException("formatter == null");
}
this.os = os;
internalSetFormatter(formatter);
}
// initialize the writer
private void initializeWriter() {
this.writerNotInitialized = false;
if (getEncoding() == null) {
this.writer = new OutputStreamWriter(this.os);
} else {
try {
this.writer = new OutputStreamWriter(this.os, getEncoding());
} catch (UnsupportedEncodingException e) {
/*
* Should not happen because it's checked in
* super.initProperties().
*/
}
}
write(getFormatter().getHead(this));
}
// Write a string to the output stream.
private void write(String s) {
try {
this.writer.write(s);
} catch (Exception e) {
getErrorManager().error("Exception occurred when writing to the output stream", e,
ErrorManager.WRITE_FAILURE);
}
}
/**
* Sets the output stream this handler writes to. Note it does nothing else.
*
* @param newOs
* the new output stream
*/
void internalSetOutputStream(OutputStream newOs) {
this.os = newOs;
}
/**
* Sets the output stream this handler writes to. If there's an existing
* output stream, the tail string of the associated formatter will be
* written to it. Then it will be flushed, closed and replaced with
* {@code os}.
*
* @param os
* the new output stream.
* @throws NullPointerException
* if {@code os} is {@code null}.
*/
protected void setOutputStream(OutputStream os) {
if (os == null) {
throw new NullPointerException("os == null");
}
LogManager.getLogManager().checkAccess();
close(true);
this.writer = null;
this.os = os;
this.writerNotInitialized = true;
}
/**
* Sets the character encoding used by this handler. A {@code null} value
* indicates that the default encoding should be used.
*
* @throws UnsupportedEncodingException if {@code charsetName} is not supported.
*/
@Override
public void setEncoding(String charsetName) throws UnsupportedEncodingException {
// Flush any existing data first.
this.flush();
super.setEncoding(charsetName);
// renew writer only if the writer exists
if (this.writer != null) {
if (getEncoding() == null) {
this.writer = new OutputStreamWriter(this.os);
} else {
try {
this.writer = new OutputStreamWriter(this.os, getEncoding());
} catch (UnsupportedEncodingException e) {
/*
* Should not happen because it's checked in
* super.initProperties().
*/
throw new AssertionError(e);
}
}
}
}
/**
* Closes this handler, but the underlying output stream is only closed if
* {@code closeStream} is {@code true}. Security is not checked.
*
* @param closeStream
* whether to close the underlying output stream.
*/
void close(boolean closeStream) {
if (this.os != null) {
if (this.writerNotInitialized) {
initializeWriter();
}
write(getFormatter().getTail(this));
try {
this.writer.flush();
if (closeStream) {
this.writer.close();
this.writer = null;
this.os = null;
}
} catch (Exception e) {
getErrorManager().error("Exception occurred when closing the output stream", e,
ErrorManager.CLOSE_FAILURE);
}
}
}
/**
* Closes this handler. The tail string of the formatter associated with
* this handler is written out. A flush operation and a subsequent close
* operation is then performed upon the output stream. Client applications
* should not use a handler after closing it.
*/
@Override
public void close() {
LogManager.getLogManager().checkAccess();
close(true);
}
/**
* Flushes any buffered output.
*/
@Override
public void flush() {
if (this.os != null) {
try {
if (this.writer != null) {
this.writer.flush();
} else {
this.os.flush();
}
} catch (Exception e) {
getErrorManager().error("Exception occurred when flushing the output stream",
e, ErrorManager.FLUSH_FAILURE);
}
}
}
/**
* Accepts a logging request. The log record is formatted and written to the
* output stream if the following three conditions are met:
* <ul>
* <li>the supplied log record has at least the required logging level;
* <li>the supplied log record passes the filter associated with this
* handler, if any;
* <li>the output stream associated with this handler is not {@code null}.
* </ul>
* If it is the first time a log record is written out, the head string of
* the formatter associated with this handler is written out first.
*
* @param record
* the log record to be logged.
*/
@Override
public synchronized void publish(LogRecord record) {
try {
if (this.isLoggable(record)) {
if (this.writerNotInitialized) {
initializeWriter();
}
String msg = null;
try {
msg = getFormatter().format(record);
} catch (Exception e) {
getErrorManager().error("Exception occurred when formatting the log record",
e, ErrorManager.FORMAT_FAILURE);
}
write(msg);
}
} catch (Exception e) {
getErrorManager().error("Exception occurred when logging the record", e,
ErrorManager.GENERIC_FAILURE);
}
}
/**
* Determines whether the supplied log record needs to be logged. The
* logging levels are checked as well as the filter. The output stream of
* this handler is also checked. If it is {@code null}, this method returns
* {@code false}.
* <p>
* Notice : Case of no output stream will return {@code false}.
*
* @param record
* the log record to be checked.
* @return {@code true} if {@code record} needs to be logged, {@code false}
* otherwise.
*/
@Override
public boolean isLoggable(LogRecord record) {
if (record == null) {
return false;
}
if (this.os != null && super.isLoggable(record)) {
return true;
}
return false;
}
}