net.sf.mmm.util.io.base.StreamUtilImpl Maven / Gradle / Ivy
/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0 */
package net.sf.mmm.util.io.base;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.mmm.util.component.base.AbstractComponent;
import net.sf.mmm.util.concurrent.api.Stoppable;
import net.sf.mmm.util.concurrent.base.SimpleExecutor;
import net.sf.mmm.util.exception.api.NlsNullPointerException;
import net.sf.mmm.util.io.api.AsyncTransferrer;
import net.sf.mmm.util.io.api.IoMode;
import net.sf.mmm.util.io.api.RuntimeIoException;
import net.sf.mmm.util.io.api.StreamUtil;
import net.sf.mmm.util.io.api.TransferCallback;
import net.sf.mmm.util.pool.api.Pool;
import net.sf.mmm.util.pool.base.NoByteArrayPool;
import net.sf.mmm.util.pool.base.NoCharArrayPool;
/**
* This is the implementation of the {@link StreamUtil} interface.
*
* @see #getInstance()
*
* @author Joerg Hohwiller (hohwille at users.sourceforge.net)
* @since 1.0.0
*/
public class StreamUtilImpl extends AbstractComponent implements StreamUtil {
private static final Logger LOG = LoggerFactory.getLogger(StreamUtilImpl.class);
private static StreamUtil instance;
private Executor executor;
private Pool byteArrayPool;
private Pool charArrayPool;
/**
* The constructor.
*/
public StreamUtilImpl() {
super();
this.executor = null;
this.byteArrayPool = null;
this.charArrayPool = null;
}
/**
* This method gets the singleton instance of this {@link StreamUtilImpl}.
* ATTENTION:
* Please prefer dependency-injection instead of using this method.
*
* @return the singleton instance.
*/
public static StreamUtil getInstance() {
if (instance == null) {
synchronized (StreamUtilImpl.class) {
if (instance == null) {
StreamUtilImpl util = new StreamUtilImpl();
util.initialize();
instance = util;
}
}
}
return instance;
}
/**
* This method gets the {@link Executor} used to run asynchronous tasks. It may use a thread-pool.
*
* @return the executor.
*/
protected Executor getExecutor() {
return this.executor;
}
/**
* This method sets the {@link #getExecutor() executor}.
*
* @param executor the executor to set.
*/
@Inject
public void setExecutor(Executor executor) {
getInitializationState().requireNotInitilized();
this.executor = executor;
}
/**
* This method gets the byte-array {@link Pool} used to transfer streams.
* The implementation should create byte-arrays with a suitable length (at least 512, suggested is 4096). Override
* this method to use a real pool implementation.
*
* @return the {@link Pool} instance.
*/
protected Pool getByteArrayPool() {
return this.byteArrayPool;
}
/**
* This method sets the {@link #getByteArrayPool() byte-array-pool}.
*
* @param byteArrayPool the byteArrayPool to set
*/
// @Resource
public void setByteArrayPool(Pool byteArrayPool) {
getInitializationState().requireNotInitilized();
this.byteArrayPool = byteArrayPool;
}
/**
* This method gets the char-array {@link Pool} used to transfer {@link Reader}s and {@link Writer}s. The
* implementation should create char-arrays with a suitable length (at least 512, suggested is 2048).
* Override this method to use a real pool implementation.
*
* @return the {@link Pool} instance.
*/
protected Pool getCharArrayPool() {
return this.charArrayPool;
}
/**
* This method sets the {@link #getCharArrayPool() char-array-pool}.
*
* @param charArrayPool the charArrayPool to set
*/
// @Resource
public void setCharArrayPool(Pool charArrayPool) {
getInitializationState().requireNotInitilized();
this.charArrayPool = charArrayPool;
}
@Override
protected void doInitialize() {
super.doInitialize();
if (this.executor == null) {
this.executor = SimpleExecutor.INSTANCE;
}
if (this.byteArrayPool == null) {
this.byteArrayPool = NoByteArrayPool.INSTANCE;
}
if (this.charArrayPool == null) {
this.charArrayPool = NoCharArrayPool.INSTANCE;
}
}
@Override
public String read(Reader reader) throws RuntimeIoException {
StringWriter writer = new StringWriter();
transfer(reader, writer, false);
return writer.toString();
}
@Override
public long transfer(Reader reader, Writer writer, boolean keepWriterOpen) throws RuntimeIoException {
ReaderTransferrer transferrer = new ReaderTransferrer(reader, writer, keepWriterOpen, null);
long bytes = transferrer.transfer();
return bytes;
}
@Override
public long transfer(FileInputStream inStream, OutputStream outStream, boolean keepOutStreamOpen) throws RuntimeIoException {
RuntimeIoException t = null;
try (FileInputStream is = inStream; FileChannel inChannel = is.getChannel()) {
WritableByteChannel outChannel = Channels.newChannel(outStream);
return inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (Exception e) {
t = new RuntimeIoException(e, IoMode.COPY);
throw t;
} finally {
if (!keepOutStreamOpen) {
try {
outStream.close();
} catch (IOException e) {
if (t != null) {
t.addSuppressed(e);
} else {
throw new RuntimeIoException(e, IoMode.CLOSE);
}
}
}
}
}
@Override
public long transfer(InputStream inStream, FileOutputStream outStream, boolean keepOutStreamOpen, long size) throws RuntimeIoException {
RuntimeIoException t = null;
try (InputStream is = inStream; ReadableByteChannel inChannel = Channels.newChannel(is)) {
FileChannel outChannel = outStream.getChannel();
return outChannel.transferFrom(inChannel, 0, size);
} catch (Exception e) {
t = new RuntimeIoException(e, IoMode.COPY);
throw t;
} finally {
if (!keepOutStreamOpen) {
try {
outStream.close();
} catch (IOException e) {
if (t != null) {
t.addSuppressed(e);
} else {
throw new RuntimeIoException(e, IoMode.CLOSE);
}
}
}
}
}
@Override
public long transfer(InputStream inStream, OutputStream outStream, boolean keepOutStreamOpen) throws RuntimeIoException {
StreamTransferrer transferrer = new StreamTransferrer(inStream, outStream, keepOutStreamOpen, null);
long bytes = transferrer.transfer();
return bytes;
}
@Override
public AsyncTransferrer transferAsync(InputStream inStream, OutputStream outStream, boolean keepOutStreamOpen) {
return transferAsync(inStream, outStream, keepOutStreamOpen, null);
}
@Override
public AsyncTransferrer transferAsync(InputStream inStream, OutputStream outStream, boolean keepOutStreamOpen, TransferCallback callback) {
StreamTransferrer transferrer = new StreamTransferrer(inStream, outStream, keepOutStreamOpen, callback);
AsyncTransferrerImpl task = new AsyncTransferrerImpl(transferrer);
this.executor.execute(task);
return task;
}
@Override
public AsyncTransferrer transferAsync(Reader reader, Writer writer, boolean keepWriterOpen) {
return transferAsync(reader, writer, keepWriterOpen, null);
}
@Override
public AsyncTransferrer transferAsync(Reader reader, Writer writer, boolean keepWriterOpen, TransferCallback callback) {
ReaderTransferrer transferrer = new ReaderTransferrer(reader, writer, keepWriterOpen, callback);
AsyncTransferrerImpl task = new AsyncTransferrerImpl(transferrer);
this.executor.execute(task);
return task;
}
@Override
public Properties loadProperties(InputStream inStream) throws RuntimeIoException {
try (InputStream is = inStream) {
Properties properties = new Properties();
properties.load(is);
return properties;
} catch (IOException e) {
throw new RuntimeIoException(e, IoMode.READ);
}
}
@Override
public Properties loadProperties(Reader reader) throws RuntimeIoException {
try (Reader r = reader) {
Properties properties = new Properties();
properties.load(r);
return properties;
} catch (IOException e) {
throw new RuntimeIoException(e, IoMode.READ);
}
}
@Override
public PrintWriter toPrintWriter(Appendable appendable) {
NlsNullPointerException.checkNotNull(Appendable.class, appendable);
if (appendable instanceof PrintWriter) {
return (PrintWriter) appendable;
} else {
return new PrintWriter(toWriter(appendable));
}
}
@Override
public Writer toWriter(Appendable appendable) {
NlsNullPointerException.checkNotNull(Appendable.class, appendable);
if (appendable instanceof Writer) {
return (Writer) appendable;
} else {
return new AppendableWriter(appendable);
}
}
@Override
public void close(InputStream inputStream) {
try {
inputStream.close();
} catch (Exception e) {
LOG.warn("Failed to close stream!", e);
}
}
@Override
public void close(OutputStream outputStream) {
try {
outputStream.close();
} catch (Exception e) {
throw new RuntimeIoException(e, IoMode.CLOSE);
}
}
@Override
public void close(Writer writer) {
try {
writer.close();
} catch (Exception e) {
throw new RuntimeIoException(e, IoMode.CLOSE);
}
}
@Override
public void close(Reader reader) {
try {
reader.close();
} catch (Exception e) {
LOG.warn("Failed to close reader!", e);
}
}
@Override
public void close(Channel channel) {
try {
channel.close();
} catch (Exception e) {
if (channel instanceof WritableByteChannel) {
throw new RuntimeIoException(e, IoMode.CLOSE);
}
LOG.warn("Failed to close channel!", e);
}
}
/**
* This is the default implementation of the {@link AsyncTransferrer} interface.
*/
protected static class AsyncTransferrerImpl extends FutureTask implements AsyncTransferrer {
/** the actual task. */
private final BaseTransferrer> transferrer;
/**
* The constructor.
*
* @param transferrer is the actual transferrer task.
*/
public AsyncTransferrerImpl(BaseTransferrer> transferrer) {
super(transferrer);
this.transferrer = transferrer;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
this.transferrer.stop();
return super.cancel(mayInterruptIfRunning);
}
}
/**
* This is the abstract base class for the {@link Callable} that transfers data of streams or readers/writers.
*/
protected abstract static class AbstractAsyncTransferrer implements Callable, Stoppable {
private volatile boolean stopped;
private volatile boolean completed;
@Override
public void stop() {
this.stopped = true;
}
/**
* This method determines if this transferrer was {@link #stop() stopped}.
*
* @return {@code true} if stopped, {@code false} otherwise.
*/
public final boolean isStopped() {
return this.stopped;
}
/**
* This method determines if the transfer has been completed successfully.
*
* @return {@code true} if successfully completed, {@code false} if still running, {@link #isStopped() stopped} or
* an exception occurred.
*/
public final boolean isCompleted() {
return this.completed;
}
/**
* This method sets the {@link #isCompleted() completed-flag}.
*/
protected void setCompleted() {
this.completed = true;
}
}
/**
* This is an abstract implementation of the {@link AsyncTransferrer} interface, that implements {@link Runnable}
* defining the main flow.
*
* @param is the generic type of the buffers provided by {@link BaseTransferrer#getPool()}.
*/
protected abstract class BaseTransferrer extends AbstractAsyncTransferrer {
/** The callback or {@code null}. */
private final TransferCallback callback;
/**
* {@code true} if {@link #getDestination() destination} should be closed.
*/
private final boolean keepDestinationOpen;
/**
* The constructor.
*
* @param callback is the callback or {@code null}.
* @param keepDestinationOpen {@code true} if the {@link #getDestination() destination} should be closed,
* {@code false} otherwise.
*/
public BaseTransferrer(TransferCallback callback, boolean keepDestinationOpen) {
super();
this.callback = callback;
this.keepDestinationOpen = keepDestinationOpen;
}
/**
* This method gets the source to transfer.
*
* @return the source (e.g. {@link InputStream} or {@link Reader}).
*/
protected abstract Closeable getSource();
/**
* This method gets the destination to transfer to.
*
* @return the destination (e.g. {@link OutputStream} or {@link Writer}).
*/
protected abstract Closeable getDestination();
/**
* This method gets the {@link Pool} to retrieve buffers.
*
* @return the {@link Pool}.
*/
protected abstract Pool getPool();
/**
* This method performs the actual transfer.
*
* @param buffer is the buffer used for the transfer.
* @return the number of bytes that have been transferred.
* @throws IOException if the transfer failed.
*/
protected abstract long transfer(BUFFER buffer) throws IOException;
/**
* This method performs the actual transfer.
*
* @return the number of bytes that have been transferred.
* @throws RuntimeIoException if the transfer failed.
*/
protected long transfer() throws RuntimeIoException {
BUFFER buffer = getPool().borrow();
RuntimeIoException t = null;
try {
return transfer(buffer);
} catch (Exception e) {
t = new RuntimeIoException(e, IoMode.COPY);
throw t;
} finally {
boolean doThrow = (t == null);
try {
getSource().close();
} catch (Exception e) {
RuntimeIoException ex = new RuntimeIoException(e, IoMode.CLOSE);
if (t != null) {
t.addSuppressed(ex);
} else {
t = ex;
}
}
if (!this.keepDestinationOpen) {
try {
getDestination().close();
} catch (Exception e) {
RuntimeIoException ex = new RuntimeIoException(e, IoMode.CLOSE);
if (t != null) {
t.addSuppressed(ex);
} else {
t = ex;
}
}
}
RuntimeException rte = null;
try {
getPool().release(buffer);
} catch (RuntimeException e) {
rte = e;
} finally {
if (rte != null) {
if (t == null) {
throw rte;
} else {
t.addSuppressed(rte);
}
}
if ((t != null) && doThrow) {
throw t;
}
}
}
}
@Override
public Long call() throws Exception {
try {
long bytes = transfer();
if (this.callback != null) {
if (isCompleted()) {
this.callback.transferCompleted(bytes);
} else {
this.callback.transferStopped(bytes);
}
}
return Long.valueOf(bytes);
} catch (Exception e) {
LOG.error("Error during async transfer!", e);
if (this.callback != null) {
this.callback.transferFailed(e);
}
throw e;
}
}
}
/**
* This inner class is used to transfer an {@link InputStream} to an {@link OutputStream}.
*/
protected class StreamTransferrer extends BaseTransferrer {
/** The source to read from (to copy). */
private final InputStream source;
/** The destination to write to. */
private final OutputStream destination;
/**
* The constructor.
*
* @see StreamUtilImpl#transfer(InputStream, OutputStream, boolean)
*
* @param source is {@link InputStream} to read from.
* @param destination the {@link OutputStream} to write to.
* @param keepDestinationOpen {@code true} if the {@code destination} should be closed.
* @param callback is the callback or {@code null}.
*/
public StreamTransferrer(InputStream source, OutputStream destination, boolean keepDestinationOpen, TransferCallback callback) {
super(callback, keepDestinationOpen);
this.source = source;
this.destination = destination;
}
@Override
protected InputStream getSource() {
return this.source;
}
@Override
protected OutputStream getDestination() {
return this.destination;
}
@Override
protected Pool getPool() {
return getByteArrayPool();
}
@Override
public long transfer(byte[] buffer) throws IOException {
long bytesTransferred = 0;
int count = this.source.read(buffer);
while ((count > 0) && !isStopped()) {
this.destination.write(buffer, 0, count);
bytesTransferred += count;
count = this.source.read(buffer);
}
if (count == -1) {
setCompleted();
}
return bytesTransferred;
}
}
/**
* This inner class is used to transfer a {@link Reader} to a {@link Writer}.
*/
protected class ReaderTransferrer extends BaseTransferrer {
/** The source to read from (to copy). */
private final Reader source;
/** The destination to write to. */
private final Writer destination;
/**
* The constructor.
*
* @see StreamUtilImpl#transfer(Reader, Writer, boolean)
*
* @param source is {@link Reader} to read from.
* @param destination the {@link Writer} to write to.
* @param keepDestinationOpen {@code true} if the {@code destination} should be closed.
* @param callback is the callback or {@code null}.
*/
public ReaderTransferrer(Reader source, Writer destination, boolean keepDestinationOpen, TransferCallback callback) {
super(callback, keepDestinationOpen);
this.source = source;
this.destination = destination;
}
@Override
protected Reader getSource() {
return this.source;
}
@Override
protected Writer getDestination() {
return this.destination;
}
@Override
protected Pool getPool() {
return getCharArrayPool();
}
@Override
public long transfer(char[] buffer) throws IOException {
long bytesTransferred = 0;
int count = this.source.read(buffer);
while ((count > 0) && !isStopped()) {
this.destination.write(buffer, 0, count);
bytesTransferred += count;
count = this.source.read(buffer);
}
if (count == -1) {
setCompleted();
}
return bytesTransferred;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy