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}