001package stdlib;
002import java.io.BufferedOutputStream;
003import java.io.FileOutputStream;
004import java.io.IOException;
005import java.io.OutputStream;
006import java.net.Socket;
007
008/* ***********************************************************************
009 *  Compilation:  javac BinaryOut.java
010 *  Execution:    java BinaryOut
011 *
012 *  Write binary data to an output stream, either one 1-bit boolean,
013 *  one 8-bit char, one 32-bit int, one 64-bit double, one 32-bit float,
014 *  or one 64-bit long at a time. The output stream can be standard
015 *  output, a file, an OutputStream or a Socket.
016 *
017 *  The bytes written are not aligned.
018 *
019 *  [wayne 7.17.2013] fixed bugs in write(char x, int r) and
020 *  write(int x, int r) to add return statement for (r == 8)
021 *  and (r == 32) cases, respectively.
022 *
023 *************************************************************************/
024
025/**
026 *  <i>Binary output</i>. This class provides methods for converting
027 *  primtive type variables ({@code boolean}, {@code byte}, {@code char},
028 *  {@code int}, {@code long}, {@code float}, and {@code double})
029 *  to sequences of bits and writing them to an output stream.
030 *  The output stream can be standard output, a file, an OutputStream or a Socket.
031 *  Uses big-endian (most-significant byte first).
032 *  <p>
033 *  The client must {@code flush()} the output stream when finished writing bits.
034 *  <p>
035 *  The client should not intermixing calls to {@code BinaryOut} with calls
036 *  to {@code Out}; otherwise unexpected behavior will result.
037 */
038public final class BinaryOut {
039
040        private BufferedOutputStream out;  // the output stream
041        private int buffer;                // 8-bit buffer of bits to write out
042        private int N;                     // number of bits remaining in buffer
043
044
045        /**
046         * Create a binary output stream from an OutputStream.
047         */
048        public BinaryOut(OutputStream os) {
049                out = new BufferedOutputStream(os);
050        }
051
052        /**
053         * Create a binary output stream from standard output.
054         */
055        public BinaryOut() {
056                out = new BufferedOutputStream(System.out);
057        }
058
059        /**
060         * Create a binary output stream from a filename.
061         */
062        public BinaryOut(String s) {
063                try {
064                        OutputStream os = new FileOutputStream(s);
065                        out = new BufferedOutputStream(os);
066                }
067                catch (IOException e) { e.printStackTrace(); }
068        }
069
070        /**
071         * Create a binary output stream from a Socket.
072         */
073        public BinaryOut(Socket socket) {
074                try {
075                        OutputStream os = socket.getOutputStream();
076                        out = new BufferedOutputStream(os);
077                }
078                catch (IOException e) { e.printStackTrace(); }
079        }
080
081
082        /**
083         * Write the specified bit to the binary output stream.
084         */
085        private void writeBit(boolean bit) {
086                // add bit to buffer
087                buffer <<= 1;
088                if (bit) buffer |= 1;
089
090                // if buffer is full (8 bits), write out as a single byte
091                N++;
092                if (N == 8) clearBuffer();
093        }
094
095        /**
096         * Write the 8-bit byte to the binary output stream.
097         */
098        private void writeByte(int x) {
099                assert x >= 0 && x < 256;
100
101                // optimized if byte-aligned
102                if (N == 0) {
103                        try { out.write(x); }
104                        catch (IOException e) { e.printStackTrace(); }
105                        return;
106                }
107
108                // otherwise write one bit at a time
109                for (int i = 0; i < 8; i++) {
110                        boolean bit = ((x >>> (8 - i - 1)) & 1) == 1;
111                        writeBit(bit);
112                }
113        }
114
115        // write out any remaining bits in buffer to the binary output stream, padding with 0s
116        private void clearBuffer() {
117                if (N == 0) return;
118                if (N > 0) buffer <<= (8 - N);
119                try { out.write(buffer); }
120                catch (IOException e) { e.printStackTrace(); }
121                N = 0;
122                buffer = 0;
123        }
124
125        /**
126         * Flush the binary output stream, padding 0s if number of bits written so far
127         * is not a multiple of 8.
128         */
129        public void flush() {
130                clearBuffer();
131                try { out.flush(); }
132                catch (IOException e) { e.printStackTrace(); }
133        }
134
135        /**
136         * Close and flush the binary output stream. Once it is closed, you can no longer write bits.
137         */
138        public void close() {
139                flush();
140                try { out.close(); }
141                catch (IOException e) { e.printStackTrace(); }
142        }
143
144
145        /**
146         * Write the specified bit to the binary output stream.
147         * @param x the {@code boolean} to write.
148         */
149        public void write(boolean x) {
150                writeBit(x);
151        }
152
153        /**
154         * Write the 8-bit byte to the binary output stream.
155         * @param x the {@code byte} to write.
156         */
157        public void write(byte x) {
158                writeByte(x & 0xff);
159        }
160
161        /**
162         * Write the 32-bit int to the binary output stream.
163         * @param x the {@code int} to write.
164         */
165        public void write(int x) {
166                writeByte((x >>> 24) & 0xff);
167                writeByte((x >>> 16) & 0xff);
168                writeByte((x >>>  8) & 0xff);
169                writeByte((x >>>  0) & 0xff);
170        }
171
172        /**
173         * Write the r-bit int to the binary output stream.
174         * @param x the {@code int} to write.
175         * @param r the number of relevant bits in the char.
176         * @throws RuntimeException if {@code r} is not between 1 and 32.
177         * @throws RuntimeException if {@code x} is not between 0 and 2<sup>r</sup> - 1.
178         */
179        public void write(int x, int r) {
180                if (r == 32) { write(x); return; }
181                if (r < 1 || r > 32)        throw new RuntimeException("Illegal value for r = " + r);
182                if (x < 0 || x >= (1 << r)) throw new RuntimeException("Illegal " + r + "-bit char = " + x);
183                for (int i = 0; i < r; i++) {
184                        boolean bit = ((x >>> (r - i - 1)) & 1) == 1;
185                        writeBit(bit);
186                }
187        }
188
189
190        /**
191         * Write the 64-bit double to the binary output stream.
192         * @param x the {@code double} to write.
193         */
194        public void write(double x) {
195                write(Double.doubleToRawLongBits(x));
196        }
197
198        /**
199         * Write the 64-bit long to the binary output stream.
200         * @param x the {@code long} to write.
201         */
202        public void write(long x) {
203                writeByte((int) ((x >>> 56) & 0xff));
204                writeByte((int) ((x >>> 48) & 0xff));
205                writeByte((int) ((x >>> 40) & 0xff));
206                writeByte((int) ((x >>> 32) & 0xff));
207                writeByte((int) ((x >>> 24) & 0xff));
208                writeByte((int) ((x >>> 16) & 0xff));
209                writeByte((int) ((x >>>  8) & 0xff));
210                writeByte((int) ((x >>>  0) & 0xff));
211        }
212
213        /**
214         * Write the 32-bit float to the binary output stream.
215         * @param x the {@code float} to write.
216         */
217        public void write(float x) {
218                write(Float.floatToRawIntBits(x));
219        }
220
221        /**
222         * Write the 16-bit int to the binary output stream.
223         * @param x the {@code short} to write.
224         */
225        public void write(short x) {
226                writeByte((x >>>  8) & 0xff);
227                writeByte((x >>>  0) & 0xff);
228        }
229
230        /**
231         * Write the 8-bit char to the binary output stream.
232         * @param x the {@code char} to write.
233         * @throws RuntimeException if {@code x} is not betwen 0 and 255.
234         */
235        public void write(char x) {
236                if (x < 0 || x >= 256) throw new RuntimeException("Illegal 8-bit char = " + x);
237                writeByte(x);
238        }
239
240        /**
241         * Write the r-bit char to the binary output stream.
242         * @param x the {@code char} to write.
243         * @param r the number of relevant bits in the char.
244         * @throws RuntimeException if {@code r} is not between 1 and 16.
245         * @throws RuntimeException if {@code x} is not between 0 and 2<sup>r</sup> - 1.
246         */
247        public void write(char x, int r) {
248                if (r == 8) { write(x); return; }
249                if (r < 1 || r > 16)        throw new RuntimeException("Illegal value for r = " + r);
250                if (x < 0 || x >= (1 << r)) throw new RuntimeException("Illegal " + r + "-bit char = " + x);
251                for (int i = 0; i < r; i++) {
252                        boolean bit = ((x >>> (r - i - 1)) & 1) == 1;
253                        writeBit(bit);
254                }
255        }
256
257        /**
258         * Write the string of 8-bit characters to the binary output stream.
259         * @param s the {@code String} to write.
260         * @throws RuntimeException if any character in the string is not
261         * between 0 and 255.
262         */
263        public void write(String s) {
264                for (int i = 0; i < s.length(); i++)
265                        write(s.charAt(i));
266        }
267
268
269        /**
270         * Write the String of r-bit characters to the binary output stream.
271         * @param s the {@code String} to write.
272         * @param r the number of relevants bits in each character.
273         * @throws RuntimeException if r is not between 1 and 16.
274         * @throws RuntimeException if any character in the string is not
275         * between 0 and 2<sup>r</sup> - 1.
276         */
277        public void write(String s, int r) {
278                for (int i = 0; i < s.length(); i++)
279                        write(s.charAt(i), r);
280        }
281
282
283        /**
284         * Test client. Read bits from standard input and write to the file
285         * specified on command line.
286         */
287        public static void main(String[] args) {
288
289                // create binary output stream to write to file
290                String filename = args[0];
291                BinaryOut out = new BinaryOut(filename);
292                BinaryIn  in  = new BinaryIn();
293
294                // read from standard input and write to file
295                while (!in.isEmpty()) {
296                        char c = in.readChar();
297                        out.write(c);
298                }
299                out.flush();
300        }
301
302}