|
|
@@ -0,0 +1,619 @@ |
|
|
|
/* |
|
|
|
* The Apache Software License, Version 1.1 |
|
|
|
* |
|
|
|
* Copyright (c) 2001 The Apache Software Foundation. All rights |
|
|
|
* reserved. |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
* modification, are permitted provided that the following conditions |
|
|
|
* are met: |
|
|
|
* |
|
|
|
* 1. Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* |
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in |
|
|
|
* the documentation and/or other materials provided with the |
|
|
|
* distribution. |
|
|
|
* |
|
|
|
* 3. The end-user documentation included with the redistribution, if |
|
|
|
* any, must include the following acknowlegement: |
|
|
|
* "This product includes software developed by the |
|
|
|
* Apache Software Foundation (http://www.apache.org/)." |
|
|
|
* Alternately, this acknowlegement may appear in the software itself, |
|
|
|
* if and wherever such third-party acknowlegements normally appear. |
|
|
|
* |
|
|
|
* 4. The names "The Jakarta Project", "Ant", and "Apache Software |
|
|
|
* Foundation" must not be used to endorse or promote products derived |
|
|
|
* from this software without prior written permission. For written |
|
|
|
* permission, please contact apache@apache.org. |
|
|
|
* |
|
|
|
* 5. Products derived from this software may not be called "Apache" |
|
|
|
* nor may "Apache" appear in their names without prior written |
|
|
|
* permission of the Apache Group. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
|
|
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
|
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
|
|
* SUCH DAMAGE. |
|
|
|
* ==================================================================== |
|
|
|
* |
|
|
|
* This software consists of voluntary contributions made by many |
|
|
|
* individuals on behalf of the Apache Software Foundation. For more |
|
|
|
* information on the Apache Software Foundation, please see |
|
|
|
* <http://www.apache.org/>. |
|
|
|
*/ |
|
|
|
|
|
|
|
package org.apache.tools.zip; |
|
|
|
|
|
|
|
import java.io.*; |
|
|
|
import java.util.Date; |
|
|
|
import java.util.Hashtable; |
|
|
|
import java.util.Vector; |
|
|
|
import java.util.zip.CRC32; |
|
|
|
import java.util.zip.Deflater; |
|
|
|
import java.util.zip.DeflaterOutputStream; |
|
|
|
import java.util.zip.ZipException; |
|
|
|
|
|
|
|
/** |
|
|
|
* Reimplementation of {@link java.util.zip.ZipOutputStream |
|
|
|
* java.util.zip.ZipOutputStream} that does handle the extended |
|
|
|
* functionality of this package, especially internal/external file |
|
|
|
* attributes and extra fields with different layouts for local file |
|
|
|
* data and central directory entries. |
|
|
|
* |
|
|
|
* <p>This implementation will use a Data Descriptor to store size and |
|
|
|
* CRC information for DEFLATED entries, this means, you don't need to |
|
|
|
* calculate them yourself. Unfortunately this is not possible for |
|
|
|
* the STORED method, here setting the CRC and uncompressed size |
|
|
|
* information is required before {@link #putNextEntry putNextEntry} |
|
|
|
* will be called.</p> |
|
|
|
* |
|
|
|
* @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a> |
|
|
|
* @version $Revision$ |
|
|
|
*/ |
|
|
|
public class ZipOutputStream extends DeflaterOutputStream { |
|
|
|
|
|
|
|
/** |
|
|
|
* Current entry. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private ZipEntry entry; |
|
|
|
|
|
|
|
/** |
|
|
|
* The file comment. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private String comment = ""; |
|
|
|
|
|
|
|
/** |
|
|
|
* Compression level for next entry. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private int level = Deflater.DEFAULT_COMPRESSION; |
|
|
|
|
|
|
|
/** |
|
|
|
* Default compression method for next entry. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private int method = DEFLATED; |
|
|
|
|
|
|
|
/** |
|
|
|
* List of ZipEntries written so far. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private Vector entries = new Vector(); |
|
|
|
|
|
|
|
/** |
|
|
|
* CRC instance to avoid parsing DEFLATED data twice. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private CRC32 crc = new CRC32(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Count the bytes written to out. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private long written = 0; |
|
|
|
|
|
|
|
/** |
|
|
|
* Data for current entry started here. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private long dataStart = 0; |
|
|
|
|
|
|
|
/** |
|
|
|
* Start of central directory. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private ZipLong cdOffset = new ZipLong(0); |
|
|
|
|
|
|
|
/** |
|
|
|
* Length of central directory. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private ZipLong cdLength = new ZipLong(0); |
|
|
|
|
|
|
|
/** |
|
|
|
* Helper, a 0 as ZipShort. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private static final byte[] ZERO = {0, 0}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Helper, a 0 as ZipLong. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private static final byte[] LZERO = {0, 0, 0, 0}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Holds the offsets of the LFH starts for each entry |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private Hashtable offsets = new Hashtable(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Compression method for deflated entries. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public static final int DEFLATED = ZipEntry.DEFLATED; |
|
|
|
|
|
|
|
/** |
|
|
|
* Compression method for deflated entries. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public static final int STORED = ZipEntry.STORED; |
|
|
|
|
|
|
|
/** |
|
|
|
* Creates a new ZIP OutputStream filtering the underlying stream. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public ZipOutputStream(OutputStream out) { |
|
|
|
super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Found out by experiment, that DeflaterOutputStream.close() |
|
|
|
* will call finish() - so we don't need to override close |
|
|
|
* ourselves.</p> |
|
|
|
*/ |
|
|
|
|
|
|
|
/** |
|
|
|
* Finishs writing the contents and closes this as well as the |
|
|
|
* underlying stream. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public void finish() throws IOException { |
|
|
|
closeEntry(); |
|
|
|
cdOffset = new ZipLong(written); |
|
|
|
for (int i=0; i<entries.size(); i++) { |
|
|
|
writeCentralFileHeader((ZipEntry) entries.elementAt(i)); |
|
|
|
} |
|
|
|
cdLength = new ZipLong(written-cdOffset.getValue()); |
|
|
|
writeCentralDirectoryEnd(); |
|
|
|
offsets.clear(); |
|
|
|
entries.removeAllElements(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Writes all necessary data for this entry. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public void closeEntry() throws IOException { |
|
|
|
if (entry == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
long realCrc = crc.getValue(); |
|
|
|
crc.reset(); |
|
|
|
|
|
|
|
if (entry.getMethod() == DEFLATED) { |
|
|
|
def.finish(); |
|
|
|
while (!def.finished()) { |
|
|
|
deflate(); |
|
|
|
} |
|
|
|
|
|
|
|
entry.setSize(def.getTotalIn()); |
|
|
|
entry.setCompressedSize(def.getTotalOut()); |
|
|
|
entry.setCrc(realCrc); |
|
|
|
|
|
|
|
def.reset(); |
|
|
|
|
|
|
|
written += entry.getCompressedSize(); |
|
|
|
} else { |
|
|
|
if (entry.getCrc() != realCrc) { |
|
|
|
throw new ZipException("bad CRC checksum for entry " |
|
|
|
+entry.getName()+ ": " |
|
|
|
+ Long.toHexString(entry.getCrc()) |
|
|
|
+ " instead of " |
|
|
|
+ Long.toHexString(realCrc)); |
|
|
|
} |
|
|
|
|
|
|
|
if (entry.getSize() != written - dataStart) { |
|
|
|
throw new ZipException("bad size for entry " |
|
|
|
+entry.getName()+ ": " |
|
|
|
+ entry.getSize() |
|
|
|
+ " instead of " |
|
|
|
+ (written - dataStart)); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
writeDataDescriptor(entry); |
|
|
|
entry = null; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Begin writing next entry. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public void putNextEntry(ZipEntry ze) throws IOException { |
|
|
|
closeEntry(); |
|
|
|
|
|
|
|
entry = ze; |
|
|
|
entries.addElement(entry); |
|
|
|
|
|
|
|
if (entry.getMethod() == -1) { // not specified |
|
|
|
entry.setMethod(method); |
|
|
|
} |
|
|
|
|
|
|
|
if (entry.getTime() == -1) { // not specified |
|
|
|
entry.setTime(System.currentTimeMillis()); |
|
|
|
} |
|
|
|
|
|
|
|
if (entry.getMethod() == STORED) { |
|
|
|
if (entry.getSize() == -1) { |
|
|
|
throw new ZipException("uncompressed size is required for STORED method"); |
|
|
|
} |
|
|
|
if (entry.getCrc() == -1) { |
|
|
|
throw new ZipException("crc checksum is required for STORED method"); |
|
|
|
} |
|
|
|
entry.setCompressedSize(entry.getSize()); |
|
|
|
} else { |
|
|
|
def.setLevel(level); |
|
|
|
} |
|
|
|
writeLocalFileHeader(entry); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Set the file comment. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public void setComment(String comment) { |
|
|
|
this.comment = comment; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Sets the compression level for subsequent entries. |
|
|
|
* |
|
|
|
* <p>Default is Deflater.DEFAULT_COMPRESSION.</p> |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public void setLevel(int level) { |
|
|
|
this.level = level; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Sets the default compression method for subsequent entries. |
|
|
|
* |
|
|
|
* <p>Default is DEFLATED.</p> |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
public void setMethod(int method) { |
|
|
|
this.method = method; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Writes bytes to ZIP entry. |
|
|
|
* |
|
|
|
* <p>Override is necessary to support STORED entries, as well as |
|
|
|
* calculationg CRC automatically for DEFLATED entries.</p> |
|
|
|
*/ |
|
|
|
public void write(byte[] b, int offset, int length) throws IOException { |
|
|
|
if (entry.getMethod() == DEFLATED) { |
|
|
|
super.write(b, offset, length); |
|
|
|
} else { |
|
|
|
out.write(b, offset, length); |
|
|
|
written += length; |
|
|
|
} |
|
|
|
crc.update(b, offset, length); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Various ZIP constants |
|
|
|
*/ |
|
|
|
/** |
|
|
|
* local file header signature |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected static final ZipLong LFH_SIG = new ZipLong(0X04034B50L); |
|
|
|
/** |
|
|
|
* data descriptor signature |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected static final ZipLong DD_SIG = new ZipLong(0X08074B50L); |
|
|
|
/** |
|
|
|
* central file header signature |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected static final ZipLong CFH_SIG = new ZipLong(0X02014B50L); |
|
|
|
/** |
|
|
|
* end of central dir signature |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L); |
|
|
|
|
|
|
|
/** |
|
|
|
* Writes the local file header entry |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected void writeLocalFileHeader(ZipEntry ze) throws IOException { |
|
|
|
offsets.put(ze, new ZipLong(written)); |
|
|
|
|
|
|
|
out.write(LFH_SIG.getBytes()); |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// version needed to extract |
|
|
|
// general purpose bit flag |
|
|
|
if (ze.getMethod() == DEFLATED) { |
|
|
|
// requires version 2 as we are going to store length info |
|
|
|
// in the data descriptor |
|
|
|
out.write((new ZipShort(20)).getBytes()); |
|
|
|
|
|
|
|
// bit3 set to signal, we use a data descriptor |
|
|
|
out.write((new ZipShort(8)).getBytes()); |
|
|
|
} else { |
|
|
|
out.write((new ZipShort(10)).getBytes()); |
|
|
|
out.write(ZERO); |
|
|
|
} |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// compression method |
|
|
|
out.write((new ZipShort(ze.getMethod())).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// last mod. time and date |
|
|
|
out.write(toDosTime(new Date(ze.getTime())).getBytes()); |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// CRC |
|
|
|
// compressed length |
|
|
|
// uncompressed length |
|
|
|
if (ze.getMethod() == DEFLATED) { |
|
|
|
out.write(LZERO); |
|
|
|
out.write(LZERO); |
|
|
|
out.write(LZERO); |
|
|
|
} else { |
|
|
|
out.write((new ZipLong(ze.getCrc())).getBytes()); |
|
|
|
out.write((new ZipLong(ze.getSize())).getBytes()); |
|
|
|
out.write((new ZipLong(ze.getSize())).getBytes()); |
|
|
|
} |
|
|
|
written += 12; |
|
|
|
|
|
|
|
// file name length |
|
|
|
byte[] name = ze.getName().getBytes(); |
|
|
|
out.write((new ZipShort(name.length)).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// extra field length |
|
|
|
byte[] extra = ze.getLocalFileDataExtra(); |
|
|
|
out.write((new ZipShort(extra.length)).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// file name |
|
|
|
out.write(name); |
|
|
|
written += name.length; |
|
|
|
|
|
|
|
// extra field |
|
|
|
out.write(extra); |
|
|
|
written += extra.length; |
|
|
|
|
|
|
|
dataStart = written; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Writes the data descriptor entry |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected void writeDataDescriptor(ZipEntry ze) throws IOException { |
|
|
|
if (ze.getMethod() != DEFLATED) { |
|
|
|
return; |
|
|
|
} |
|
|
|
out.write(DD_SIG.getBytes()); |
|
|
|
out.write((new ZipLong(entry.getCrc())).getBytes()); |
|
|
|
out.write((new ZipLong(entry.getCompressedSize())).getBytes()); |
|
|
|
out.write((new ZipLong(entry.getSize())).getBytes()); |
|
|
|
written += 16; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Writes the central file header entry |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected void writeCentralFileHeader(ZipEntry ze) throws IOException { |
|
|
|
out.write(CFH_SIG.getBytes()); |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// version made by |
|
|
|
out.write((new ZipShort(20)).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// version needed to extract |
|
|
|
// general purpose bit flag |
|
|
|
if (ze.getMethod() == DEFLATED) { |
|
|
|
// requires version 2 as we are going to store length info |
|
|
|
// in the data descriptor |
|
|
|
out.write((new ZipShort(20)).getBytes()); |
|
|
|
|
|
|
|
// bit3 set to signal, we use a data descriptor |
|
|
|
out.write((new ZipShort(8)).getBytes()); |
|
|
|
} else { |
|
|
|
out.write((new ZipShort(10)).getBytes()); |
|
|
|
out.write(ZERO); |
|
|
|
} |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// compression method |
|
|
|
out.write((new ZipShort(ze.getMethod())).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// last mod. time and date |
|
|
|
out.write(toDosTime(new Date(ze.getTime())).getBytes()); |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// CRC |
|
|
|
// compressed length |
|
|
|
// uncompressed length |
|
|
|
out.write((new ZipLong(ze.getCrc())).getBytes()); |
|
|
|
out.write((new ZipLong(ze.getCompressedSize())).getBytes()); |
|
|
|
out.write((new ZipLong(ze.getSize())).getBytes()); |
|
|
|
written += 12; |
|
|
|
|
|
|
|
// file name length |
|
|
|
byte[] name = ze.getName().getBytes(); |
|
|
|
out.write((new ZipShort(name.length)).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// extra field length |
|
|
|
byte[] extra = ze.getCentralDirectoryExtra(); |
|
|
|
out.write((new ZipShort(extra.length)).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// file comment length |
|
|
|
String comm = ze.getComment(); |
|
|
|
if (comm == null) { |
|
|
|
comm = ""; |
|
|
|
} |
|
|
|
byte[] comment = comm.getBytes(); |
|
|
|
out.write((new ZipShort(comment.length)).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// disk number start |
|
|
|
out.write(ZERO); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// internal file attributes |
|
|
|
out.write((new ZipShort(ze.getInternalAttributes())).getBytes()); |
|
|
|
written += 2; |
|
|
|
|
|
|
|
// external file attributes |
|
|
|
out.write((new ZipLong(ze.getExternalAttributes())).getBytes()); |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// relative offset of LFH |
|
|
|
out.write(((ZipLong) offsets.get(ze)).getBytes()); |
|
|
|
written += 4; |
|
|
|
|
|
|
|
// file name |
|
|
|
out.write(name); |
|
|
|
written += name.length; |
|
|
|
|
|
|
|
// extra field |
|
|
|
out.write(extra); |
|
|
|
written += extra.length; |
|
|
|
|
|
|
|
// file comment |
|
|
|
out.write(comment); |
|
|
|
written += comment.length; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Writes the "End of central dir record" |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected void writeCentralDirectoryEnd() throws IOException { |
|
|
|
out.write(EOCD_SIG.getBytes()); |
|
|
|
|
|
|
|
// disk numbers |
|
|
|
out.write(ZERO); |
|
|
|
out.write(ZERO); |
|
|
|
|
|
|
|
// number of entries |
|
|
|
byte[] num = (new ZipShort(entries.size())).getBytes(); |
|
|
|
out.write(num); |
|
|
|
out.write(num); |
|
|
|
|
|
|
|
// length and location of CD |
|
|
|
out.write(cdLength.getBytes()); |
|
|
|
out.write(cdOffset.getBytes()); |
|
|
|
|
|
|
|
// ZIP file comment |
|
|
|
byte[] data = comment.getBytes(); |
|
|
|
out.write((new ZipShort(data.length)).getBytes()); |
|
|
|
out.write(data); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Smallest date/time ZIP can handle. |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
private static final ZipLong DOS_TIME_MIN = new ZipLong(0x00002100L); |
|
|
|
|
|
|
|
/** |
|
|
|
* Convert a Date object to a DOS date/time field. |
|
|
|
* |
|
|
|
* <p>Stolen from InfoZip's <code>fileio.c</code></p> |
|
|
|
* |
|
|
|
* @since 1.1 |
|
|
|
*/ |
|
|
|
protected static ZipLong toDosTime(Date time) { |
|
|
|
int year = time.getYear() + 1900; |
|
|
|
int month = time.getMonth() + 1; |
|
|
|
if (year < 1980) { |
|
|
|
return DOS_TIME_MIN; |
|
|
|
} |
|
|
|
long value = ((year - 1980) << 25) |
|
|
|
| (month << 21) |
|
|
|
| (time.getDate() << 16) |
|
|
|
| (time.getHours() << 11) |
|
|
|
| (time.getMinutes() << 5) |
|
|
|
| (time.getSeconds() >> 1); |
|
|
|
|
|
|
|
byte[] result = new byte[4]; |
|
|
|
result[0] = (byte) ((value & 0xFF)); |
|
|
|
result[1] = (byte) ((value & 0xFF00) >> 8); |
|
|
|
result[2] = (byte) ((value & 0xFF0000) >> 16); |
|
|
|
result[3] = (byte) ((value & 0xFF000000l) >> 24); |
|
|
|
return new ZipLong(result); |
|
|
|
} |
|
|
|
|
|
|
|
} |