org.spf4j.io.PipedOutputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spf4j-core Show documentation
Show all versions of spf4j-core Show documentation
A continuously growing collection of utilities to measure performance, get better diagnostics,
improve performance, or do things more reliably, faster that other open source libraries...
/*
* Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
*
* This library 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 library 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 Lesser 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.
*
* Additionally licensed with:
*
* Licensed 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 org.spf4j.io;
import com.google.common.io.BaseEncoding;
import edu.umd.cs.findbugs.annotations.CleanupObligation;
import edu.umd.cs.findbugs.annotations.DischargesObligation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.spf4j.base.ExecutionContexts;
import org.spf4j.base.TimeSource;
import org.spf4j.base.Timing;
import org.spf4j.recyclable.SizedRecyclingSupplier;
import org.spf4j.recyclable.impl.ArraySuppliers;
/**
* Equivalent to Java piped input/output stream.
*
* This implementation supports timeouts, timeout are specified by setting in the ExecutionContext
*
* Implementation supports multiple readers and writers.
*
* Data is available to readers only after it is flushed. (happens automatically when buffer is full)
*
* This implementation should be slightly faster than the JDK implementation.
*
* @author zoly
*/
@ThreadSafe
@CleanupObligation
@SuppressWarnings("checkstyle:InnerAssignment")
public final class PipedOutputStream extends OutputStream {
private byte[] buffer;
private final Object sync = new Object();
private int startIdx;
private int endIdx;
private int readerPerceivedEndIdx;
private boolean writerClosed;
private Exception closedException;
private int nrReadStreams;
private final SizedRecyclingSupplier bufferProvider;
private final Long globalDeadlineNanos;
public PipedOutputStream() {
this(8192);
}
public PipedOutputStream(final int bufferSize) {
this(bufferSize, ArraySuppliers.Bytes.JAVA_NEW);
}
/**
* @deprecated use constructor that takes globalDeadlineNanos.
*/
@Deprecated
public PipedOutputStream(final int bufferSize, final long globalDeadlineMillis) {
this(bufferSize, ArraySuppliers.Bytes.JAVA_NEW, globalDeadlineMillis);
}
/**
* Create a PipedOutputStream with a global deadline relative to System.nanoTime().
* @param globalDeadlineNanos deadline relative to System.nanoTime().
* @param bufferSize the buffer size.
*/
public PipedOutputStream(final long globalDeadlineNanos, final int bufferSize) {
this(globalDeadlineNanos, bufferSize, ArraySuppliers.Bytes.JAVA_NEW);
}
public PipedOutputStream(final int bufferSize,
final SizedRecyclingSupplier bufferProvider) {
this(null, bufferSize, bufferProvider);
}
/**
* @deprecated use constructor that takes globalDeadlineNanos.
*/
@Deprecated
public PipedOutputStream(final int bufferSize,
final SizedRecyclingSupplier bufferProvider, @Nullable final Long globalDeadlineMillis) {
this(globalDeadlineMillis == null ? null
: Timing.getCurrentTiming().fromEpochMillisToNanoTime(globalDeadlineMillis), bufferSize, bufferProvider);
}
/**
* Create a PipedOutputStream.
* @param globalDeadlineNanos the deadline relative to System.nanoTime().
* @param bufferSize the buffer size in bytes.
* @param bufferProvider a buffer provider. (to allow more efficient recycling)
*/
public PipedOutputStream(@Nullable final Long globalDeadlineNanos, final int bufferSize,
final SizedRecyclingSupplier bufferProvider) {
if (bufferSize < 2) {
throw new IllegalArgumentException("Illegal buffer size " + bufferSize);
}
this.bufferProvider = bufferProvider;
buffer = bufferProvider.get(bufferSize);
startIdx = 0;
endIdx = 0;
readerPerceivedEndIdx = 0;
writerClosed = false;
nrReadStreams = 0;
closedException = null;
this.globalDeadlineNanos = globalDeadlineNanos;
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
long deadline = getNanoDeadline();
writeUntil(b, off, len, deadline);
}
public long getNanoDeadline() {
if (globalDeadlineNanos == null) {
return ExecutionContexts.getContextDeadlineNanos();
} else {
return globalDeadlineNanos;
}
}
public void writeUntil(final byte[] b, final int off, final int len, final long deadlineNanos) throws IOException {
int bytesWritten = 0;
while (bytesWritten < len) {
synchronized (sync) {
int a2w = 0;
while (!writerClosed && (a2w = availableToWrite()) < 1) {
long timeToWaitNanos = deadlineNanos - TimeSource.nanoTime();
if (timeToWaitNanos <= 0) {
throw new IOTimeoutException(deadlineNanos, -timeToWaitNanos);
}
try {
TimeUnit.NANOSECONDS.timedWait(sync, timeToWaitNanos);
} catch (InterruptedException ex) {
throw new IOException("Interrupted while writing " + Arrays.toString(b), ex);
}
}
if (writerClosed) {
throw new IOException("Cannot write, stream closed " + this, closedException);
}
a2w = Math.min(a2w, len - bytesWritten);
int wrToEnd = Math.min(a2w, buffer.length - endIdx);
System.arraycopy(b, off + bytesWritten, buffer, endIdx, wrToEnd);
endIdx += wrToEnd;
bytesWritten += wrToEnd;
int wrapArround = a2w - wrToEnd;
if (wrapArround > 0) {
System.arraycopy(b, off + bytesWritten, buffer, 0, wrapArround);
endIdx = wrapArround;
bytesWritten += wrapArround;
} else if (endIdx >= buffer.length) {
endIdx = 0;
}
if (availableToWrite() < 1) {
flush();
}
}
}
}
@Override
public void write(final int b) throws IOException {
long deadline = getNanoDeadline();
writeUntil(b, deadline);
}
public void writeUntil(final int b, final long deadlineNanos) throws IOException {
synchronized (sync) {
int a2w = 0;
while (!writerClosed && (a2w = availableToWrite()) < 1) {
try {
long timeToWaitNanos = deadlineNanos - TimeSource.nanoTime();
if (timeToWaitNanos <= 0) {
throw new IOTimeoutException(deadlineNanos, -timeToWaitNanos);
}
TimeUnit.NANOSECONDS.timedWait(sync, timeToWaitNanos);
} catch (InterruptedException ex) {
throw new IOException("Interrupted while writing " + b, ex);
}
}
if (writerClosed) {
throw new IOException("Cannot write stream closed " + this, closedException);
}
buffer[endIdx++] = (byte) b;
if (endIdx >= buffer.length) {
endIdx = 0;
}
if (a2w < 2) {
flush();
}
}
}
private int availableToWrite() {
if (startIdx <= endIdx) {
return startIdx + buffer.length - endIdx - 1;
} else {
return startIdx - endIdx - 1;
}
}
private int availableToRead() {
if (startIdx <= readerPerceivedEndIdx) {
return readerPerceivedEndIdx - startIdx;
} else {
return buffer.length - startIdx + readerPerceivedEndIdx;
}
}
private int contentInBuffer() {
if (startIdx <= endIdx) {
return endIdx - startIdx;
} else {
return buffer.length - startIdx + endIdx;
}
}
@Override
public void flush() {
synchronized (sync) {
if (readerPerceivedEndIdx != endIdx) {
readerPerceivedEndIdx = endIdx;
sync.notifyAll();
}
}
}
@Override
@DischargesObligation
public void close() {
synchronized (sync) {
if (!writerClosed) {
try {
writerClosed = true;
flush();
} finally {
if (nrReadStreams == 0 && availableToRead() == 0) {
bufferProvider.recycle(buffer);
buffer = null;
}
sync.notifyAll();
}
}
}
}
/**
* Close this piped output stream, and provide a exception reason.
* Designed to propagate exception details from the consumer to the producer.
* A consumer that reads data from a InputStream connected to this PipedOutpuStream,
* can close this pipe, and provide a exception. This exception will be used as cause for any
* exceptions thrown by subsequent attempted write operations.
* @param ex the exception to use as root cause.
*/
@DischargesObligation
@SuppressFBWarnings("EI_EXPOSE_REP2")
public void close(final Exception ex) {
synchronized (sync) {
if (closedException != null) {
ex.addSuppressed(closedException);
}
closedException = ex;
close();
}
}
public InputStream getInputStream() {
synchronized (sync) {
if (writerClosed && availableToRead() == 0) {
return EmptyInputStream.INSTANCE;
}
nrReadStreams++;
return new PipedInputStream();
}
}
public synchronized byte[] getUnreadBytesFromBuffer() {
final int size = contentInBuffer();
if (size == 0) {
return org.spf4j.base.Arrays.EMPTY_BYTE_ARRAY;
}
byte[] result = new byte[size];
if (startIdx < endIdx) {
System.arraycopy(buffer, startIdx, result, 0, result.length);
} else {
final int toEnd = buffer.length - startIdx;
System.arraycopy(buffer, startIdx, result, 0, toEnd);
System.arraycopy(buffer, 0, result, toEnd, endIdx);
}
return result;
}
@Override
public String toString() {
synchronized (sync) {
if (buffer == null) {
return "PipedOutputStream{readers=" + nrReadStreams + ", startIdx=" + startIdx
+ ", endIdx=" + endIdx
+ ", readerPerceivedEndIdx=" + readerPerceivedEndIdx
+ ", closed=" + writerClosed + '}';
} else {
return "PipedOutputStream{readers=" + nrReadStreams + ", bufferLength="
+ buffer.length + ", startIdx=" + startIdx
+ ", endIdx=" + endIdx
+ ", readerPerceivedEndIdx=" + readerPerceivedEndIdx
+ ((writerClosed)
? ", closed=" + writerClosed
: ", unread=" + BaseEncoding.base64().encode(getUnreadBytesFromBuffer()))
+ '}';
}
}
}
public final class PipedInputStream extends InputStream {
private boolean readerClosed = false;
private PipedInputStream() {
}
@SuppressFBWarnings("EI_EXPOSE_REP")
public PipedOutputStream getOutputStream() {
return PipedOutputStream.this;
}
@Override
public int read() throws IOException {
long deadline = getNanoDeadline();
return readUntil(deadline);
}
public int readUntil(final long deadlineNanos) throws IOException {
synchronized (sync) {
int availableToRead = 0;
while (!readerClosed && (availableToRead = availableToRead()) < 1 && !writerClosed) {
long timeToWaitNanos = deadlineNanos - TimeSource.nanoTime();
if (timeToWaitNanos <= 0) {
throw new IOTimeoutException(deadlineNanos, -timeToWaitNanos);
}
try {
TimeUnit.NANOSECONDS.timedWait(sync, timeToWaitNanos);
} catch (InterruptedException ex) {
throw new IOException("Interrupted while reading from "
+ PipedOutputStream.this, ex);
}
}
if (readerClosed) {
throw new IOException("Reader is closed for " + PipedOutputStream.this);
}
if (availableToRead == 0) {
if (!writerClosed) {
throw new IllegalStateException("Stream must be closed " + PipedOutputStream.this);
}
return -1;
}
int result = buffer[startIdx];
startIdx++;
if (startIdx >= buffer.length) {
startIdx = 0;
}
sync.notifyAll();
return result;
}
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
long deadline = getNanoDeadline();
return readUntil(len, b, off, deadline);
}
public int readUntil(final int len, final byte[] b, final int off, final long deadline)
throws IOException {
int bytesWritten = 0;
synchronized (sync) {
int availableToRead = 0;
while (!readerClosed && (availableToRead = availableToRead()) < 1 && !writerClosed) {
long timeToWaitNanos = deadline - TimeSource.nanoTime();
if (timeToWaitNanos <= 0) {
throw new IOTimeoutException(deadline, -timeToWaitNanos);
}
try {
TimeUnit.NANOSECONDS.timedWait(sync, timeToWaitNanos);
} catch (InterruptedException ex) {
throw new IOException("Interrupted while reading from " + PipedOutputStream.this, ex);
}
}
if (readerClosed) {
throw new IOException("Reader is closed for " + PipedOutputStream.this);
}
if (availableToRead == 0) {
if (!writerClosed) {
throw new IllegalStateException("Stream should be closed, " + PipedOutputStream.this);
}
return -1;
}
availableToRead = Math.min(availableToRead, len);
int readToEnd = Math.min(availableToRead, buffer.length - startIdx);
System.arraycopy(buffer, startIdx, b, off, readToEnd);
bytesWritten += readToEnd;
startIdx += readToEnd;
int remaining = availableToRead - readToEnd;
if (remaining > 0) {
System.arraycopy(buffer, 0, b, off + readToEnd, remaining);
bytesWritten += remaining;
startIdx = remaining;
} else if (startIdx >= buffer.length) {
startIdx = 0;
}
sync.notifyAll();
return bytesWritten;
}
}
@Override
public int available() {
synchronized (sync) {
if (readerClosed) {
throw new UncheckedIOException("Reader is closed for " + PipedOutputStream.this, null);
}
return availableToRead();
}
}
@Override
public void close() {
synchronized (sync) {
nrReadStreams--;
readerClosed = true;
if (writerClosed && nrReadStreams == 0 && availableToRead() == 0) {
bufferProvider.recycle(buffer);
buffer = null;
}
sync.notifyAll();
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy