/**
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.commons.utils;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.gatein.common.io.UndeclaredIOException;
/**
* <p>
* An extension of {@link Printer} that encodes the text with a provided encoder and sends the resulting bytes on an
* {@link java.io.OutputStream}. The instance can be configured to have different behavior on failure of the output stream.
* </p>
*
* <p>
* The <tt>ignoreOnFailure</tt> property will stop to make further invocations to the output stream if an exception is thrown by
* the output stream except for the {@link #close()} method.
* </p>
*
* <p>
* The <tt>failureFlow</tt> property modifies the control flow of the method invocation when the output stream throws an
* {@link java.io.IOException}.
*
* <ul>
* <li>The {@link IOFailureFlow#IGNORE} value ignores the exception.</li>
* <li>The {@link IOFailureFlow#RETHROW} value rethrows the exception.</li>
* <li>The {@link IOFailureFlow#THROW_UNDECLARED} value throws instead a {@link UndeclaredIOException} exception wrapping the
* original exception.</li>
* </ul>
*
* </p>
*
* <p>
* The class provides direct write access to the underlying output stream when the client of the stream can provides bytes
* directly.
* </p>
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class OutputStreamPrinter extends Printer implements BinaryOutput {
private final IOFailureFlow failureFlow;
private final boolean ignoreOnFailure;
private final OutputStream out;
private final TextEncoder encoder;
private boolean failed;
private final boolean flushOnClose;
/**
* Builds an instance with the failureFlow being {@link IOFailureFlow#RETHROW} and a the ignoreOnFailure property set to
* false.
*
* @param encoder the encoder
* @param out the output
* @param flushOnClose flush when stream is closed
* @throws IllegalArgumentException if any argument is null
*/
public OutputStreamPrinter(TextEncoder encoder, OutputStream out, boolean flushOnClose) throws IllegalArgumentException {
this(encoder, out, IOFailureFlow.RETHROW, false, flushOnClose, 0, false);
}
/**
* Builds an instance with the failureFlow being {@link IOFailureFlow#RETHROW} and a the ignoreOnFailure property set to
* false.
*
* @param encoder the encoder
* @param out the output
* @param flushOnClose flush when stream is closed
* @param bufferSize the size of the buffer
* @throws IllegalArgumentException if any argument is null
*/
public OutputStreamPrinter(TextEncoder encoder, OutputStream out, boolean flushOnClose, int bufferSize)
throws IllegalArgumentException {
this(encoder, out, IOFailureFlow.RETHROW, false, flushOnClose, bufferSize, false);
}
/**
* Builds an instance with the failureFlow being {@link IOFailureFlow#RETHROW} and a the ignoreOnFailure property set to
* false.
*
* @param encoder the encoder
* @param out the output
* @param flushOnClose flush when stream is closed
* @param bufferSize the initial size of the buffer
* @param growing if the buffer should grow in size once full
* @throws IllegalArgumentException if any argument is null
*/
public OutputStreamPrinter(TextEncoder encoder, OutputStream out, boolean flushOnClose, int bufferSize, boolean growing)
throws IllegalArgumentException {
this(encoder, out, IOFailureFlow.RETHROW, false, flushOnClose, bufferSize, growing);
}
/**
* Builds an instance with the failureFlow being {@link IOFailureFlow#RETHROW} and a the ignoreOnFailure property set to
* false.
*
* @param encoder the encoder
* @param out the output
* @throws IllegalArgumentException if any argument is null
*/
public OutputStreamPrinter(TextEncoder encoder, OutputStream out) throws IllegalArgumentException {
this(encoder, out, IOFailureFlow.RETHROW, false, false, 0, false);
}
/**
* Builds a new instance with the specified parameters and the delegate output.
*
* @param encoder the encoder
* @param out the output
* @param failureFlow the control flow failureFlow
* @param ignoreOnFailure the behavior on failure
* @param flushOnClose flush when stream is closed
* @param bufferSize the buffer size
* @throws IllegalArgumentException if any argument is null
*/
public OutputStreamPrinter(TextEncoder encoder, OutputStream out, IOFailureFlow failureFlow, boolean ignoreOnFailure,
boolean flushOnClose, int bufferSize) throws IllegalArgumentException {
this(encoder, out, failureFlow, ignoreOnFailure, flushOnClose, bufferSize, false);
}
public OutputStreamPrinter(TextEncoder encoder, OutputStream out, IOFailureFlow failureFlow, boolean ignoreOnFailure,
boolean flushOnClose, int bufferSize, boolean growing) throws IllegalArgumentException {
if (encoder == null) {
throw new IllegalArgumentException("No null encoder accepted");
}
if (out == null) {
throw new IllegalArgumentException("No null output stream accepted");
}
if (failureFlow == null) {
throw new IllegalArgumentException("No null control flow mode accepted");
}
if (bufferSize < 0) {
throw new IllegalArgumentException("Invalid negative max buffer size: " + bufferSize);
}
//
if (bufferSize > 0 && !growing) {
out = new BufferingOutputStream(out, bufferSize);
} else if (growing) {
out = new GrowingOutputStream(out, bufferSize);
}
//
this.encoder = encoder;
this.out = out;
this.failureFlow = failureFlow;
this.failed = false;
this.ignoreOnFailure = ignoreOnFailure;
this.flushOnClose = flushOnClose;
}
public final Charset getCharset() {
return encoder.getCharset();
}
/**
* Returns the failure flow.
*
* @return the failure flow
*/
public final IOFailureFlow getFailureFlow() {
return failureFlow;
}
/**
* Returns the ignore on failure property.
*
* @return the ignore on failure property
*/
public final boolean getIgnoreOnFailure() {
return ignoreOnFailure;
}
public final boolean isFailed() {
return failed;
}
// Bytes access interface
public final void write(byte b) throws IOException {
if (!failed) {
try {
out.write(b);
} catch (IOException e) {
handle(e);
}
}
}
public final void write(byte[] bytes) throws IOException {
if (!failed) {
try {
out.write(bytes);
} catch (IOException e) {
handle(e);
}
}
}
public final void write(byte[] bytes, int off, int len) throws IOException {
if (!failed) {
try {
out.write(bytes, off, len);
} catch (IOException e) {
handle(e);
}
}
}
//
@Override
// Note that the parent method has a synchronisation that we want to avoid
// for performance reasons
public final void write(int c) throws IOException {
if (!failed) {
try {
encoder.encode((char) c, out);
} catch (IOException e) {
handle(e);
}
}
}
@Override
public final void write(char[] cbuf) throws IOException {
if (!failed) {
try {
encoder.encode(cbuf, 0, cbuf.length, out);
} catch (IOException e) {
handle(e);
}
}
}
@Override
public final void write(String str) throws IOException {
if (!failed) {
try {
encoder.encode(str, 0, str.length(), out);
} catch (IOException e) {
handle(e);
}
}
}
@Override
// Note that the parent method has a synchronisation that we want to avoid
// for performance reasons
public final void write(String str, int off, int len) throws IOException {
if (!failed) {
try {
encoder.encode(str, off, len, out);
} catch (IOException e) {
handle(e);
}
}
}
public final void write(char[] cbuf, int off, int len) throws IOException {
if (!failed) {
try {
encoder.encode(cbuf, off, len, out);
} catch (IOException e) {
handle(e);
}
}
}
public final void flush() throws IOException {
if (!failed && !flushOnClose) {
try {
out.flush();
} catch (IOException e) {
handle(e);
}
}
}
public final void close() throws IOException {
try {
out.close();
} catch (IOException e) {
handle(e);
}
}
private void handle(IOException e) throws IOException {
if (ignoreOnFailure) {
failed = true;
}
switch (failureFlow) {
case IGNORE:
break;
case THROW_UNDECLARED:
throw new UndeclaredIOException(e);
case RETHROW:
throw e;
}
}
/**
* Flush the output stream. This allows for the outputstream to be independently flushed regardless of the flushOnClose
* setting.
*/
public void flushOutputStream() throws IOException {
if (!failed) {
try {
out.flush();
} catch (IOException e) {
handle(e);
}
}
}
}