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