001package stdlib;
002import java.io.BufferedInputStream;
003import java.io.File;
004import java.io.FileInputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.net.Socket;
008import java.net.URISyntaxException;
009import java.net.URL;
010import java.net.URLConnection;
011
012/* ***********************************************************************
013 *  Compilation:  javac BinaryIn.java
014 *  Execution:    java BinaryIn input output
015 *
016 *  This library is for reading binary data from an input stream.
017 *
018 *  % java BinaryIn http://introcs.cs.princeton.edu/cover.jpg output.jpg
019 *
020 *************************************************************************/
021
022/**
023 *  <i>Binary input</i>. This class provides methods for reading
024 *  in bits from a binary input stream, either
025 *  one bit at a time (as a {@code boolean}),
026 *  8 bits at a time (as a {@code byte} or {@code char}),
027 *  16 bits at a time (as a {@code short}),
028 *  32 bits at a time (as an {@code int} or {@code float}), or
029 *  64 bits at a time (as a {@code double} or {@code long}).
030 *  <p>
031 *  The binary input stream can be from standard input, a filename,
032 *  a URL name, a Socket, or an InputStream.
033 *  <p>
034 *  All primitive types are assumed to be represented using their
035 *  standard Java representations, in big-endian (most significant
036 *  byte first) order.
037 *  <p>
038 *  The client should not intermix calls to {@code BinaryIn} with calls
039 *  to {@code In}; otherwise unexpected behavior will result.
040 */
041public final class BinaryIn {
042        private static final int EOF = -1;   // end of file
043
044        private BufferedInputStream in;      // the input stream
045        private int buffer;                  // one character buffer
046        private int N;                       // number of bits left in buffer
047
048        /**
049         * Create a binary input stream from standard input.
050         */
051        public BinaryIn() {
052                in = new BufferedInputStream(System.in);
053                fillBuffer();
054        }
055
056        /**
057         * Create a binary input stream from an InputStream.
058         */
059        public BinaryIn(InputStream is) {
060                in = new BufferedInputStream(is);
061                fillBuffer();
062        }
063
064        /**
065         * Create a binary input stream from a socket.
066         */
067        public BinaryIn(Socket socket) {
068                try {
069                        InputStream is = socket.getInputStream();
070                        in = new BufferedInputStream(is);
071                        fillBuffer();
072                }
073                catch (IOException ioe) {
074                        System.err.println("Could not open " + socket);
075                }
076        }
077
078        /**
079         * Create a binary input stream from a URL.
080         */
081        public BinaryIn(URL url) {
082                try {
083                        URLConnection site = url.openConnection();
084                        InputStream is     = site.getInputStream();
085                        in = new BufferedInputStream(is);
086                        fillBuffer();
087                }
088                catch (IOException ioe) {
089                        System.err.println("Could not open " + url);
090                }
091        }
092
093        /**
094         * Create a binary input stream from a filename or URL name.
095         */
096        public BinaryIn(String s) {
097
098                try {
099                        // first try to read file from local file system
100                        File file = new File(s);
101                        if (file.exists()) {
102                                FileInputStream fis = new FileInputStream(file);
103                                in = new BufferedInputStream(fis);
104                                fillBuffer();
105                                return;
106                        }
107
108                        // next try for files included in jar
109                        URL url = getClass().getResource(s);
110
111                        // or URL from web
112                        if (url == null) { url = new java.net.URI(s).toURL(); }
113
114                        URLConnection site = url.openConnection();
115                        InputStream is     = site.getInputStream();
116                        in = new BufferedInputStream(is);
117                        fillBuffer();
118                }
119                catch (IOException ioe) {
120                        System.err.println("Could not open " + s);
121                } catch (URISyntaxException e) {
122                        System.err.println("Could not open " + s);
123                }
124        }
125
126        private void fillBuffer() {
127                try { buffer = in.read(); N = 8; }
128                catch (IOException e) { System.err.println("EOF"); buffer = EOF; N = -1; }
129        }
130
131        /**
132         * Does the binary input stream exist?
133         */
134        public boolean exists()  {
135                return in != null;
136        }
137
138        /**
139         * Returns true if the binary input stream is empty.
140         * @return true if and only if the binary input stream is empty
141         */
142        public boolean isEmpty() {
143                return buffer == EOF;
144        }
145
146        /**
147         * Read the next bit of data from the binary input stream and return as a boolean.
148         * @return the next bit of data from the binary input stream as a {@code boolean}
149         * @throws RuntimeException if the input stream is empty
150         */
151        public boolean readBoolean() {
152                if (isEmpty()) throw new RuntimeException("Reading from empty input stream");
153                N--;
154                boolean bit = ((buffer >> N) & 1) == 1;
155                if (N == 0) fillBuffer();
156                return bit;
157        }
158
159        /**
160         * Read the next 8 bits from the binary input stream and return as an 8-bit char.
161         * @return the next 8 bits of data from the binary input stream as a {@code char}
162         * @throws RuntimeException if there are fewer than 8 bits available
163         */
164        public char readChar() {
165                if (isEmpty()) throw new RuntimeException("Reading from empty input stream");
166
167                // special case when aligned byte
168                if (N == 8) {
169                        int x = buffer;
170                        fillBuffer();
171                        return (char) (x & 0xff);
172                }
173
174                // combine last N bits of current buffer with first 8-N bits of new buffer
175                int x = buffer;
176                x <<= (8-N);
177                int oldN = N;
178                fillBuffer();
179                if (isEmpty()) throw new RuntimeException("Reading from empty input stream");
180                N = oldN;
181                x |= (buffer >>> N);
182                return (char) (x & 0xff);
183                // the above code doesn't quite work for the last character if N = 8
184                // because buffer will be -1
185        }
186
187
188        /**
189         * Read the next r bits from the binary input stream and return as an r-bit character.
190         * @param r number of bits to read.
191         * @return the next r bits of data from the binary input streamt as a {@code char}
192         * @throws RuntimeException if there are fewer than r bits available
193         */
194        public char readChar(int r) {
195                if (r < 1 || r > 16) throw new RuntimeException("Illegal value of r = " + r);
196
197                // optimize r = 8 case
198                if (r == 8) return readChar();
199
200                char x = 0;
201                for (int i = 0; i < r; i++) {
202                        x <<= 1;
203                        boolean bit = readBoolean();
204                        if (bit) x |= 1;
205                }
206                return x;
207        }
208
209
210        /**
211         * Read the remaining bytes of data from the binary input stream and return as a string.
212         * @return the remaining bytes of data from the binary input stream as a {@code String}
213         * @throws RuntimeException if the input stream is empty or if the number of bits
214         * available is not a multiple of 8 (byte-aligned)
215         */
216        public String readString() {
217                if (isEmpty()) throw new RuntimeException("Reading from empty input stream");
218
219                StringBuilder sb = new StringBuilder();
220                while (!isEmpty()) {
221                        char c = readChar();
222                        sb.append(c);
223                }
224                return sb.toString();
225        }
226
227
228        /**
229         * Read the next 16 bits from the binary input stream and return as a 16-bit short.
230         * @return the next 16 bits of data from the binary standard input as a {@code short}
231         * @throws RuntimeException if there are fewer than 16 bits available
232         */
233        public short readShort() {
234                short x = 0;
235                for (int i = 0; i < 2; i++) {
236                        char c = readChar();
237                        x <<= 8;
238                        x |= c;
239                }
240                return x;
241        }
242
243        /**
244         * Read the next 32 bits from the binary input stream and return as a 32-bit int.
245         * @return the next 32 bits of data from the binary input stream as a {@code int}
246         * @throws RuntimeException if there are fewer than 32 bits available
247         */
248        public int readInt() {
249                int x = 0;
250                for (int i = 0; i < 4; i++) {
251                        char c = readChar();
252                        x <<= 8;
253                        x |= c;
254                }
255                return x;
256        }
257
258        /**
259         * Read the next r bits from the binary input stream return as an r-bit int.
260         * @param r number of bits to read.
261         * @return the next r bits of data from the binary input stream as a {@code int}
262         * @throws RuntimeException if there are fewer than r bits available on standard input
263         */
264        public int readInt(int r) {
265                if (r < 1 || r > 32) throw new RuntimeException("Illegal value of r = " + r);
266
267                // optimize r = 32 case
268                if (r == 32) return readInt();
269
270                int x = 0;
271                for (int i = 0; i < r; i++) {
272                        x <<= 1;
273                        boolean bit = readBoolean();
274                        if (bit) x |= 1;
275                }
276                return x;
277        }
278
279        /**
280         * Read the next 64 bits from the binary input stream and return as a 64-bit long.
281         * @return the next 64 bits of data from the binary input stream as a {@code long}
282         * @throws RuntimeException if there are fewer than 64 bits available
283         */
284        public long readLong() {
285                long x = 0;
286                for (int i = 0; i < 8; i++) {
287                        char c = readChar();
288                        x <<= 8;
289                        x |= c;
290                }
291                return x;
292        }
293
294        /**
295         * Read the next 64 bits from the binary input stream and return as a 64-bit double.
296         * @return the next 64 bits of data from the binary input stream as a {@code double}
297         * @throws RuntimeException if there are fewer than 64 bits available
298         */
299        public double readDouble() {
300                return Double.longBitsToDouble(readLong());
301        }
302
303        /**
304         * Read the next 32 bits from standard input and return as a 32-bit float.
305         * @return the next 32 bits of data from standard input as a {@code float}
306         * @throws RuntimeException if there are fewer than 32 bits available on standard input
307         */
308        public float readFloat() {
309                return Float.intBitsToFloat(readInt());
310        }
311
312
313        /**
314         * Read the next 8 bits from the binary input stream and return as an 8-bit byte.
315         * @return the next 8 bits of data from the binary input stream as a {@code byte}
316         * @throws RuntimeException if there are fewer than 8 bits available
317         */
318        public byte readByte() {
319                char c = readChar();
320                byte x = (byte) (c & 0xff);
321                return x;
322        }
323
324        /**
325         * Test client. Reads in the name of a file or url (first command-line
326         * argument) and writes it to a file (second command-line argument).
327         */
328        public static void main(String[] args) {
329                BinaryIn  in  = new BinaryIn(args[0]);
330                BinaryOut out = new BinaryOut(args[1]);
331
332                // read one 8-bit char at a time
333                while (!in.isEmpty()) {
334                        char c = in.readChar();
335                        out.write(c);
336                }
337                out.flush();
338        }
339}