/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.socket;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.util.LinkedList;
import java.util.Queue;
/**
* Provides the stream data source for JxtaSocket.
*
* @author Athomas Goldberg
*/
class JxtaSocketInputStream extends InputStream {
/**
* We push this "poison" value into the accept backlog queue in order to
* signal that the queue has been closed.
*/
protected static final MessageElement QUEUE_END = new StringMessageElement("Terminal", "Terminal", null);
/**
* Our read timeout.
*/
private long timeout = 60 * 1000;
/**
* The associated socket.
*/
private final JxtaSocket socket;
/**
* Our queue of message elements waiting to be read.
*/
protected final Queue<MessageElement> queue;
/**
* The maximum number of message elements we will allow in the queue.
*/
protected final int queueSize;
/**
* The current message element input stream we are processing.
*/
private InputStream currentMsgStream = null;
/**
* Construct an InputStream for a specified JxtaSocket.
*
* @param socket the JxtaSocket
* @param queueSize the queue size
*/
JxtaSocketInputStream(JxtaSocket socket, int queueSize) {
this.socket = socket;
this.queueSize = queueSize;
queue = new LinkedList<MessageElement>();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized int available() throws IOException {
int result;
InputStream in = getCurrentStream(false);
if (in != null) {
result = in.available();
} else {
// We chose not to block, if we have no inputstream then
// that means there are no bytes available.
result = 0;
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized int read() throws IOException {
byte[] b = new byte[1];
int result = 0;
// The result of read() can be -1 (EOF), 0 (yes, its true) or 1.
while (0 == result) {
result = read(b, 0, 1);
}
if (-1 != result) {
result = (int) b[0];
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized int read(byte b[], int off, int len) throws IOException {
if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
while (true) {
int result = -1;
InputStream in = getCurrentStream(true);
if (null == in) {
return -1;
}
result = in.read(b, off, len);
if (0 == result) {
// Some streams annoyingly return 0 result. We won't
// perpetuate this behaviour.
continue;
}
if (result == -1) {
closeCurrentStream();
continue;
}
return result;
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void close() {
queue.clear();
closeCurrentStream();
queue.offer(QUEUE_END);
notify();
}
/**
* Rather than force the InputStream closed we add the EOF at the end of
* any current data.
*/
synchronized void softClose() {
queue.offer(QUEUE_END);
notify();
}
/**
* Get the input stream for the current segment and optionally block until
* a segment is available.
*
* @param block If {@code true} then block until a segment is available.
* @return the InputStream
* @throws IOException if an io error occurs
*/
private InputStream getCurrentStream(boolean block) throws IOException {
if (currentMsgStream == null) {
if (QUEUE_END == queue.peek()) {
// We are at the end of the queue.
return null;
}
MessageElement me = null;
long pollUntil = (Long.MAX_VALUE == timeout) ? Long.MAX_VALUE : System.currentTimeMillis() + timeout;
while (pollUntil >= System.currentTimeMillis()) {
try {
me = queue.poll();
if (null == me) {
long sleepFor = pollUntil - System.currentTimeMillis();
if (sleepFor > 0) {
wait(sleepFor);
}
} else {
break;
}
} catch (InterruptedException woken) {
InterruptedIOException incomplete = new InterruptedIOException("Interrupted waiting for data.");
incomplete.initCause(woken);
incomplete.bytesTransferred = 0;
throw incomplete;
}
}
if (block && (null == me)) {
throw new SocketTimeoutException("Socket timeout during read.");
}
if (me != null) {
currentMsgStream = me.getStream();
}
}
return currentMsgStream;
}
private void closeCurrentStream() {
if (currentMsgStream != null) {
try {
currentMsgStream.close();
} catch (IOException ignored) {// ignored
}
currentMsgStream = null;
}
}
synchronized void enqueue(MessageElement element) {
if (queue.contains(QUEUE_END)) {
// We have already marked the end of the queue.
return;
}
if (queue.size() < queueSize) {
queue.offer(element);
}
notify();
}
/**
* Returns the timeout value for this socket. This is the amount of time in
* relative milliseconds which we will wait for read() operations to
* complete.
*
* @return The timeout value in milliseconds or 0 (zero) for
* infinite timeout.
*/
long getTimeout() {
if (timeout < Long.MAX_VALUE) {
return timeout;
} else {
return 0;
}
}
/**
* Returns the timeout value for this socket. This is the amount of time in
* relative milliseconds which we will wait for read() operations to
* operations to complete.
*
* @param timeout The timeout value in milliseconds or 0 (zero) for
* infinite timeout.
*/
void setTimeout(long timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout not allowed.");
}
if (0 == timeout) {
timeout = Long.MAX_VALUE;
}
this.timeout = timeout;
}
}