1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.io;
18
19import java.io.ByteArrayOutputStream;
20import java.io.EOFException;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.OutputStream;
24import java.io.Reader;
25import java.io.StringWriter;
26import java.util.Arrays;
27import java.util.concurrent.atomic.AtomicReference;
28
29public final class Streams {
30    private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
31
32    private Streams() {}
33
34    /**
35     * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int).
36     * InputStream assumes that you implement InputStream.read(int) and provides default
37     * implementations of the others, but often the opposite is more efficient.
38     */
39    public static int readSingleByte(InputStream in) throws IOException {
40        byte[] buffer = new byte[1];
41        int result = in.read(buffer, 0, 1);
42        return (result != -1) ? buffer[0] & 0xff : -1;
43    }
44
45    /**
46     * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int).
47     * OutputStream assumes that you implement OutputStream.write(int) and provides default
48     * implementations of the others, but often the opposite is more efficient.
49     */
50    public static void writeSingleByte(OutputStream out, int b) throws IOException {
51        byte[] buffer = new byte[1];
52        buffer[0] = (byte) (b & 0xff);
53        out.write(buffer);
54    }
55
56    /**
57     * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available.
58     */
59    public static void readFully(InputStream in, byte[] dst) throws IOException {
60        readFully(in, dst, 0, dst.length);
61    }
62
63    /**
64     * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws
65     * EOFException if insufficient bytes are available.
66     *
67     * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}.
68     */
69    public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) throws IOException {
70        if (byteCount == 0) {
71            return;
72        }
73        if (in == null) {
74            throw new NullPointerException("in == null");
75        }
76        if (dst == null) {
77            throw new NullPointerException("dst == null");
78        }
79        Arrays.checkOffsetAndCount(dst.length, offset, byteCount);
80        while (byteCount > 0) {
81            int bytesRead = in.read(dst, offset, byteCount);
82            if (bytesRead < 0) {
83                throw new EOFException();
84            }
85            offset += bytesRead;
86            byteCount -= bytesRead;
87        }
88    }
89
90    /**
91     * Returns a byte[] containing the remainder of 'in', closing it when done.
92     */
93    public static byte[] readFully(InputStream in) throws IOException {
94        try {
95            return readFullyNoClose(in);
96        } finally {
97            in.close();
98        }
99    }
100
101    /**
102     * Returns a byte[] containing the remainder of 'in'.
103     */
104    public static byte[] readFullyNoClose(InputStream in) throws IOException {
105        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
106        byte[] buffer = new byte[1024];
107        int count;
108        while ((count = in.read(buffer)) != -1) {
109            bytes.write(buffer, 0, count);
110        }
111        return bytes.toByteArray();
112    }
113
114    /**
115     * Returns the remainder of 'reader' as a string, closing it when done.
116     */
117    public static String readFully(Reader reader) throws IOException {
118        try {
119            StringWriter writer = new StringWriter();
120            char[] buffer = new char[1024];
121            int count;
122            while ((count = reader.read(buffer)) != -1) {
123                writer.write(buffer, 0, count);
124            }
125            return writer.toString();
126        } finally {
127            reader.close();
128        }
129    }
130
131    public static void skipAll(InputStream in) throws IOException {
132        do {
133            in.skip(Long.MAX_VALUE);
134        } while (in.read() != -1);
135    }
136
137    /**
138     * Skip <b>at most</b> {@code byteCount} bytes from {@code in} by calling read
139     * repeatedly until either the stream is exhausted or we read fewer bytes than
140     * we ask for.
141     *
142     * <p>This method reuses the skip buffer but is careful to never use it at
143     * the same time that another stream is using it. Otherwise streams that use
144     * the caller's buffer for consistency checks like CRC could be clobbered by
145     * other threads. A thread-local buffer is also insufficient because some
146     * streams may call other streams in their skip() method, also clobbering the
147     * buffer.
148     */
149    public static long skipByReading(InputStream in, long byteCount) throws IOException {
150        // acquire the shared skip buffer.
151        byte[] buffer = skipBuffer.getAndSet(null);
152        if (buffer == null) {
153            buffer = new byte[4096];
154        }
155
156        long skipped = 0;
157        while (skipped < byteCount) {
158            int toRead = (int) Math.min(byteCount - skipped, buffer.length);
159            int read = in.read(buffer, 0, toRead);
160            if (read == -1) {
161                break;
162            }
163            skipped += read;
164            if (read < toRead) {
165                break;
166            }
167        }
168
169        // release the shared skip buffer.
170        skipBuffer.set(buffer);
171
172        return skipped;
173    }
174
175    /**
176     * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
177     * Returns the total number of bytes transferred.
178     */
179    public static int copy(InputStream in, OutputStream out) throws IOException {
180        int total = 0;
181        byte[] buffer = new byte[8192];
182        int c;
183        while ((c = in.read(buffer)) != -1) {
184            total += c;
185            out.write(buffer, 0, c);
186        }
187        return total;
188    }
189
190    /**
191     * Returns the ASCII characters up to but not including the next "\r\n", or
192     * "\n".
193     *
194     * @throws java.io.EOFException if the stream is exhausted before the next newline
195     *     character.
196     */
197    public static String readAsciiLine(InputStream in) throws IOException {
198        // TODO: support UTF-8 here instead
199
200        StringBuilder result = new StringBuilder(80);
201        while (true) {
202            int c = in.read();
203            if (c == -1) {
204                throw new EOFException();
205            } else if (c == '\n') {
206                break;
207            }
208
209            result.append((char) c);
210        }
211        int length = result.length();
212        if (length > 0 && result.charAt(length - 1) == '\r') {
213            result.setLength(length - 1);
214        }
215        return result.toString();
216    }
217}
218