1 16 17 package de.schlichtherle.util.zip; 18 19 import de.schlichtherle.io.util.LEDataOutputStream; 20 21 import java.io.FilterOutputStream ; 22 import java.io.IOException ; 23 import java.io.OutputStream ; 24 import java.io.UnsupportedEncodingException ; 25 import java.util.Collections ; 26 import java.util.Enumeration ; 27 import java.util.Iterator ; 28 import java.util.LinkedHashMap ; 29 import java.util.Map ; 30 import java.util.zip.CRC32 ; 31 import java.util.zip.Deflater ; 32 import java.util.zip.ZipException ; 33 34 49 public class BasicZipOutputStream 50 extends FilterOutputStream 51 implements ZipConstants { 52 53 56 private String comment = ""; 57 58 61 private short method = ZipEntry.DEFLATED; 62 63 67 private final Map entries = new LinkedHashMap (); 68 69 72 private long dataStart; 73 74 77 private long cdOffset; 78 79 82 private long cdLength; 83 84 87 private final String encoding; 88 89 private boolean finished; 90 91 private boolean closed; 92 93 96 private ZipEntry entry; 97 98 103 private boolean deflate; 105 108 private final CRC32 crc = new CRC32 (); 109 110 114 private final Deflater def = new ZipDeflater(); 115 116 119 private final byte[] dbuf = new byte[FLATER_BUF_LENGTH]; 120 121 private final byte[] sbuf = new byte[1]; 122 123 129 public BasicZipOutputStream( 130 final OutputStream out) 131 throws NullPointerException { 132 super(toLEDataOutputStream(out)); 133 134 if (out == null) 136 throw new NullPointerException ("out"); 137 138 this.encoding = DEFAULT_ENCODING; 139 } 140 141 149 public BasicZipOutputStream( 150 final OutputStream out, 151 final String encoding) 152 throws NullPointerException , 153 UnsupportedEncodingException { 154 super(toLEDataOutputStream(out)); 155 156 if (out == null) 158 throw new NullPointerException ("out"); 159 if (encoding == null) 160 throw new NullPointerException ("encoding"); 161 "".getBytes(encoding); 163 this.encoding = encoding; 164 } 165 166 private static LEDataOutputStream toLEDataOutputStream(OutputStream out) { 167 return out instanceof LEDataOutputStream 168 ? (LEDataOutputStream) out 169 : new LEDataOutputStream(out); 170 } 171 172 175 public String getEncoding() { 176 return encoding; 177 } 178 179 182 public int size() { 183 return entries.size(); 184 } 185 186 192 public Enumeration entries() { 193 return Collections.enumeration(entries.values()); 194 } 195 196 204 public ZipEntry getEntry(String name) { 205 return (ZipEntry) entries.get(name); 206 } 207 208 211 public void setComment(String comment) { 212 this.comment = comment; 213 } 214 215 public String getComment() { 216 return comment; 217 } 218 219 222 public void setLevel(int level) { 223 def.setLevel(level); 224 } 225 226 229 public int getLevel() { 230 return ((ZipDeflater) def).getLevel(); 231 } 232 233 238 public void setMethod(short method) { 239 if (method != STORED && method != DEFLATED) 240 throw new IllegalArgumentException ("Invalid compression method!"); 241 this.method = method; 242 } 243 244 public short getMethod() { 245 return method; 246 } 247 248 252 public long length() { 253 return ((LEDataOutputStream) out).size(); 254 } 255 256 260 public boolean busy() { 261 return entry != null; 262 } 263 264 268 public final void putNextEntry(final ZipEntry ze) 269 throws IOException { 270 putNextEntry(ze, true); 271 } 272 273 293 public void putNextEntry(final ZipEntry ze, final boolean deflate) 294 throws IOException { 295 closeEntry(); 296 297 final String name = ze.getName(); 298 300 301 if (ze.getMethod() == -1) ze.setMethod(getMethod()); 303 if (ze.getTime() == -1) ze.setTime(System.currentTimeMillis()); 305 306 int size = name.getBytes(encoding).length; 308 final byte[] extra = ze.getExtra(); 309 if (extra != null) 310 size += extra.length; 311 final String comment = ze.getComment(); 312 if (comment != null) 313 size += comment.getBytes(encoding).length; 314 if (size > 0xFFFF) 315 throw new ZipException ( 316 "Sum of entry name, extra fields and comment too long (max " + 0xFFFF + "): " + size); 317 318 switch (ze.getMethod()) { 319 case ZipEntry.STORED: 320 { 321 final String s = " is required for STORED method!"; 322 if (ze.getCrc() == -1) 323 throw new ZipException ("CRC checksum" + s); 324 if (ze.getSize() == -1) 325 throw new ZipException ("Uncompressed size" + s); 326 ze.setCompressedSize(ze.getSize()); 327 } 328 this.deflate = false; 329 break; 330 331 case ZipEntry.DEFLATED: 332 if (!deflate) { 333 final String s = " is required for DEFLATED method when writing raw deflated data!"; 334 if (ze.getCrc() == -1) 335 throw new ZipException ("CRC checksum" + s); 336 if (ze.getCompressedSize() == -1) 337 throw new ZipException ("Compressed size" + s); 338 if (ze.getSize() == -1) 339 throw new ZipException ("Uncompressed size" + s); 340 } 341 this.deflate = deflate; 342 break; 343 344 default: 345 throw new ZipException ( 346 "Unsupported compression method: " + ze.getMethod()); 347 } 348 349 finished = false; 350 351 entry = ze; 353 writeLocalFileHeader(); 354 355 final ZipEntry old = (ZipEntry) entries.put(name, ze); 358 assert old == null; 359 } 360 361 364 private void writeLocalFileHeader() throws IOException { 365 final ZipEntry entry = this.entry; 366 assert entry != null; 367 368 final LEDataOutputStream dos = (LEDataOutputStream) out; 369 370 entry.offset = dos.size(); 371 372 dos.writeInt(LFH_SIG); 373 374 final boolean useDD = entry.getMethod() == DEFLATED; 377 if (useDD) { 378 dos.writeShort(20); 380 dos.writeShort(8); 382 } else { 383 dos.writeShort(10); 384 dos.writeShort(0); 385 } 386 387 dos.writeShort(entry.getMethod()); 389 390 dos.writeInt((int) entry.getDosTime()); 392 393 if (useDD) { 397 dos.writeInt(0); 398 dos.writeInt(0); 399 dos.writeInt(0); 400 } else { 401 dos.writeInt((int) entry.getCrc()); 402 dos.writeInt((int) entry.getCompressedSize()); 403 dos.writeInt((int) entry.getSize()); 404 } 405 406 final byte[] name = entry.getName().getBytes(encoding); 408 dos.writeShort(name.length); 409 410 byte[] extra = entry.getExtra(); 412 if (extra == null) 413 extra = new byte[0]; 414 dos.writeShort(extra.length); 415 416 dos.write(name); 418 419 dos.write(extra); 421 422 dataStart = dos.size(); 423 } 424 425 428 public void write(int b) throws IOException { 429 byte[] buf = sbuf; 430 buf[0] = (byte) b; 431 write(buf, 0, 1); 432 } 433 434 437 public void write(final byte[] b, final int off, final int len) 438 throws IOException { 439 if (entry != null) { 440 if (len <= 0) 441 return; 442 if (deflate) { 443 assert !def.finished(); 445 def.setInput(b, off, len); 446 while (!def.needsInput()) 447 deflate(); 448 crc.update(b, off, len); 449 } else { 450 out.write(b, off, len); 451 if (entry.getMethod() != DEFLATED) 452 crc.update(b, off, len); 453 } 454 } else { 455 out.write(b, off, len); 456 } 457 } 458 459 462 466 467 private final void deflate() throws IOException { 468 final int dlen = def.deflate(dbuf, 0, dbuf.length); 469 if (dlen > 0) 470 out.write(dbuf, 0, dlen); 471 } 472 473 481 public void closeEntry() throws IOException { 482 if (entry == null) 483 return; 484 485 switch (entry.getMethod()) { 486 case ZipEntry.STORED: 487 final long expectedCrc = crc.getValue(); 488 if (entry.getCrc() != expectedCrc) { 489 throw new ZipException ("Bad CRC checksum for entry " 490 + entry.getName() + ": " 491 + Long.toHexString(entry.getCrc()) 492 + " instead of " 493 + Long.toHexString(expectedCrc)); 494 } 495 final long written = ((LEDataOutputStream) out).size(); 496 if (entry.getSize() != written - dataStart) { 497 throw new ZipException ("Bad size for entry " 498 + entry.getName() + ": " 499 + entry.getSize() 500 + " instead of " 501 + (written - dataStart)); 502 } 503 break; 504 505 case ZipEntry.DEFLATED: 506 if (deflate) { 507 assert !def.finished(); 508 def.finish(); 509 while (!def.finished()) 510 deflate(); 511 512 entry.setCrc(crc.getValue()); 513 entry.setCompressedSize(def.getTotalOut() & 0xFFFFFFFFl); 514 entry.setSize(def.getTotalIn() & 0xFFFFFFFFl); 515 516 def.reset(); 517 } else { 518 } 522 break; 523 524 default: 525 throw new ZipException ( 526 "Unsupported compression method: " + entry.getMethod()); 527 } 528 529 writeDataDescriptor(); 530 flush(); 531 crc.reset(); 532 entry = null; 533 } 534 535 538 private void writeDataDescriptor() throws IOException { 539 final ZipEntry entry = this.entry; 540 assert entry != null; 541 542 if (entry.getMethod() == STORED) 543 return; 544 545 final LEDataOutputStream dos = (LEDataOutputStream) out; 546 547 dos.writeInt(DD_SIG); 548 dos.writeInt((int) entry.getCrc()); 549 dos.writeInt((int) entry.getCompressedSize()); 550 dos.writeInt((int) entry.getSize()); 551 } 552 553 571 public void finish() throws IOException { 572 if (finished) 573 return; 574 575 finished = true; 577 578 closeEntry(); 579 final LEDataOutputStream dos = (LEDataOutputStream) out; 580 cdOffset = dos.size(); 581 final Iterator i = entries.values().iterator(); 582 while (i.hasNext()) 583 writeCentralFileHeader((ZipEntry) i.next()); 584 cdLength = dos.size() - cdOffset; 585 writeEndOfCentralDirectory(); 586 } 587 588 593 private void writeCentralFileHeader(final ZipEntry ze) throws IOException { 594 assert ze != null; 595 596 final LEDataOutputStream dos = (LEDataOutputStream) out; 597 598 dos.writeInt(CFH_SIG); 599 600 dos.writeShort((ze.getPlatform() << 8) | 20); 602 603 if (ze.getMethod() == DEFLATED) { 606 dos.writeShort(20); 609 610 dos.writeShort(8); 612 } else { 613 dos.writeShort(10); 614 dos.writeShort(0); 615 } 616 617 dos.writeShort(ze.getMethod()); 619 620 dos.writeInt((int) ze.getDosTime()); 622 623 dos.writeInt((int) ze.getCrc()); 627 dos.writeInt((int) ze.getCompressedSize()); 628 dos.writeInt((int) ze.getSize()); 629 630 final byte[] name = ze.getName().getBytes(encoding); 632 dos.writeShort(name.length); 633 634 byte[] extra = ze.getExtra(); 636 if (extra == null) 637 extra = new byte[0]; 638 dos.writeShort(extra.length); 639 640 String comment = ze.getComment(); 642 if (comment == null) 643 comment = ""; 644 final byte[] data = comment.getBytes(encoding); 645 dos.writeShort(data.length); 646 647 dos.writeShort(0); 649 650 dos.writeShort(0); 652 653 dos.writeInt(0); 655 656 dos.writeInt((int) ze.offset); 658 659 dos.write(name); 661 662 dos.write(extra); 664 665 dos.write(data); 667 } 668 669 674 private void writeEndOfCentralDirectory() throws IOException { 675 final LEDataOutputStream dos = (LEDataOutputStream) out; 676 677 dos.writeInt(EOCD_SIG); 678 679 dos.writeShort(0); 681 dos.writeShort(0); 682 683 dos.writeShort(entries.size()); 685 dos.writeShort(entries.size()); 686 687 dos.writeInt((int) cdLength); 689 dos.writeInt((int) cdOffset); 690 691 String comment = getComment(); 693 if (comment == null) 694 comment = ""; 695 byte[] data = comment.getBytes(encoding); 696 dos.writeShort(data.length); 697 dos.write(data); 698 } 699 700 708 public void close() throws IOException { 709 if (closed) 710 return; 711 712 closed = true; 714 715 try { 716 finish(); 717 } finally { 718 entries.clear(); 719 super.close(); 720 } 721 } 722 723 726 private static class ZipDeflater extends Deflater { 727 private int level = Deflater.DEFAULT_COMPRESSION; 728 729 public ZipDeflater() { 730 super(Deflater.DEFAULT_COMPRESSION, true); 731 } 732 733 public int getLevel() { 734 return level; 735 } 736 737 public void setLevel(int level) { 738 super.setLevel(level); 739 this.level = level; 740 } 741 } 742 } 743 | Popular Tags |