/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.io;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
/**
* Readable byte channel wrapping an input stream.
*
* @author Jerome Louvel
*/
public class InputStreamChannel implements ReadableByteChannel {
/** The underlying input stream. */
private final InputStream inputStream;
/** Indicates if the channel is blocking. */
private final boolean blocking;
/** Optional byte array buffer. */
private volatile byte buffer[] = new byte[0];
/** Indicates if the underlying stream is still open. */
private volatile boolean open;
/**
* Constructor.
*
* @param inputStream
* @throws IOException
*/
public InputStreamChannel(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
this.open = true;
this.blocking = (inputStream.available() <= 0);
}
/**
* Closes the underlying input stream.
*/
public void close() throws IOException {
getInputStream().close();
this.open = false;
}
/**
* Returns the underlying input stream.
*
* @return The underlying input stream.
*/
protected InputStream getInputStream() {
return inputStream;
}
/**
* True if the underlying input stream is able to indicate available bytes
* upfront.
*
* @return True if the channel is blocking.
*/
public boolean isBlocking() {
return this.blocking;
}
/**
* Indicates if the channel and its underlying stream are open.
*
* @return True if the channel and its underlying stream are open.
*/
public boolean isOpen() {
return this.open;
}
/**
* Reads bytes from the underlying stream to the target buffer.
*
* @param target
* The target byte buffer.
* @return The number of bytes read.
*/
public int read(ByteBuffer target) throws IOException {
int readLength = 0;
if (isBlocking()) {
// Potentially blocking read
readLength = IoUtils.BUFFER_SIZE;
} else {
int available = getInputStream().available();
if (available > 0) {
// Attempt to read only the available byte to prevent blocking
readLength = Math.min(available, target.remaining());
} else {
// Attempt to read as many bytes as possible even if blocking
// occurs
readLength = target.remaining();
}
}
// Create or reuse a specific byte array as buffer
return read(target, readLength);
}
/**
* Reads a given number of bytes into a target byte buffer.
*
* @param target
* The target byte buffer.
* @param readLength
* The maximum number of bytes to read.
* @return The number of bytes effectively read or -1 if end reached.
* @throws IOException
*/
private int read(ByteBuffer target, int readLength) throws IOException {
int result = 0;
if (target.hasArray()) {
// Use directly the underlying byte array
byte[] byteArray = target.array();
result = getInputStream().read(byteArray, target.position(),
Math.min(readLength, target.remaining()));
if (result > 0) {
target.position(target.position() + result);
}
} else {
if (this.buffer.length < IoUtils.BUFFER_SIZE) {
this.buffer = new byte[IoUtils.BUFFER_SIZE];
}
result = getInputStream().read(
this.buffer,
0,
Math.min(Math.min(readLength, IoUtils.BUFFER_SIZE),
target.remaining()));
if (result > 0) {
target.put(buffer, 0, result);
}
}
return result;
}
}