com.github.mrstampy.kitchensync.stream.BufferedInputStreamStreamer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of KitchenSync-core Show documentation
Show all versions of KitchenSync-core Show documentation
KitchenSync-core - A Java Library for Distributed Communication
The newest version!
/*
* KitchenSync-core Java Library Copyright (C) 2014 Burton Alexander
*
* This program 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 2 of the License, or (at your option) any later
* version.
*
* This program 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
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
package com.github.mrstampy.kitchensync.stream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.mrstampy.kitchensync.netty.channel.KiSyChannel;
import com.github.mrstampy.kitchensync.util.KiSyUtils;
/**
* This implementation of the {@link Streamer} interface streams the content of
* the specified {@link InputStream} to the remote destination. The
* {@link InputStream} is wrapped by a {@link BufferedInputStream} if necessary
* for efficiency in reading from the stream.
*/
public class BufferedInputStreamStreamer extends AbstractStreamer {
private static final Logger log = LoggerFactory.getLogger(BufferedInputStreamStreamer.class);
/** The in. */
protected BufferedInputStream in;
private boolean finishOnEmptyStream = true;
private boolean inputStreamResettable;
/**
* The Constructor.
*
* @param in
* the input stream
* @param channel
* the channel
* @param destination
* the destination
* @throws Exception
* the exception
*/
public BufferedInputStreamStreamer(InputStream in, KiSyChannel channel, InetSocketAddress destination)
throws Exception {
this(in, channel, destination, DEFAULT_CHUNK_SIZE);
}
/**
* The Constructor.
*
* @param in
* the input stream
* @param channel
* the channel
* @param destination
* the destination
* @param chunkSize
* the chunk size
* @throws Exception
* the exception
*/
public BufferedInputStreamStreamer(InputStream in, KiSyChannel channel, InetSocketAddress destination, int chunkSize)
throws Exception {
super(channel, destination, chunkSize);
prepareForSend(in);
}
/**
* If not {@link #isFinishOnEmptyStream()} then if
* {@link #isChunksPerSecond()} the streaming is paused, else this method
* returns when bytes are next available from the input stream. If
* {@link #isChunksPerSecond()} streaming is automatically restarted when
* bytes are available.
*
* By default if {@link InputStream#available()} returns 0 then finalization
* logic is executed.
*
* @return the chunk
* @throws Exception
* the exception
* @see com.github.mrstampy.kitchensync.stream.AbstractStreamer#getChunk()
*/
protected byte[] getChunk() throws Exception {
int remaining = (int) remaining();
if (remaining == 0 && isFinishOnEmptyStream()) finish(true, null);
if (remaining <= 0) return null;
// Blue sky
byte[] chunk = createByteArray(remaining);
in.read(chunk);
return chunk;
}
/**
* Not implemented.
*
* @param newPosition
* the new position
*/
@Override
public void resetPosition(int newPosition) {
// not implemented for BufferedInputStreamStreamer
}
/*
* (non-Javadoc)
*
* @see com.github.mrstampy.kitchensync.stream.Streamer#reset()
*/
@Override
public void reset() {
if (isStreaming()) throw new IllegalStateException("Cannot reset when streaming");
try {
if (markSupported()) in.reset();
init();
} catch (IOException e) {
log.error("Could not reset the input stream", e);
}
}
/*
* (non-Javadoc)
*
* @see com.github.mrstampy.kitchensync.stream.Streamer#remaining()
*/
@Override
public long remaining() {
try {
return in.available();
} catch (IOException e) {
if (isStreaming()) {
log.error("Unexpected exception", e);
finish(false, e);
}
}
return -1;
}
/*
* (non-Javadoc)
*
* @see
* com.github.mrstampy.kitchensync.stream.AbstractStreamer#prepareForSend(
* java.lang.Object)
*/
@Override
protected void prepareForSend(InputStream message) throws IOException {
in = (message instanceof BufferedInputStream) ? (BufferedInputStream) message : new BufferedInputStream(message);
setSize(in.available());
in.mark((int) size());
}
/**
* Overriding to prevent finishing on an empty chunk when not
* {@link #isFinishOnEmptyStream()}.
*
* @param chunk
* the chunk
*/
protected void processChunk(byte[] chunk) {
if (isPausible(chunk)) {
pause();
} else if (chunk.length > 0) {
sendChunk(chunk);
} else if (isFinishOnEmptyStream()) {
finish(true, null);
}
KiSyUtils.snooze(0); // necessary for packet fidelity when @ full throttle
}
private boolean isPausible(byte[] chunk) {
return chunk == null || (chunk.length == 0 && !isFinishOnEmptyStream());
}
private boolean markSupported() {
return in != null && in.markSupported() && isInputStreamResettable();
}
/**
* Returns true if this {@link Streamer} finalizes when the input stream is
* empty. If false then finalization will occur via a call to
* {@link #cancel()} or on error.
*
* @return true if empty stream indicates the end of message
*/
public boolean isFinishOnEmptyStream() {
return finishOnEmptyStream;
}
/**
* Set to false to pause sending if the stream is currently empty. If true
* (the default) the message finalization will occur when the stream is empty.
* If false then {@link #setInputStreamResettable(boolean)} is also set to
* false.
*
* @param finishOnEmptyStream
* true if empty stream indicates the end of message
*/
public void setFinishOnEmptyStream(boolean finishOnEmptyStream) {
this.finishOnEmptyStream = finishOnEmptyStream;
if (!finishOnEmptyStream) setInputStreamResettable(false);
}
/**
* If true then {@link InputStream#mark(int)} and {@link InputStream#reset()}
* will be invoked appropriately on the input stream.
*
* @return true, if checks if is input stream resettable
*/
public boolean isInputStreamResettable() {
return inputStreamResettable;
}
/**
* If set to false will prevent {@link InputStream#mark(int)} and
* {@link InputStream#reset()} from being invoked.
*
* @param inputStreamResettable
* the input stream resettable
* @see #isFinishOnEmptyStream()
*/
public void setInputStreamResettable(boolean inputStreamResettable) {
this.inputStreamResettable = inputStreamResettable;
}
}