1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.nio.channels;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.InputStreamReader;
23import java.io.OutputStream;
24import java.io.OutputStreamWriter;
25import java.io.Reader;
26import java.io.Writer;
27import java.nio.ByteBuffer;
28import java.nio.channels.spi.AbstractInterruptibleChannel;
29import java.nio.charset.Charset;
30import java.nio.charset.CharsetDecoder;
31import java.nio.charset.CharsetEncoder;
32import libcore.io.Streams;
33
34/**
35 * This class provides several utilities to get I/O streams from channels.
36 */
37public final class Channels {
38
39    private Channels() {}
40
41    /**
42     * Returns an input stream on the given channel. The resulting stream has
43     * the following properties:
44     * <ul>
45     * <li>If the stream is closed, then the underlying channel is closed as
46     * well.</li>
47     * <li>It is thread safe.</li>
48     * <li>It throws an {@link IllegalBlockingModeException} if the channel is
49     * in non-blocking mode and {@code read} is called.</li>
50     * <li>Neither {@code mark} nor {@code reset} is supported.</li>
51     * <li>It is not buffered.</li>
52     * </ul>
53     *
54     * @param channel
55     *            the channel to be wrapped by an InputStream.
56     * @return an InputStream that takes bytes from the given byte channel.
57     */
58    public static InputStream newInputStream(ReadableByteChannel channel) {
59        return new ChannelInputStream(channel);
60    }
61
62    /**
63     * Returns an output stream on the given channel. The resulting stream has
64     * the following properties:
65     * <ul>
66     * <li>If the stream is closed, then the underlying channel is closed as
67     * well.</li>
68     * <li>It is thread safe.</li>
69     * <li>It throws an {@link IllegalBlockingModeException} if the channel is
70     * in non-blocking mode and {@code write} is called.</li>
71     * <li>It is not buffered.</li>
72     * </ul>
73     *
74     * @param channel
75     *            the channel to be wrapped by an OutputStream.
76     * @return an OutputStream that puts bytes onto the given byte channel.
77     */
78    public static OutputStream newOutputStream(WritableByteChannel channel) {
79        return new ChannelOutputStream(channel);
80    }
81
82    /**
83     * Returns a readable channel on the given input stream. The resulting
84     * channel has the following properties:
85     * <ul>
86     * <li>If the channel is closed, then the underlying stream is closed as
87     * well.</li>
88     * <li>It is not buffered.</li>
89     * </ul>
90     *
91     * @param inputStream
92     *            the stream to be wrapped by a byte channel.
93     * @return a byte channel that reads bytes from the input stream.
94     */
95    public static ReadableByteChannel newChannel(InputStream inputStream) {
96        return new InputStreamChannel(inputStream);
97    }
98
99    /**
100     * Returns a writable channel on the given output stream.
101     *
102     * The resulting channel has following properties:
103     * <ul>
104     * <li>If the channel is closed, then the underlying stream is closed as
105     * well.</li>
106     * <li>It is not buffered.</li>
107     * </ul>
108     *
109     * @param outputStream
110     *            the stream to be wrapped by a byte channel.
111     * @return a byte channel that writes bytes to the output stream.
112     */
113    public static WritableByteChannel newChannel(OutputStream outputStream) {
114        return new OutputStreamChannel(outputStream);
115    }
116
117    /**
118     * Returns a reader that decodes bytes from a channel.
119     *
120     * @param channel
121     *            the Channel to be read.
122     * @param decoder
123     *            the Charset decoder to be used.
124     * @param minBufferCapacity
125     *            The minimum size of the byte buffer, -1 means to use the
126     *            default size.
127     * @return the reader.
128     */
129    public static Reader newReader(ReadableByteChannel channel,
130            CharsetDecoder decoder, int minBufferCapacity) {
131        /*
132         * This method doesn't honor minBufferCapacity. Ignoring that parameter
133         * saves us from having to add a hidden constructor to InputStreamReader.
134         */
135        return new InputStreamReader(new ChannelInputStream(channel), decoder);
136    }
137
138    /**
139     * Returns a reader that decodes bytes from a channel. This method creates a
140     * reader with a buffer of default size.
141     *
142     * @param channel
143     *            the Channel to be read.
144     * @param charsetName
145     *            the name of the charset.
146     * @return the reader.
147     * @throws java.nio.charset.UnsupportedCharsetException
148     *             if the given charset name is not supported.
149     */
150    public static Reader newReader(ReadableByteChannel channel,
151            String charsetName) {
152        if (charsetName == null) {
153            throw new NullPointerException("charsetName == null");
154        }
155        return newReader(channel, Charset.forName(charsetName).newDecoder(), -1);
156    }
157
158    /**
159     * Returns a writer that encodes characters with the specified
160     * {@code encoder} and sends the bytes to the specified channel.
161     *
162     * @param channel
163     *            the Channel to write to.
164     * @param encoder
165     *            the CharsetEncoder to be used.
166     * @param minBufferCapacity
167     *            the minimum size of the byte buffer, -1 means to use the
168     *            default size.
169     * @return the writer.
170     */
171    public static Writer newWriter(WritableByteChannel channel,
172            CharsetEncoder encoder, int minBufferCapacity) {
173        /*
174         * This method doesn't honor minBufferCapacity. Ignoring that parameter
175         * saves us from having to add a hidden constructor to OutputStreamWriter.
176         */
177        return new OutputStreamWriter(new ChannelOutputStream(channel), encoder);
178    }
179
180    /**
181     * Returns a writer that encodes characters with the specified
182     * {@code encoder} and sends the bytes to the specified channel. This method
183     * creates a writer with a buffer of default size.
184     *
185     * @param channel
186     *            the Channel to be written to.
187     * @param charsetName
188     *            the name of the charset.
189     * @return the writer.
190     * @throws java.nio.charset.UnsupportedCharsetException
191     *             if the given charset name is not supported.
192     */
193    public static Writer newWriter(WritableByteChannel channel,
194            String charsetName) {
195        if (charsetName == null) {
196            throw new NullPointerException("charsetName == null");
197        }
198        return newWriter(channel, Charset.forName(charsetName).newEncoder(), -1);
199    }
200
201    /**
202     * An input stream that delegates to a readable channel.
203     */
204    private static class ChannelInputStream extends InputStream {
205
206        private final ReadableByteChannel channel;
207
208        ChannelInputStream(ReadableByteChannel channel) {
209            if (channel == null) {
210                throw new NullPointerException("channel == null");
211            }
212            this.channel = channel;
213        }
214
215        @Override public synchronized int read() throws IOException {
216            return Streams.readSingleByte(this);
217        }
218
219        @Override public synchronized int read(byte[] target, int byteOffset, int byteCount) throws IOException {
220            ByteBuffer buffer = ByteBuffer.wrap(target, byteOffset, byteCount);
221            checkBlocking(channel);
222            return channel.read(buffer);
223        }
224
225        @Override public int available() throws IOException {
226            if (channel instanceof FileChannel) {
227                FileChannel fileChannel = (FileChannel) channel;
228                long result = fileChannel.size() - fileChannel.position();
229                return result > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) result;
230            } else {
231                return super.available();
232            }
233        }
234
235        @Override
236        public synchronized void close() throws IOException {
237            channel.close();
238        }
239    }
240
241    /**
242     * An output stream that delegates to a writable channel.
243     */
244    private static class ChannelOutputStream extends OutputStream {
245
246        private final WritableByteChannel channel;
247
248        ChannelOutputStream(WritableByteChannel channel) {
249            if (channel == null) {
250                throw new NullPointerException("channel == null");
251            }
252            this.channel = channel;
253        }
254
255        @Override
256        public synchronized void write(int oneByte) throws IOException {
257            byte[] wrappedByte = { (byte) oneByte };
258            write(wrappedByte);
259        }
260
261        @Override
262        public synchronized void write(byte[] source, int offset, int length) throws IOException {
263            ByteBuffer buffer = ByteBuffer.wrap(source, offset, length);
264            checkBlocking(channel);
265            int total = 0;
266            while (total < length) {
267                total += channel.write(buffer);
268            }
269        }
270
271        @Override
272        public synchronized void close() throws IOException {
273            channel.close();
274        }
275    }
276
277    static void checkBlocking(Channel channel) {
278        if (channel instanceof SelectableChannel && !((SelectableChannel) channel).isBlocking()) {
279            throw new IllegalBlockingModeException();
280        }
281    }
282
283    /**
284     * A readable channel that delegates to an input stream.
285     */
286    private static class InputStreamChannel extends AbstractInterruptibleChannel
287            implements ReadableByteChannel {
288        private final InputStream inputStream;
289
290        InputStreamChannel(InputStream inputStream) {
291            if (inputStream == null) {
292                throw new NullPointerException("inputStream == null");
293            }
294            this.inputStream = inputStream;
295        }
296
297        public synchronized int read(ByteBuffer target) throws IOException {
298            if (!isOpen()) {
299                throw new ClosedChannelException();
300            }
301            int bytesRemain = target.remaining();
302            byte[] bytes = new byte[bytesRemain];
303            int readCount = 0;
304            try {
305                begin();
306                readCount = inputStream.read(bytes);
307            } finally {
308                end(readCount >= 0);
309            }
310            if (readCount > 0) {
311                target.put(bytes, 0, readCount);
312            }
313            return readCount;
314        }
315
316        @Override
317        protected void implCloseChannel() throws IOException {
318            inputStream.close();
319        }
320    }
321
322    /**
323     * A writable channel that delegates to an output stream.
324     */
325    private static class OutputStreamChannel extends AbstractInterruptibleChannel
326            implements WritableByteChannel {
327        private final OutputStream outputStream;
328
329        OutputStreamChannel(OutputStream outputStream) {
330            if (outputStream == null) {
331                throw new NullPointerException("outputStream == null");
332            }
333            this.outputStream = outputStream;
334        }
335
336        public synchronized int write(ByteBuffer source) throws IOException {
337            if (!isOpen()) {
338                throw new ClosedChannelException();
339            }
340            int bytesRemain = source.remaining();
341            if (bytesRemain == 0) {
342                return 0;
343            }
344            byte[] buf = new byte[bytesRemain];
345            source.get(buf);
346            try {
347                begin();
348                outputStream.write(buf, 0, bytesRemain);
349            } finally {
350                end(bytesRemain >= 0);
351            }
352            return bytesRemain;
353        }
354
355        @Override
356        protected void implCloseChannel() throws IOException {
357            outputStream.close();
358        }
359    }
360}
361