1 /* 2 * Copyright 2009 Google, Inc. All Rights Reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 4206909 4813885 8189789 27 * @summary Test basic functionality of DeflaterOutputStream/InflaterInputStream and GZIPOutputStream/GZIPInputStream, including flush 28 */ 29 30 import java.io.*; 31 import java.util.*; 32 import java.util.zip.*; 33 34 public class InflateIn_DeflateOut { 35 36 private static class PairedInputStream extends ByteArrayInputStream { 37 private PairedOutputStream out = null; 38 private Random random; 39 40 public PairedInputStream() { 41 // The ByteArrayInputStream needs to start with a buffer, but we 42 // need to set it to have no data 43 super(new byte[1]); 44 count = 0; 45 pos = 0; 46 random = new Random(new Date().getTime()); 47 } 48 49 public void setPairedOutputStream(PairedOutputStream out) { 50 this.out = out; 51 } 52 53 private void maybeFlushPair() { 54 if (random.nextInt(100) < 10) { 55 out.flush(); 56 } 57 } 58 59 public int read() { 60 maybeFlushPair(); 61 return super.read(); 62 } 63 64 public int read(byte b[], int off, int len) { 65 maybeFlushPair(); 66 return super.read(b, off, len); 67 } 68 69 public void addBytes(byte[] bytes, int len) { 70 int oldavail = count - pos; 71 int newcount = oldavail + len; 72 byte[] newbuf = new byte[newcount]; 73 System.arraycopy(buf, pos, newbuf, 0, oldavail); 74 System.arraycopy(bytes, 0, newbuf, oldavail, len); 75 pos = 0; 76 count = newcount; 77 buf = newbuf; 78 } 79 } 80 81 private static class PairedOutputStream extends ByteArrayOutputStream { 82 private PairedInputStream pairedStream = null; 83 84 public PairedOutputStream(PairedInputStream inputPair) { 85 super(); 86 this.pairedStream = inputPair; 87 } 88 89 public void flush() { 90 if (count > 0) { 91 pairedStream.addBytes(buf, count); 92 reset(); 93 } 94 } 95 96 public void close() { 97 flush(); 98 } 99 } 100 101 private static boolean readFully(InputStream in, byte[] buf, int length) 102 throws IOException { 103 int pos = 0; 104 int n; 105 while ((n = in.read(buf, pos, length - pos)) > 0) { 106 pos += n; 107 if (pos == length) return true; 108 } 109 return false; 110 } 111 112 private static boolean readLineIfAvailable(InputStream in, StringBuilder sb) 113 throws IOException { 114 try { 115 while (in.available() > 0) { 116 int i = in.read(); 117 if (i < 0) break; 118 char c = (char) (((byte) i) & 0xff); 119 sb.append(c); 120 if (c == '\n') return true; 121 } 122 } catch (EOFException e) { 123 // empty 124 } 125 return false; 126 } 127 128 /** Check that written, closed and read */ 129 private static void WriteCloseRead() throws Throwable { 130 Random random = new Random(new Date().getTime()); 131 132 PairedInputStream pis = new PairedInputStream(); 133 InflaterInputStream iis = new InflaterInputStream(pis); 134 135 PairedOutputStream pos = new PairedOutputStream(pis); 136 pis.setPairedOutputStream(pos); 137 138 byte[] data = new byte[random.nextInt(1024 * 1024)]; 139 byte[] buf = new byte[data.length]; 140 random.nextBytes(data); 141 142 try (DeflaterOutputStream dos = new DeflaterOutputStream(pos, true)) { 143 dos.write(data); 144 } 145 check(readFully(iis, buf, buf.length)); 146 check(Arrays.equals(data, buf)); 147 } 148 149 private static void TestFlushableGZIPOutputStream() throws Throwable { 150 Random random = new Random(new Date().getTime()); 151 152 ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); 153 OutputStream output = new FlushableGZIPOutputStream(byteOutStream); 154 155 byte[] data = new byte[random.nextInt(1024 * 1024)]; 156 byte[] buf = new byte[data.length]; 157 random.nextBytes(data); 158 159 output.write(data); 160 for (int i=0; i<data.length; i++) { 161 output.write(data[i]); 162 } 163 output.flush(); 164 for (int i=0; i<data.length; i++) { 165 output.write(data[i]); 166 } 167 output.write(data); 168 output.close(); 169 170 ByteArrayInputStream byteInStream = 171 new ByteArrayInputStream(byteOutStream.toByteArray()); 172 173 GZIPInputStream gzis = new GZIPInputStream(byteInStream); 174 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 175 int numRead; 176 byte[] b = new byte[4 * 1024]; 177 try { 178 while ((numRead = gzis.read(buf)) >= 0) { 179 baos.write(b, 0, numRead); 180 } 181 } finally { 182 baos.close(); 183 } 184 185 byte[] decompressedBytes = baos.toByteArray(); 186 187 check(decompressedBytes.length == data.length * 4); 188 } 189 190 private static void check(InputStream is, OutputStream os) 191 throws Throwable 192 { 193 Random random = new Random(new Date().getTime()); 194 // Large writes 195 for (int x = 0; x < 200 ; x++) { 196 // byte[] data = new byte[random.nextInt(1024 * 1024)]; 197 byte[] data = new byte[1024]; 198 byte[] buf = new byte[data.length]; 199 random.nextBytes(data); 200 201 os.write(data); 202 os.flush(); 203 check(readFully(is, buf, buf.length)); 204 check(Arrays.equals(data, buf)); 205 } 206 207 // Small writes 208 for (int x = 0; x < 2000 ; x++) { 209 byte[] data = new byte[random.nextInt(20) + 10]; 210 byte[] buf = new byte[data.length]; 211 random.nextBytes(data); 212 213 os.write(data); 214 os.flush(); 215 if (!readFully(is, buf, buf.length)) { 216 fail("Didn't read full buffer of " + buf.length); 217 } 218 check(Arrays.equals(data, buf)); 219 } 220 221 String quit = "QUIT\r\n"; 222 223 // Close it out 224 os.write(quit.getBytes()); 225 os.close(); 226 227 StringBuilder sb = new StringBuilder(); 228 check(readLineIfAvailable(is, sb)); 229 equal(sb.toString(), quit); 230 } 231 232 /** Check that written, flushed and read */ 233 private static void WriteFlushRead() throws Throwable { 234 PairedInputStream pis = new PairedInputStream(); 235 InflaterInputStream iis = new InflaterInputStream(pis); 236 237 PairedOutputStream pos = new PairedOutputStream(pis); 238 pis.setPairedOutputStream(pos); 239 DeflaterOutputStream dos = new DeflaterOutputStream(pos, true); 240 241 check(iis, dos); 242 } 243 244 private static void GZWriteFlushRead() throws Throwable { 245 PairedInputStream pis = new PairedInputStream(); 246 PairedOutputStream pos = new PairedOutputStream(pis); 247 pis.setPairedOutputStream(pos); 248 249 GZIPOutputStream gos = new GZIPOutputStream(pos, true); 250 gos.flush(); // flush the head out, so gis can read 251 GZIPInputStream gis = new GZIPInputStream(pis); 252 253 check(gis, gos); 254 } 255 256 private static void checkLOP(InputStream is, OutputStream os) 257 throws Throwable 258 { 259 boolean flushed = false; 260 int count = 0; 261 262 // Do at least a certain number of lines, but too many without a 263 // flush means this test isn't testing anything 264 while ((count < 10 && flushed) || (count < 1000 && !flushed)) { 265 String command = "PING " + count + "\r\n"; 266 os.write(command.getBytes()); 267 268 StringBuilder buf = new StringBuilder(); 269 if (!readLineIfAvailable(is, buf)) { 270 flushed = true; 271 os.flush(); 272 check(readLineIfAvailable(is, buf)); 273 } 274 equal(buf.toString(), command); 275 count++; 276 } 277 check(flushed); 278 } 279 280 /** Validate that we need to use flush at least once on a line 281 * oriented protocol */ 282 private static void LineOrientedProtocol() throws Throwable { 283 PairedInputStream pis = new PairedInputStream(); 284 InflaterInputStream iis = new InflaterInputStream(pis); 285 286 PairedOutputStream pos = new PairedOutputStream(pis); 287 pis.setPairedOutputStream(pos); 288 DeflaterOutputStream dos = new DeflaterOutputStream(pos, true); 289 290 checkLOP(iis, dos); 291 } 292 293 private static void GZLineOrientedProtocol() throws Throwable { 294 PairedInputStream pis = new PairedInputStream(); 295 PairedOutputStream pos = new PairedOutputStream(pis); 296 pis.setPairedOutputStream(pos); 297 298 GZIPOutputStream gos = new GZIPOutputStream(pos, true); 299 gos.flush(); // flush the head out, so gis can read 300 GZIPInputStream gis = new GZIPInputStream(pis); 301 302 checkLOP(gis, gos); 303 } 304 305 public static void realMain(String[] args) throws Throwable { 306 WriteCloseRead(); 307 WriteFlushRead(); 308 LineOrientedProtocol(); 309 GZWriteFlushRead(); 310 GZLineOrientedProtocol(); 311 TestFlushableGZIPOutputStream(); 312 } 313 314 //--------------------- Infrastructure --------------------------- 315 static volatile int passed = 0, failed = 0; 316 static void pass() {passed++;} 317 static void fail() {failed++; Thread.dumpStack();} 318 static void fail(String msg) {System.out.println(msg); fail();} 319 static void unexpected(Throwable t) {failed++; t.printStackTrace();} 320 static void check(boolean cond) {if (cond) pass(); else fail();} 321 static void equal(Object x, Object y) { 322 if (x == null ? y == null : x.equals(y)) pass(); 323 else fail(x + " not equal to " + y);} 324 public static void main(String[] args) throws Throwable { 325 try {realMain(args);} catch (Throwable t) {unexpected(t);} 326 System.out.println("\nPassed = " + passed + " failed = " + failed); 327 if (failed > 0) throw new AssertionError("Some tests failed");} 328 } 329 330 class FlushableGZIPOutputStream extends GZIPOutputStream { 331 public FlushableGZIPOutputStream(OutputStream os) throws IOException { 332 super(os); 333 } 334 335 private static final byte[] EMPTYBYTEARRAY = new byte[0]; 336 private boolean hasData = false; 337 338 /** 339 * Here we make sure we have received data, so that the header has been for 340 * sure written to the output stream already. 341 */ 342 @Override 343 public synchronized void write(byte[] bytes, int i, int i1) 344 throws IOException { 345 super.write(bytes, i, i1); 346 hasData = true; 347 } 348 349 @Override 350 public synchronized void write(int i) throws IOException { 351 super.write(i); 352 hasData = true; 353 } 354 355 @Override 356 public synchronized void write(byte[] bytes) throws IOException { 357 super.write(bytes); 358 hasData = true; 359 } 360 361 @Override 362 public synchronized void flush() throws IOException { 363 if (!hasData) { 364 return; // do not allow the gzip header to be flushed on its own 365 } 366 367 // trick the deflater to flush 368 /** 369 * Now this is tricky: We force the Deflater to flush its data by 370 * switching compression level. As yet, a perplexingly simple workaround 371 * for 372 * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html 373 */ 374 if (!def.finished()) { 375 def.setInput(EMPTYBYTEARRAY, 0, 0); 376 377 def.setLevel(Deflater.NO_COMPRESSION); 378 deflate(); 379 380 def.setLevel(Deflater.DEFAULT_COMPRESSION); 381 deflate(); 382 383 out.flush(); 384 } 385 386 hasData = false; // no more data to flush 387 } 388 389 /* 390 * Keep on calling deflate until it runs dry. The default implementation 391 * only does it once and can therefore hold onto data when they need to be 392 * flushed out. 393 */ 394 @Override 395 protected void deflate() throws IOException { 396 int len; 397 do { 398 len = def.deflate(buf, 0, buf.length); 399 if (len > 0) { 400 out.write(buf, 0, len); 401 } 402 } while (len != 0); 403 } 404 405 }