Java Source Code: org.apache.tools.zip.ZipOutputStream


   1: /*
   2:  *  Licensed to the Apache Software Foundation (ASF) under one or more
   3:  *  contributor license agreements.  See the NOTICE file distributed with
   4:  *  this work for additional information regarding copyright ownership.
   5:  *  The ASF licenses this file to You under the Apache License, Version 2.0
   6:  *  (the "License"); you may not use this file except in compliance with
   7:  *  the License.  You may obtain a copy of the License at
   8:  *
   9:  *      http://www.apache.org/licenses/LICENSE-2.0
  10:  *
  11:  *  Unless required by applicable law or agreed to in writing, software
  12:  *  distributed under the License is distributed on an "AS IS" BASIS,
  13:  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14:  *  See the License for the specific language governing permissions and
  15:  *  limitations under the License.
  16:  *
  17:  */
  18: 
  19: package org.apache.tools.zip;
  20: 
  21: import java.io.File;
  22: import java.io.FileOutputStream;
  23: import java.io.FilterOutputStream;
  24: import java.io.IOException;
  25: import java.io.OutputStream;
  26: import java.io.RandomAccessFile;
  27: import java.io.UnsupportedEncodingException;
  28: import java.util.Date;
  29: import java.util.Hashtable;
  30: import java.util.Vector;
  31: import java.util.zip.CRC32;
  32: import java.util.zip.Deflater;
  33: import java.util.zip.ZipException;
  34: 
  35: /**
  36:	   * Reimplementation of {@link java.util.zip.ZipOutputStream
  37: * java.util.zip.ZipOutputStream} that does handle the extended
  38: * functionality of this package, especially internal/external file
  39: * attributes and extra fields with different layouts for local file
  40: * data and central directory entries.
  41: *
  42:	   * <p>This class will try to use {@link java.io.RandomAccessFile
  43: * RandomAccessFile} when you know that the output is going to go to a
  44: * file.</p>
  45: *
  46: * <p>If RandomAccessFile cannot be used, this implementation will use
  47:	   * a Data Descriptor to store size and CRC information for {@link
  48: * #DEFLATED DEFLATED} entries, this means, you don't need to
  49: * calculate them yourself.  Unfortunately this is not possible for
  50: * the {@link #STORED STORED} method, here setting the CRC and
  51:	   * uncompressed size information is required before {@link
  52: * #putNextEntry putNextEntry} can be called.</p>
  53: *
  54: */
  55:	  public class ZipOutputStream extends FilterOutputStream {
  56:
  57:    /**
  58:     * Compression method for deflated entries.
  59:     *
  60:     * @since 1.1
  61:     */
  62:    public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
  63:
  64:    /**
  65:     * Default compression level for deflated entries.
  66:     *
  67:     * @since Ant 1.7
  68:     */
  69:    public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
  70:
  71:    /**
  72:     * Compression method for stored entries.
  73:     *
  74:     * @since 1.1
  75:     */
  76:    public static final int STORED = java.util.zip.ZipEntry.STORED;
  77:
  78:    /**
  79:     * Current entry.
  80:     *
  81:     * @since 1.1
  82:     */
  83:    private ZipEntry entry;
  84:
  85:    /**
  86:     * The file comment.
  87:     *
  88:     * @since 1.1
  89:     */
  90:    private String comment = "";
  91:
  92:    /**
  93:     * Compression level for next entry.
  94:     *
  95:     * @since 1.1
  96:     */
  97:    private int level = DEFAULT_COMPRESSION;
  98:
  99:    /**
 100:     * Has the compression level changed when compared to the last
 101:     * entry?
 102:     *
 103:     * @since 1.5
 104:     */
 105:    private boolean hasCompressionLevelChanged = false;
 106:
 107:    /**
 108:     * Default compression method for next entry.
 109:     *
 110:     * @since 1.1
 111:     */
 112:    private int method = java.util.zip.ZipEntry.DEFLATED;
 113:
 114:    /**
 115:     * List of ZipEntries written so far.
 116:     *
 117:     * @since 1.1
 118:     */
 119:    private Vector entries = new Vector();
 120:
 121:    /**
 122:     * CRC instance to avoid parsing DEFLATED data twice.
 123:     *
 124:     * @since 1.1
 125:     */
 126:    private CRC32 crc = new CRC32();
 127:
 128:    /**
 129:     * Count the bytes written to out.
 130:     *
 131:     * @since 1.1
 132:     */
 133:    private long written = 0;
 134:
 135:    /**
 136:     * Data for local header data
 137:     *
 138:     * @since 1.1
 139:     */
 140:    private long dataStart = 0;
 141:
 142:    /**
 143:     * Offset for CRC entry in the local file header data for the
 144:     * current entry starts here.
 145:     *
 146:     * @since 1.15
 147:     */
 148:    private long localDataStart = 0;
 149:
 150:    /**
 151:     * Start of central directory.
 152:     *
 153:     * @since 1.1
 154:     */
 155:    private long cdOffset = 0;
 156:
 157:    /**
 158:     * Length of central directory.
 159:     *
 160:     * @since 1.1
 161:     */
 162:    private long cdLength = 0;
 163:
 164:    /**
 165:     * Helper, a 0 as ZipShort.
 166:     *
 167:     * @since 1.1
 168:     */
 169:    private static final byte[] ZERO = {0, 0};
 170:
 171:    /**
 172:     * Helper, a 0 as ZipLong.
 173:     *
 174:     * @since 1.1
 175:     */
 176:    private static final byte[] LZERO = {0, 0, 0, 0};
 177:
 178:    /**
 179:     * Holds the offsets of the LFH starts for each entry.
 180:     *
 181:     * @since 1.1
 182:     */
 183:    private Hashtable offsets = new Hashtable();
 184:
 185:    /**
 186:     * The encoding to use for filenames and the file comment.
 187:     *
 188:     * <p>For a list of possible values see <a
 189:     * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
 190:     * Defaults to the platform's default character encoding.</p>
 191:     *
 192:     * @since 1.3
 193:     */
 194:    private String encoding = null;
 195:
 196:    // CheckStyle:VisibilityModifier OFF - bc
 197:
 198:    /**
 199:     * This Deflater object is used for output.
 200:     *
 201:     * <p>This attribute is only protected to provide a level of API
 202:	       * backwards compatibility.  This class used to extend {@link
 203:     * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
 204:     * Revision 1.13.</p>
 205:     *
 206:     * @since 1.14
 207:     */
 208:    protected Deflater def = new Deflater(level, true);
 209:
 210:    /**
 211:     * This buffer servers as a Deflater.
 212:     *
 213:     * <p>This attribute is only protected to provide a level of API
 214:	       * backwards compatibility.  This class used to extend {@link
 215:     * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
 216:     * Revision 1.13.</p>
 217:     *
 218:     * @since 1.14
 219:     */
 220:    protected byte[] buf = new byte[512];
 221:
 222:    // CheckStyle:VisibilityModifier ON
 223:
 224:    /**
 225:     * Optional random access output.
 226:     *
 227:     * @since 1.14
 228:     */
 229:    private RandomAccessFile raf = null;
 230:
 231:    /**
 232:     * Creates a new ZIP OutputStream filtering the underlying stream.
 233:     * @param out the outputstream to zip
 234:     * @since 1.1
 235:     */
 236:	      public ZipOutputStream(OutputStream out) {
 237:        super(out);
 238:    }
 239:
 240:    /**
 241:     * Creates a new ZIP OutputStream writing to a File.  Will use
 242:     * random access if possible.
 243:     * @param file the file to zip to
 244:     * @since 1.14
 245:     * @throws IOException on error
 246:     */
 247:	      public ZipOutputStream(File file) throws IOException {
 248:        super(null);
 249:
 250:	          try {
 251:            raf = new RandomAccessFile(file, "rw");
 252:            raf.setLength(0);
 253:        } catch (IOException e) {
 254:	              if (raf != null) {
 255:	                  try {
 256:                    raf.close();
 257:                } catch (IOException inner) {
 258:                    // ignore
 259:                }
 260:                raf = null;
 261:            }
 262:            out = new FileOutputStream(file);
 263:        }
 264:    }
 265:
 266:    /**
 267:     * This method indicates whether this archive is writing to a seekable stream (i.e., to a random
 268:     * access file).
 269:     *
 270:     * <p>For seekable streams, you don't need to calculate the CRC or
 271:     * uncompressed size for {@link #STORED} entries before
 272:     * invoking {@link #putNextEntry}.
 273:     * @return true if seekable
 274:     * @since 1.17
 275:     */
 276:	      public boolean isSeekable() {
 277:        return raf != null;
 278:    }
 279:
 280:    /**
 281:     * The encoding to use for filenames and the file comment.
 282:     *
 283:     * <p>For a list of possible values see <a
 284:     * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
 285:     * Defaults to the platform's default character encoding.</p>
 286:     * @param encoding the encoding value
 287:     * @since 1.3
 288:     */
 289:	      public void setEncoding(String encoding) {
 290:        this.encoding = encoding;
 291:    }
 292:
 293:    /**
 294:     * The encoding to use for filenames and the file comment.
 295:     *
 296:     * @return null if using the platform's default character encoding.
 297:     *
 298:     * @since 1.3
 299:     */
 300:	      public String getEncoding() {
 301:        return encoding;
 302:    }
 303:
 304:    /**
 305:     * Finishs writing the contents and closes this as well as the
 306:     * underlying stream.
 307:     *
 308:     * @since 1.1
 309:     * @throws IOException on error
 310:     */
 311:	      public void finish() throws IOException {
 312:        closeEntry();
 313:        cdOffset = written;
 314:	          for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
 315:            writeCentralFileHeader((ZipEntry) entries.elementAt(i));
 316:        }
 317:        cdLength = written - cdOffset;
 318:        writeCentralDirectoryEnd();
 319:        offsets.clear();
 320:        entries.removeAllElements();
 321:    }
 322:
 323:    /**
 324:     * Writes all necessary data for this entry.
 325:     *
 326:     * @since 1.1
 327:     * @throws IOException on error
 328:     */
 329:	      public void closeEntry() throws IOException {
 330:	          if (entry == null) {
 331:            return;
 332:        }
 333:
 334:        long realCrc = crc.getValue();
 335:        crc.reset();
 336:
 337:	          if (entry.getMethod() == DEFLATED) {
 338:            def.finish();
 339:	              while (!def.finished()) {
 340:                deflate();
 341:            }
 342:
 343:            entry.setSize(adjustToLong(def.getTotalIn()));
 344:            entry.setCompressedSize(adjustToLong(def.getTotalOut()));
 345:            entry.setCrc(realCrc);
 346:
 347:            def.reset();
 348:
 349:            written += entry.getCompressedSize();
 350:        } else if (raf == null) {
 351:	              if (entry.getCrc() != realCrc) {
 352:                throw new ZipException("bad CRC checksum for entry "
 353:                                       + entry.getName() + ": "
 354:                                       + Long.toHexString(entry.getCrc())
 355:                                       + " instead of "
 356:                                       + Long.toHexString(realCrc));
 357:            }
 358:
 359:	              if (entry.getSize() != written - dataStart) {
 360:                throw new ZipException("bad size for entry "
 361:                                       + entry.getName() + ": "
 362:                                       + entry.getSize()
 363:                                       + " instead of "
 364:                                       + (written - dataStart));
 365:            }
 366:        } else { /* method is STORED and we used RandomAccessFile */
 367:            long size = written - dataStart;
 368:
 369:            entry.setSize(size);
 370:            entry.setCompressedSize(size);
 371:            entry.setCrc(realCrc);
 372:        }
 373:
 374:        // If random access output, write the local file header containing
 375:        // the correct CRC and compressed/uncompressed sizes
 376:	          if (raf != null) {
 377:            long save = raf.getFilePointer();
 378:
 379:            raf.seek(localDataStart);
 380:            writeOut(ZipLong.getBytes(entry.getCrc()));
 381:            writeOut(ZipLong.getBytes(entry.getCompressedSize()));
 382:            writeOut(ZipLong.getBytes(entry.getSize()));
 383:            raf.seek(save);
 384:        }
 385:
 386:        writeDataDescriptor(entry);
 387:        entry = null;
 388:    }
 389:
 390:    /**
 391:     * Begin writing next entry.
 392:     * @param ze the entry to write
 393:     * @since 1.1
 394:     * @throws IOException on error
 395:     */
 396:	      public void putNextEntry(ZipEntry ze) throws IOException {
 397:        closeEntry();
 398:
 399:        entry = ze;
 400:        entries.addElement(entry);
 401:
 402:	          if (entry.getMethod() == -1) { // not specified
 403:            entry.setMethod(method);
 404:        }
 405:
 406:	          if (entry.getTime() == -1) { // not specified
 407:            entry.setTime(System.currentTimeMillis());
 408:        }
 409:
 410:        // Size/CRC not required if RandomAccessFile is used
 411:	          if (entry.getMethod() == STORED && raf == null) {
 412:	              if (entry.getSize() == -1) {
 413:                throw new ZipException("uncompressed size is required for"
 414:                                       + " STORED method when not writing to a"
 415:                                       + " file");
 416:            }
 417:	              if (entry.getCrc() == -1) {
 418:                throw new ZipException("crc checksum is required for STORED"
 419:                                       + " method when not writing to a file");
 420:            }
 421:            entry.setCompressedSize(entry.getSize());
 422:        }
 423:
 424:	          if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
 425:            def.setLevel(level);
 426:            hasCompressionLevelChanged = false;
 427:        }
 428:        writeLocalFileHeader(entry);
 429:    }
 430:
 431:    /**
 432:     * Set the file comment.
 433:     * @param comment the comment
 434:     * @since 1.1
 435:     */
 436:	      public void setComment(String comment) {
 437:        this.comment = comment;
 438:    }
 439:
 440:    /**
 441:     * Sets the compression level for subsequent entries.
 442:     *
 443:     * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
 444:     * @param level the compression level.
 445:     * @throws IllegalArgumentException if an invalid compression level is specified.
 446:     * @since 1.1
 447:     */
 448:	      public void setLevel(int level) {
 449:        if (level < Deflater.DEFAULT_COMPRESSION
 450:	              || level > Deflater.BEST_COMPRESSION) {
 451:            throw new IllegalArgumentException(
 452:                "Invalid compression level: " + level);
 453:        }
 454:        hasCompressionLevelChanged = (this.level != level);
 455:        this.level = level;
 456:    }
 457:
 458:    /**
 459:     * Sets the default compression method for subsequent entries.
 460:     *
 461:     * <p>Default is DEFLATED.</p>
 462:     * @param method an <code>int</code> from java.util.zip.ZipEntry
 463:     * @since 1.1
 464:     */
 465:	      public void setMethod(int method) {
 466:        this.method = method;
 467:    }
 468:
 469:    /**
 470:     * Writes bytes to ZIP entry.
 471:     * @param b the byte array to write
 472:     * @param offset the start position to write from
 473:     * @param length the number of bytes to write
 474:     * @throws IOException on error
 475:     */
 476:	      public void write(byte[] b, int offset, int length) throws IOException {
 477:	          if (entry.getMethod() == DEFLATED) {
 478:	              if (length > 0) {
 479:	                  if (!def.finished()) {
 480:                    def.setInput(b, offset, length);
 481:	                      while (!def.needsInput()) {
 482:                        deflate();
 483:                    }
 484:                }
 485:            }
 486:        } else {
 487:            writeOut(b, offset, length);
 488:            written += length;
 489:        }
 490:        crc.update(b, offset, length);
 491:    }
 492:
 493:    /**
 494:     * Writes a single byte to ZIP entry.
 495:     *
 496:     * <p>Delegates to the three arg method.</p>
 497:     * @param b the byte to write
 498:     * @since 1.14
 499:     * @throws IOException on error
 500:     */
 501:	      public void write(int b) throws IOException {
 502:        byte[] buff = new byte[1];
 503:        buff[0] = (byte) (b & 0xff);
 504:        write(buff, 0, 1);
 505:    }
 506:
 507:    /**
 508:     * Closes this output stream and releases any system resources
 509:     * associated with the stream.
 510:     *
 511:     * @exception  IOException  if an I/O error occurs.
 512:     * @since 1.14
 513:     */
 514:	      public void close() throws IOException {
 515:        finish();
 516:
 517:	          if (raf != null) {
 518:            raf.close();
 519:        }
 520:	          if (out != null) {
 521:            out.close();
 522:        }
 523:    }
 524:
 525:    /**
 526:     * Flushes this output stream and forces any buffered output bytes
 527:     * to be written out to the stream.
 528:     *
 529:     * @exception  IOException  if an I/O error occurs.
 530:     * @since 1.14
 531:     */
 532:	      public void flush() throws IOException {
 533:	          if (out != null) {
 534:            out.flush();
 535:        }
 536:    }
 537:
 538:    /*
 539:     * Various ZIP constants
 540:     */
 541:    /**
 542:     * local file header signature
 543:     *
 544:     * @since 1.1
 545:     */
 546:    protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);
 547:    /**
 548:     * data descriptor signature
 549:     *
 550:     * @since 1.1
 551:     */
 552:    protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
 553:    /**
 554:     * central file header signature
 555:     *
 556:     * @since 1.1
 557:     */
 558:    protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);
 559:    /**
 560:     * end of central dir signature
 561:     *
 562:     * @since 1.1
 563:     */
 564:    protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
 565:
 566:    /**
 567:     * Writes next block of compressed data to the output stream.
 568:     * @throws IOException on error
 569:     *
 570:     * @since 1.14
 571:     */
 572:	      protected final void deflate() throws IOException {
 573:        int len = def.deflate(buf, 0, buf.length);
 574:	          if (len > 0) {
 575:            writeOut(buf, 0, len);
 576:        }
 577:    }
 578:
 579:    /**
 580:     * Writes the local file header entry
 581:     * @param ze the entry to write
 582:     * @throws IOException on error
 583:     *
 584:     * @since 1.1
 585:     */
 586:	      protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
 587:        offsets.put(ze, ZipLong.getBytes(written));
 588:
 589:        writeOut(LFH_SIG);
 590:        written += 4;
 591:
 592:        //store method in local variable to prevent multiple method calls
 593:        final int zipMethod = ze.getMethod();
 594:
 595:        // version needed to extract
 596:        // general purpose bit flag
 597:	          if (zipMethod == DEFLATED && raf == null) {
 598:            // requires version 2 as we are going to store length info
 599:            // in the data descriptor
 600:            writeOut(ZipShort.getBytes(20));
 601:
 602:            // bit3 set to signal, we use a data descriptor
 603:            writeOut(ZipShort.getBytes(8));
 604:        } else {
 605:            writeOut(ZipShort.getBytes(10));
 606:            writeOut(ZERO);
 607:        }
 608:        written += 4;
 609:
 610:        // compression method
 611:        writeOut(ZipShort.getBytes(zipMethod));
 612:        written += 2;
 613:
 614:        // last mod. time and date
 615:        writeOut(toDosTime(ze.getTime()));
 616:        written += 4;
 617:
 618:        // CRC
 619:        // compressed length
 620:        // uncompressed length
 621:        localDataStart = written;
 622:	          if (zipMethod == DEFLATED || raf != null) {
 623:            writeOut(LZERO);
 624:            writeOut(LZERO);
 625:            writeOut(LZERO);
 626:        } else {
 627:            writeOut(ZipLong.getBytes(ze.getCrc()));
 628:            writeOut(ZipLong.getBytes(ze.getSize()));
 629:            writeOut(ZipLong.getBytes(ze.getSize()));
 630:        }
 631:        written += 12;
 632:
 633:        // file name length
 634:        byte[] name = getBytes(ze.getName());
 635:        writeOut(ZipShort.getBytes(name.length));
 636:        written += 2;
 637:
 638:        // extra field length
 639:        byte[] extra = ze.getLocalFileDataExtra();
 640:        writeOut(ZipShort.getBytes(extra.length));
 641:        written += 2;
 642:
 643:        // file name
 644:        writeOut(name);
 645:        written += name.length;
 646:
 647:        // extra field
 648:        writeOut(extra);
 649:        written += extra.length;
 650:
 651:        dataStart = written;
 652:    }
 653:
 654:    /**
 655:     * Writes the data descriptor entry.
 656:     * @param ze the entry to write
 657:     * @throws IOException on error
 658:     *
 659:     * @since 1.1
 660:     */
 661:	      protected void writeDataDescriptor(ZipEntry ze) throws IOException {
 662:	          if (ze.getMethod() != DEFLATED || raf != null) {
 663:            return;
 664:        }
 665:        writeOut(DD_SIG);
 666:        writeOut(ZipLong.getBytes(entry.getCrc()));
 667:        writeOut(ZipLong.getBytes(entry.getCompressedSize()));
 668:        writeOut(ZipLong.getBytes(entry.getSize()));
 669:        written += 16;
 670:    }
 671:
 672:    /**
 673:     * Writes the central file header entry.
 674:     * @param ze the entry to write
 675:     * @throws IOException on error
 676:     *
 677:     * @since 1.1
 678:     */
 679:	      protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
 680:        writeOut(CFH_SIG);
 681:        written += 4;
 682:
 683:        // version made by
 684:        writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
 685:        written += 2;
 686:
 687:        // version needed to extract
 688:        // general purpose bit flag
 689:	          if (ze.getMethod() == DEFLATED && raf == null) {
 690:            // requires version 2 as we are going to store length info
 691:            // in the data descriptor
 692:            writeOut(ZipShort.getBytes(20));
 693:
 694:            // bit3 set to signal, we use a data descriptor
 695:            writeOut(ZipShort.getBytes(8));
 696:        } else {
 697:            writeOut(ZipShort.getBytes(10));
 698:            writeOut(ZERO);
 699:        }
 700:        written += 4;
 701:
 702:        // compression method
 703:        writeOut(ZipShort.getBytes(ze.getMethod()));
 704:        written += 2;
 705:
 706:        // last mod. time and date
 707:        writeOut(toDosTime(ze.getTime()));
 708:        written += 4;
 709:
 710:        // CRC
 711:        // compressed length
 712:        // uncompressed length
 713:        writeOut(ZipLong.getBytes(ze.getCrc()));
 714:        writeOut(ZipLong.getBytes(ze.getCompressedSize()));
 715:        writeOut(ZipLong.getBytes(ze.getSize()));
 716:        written += 12;
 717:
 718:        // file name length
 719:        byte[] name = getBytes(ze.getName());
 720:        writeOut(ZipShort.getBytes(name.length));
 721:        written += 2;
 722:
 723:        // extra field length
 724:        byte[] extra = ze.getCentralDirectoryExtra();
 725:        writeOut(ZipShort.getBytes(extra.length));
 726:        written += 2;
 727:
 728:        // file comment length
 729:        String comm = ze.getComment();
 730:	          if (comm == null) {
 731:            comm = "";
 732:        }
 733:        byte[] commentB = getBytes(comm);
 734:        writeOut(ZipShort.getBytes(commentB.length));
 735:        written += 2;
 736:
 737:        // disk number start
 738:        writeOut(ZERO);
 739:        written += 2;
 740:
 741:        // internal file attributes
 742:        writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
 743:        written += 2;
 744:
 745:        // external file attributes
 746:        writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
 747:        written += 4;
 748:
 749:        // relative offset of LFH
 750:        writeOut((byte[]) offsets.get(ze));
 751:        written += 4;
 752:
 753:        // file name
 754:        writeOut(name);
 755:        written += name.length;
 756:
 757:        // extra field
 758:        writeOut(extra);
 759:        written += extra.length;
 760:
 761:        // file comment
 762:        writeOut(commentB);
 763:        written += commentB.length;
 764:    }
 765:
 766:    /**
 767:     * Writes the &quot;End of central dir record&quot;.
 768:     * @throws IOException on error
 769:     *
 770:     * @since 1.1
 771:     */
 772:	      protected void writeCentralDirectoryEnd() throws IOException {
 773:        writeOut(EOCD_SIG);
 774:
 775:        // disk numbers
 776:        writeOut(ZERO);
 777:        writeOut(ZERO);
 778:
 779:        // number of entries
 780:        byte[] num = ZipShort.getBytes(entries.size());
 781:        writeOut(num);
 782:        writeOut(num);
 783:
 784:        // length and location of CD
 785:        writeOut(ZipLong.getBytes(cdLength));
 786:        writeOut(ZipLong.getBytes(cdOffset));
 787:
 788:        // ZIP file comment
 789:        byte[] data = getBytes(comment);
 790:        writeOut(ZipShort.getBytes(data.length));
 791:        writeOut(data);
 792:    }
 793:
 794:    /**
 795:     * Smallest date/time ZIP can handle.
 796:     *
 797:     * @since 1.1
 798:     */
 799:    private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
 800:
 801:    /**
 802:     * Convert a Date object to a DOS date/time field.
 803:     * @param time the <code>Date</code> to convert
 804:     * @return the date as a <code>ZipLong</code>
 805:     * @since 1.1
 806:     */
 807:	      protected static ZipLong toDosTime(Date time) {
 808:        return new ZipLong(toDosTime(time.getTime()));
 809:    }
 810:
 811:    /**
 812:     * Convert a Date object to a DOS date/time field.
 813:     *
 814:     * <p>Stolen from InfoZip's <code>fileio.c</code></p>
 815:     * @param t number of milliseconds since the epoch
 816:     * @return the date as a byte array
 817:     * @since 1.26
 818:     */
 819:	      protected static byte[] toDosTime(long t) {
 820:        Date time = new Date(t);
 821:        int year = time.getYear() + 1900;
 822:	          if (year < 1980) {
 823:            return DOS_TIME_MIN;
 824:        }
 825:        int month = time.getMonth() + 1;
 826:        long value =  ((year - 1980) << 25)
 827:            |         (month << 21)
 828:            |         (time.getDate() << 16)
 829:            |         (time.getHours() << 11)
 830:            |         (time.getMinutes() << 5)
 831:            |         (time.getSeconds() >> 1);
 832:        return ZipLong.getBytes(value);
 833:    }
 834:
 835:    /**
 836:     * Retrieve the bytes for the given String in the encoding set for
 837:     * this Stream.
 838:     * @param name the string to get bytes from
 839:     * @return the bytes as a byte array
 840:     * @throws ZipException on error
 841:     *
 842:     * @since 1.3
 843:     */
 844:	      protected byte[] getBytes(String name) throws ZipException {
 845:	          if (encoding == null) {
 846:            return name.getBytes();
 847:        } else {
 848:	              try {
 849:                return name.getBytes(encoding);
 850:            } catch (UnsupportedEncodingException uee) {
 851:                throw new ZipException(uee.getMessage());
 852:            }
 853:        }
 854:    }
 855:
 856:    /**
 857:     * Write bytes to output or random access file.
 858:     * @param data the byte array to write
 859:     * @throws IOException on error
 860:     *
 861:     * @since 1.14
 862:     */
 863:	      protected final void writeOut(byte[] data) throws IOException {
 864:        writeOut(data, 0, data.length);
 865:    }
 866:
 867:    /**
 868:     * Write bytes to output or random access file.
 869:     * @param data the byte array to write
 870:     * @param offset the start position to write from
 871:     * @param length the number of bytes to write
 872:     * @throws IOException on error
 873:     *
 874:     * @since 1.14
 875:     */
 876:    protected final void writeOut(byte[] data, int offset, int length)
 877:	          throws IOException {
 878:	          if (raf != null) {
 879:            raf.write(data, offset, length);
 880:        } else {
 881:            out.write(data, offset, length);
 882:        }
 883:    }
 884:
 885:    /**
 886:     * Assumes a negative integer really is a positive integer that
 887:     * has wrapped around and re-creates the original value.
 888:     * @param i the value to treat as unsigned int.
 889:     * @return the unsigned int as a long.
 890:     * @since 1.34
 891:     */
 892:	      protected static long adjustToLong(int i) {
 893:	          if (i < 0) {
 894:            return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
 895:        } else {
 896:            return i;
 897:        }
 898:    }
 899:
 900:}