/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.nio.channels;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.channels.spi.AbstractInterruptibleChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import libcore.io.Streams;
/**
* This class provides several utilities to get I/O streams from channels.
*/
public final class Channels {
private Channels() {}
/**
* Returns an input stream on the given channel. The resulting stream has
* the following properties:
* <ul>
* <li>If the stream is closed, then the underlying channel is closed as
* well.</li>
* <li>It is thread safe.</li>
* <li>It throws an {@link IllegalBlockingModeException} if the channel is
* in non-blocking mode and {@code read} is called.</li>
* <li>Neither {@code mark} nor {@code reset} is supported.</li>
* <li>It is not buffered.</li>
* </ul>
*
* @param channel
* the channel to be wrapped by an InputStream.
* @return an InputStream that takes bytes from the given byte channel.
*/
public static InputStream newInputStream(ReadableByteChannel channel) {
return new ChannelInputStream(channel);
}
/**
* Returns an output stream on the given channel. The resulting stream has
* the following properties:
* <ul>
* <li>If the stream is closed, then the underlying channel is closed as
* well.</li>
* <li>It is thread safe.</li>
* <li>It throws an {@link IllegalBlockingModeException} if the channel is
* in non-blocking mode and {@code write} is called.</li>
* <li>It is not buffered.</li>
* </ul>
*
* @param channel
* the channel to be wrapped by an OutputStream.
* @return an OutputStream that puts bytes onto the given byte channel.
*/
public static OutputStream newOutputStream(WritableByteChannel channel) {
return new ChannelOutputStream(channel);
}
/**
* Returns a readable channel on the given input stream. The resulting
* channel has the following properties:
* <ul>
* <li>If the channel is closed, then the underlying stream is closed as
* well.</li>
* <li>It is not buffered.</li>
* </ul>
*
* @param inputStream
* the stream to be wrapped by a byte channel.
* @return a byte channel that reads bytes from the input stream.
*/
public static ReadableByteChannel newChannel(InputStream inputStream) {
return new InputStreamChannel(inputStream);
}
/**
* Returns a writable channel on the given output stream.
*
* The resulting channel has following properties:
* <ul>
* <li>If the channel is closed, then the underlying stream is closed as
* well.</li>
* <li>It is not buffered.</li>
* </ul>
*
* @param outputStream
* the stream to be wrapped by a byte channel.
* @return a byte channel that writes bytes to the output stream.
*/
public static WritableByteChannel newChannel(OutputStream outputStream) {
return new OutputStreamChannel(outputStream);
}
/**
* Returns a reader that decodes bytes from a channel.
*
* @param channel
* the Channel to be read.
* @param decoder
* the Charset decoder to be used.
* @param minBufferCapacity
* The minimum size of the byte buffer, -1 means to use the
* default size.
* @return the reader.
*/
public static Reader newReader(ReadableByteChannel channel,
CharsetDecoder decoder, int minBufferCapacity) {
/*
* This method doesn't honor minBufferCapacity. Ignoring that parameter
* saves us from having to add a hidden constructor to InputStreamReader.
*/
return new InputStreamReader(new ChannelInputStream(channel), decoder);
}
/**
* Returns a reader that decodes bytes from a channel. This method creates a
* reader with a buffer of default size.
*
* @param channel
* the Channel to be read.
* @param charsetName
* the name of the charset.
* @return the reader.
* @throws java.nio.charset.UnsupportedCharsetException
* if the given charset name is not supported.
*/
public static Reader newReader(ReadableByteChannel channel,
String charsetName) {
if (charsetName == null) {
throw new NullPointerException("charsetName == null");
}
return newReader(channel, Charset.forName(charsetName).newDecoder(), -1);
}
/**
* Returns a writer that encodes characters with the specified
* {@code encoder} and sends the bytes to the specified channel.
*
* @param channel
* the Channel to write to.
* @param encoder
* the CharsetEncoder to be used.
* @param minBufferCapacity
* the minimum size of the byte buffer, -1 means to use the
* default size.
* @return the writer.
*/
public static Writer newWriter(WritableByteChannel channel,
CharsetEncoder encoder, int minBufferCapacity) {
/*
* This method doesn't honor minBufferCapacity. Ignoring that parameter
* saves us from having to add a hidden constructor to OutputStreamWriter.
*/
return new OutputStreamWriter(new ChannelOutputStream(channel), encoder);
}
/**
* Returns a writer that encodes characters with the specified
* {@code encoder} and sends the bytes to the specified channel. This method
* creates a writer with a buffer of default size.
*
* @param channel
* the Channel to be written to.
* @param charsetName
* the name of the charset.
* @return the writer.
* @throws java.nio.charset.UnsupportedCharsetException
* if the given charset name is not supported.
*/
public static Writer newWriter(WritableByteChannel channel,
String charsetName) {
if (charsetName == null) {
throw new NullPointerException("charsetName == null");
}
return newWriter(channel, Charset.forName(charsetName).newEncoder(), -1);
}
/**
* An input stream that delegates to a readable channel.
*/
private static class ChannelInputStream extends InputStream {
private final ReadableByteChannel channel;
ChannelInputStream(ReadableByteChannel channel) {
if (channel == null) {
throw new NullPointerException("channel == null");
}
this.channel = channel;
}
@Override public synchronized int read() throws IOException {
return Streams.readSingleByte(this);
}
@Override public synchronized int read(byte[] target, int byteOffset, int byteCount) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(target, byteOffset, byteCount);
checkBlocking(channel);
return channel.read(buffer);
}
@Override public int available() throws IOException {
if (channel instanceof FileChannel) {
FileChannel fileChannel = (FileChannel) channel;
long result = fileChannel.size() - fileChannel.position();
return result > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) result;
} else {
return super.available();
}
}
@Override
public synchronized void close() throws IOException {
channel.close();
}
}
/**
* An output stream that delegates to a writable channel.
*/
private static class ChannelOutputStream extends OutputStream {
private final WritableByteChannel channel;
ChannelOutputStream(WritableByteChannel channel) {
if (channel == null) {
throw new NullPointerException("channel == null");
}
this.channel = channel;
}
@Override
public synchronized void write(int oneByte) throws IOException {
byte[] wrappedByte = { (byte) oneByte };
write(wrappedByte);
}
@Override
public synchronized void write(byte[] source, int offset, int length) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(source, offset, length);
checkBlocking(channel);
int total = 0;
while (total < length) {
total += channel.write(buffer);
}
}
@Override
public synchronized void close() throws IOException {
channel.close();
}
}
static void checkBlocking(Channel channel) {
if (channel instanceof SelectableChannel && !((SelectableChannel) channel).isBlocking()) {
throw new IllegalBlockingModeException();
}
}
/**
* A readable channel that delegates to an input stream.
*/
private static class InputStreamChannel extends AbstractInterruptibleChannel
implements ReadableByteChannel {
private final InputStream inputStream;
InputStreamChannel(InputStream inputStream) {
if (inputStream == null) {
throw new NullPointerException("inputStream == null");
}
this.inputStream = inputStream;
}
public synchronized int read(ByteBuffer target) throws IOException {
if (!isOpen()) {
throw new ClosedChannelException();
}
int bytesRemain = target.remaining();
byte[] bytes = new byte[bytesRemain];
int readCount = 0;
try {
begin();
readCount = inputStream.read(bytes);
} finally {
end(readCount >= 0);
}
if (readCount > 0) {
target.put(bytes, 0, readCount);
}
return readCount;
}
@Override
protected void implCloseChannel() throws IOException {
inputStream.close();
}
}
/**
* A writable channel that delegates to an output stream.
*/
private static class OutputStreamChannel extends AbstractInterruptibleChannel
implements WritableByteChannel {
private final OutputStream outputStream;
OutputStreamChannel(OutputStream outputStream) {
if (outputStream == null) {
throw new NullPointerException("outputStream == null");
}
this.outputStream = outputStream;
}
public synchronized int write(ByteBuffer source) throws IOException {
if (!isOpen()) {
throw new ClosedChannelException();
}
int bytesRemain = source.remaining();
if (bytesRemain == 0) {
return 0;
}
byte[] buf = new byte[bytesRemain];
source.get(buf);
try {
begin();
outputStream.write(buf, 0, bytesRemain);
} finally {
end(bytesRemain >= 0);
}
return bytesRemain;
}
@Override
protected void implCloseChannel() throws IOException {
outputStream.close();
}
}
}