From 055f820cacbad6f97f0d84a47103fa59904c649d Mon Sep 17 00:00:00 2001 From: Chris Jordan Date: Fri, 5 Jul 2013 14:39:30 -0600 Subject: [PATCH 1/6] adding zipentry and zipoutputstream classes --- classpath/java/util/zip/ZipEntry.java | 169 ++++++++++++++-- classpath/java/util/zip/ZipOutputStream.java | 200 +++++++++++++++++++ 2 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 classpath/java/util/zip/ZipOutputStream.java diff --git a/classpath/java/util/zip/ZipEntry.java b/classpath/java/util/zip/ZipEntry.java index f52da8f4ca..b396127187 100644 --- a/classpath/java/util/zip/ZipEntry.java +++ b/classpath/java/util/zip/ZipEntry.java @@ -1,21 +1,160 @@ -/* Copyright (c) 2008-2013, Avian Contributors - - Permission to use, copy, modify, and/or distribute this software - for any purpose with or without fee is hereby granted, provided - that the above copyright notice and this permission notice appear - in all copies. - - There is NO WARRANTY for this software. See license.txt for - details. */ - package java.util.zip; -public abstract class ZipEntry { - public abstract String getName(); - public abstract int getCompressedSize(); - public abstract int getSize(); +import java.util.Calendar; +import java.util.Date; +/** + * class ZipEntry: + * + * Class to store and retrieve information for entries in a zip file + * Contains variables for all standard zip format field as well as + * setter and accessor methods + * + * @author ReadyTalk Summer 2013 Intern Team + **/ +public class ZipEntry { + String name; + //Minimum version needed to extract the file(s) from a compressed state + short reqVersion; + + //Method used to compress file + short compressionMethod; + + //Format of date and time are both 2 byte fields + short modTime; + short modDate; + + //CRC-32 + int crc; + + //Sizes of file + int compSize; + int uncompSize; + + int offset; + + public ZipEntry(String name) { + this.name = name; + setTimeDate(); + compSize = 0; + uncompSize = 0; + crc = 0; + offset = 0; + } + + //Method to return name of the file + public String getName() { + return name; + } + + //Method to check if file is a directory public boolean isDirectory() { return getName().endsWith("/"); } -} + + //Method to return the compressed size of the file + public int getCompressedSize() { + return compSize; + } + + //Method to return the uncompressed size of the file + public int getSize() { + return uncompSize; + } + + /** + * Method setTime Date(): + * Creates a calendar object to retrieve date and time information + * + * Time is stored in the MSDOS format 5 bits for hour, 6 bits for min, 5 bits for seconds + * Hours are in military time and must be adjusted to time zone + * Seconds are divided by two before storing and must be multiplied by two to retrieve + * + * Date is stored in the MSDOS format 7 bit for year, 4 bit for month, 5 bits for day of month + * Year is the number of years since 1980 per, the month is stored starting at 0 + * for January. Day of month is stored with nature numbering + * + * Bit masks and shifting are used to build the time and date bytes + * + * @author cjordan + **/ + public void setTimeDate(){ + final int DAY_OF_MONTH = 5; + final int HOUR_OF_DAY = 11; + final int MINUTE = 12; + final int MONTH = 2; + final int SECOND = 13; + final int YEAR = 1; + + Calendar modCalendar = Calendar.getInstance(); + + //Hour + int timeBits = modCalendar.get(HOUR_OF_DAY); + timeBits = timeBits - 6; + timeBits = timeBits << 6; + + //Minutes + int minBits = 0x3f & (modCalendar.get(MINUTE));; + timeBits = timeBits ^ minBits; + timeBits = timeBits << 5; + + //Seconds + int secBits = 0x1f & (modCalendar.get(SECOND)); + secBits = secBits >> 1; + timeBits = timeBits ^ secBits; + + //Store Time + modTime = (short)timeBits; + + //Year + int dateBits = (modCalendar.get(YEAR) -1980); + dateBits = dateBits << 4; + + //Month + int month = 0xf & ((modCalendar.get(MONTH)) + 1); + dateBits = dateBits ^ month; + dateBits = dateBits << 5; + + //Day of month + int dayBits = 0x1f & modCalendar.get(DAY_OF_MONTH); + dateBits = dateBits ^ dayBits; + + //Store Date + modDate = (short)dateBits; + + } + + //Method to set the minimum version required to open the zip file + //Valid values for the compression method are the numbers 1.0 to 10.0 + public boolean setRequiredVersion(float versionFloat){ + //Check for valid version numbers + if (versionFloat < 1 || versionFloat > 100){ + return false; + } + + //Convert to short value for storage + versionFloat = versionFloat * 10; + short versionShort = (short)versionFloat; + + //Set value of version + reqVersion = versionShort; + return true; + } + + //Method to set the compression method for the file + //Valid values for the compression method are the numbers 0 to 19 and 99 + public boolean setCompressionMethod(short compMethod){ + if (compMethod == 99){ + compressionMethod = compMethod; + return true; + } + else if (compMethod < 0 || compMethod > 19){ + return false; + } + else{ + compressionMethod = compMethod; + return true; + } + } + +} \ No newline at end of file diff --git a/classpath/java/util/zip/ZipOutputStream.java b/classpath/java/util/zip/ZipOutputStream.java new file mode 100644 index 0000000000..aa00705375 --- /dev/null +++ b/classpath/java/util/zip/ZipOutputStream.java @@ -0,0 +1,200 @@ +package java.util.zip; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.CRC32; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Deflater; +import java.io.BufferedOutputStream; + + +/** + * An as simple as possible implementation of ZipOutputStream + * Compression method defaults to DEFLATE + * All hardcoded defaults match the defaults for openJDK, + * including PKZip version, bit flags set, compression level, etc + * + * @author ReadyTalk Summer 2013 Intern Team + */ +public class ZipOutputStream extends DeflaterOutputStream { + private static final int SIGNATURE = 0x04034b50; + private static final short VERSION = 0x0014; + private static final short BITFLAG = 0x0008; + private static final short METHOD = 0x0008; + private static final int CENTRAL_FILE_HEADER = 0x02014b50; + private static final int DATA_DESCRIPTER_HEADER = 0x08074b50; + private static final int END_OF_CENTRAL_DIRECTORY_SIG = 0x06054b50; + private static final int DEFAULT_LEVEL = 6; + + private final OutputStream out; + private DeflaterOutputStream deflaterStream; + private Deflater deflater; + private List entries; + private CRC32 crc = new CRC32(); + private ZipEntry currentEntry; + + private int bytesWritten; + private int sizeOfCentralDirectory; + private byte[] buffer; + + public ZipOutputStream(OutputStream outStream, int bufferSize) { + super(outStream); + out = outStream; + bytesWritten = 0; + sizeOfCentralDirectory = 0; + entries = new ArrayList(); + buffer = new byte[bufferSize]; + deflater = new Deflater(DEFAULT_LEVEL, true); + } + + public ZipOutputStream(OutputStream outStream) { + this(outStream, 4 * 1024); + } + + public void putNextEntry(ZipEntry e) throws IOException { + e.offset = bytesWritten; + currentEntry = e; + entries.add(e); + writeLocalHeader(e); + } + + public void closeEntry() throws IOException { + deflater.finish(); + while (!deflater.finished()) { + deflate(); + } + deflater.dispose(); + deflater.reset(); + + currentEntry.crc = (int) crc.getValue(); + crc.reset(); + writeDataDescriptor(currentEntry); + } + + private void writeLocalHeader(ZipEntry e) throws IOException { + writeFourBytes(SIGNATURE); // local header signature + writeTwoBytes(VERSION); // version used + writeTwoBytes(BITFLAG); // flags + writeTwoBytes(METHOD); // compression method + writeTwoBytes(e.modTime); // last modified time + writeTwoBytes(e.modDate); // last modified date + writeFourBytes(0); // CRC is 0 for local header + + // with default flag settings, the compressed and uncompressed size + // is written here as 0 and written correctly in the data descripter + writeFourBytes(0); // compressed size + writeFourBytes(0); // uncompressed size + writeTwoBytes(e.name.length()); // length of file name + + // extra field length, in this implementation extra field in not used + writeTwoBytes(0); + + // write file name, return the number of bytes written + int len = writeVariableByteLength(e.getName()); + + bytesWritten += 30 + len; + } + + private void writeDataDescriptor(ZipEntry currentEntry) throws IOException { + writeFourBytes(DATA_DESCRIPTER_HEADER); // data descripter header + writeFourBytes(currentEntry.crc); // crc value + writeFourBytes(currentEntry.compSize); // compressed size + writeFourBytes(currentEntry.uncompSize); // uncompressed size + bytesWritten += 16; + } + + public void write(byte[] b, int offset, int length) throws IOException { + currentEntry.uncompSize += length; + crc.update(b, offset, length); + currentEntry.crc = (int) crc.getValue(); + + deflater.setInput(b, offset, length); + while (deflater.getRemaining() > 0) + deflate(); + } + + public void write(int b) throws IOException { + byte[] buf = new byte[1]; + buf[0] = (byte)(b & 0xff); + write(buf, 0, 1); + } + + private void deflate() throws IOException { + int len = deflater.deflate(buffer, 0, buffer.length); + currentEntry.compSize += len; + bytesWritten += len; + if (len > 0) + out.write(buffer, 0 , len); + } + + private void writeCentralDirectoryHeader(ZipEntry e) throws IOException { + writeFourBytes(CENTRAL_FILE_HEADER); // central directory header signature + writeTwoBytes(VERSION); // version made by + writeTwoBytes(VERSION); // version needed + writeTwoBytes(BITFLAG); // flags + writeTwoBytes(METHOD); // compression method + + writeTwoBytes(e.modTime); // last mod time + writeTwoBytes(e.modDate); // last mod date + writeFourBytes(e.crc); // crc + writeFourBytes(e.compSize); // compressed size + writeFourBytes(e.uncompSize); // uncompressed size + + writeTwoBytes(e.getName().length()); // file name length + + // the following 5 fields are all 0 for a simple default compression + writeTwoBytes(0); // extra field length (not used) + writeTwoBytes(0); // comment length (not used) + writeTwoBytes(0); // disk number start + writeTwoBytes(0); // internal file attribute + writeFourBytes(0); // external file attribute + + writeFourBytes((int) e.offset); // relative offset of local header + + int len = writeVariableByteLength(e.getName()); + + bytesWritten += 46 + len; + sizeOfCentralDirectory += 46 + len; + } + + private void writeEndofCentralDirectory(int offset) throws IOException { + short numEntries = (short) entries.size(); + writeFourBytes(END_OF_CENTRAL_DIRECTORY_SIG); // end of central directory signature + writeTwoBytes(0); // disk number + writeTwoBytes(0); // disk number where central dir starts + writeTwoBytes(numEntries); // number of entries on this disk + writeTwoBytes(numEntries); // number of entries in central dir + writeFourBytes(sizeOfCentralDirectory); // length of central directory + writeFourBytes(offset); // offset of central directory + writeTwoBytes(0); // length of added comments (not used) + bytesWritten += 22; + } + + public void close() throws IOException { + int centralDirOffset = bytesWritten; + for (ZipEntry e : entries) + writeCentralDirectoryHeader(e); + writeEndofCentralDirectory(centralDirOffset); + } + + private void writeTwoBytes(int bytes) throws IOException { + out.write(bytes & 0xff); + out.write((bytes >> 8) & 0xff); + } + + private void writeFourBytes(int bytes) throws IOException { + out.write(bytes & 0xff); + out.write((bytes >> 8) & 0xff); + out.write((bytes >> 16) & 0xff); + out.write((bytes >> 24) & 0xff); + } + + private int writeVariableByteLength(String text) throws IOException { + byte[] bytes = text.getBytes("UTF-8"); + out.write(bytes, 0, bytes.length); + return bytes.length; + } +} \ No newline at end of file From 6970bb26ae429b870bfe57648bbba7743d9e8f6b Mon Sep 17 00:00:00 2001 From: Chris Jordan Date: Mon, 8 Jul 2013 13:55:00 -0600 Subject: [PATCH 2/6] updating zipentry --- classpath/java/util/zip/ZipEntry.java | 215 ++++++++++++++++---------- 1 file changed, 133 insertions(+), 82 deletions(-) diff --git a/classpath/java/util/zip/ZipEntry.java b/classpath/java/util/zip/ZipEntry.java index b396127187..d92e9f8113 100644 --- a/classpath/java/util/zip/ZipEntry.java +++ b/classpath/java/util/zip/ZipEntry.java @@ -1,45 +1,54 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + package java.util.zip; -import java.util.Calendar; -import java.util.Date; - /** - * class ZipEntry: + * Class ZipEntry: * * Class to store and retrieve information for entries in a zip file * Contains variables for all standard zip format field as well as * setter and accessor methods * - * @author ReadyTalk Summer 2013 Intern Team - **/ + * "name" is used to store the string name of the entrys + * "reqVersion" stores a byte encoded minimum required version to open the zip + * "compressionMethod" stores the method used to compress the zip + * "modTimeDate" stores an MSDOS time field "millisTime" stores time in long format + * "crc" stores crc data for the zip entry + * "compSize" and "uncompSize" store compressed and uncompressed sizes + * "offset" stores data regarding the offset from the start of the zip file + * + * @author Christopher Jordan + * @author David Chau + * @author Aaron Davis + * @author Riley Moses + */ + +import java.util.Calendar; +import java.util.Date; + + public class ZipEntry { String name; - //Minimum version needed to extract the file(s) from a compressed state - short reqVersion; - - //Method used to compress file - short compressionMethod; - - //Format of date and time are both 2 byte fields - short modTime; - short modDate; - - //CRC-32 - int crc; - - //Sizes of file - int compSize; - int uncompSize; - - int offset; + short reqVersion = -1; + short compressionMethod = -1; + int modTimeDate = -1; + long millisTime = -1; + int crc = -1; + int compSize = 0; + int uncompSize = 0; + int offset = -1; public ZipEntry(String name) { this.name = name; - setTimeDate(); - compSize = 0; - uncompSize = 0; - crc = 0; - offset = 0; + setTime(System.currentTimeMillis()); } //Method to return name of the file @@ -52,19 +61,69 @@ public class ZipEntry { return getName().endsWith("/"); } - //Method to return the compressed size of the file - public int getCompressedSize() { - return compSize; - } - - //Method to return the uncompressed size of the file - public int getSize() { - return uncompSize; + /** + * Method setRequiredVersion: + * + * Method to set the minimum version required to open the zip file + * Valid values for the compression method are the numbers 1.0 to 10.0 + * + * @author Christopher Jordan + */ + private boolean setRequiredVersion(float versionFloat){ + //Check for valid version numbers + if (versionFloat < 1 || versionFloat > 100){ + return false; + } + + //Convert to short value for storage + versionFloat = versionFloat * 10; + short versionShort = (short)versionFloat; + + //Set value of version + reqVersion = versionShort; + return true; } + //Method to set the compression method for the file + //Valid methods are "stored" = 0 or "deflated" = 8 + public void setMethod(short compMethod){ + if (compMethod == 0 || compMethod == 8){ + this.compressionMethod = compMethod; + } + } + + public int getMethod(){ + return this.compressionMethod; + } + + //Methods to set and get the crc for the entry + public void setCrc(int crc){ + if (crc < 0 || crc > 0xffffffff){ + return; + } + else + this.crc = crc; + } + + public int getCrc(){ + return this.crc; + } + + //Methods to set and get the time and date + public void setTime(long currentTime){ + modTimeDate = computeDOSDateTime(currentTime); + millisTime = currentTime; + } + + public long getTime(){ + return millisTime; + + } + /** - * Method setTime Date(): - * Creates a calendar object to retrieve date and time information + * Method computeDOSDateTime(): + * + * Takes the time from a long and converts to a Calendar object * * Time is stored in the MSDOS format 5 bits for hour, 6 bits for min, 5 bits for seconds * Hours are in military time and must be adjusted to time zone @@ -76,9 +135,10 @@ public class ZipEntry { * * Bit masks and shifting are used to build the time and date bytes * - * @author cjordan + * @author Christopher Jordan + * @author Aaron Davis **/ - public void setTimeDate(){ + private static int computeDOSDateTime(long currentTime){ final int DAY_OF_MONTH = 5; final int HOUR_OF_DAY = 11; final int MINUTE = 12; @@ -86,11 +146,13 @@ public class ZipEntry { final int SECOND = 13; final int YEAR = 1; + //Create calendar object, then set time to value passed in Calendar modCalendar = Calendar.getInstance(); + modCalendar.setTime(new Date(currentTime)); //Hour int timeBits = modCalendar.get(HOUR_OF_DAY); - timeBits = timeBits - 6; + timeBits = timeBits - 6; timeBits = timeBits << 6; //Minutes @@ -100,18 +162,15 @@ public class ZipEntry { //Seconds int secBits = 0x1f & (modCalendar.get(SECOND)); - secBits = secBits >> 1; + secBits = secBits >> 1; //Divide by 2 timeBits = timeBits ^ secBits; - //Store Time - modTime = (short)timeBits; - //Year int dateBits = (modCalendar.get(YEAR) -1980); dateBits = dateBits << 4; //Month - int month = 0xf & ((modCalendar.get(MONTH)) + 1); + int month = 0xf & ((modCalendar.get(MONTH)) + 1); dateBits = dateBits ^ month; dateBits = dateBits << 5; @@ -120,41 +179,33 @@ public class ZipEntry { dateBits = dateBits ^ dayBits; //Store Date - modDate = (short)dateBits; - + int storeDate = ((dateBits << 16) ^ (timeBits)); + return storeDate; } - - //Method to set the minimum version required to open the zip file - //Valid values for the compression method are the numbers 1.0 to 10.0 - public boolean setRequiredVersion(float versionFloat){ - //Check for valid version numbers - if (versionFloat < 1 || versionFloat > 100){ - return false; - } - - //Convert to short value for storage - versionFloat = versionFloat * 10; - short versionShort = (short)versionFloat; - - //Set value of version - reqVersion = versionShort; - return true; + + //Methods to set and get the uncompressed size of the entry + public void setSize(int size){ + if (size < 0){ + return; + } + else + uncompSize = size; } - - //Method to set the compression method for the file - //Valid values for the compression method are the numbers 0 to 19 and 99 - public boolean setCompressionMethod(short compMethod){ - if (compMethod == 99){ - compressionMethod = compMethod; - return true; - } - else if (compMethod < 0 || compMethod > 19){ - return false; - } - else{ - compressionMethod = compMethod; - return true; - } + + public int getSize() { + return uncompSize; } - -} \ No newline at end of file + + //Methods to set and get the compressed size of the entry + public void setCompressedSize(int size){ + if (size < 0){ + return; + } + else + compSize = size; + } + + public int getCompressedSize() { + return compSize; + } +} From 54484bc2eb22f475311a66688ffdb98161a1e444 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Mon, 8 Jul 2013 14:13:08 -0600 Subject: [PATCH 3/6] add zipOutputStream and change DeflaterOutputStream --- .../java/util/zip/DeflaterOutputStream.java | 10 +- classpath/java/util/zip/ZipOutputStream.java | 246 +++++++++++------- 2 files changed, 151 insertions(+), 105 deletions(-) diff --git a/classpath/java/util/zip/DeflaterOutputStream.java b/classpath/java/util/zip/DeflaterOutputStream.java index c0a2a00208..64bbcb9be4 100644 --- a/classpath/java/util/zip/DeflaterOutputStream.java +++ b/classpath/java/util/zip/DeflaterOutputStream.java @@ -11,16 +11,16 @@ package java.util.zip; import java.io.OutputStream; +import java.io.FilterOutputStream; import java.io.IOException; -public class DeflaterOutputStream extends OutputStream { - private final OutputStream out; - private final Deflater deflater; - private final byte[] buffer; +public class DeflaterOutputStream extends FilterOutputStream { + protected final Deflater deflater; + protected final byte[] buffer; public DeflaterOutputStream(OutputStream out, Deflater deflater, int bufferSize) { - this.out = out; + super(out); this.deflater = deflater; this.buffer = new byte[bufferSize]; } diff --git a/classpath/java/util/zip/ZipOutputStream.java b/classpath/java/util/zip/ZipOutputStream.java index aa00705375..02702218da 100644 --- a/classpath/java/util/zip/ZipOutputStream.java +++ b/classpath/java/util/zip/ZipOutputStream.java @@ -1,3 +1,13 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + package java.util.zip; import java.io.IOException; @@ -8,7 +18,6 @@ import java.util.List; import java.util.zip.CRC32; import java.util.zip.DeflaterOutputStream; import java.util.zip.Deflater; -import java.io.BufferedOutputStream; /** @@ -16,8 +25,12 @@ import java.io.BufferedOutputStream; * Compression method defaults to DEFLATE * All hardcoded defaults match the defaults for openJDK, * including PKZip version, bit flags set, compression level, etc + * + * @author David Chau + * @author Aaron Davis + * @author Christopher Jordan + * @author Riley Moses * - * @author ReadyTalk Summer 2013 Intern Team */ public class ZipOutputStream extends DeflaterOutputStream { private static final int SIGNATURE = 0x04034b50; @@ -28,30 +41,25 @@ public class ZipOutputStream extends DeflaterOutputStream { private static final int DATA_DESCRIPTER_HEADER = 0x08074b50; private static final int END_OF_CENTRAL_DIRECTORY_SIG = 0x06054b50; private static final int DEFAULT_LEVEL = 6; + + private static final int INPUT_BUFFER_SIZE = 1024; - private final OutputStream out; - private DeflaterOutputStream deflaterStream; - private Deflater deflater; private List entries; private CRC32 crc = new CRC32(); - private ZipEntry currentEntry; - - private int bytesWritten; - private int sizeOfCentralDirectory; - private byte[] buffer; + private ZipEntry currentEntry; // holder for current entry + private int bytesWritten; // a counter for total bytes written + private int sizeOfCentralDirectory; // a counter for central dir size - public ZipOutputStream(OutputStream outStream, int bufferSize) { - super(outStream); - out = outStream; + // these are used for the function write(int b) to provide a speed increase + private byte[] inputBuffer = new byte[INPUT_BUFFER_SIZE]; + private int bufferIndex; + + + public ZipOutputStream(OutputStream outStream) { + super(outStream, new Deflater(DEFAULT_LEVEL, true)); bytesWritten = 0; sizeOfCentralDirectory = 0; entries = new ArrayList(); - buffer = new byte[bufferSize]; - deflater = new Deflater(DEFAULT_LEVEL, true); - } - - public ZipOutputStream(OutputStream outStream) { - this(outStream, 4 * 1024); } public void putNextEntry(ZipEntry e) throws IOException { @@ -62,51 +70,24 @@ public class ZipOutputStream extends DeflaterOutputStream { } public void closeEntry() throws IOException { - deflater.finish(); - while (!deflater.finished()) { - deflate(); + // write remainder of buffer if partially full + if (bufferIndex != 0) { + write(inputBuffer, 0, bufferIndex); + bufferIndex = 0; } - deflater.dispose(); - deflater.reset(); + + finish(); currentEntry.crc = (int) crc.getValue(); crc.reset(); writeDataDescriptor(currentEntry); } - private void writeLocalHeader(ZipEntry e) throws IOException { - writeFourBytes(SIGNATURE); // local header signature - writeTwoBytes(VERSION); // version used - writeTwoBytes(BITFLAG); // flags - writeTwoBytes(METHOD); // compression method - writeTwoBytes(e.modTime); // last modified time - writeTwoBytes(e.modDate); // last modified date - writeFourBytes(0); // CRC is 0 for local header - - // with default flag settings, the compressed and uncompressed size - // is written here as 0 and written correctly in the data descripter - writeFourBytes(0); // compressed size - writeFourBytes(0); // uncompressed size - writeTwoBytes(e.name.length()); // length of file name - - // extra field length, in this implementation extra field in not used - writeTwoBytes(0); - - // write file name, return the number of bytes written - int len = writeVariableByteLength(e.getName()); - - bytesWritten += 30 + len; - } - - private void writeDataDescriptor(ZipEntry currentEntry) throws IOException { - writeFourBytes(DATA_DESCRIPTER_HEADER); // data descripter header - writeFourBytes(currentEntry.crc); // crc value - writeFourBytes(currentEntry.compSize); // compressed size - writeFourBytes(currentEntry.uncompSize); // uncompressed size - bytesWritten += 16; - } - + @Override public void write(byte[] b, int offset, int length) throws IOException { + if (offset < 0 || length < 0 || b.length - (offset + length) < 0) + throw new IndexOutOfBoundsException(); + currentEntry.uncompSize += length; crc.update(b, offset, length); currentEntry.crc = (int) crc.getValue(); @@ -116,10 +97,14 @@ public class ZipOutputStream extends DeflaterOutputStream { deflate(); } + @Override public void write(int b) throws IOException { - byte[] buf = new byte[1]; - buf[0] = (byte)(b & 0xff); - write(buf, 0, 1); + inputBuffer[bufferIndex] = (byte)(b & 0xff); + bufferIndex += 1; + if (bufferIndex == 1024) { + write(inputBuffer, 0, bufferIndex); + bufferIndex = 0; + } } private void deflate() throws IOException { @@ -127,72 +112,133 @@ public class ZipOutputStream extends DeflaterOutputStream { currentEntry.compSize += len; bytesWritten += len; if (len > 0) - out.write(buffer, 0 , len); + out.write(buffer, 0, len); + } + + private void writeLocalHeader(ZipEntry e) throws IOException { + byte[] tmpBuffer = new byte[30]; + + addFourBytes(SIGNATURE, 0, tmpBuffer); // local header signature + addTwoBytes(VERSION, 4, tmpBuffer); // version used + addTwoBytes(BITFLAG, 6, tmpBuffer); // flags + addTwoBytes(METHOD, 8, tmpBuffer); // compression method + addFourBytes(e.modTimeDate, 10, tmpBuffer); // last mod date and time + addFourBytes(0, 14, tmpBuffer); // CRC is 0 for local header + + // with default flag settings (bit 3 set) the compressed and uncompressed size + // is written here as 0 and written correctly in the data descripter + addFourBytes(0, 18, tmpBuffer); // compressed size + addFourBytes(0, 22, tmpBuffer); // uncompressed size + addTwoBytes(e.name.length(), 26, tmpBuffer); // length of file name + + // extra field length, in this implementation extra field in not used + addTwoBytes(0, 28, tmpBuffer); + + out.write(tmpBuffer, 0, 30); + + // write file name, return the number of bytes written + int len = writeUTF8(e.getName()); + + bytesWritten += 30 + len; + } + + private void writeDataDescriptor(ZipEntry currentEntry) throws IOException { + byte[] tmpBuffer = new byte[16]; + + addFourBytes(DATA_DESCRIPTER_HEADER, 0, tmpBuffer); // data descripter header + addFourBytes(currentEntry.crc, 4, tmpBuffer); // crc value + addFourBytes(currentEntry.compSize, 8, tmpBuffer); // compressed size + addFourBytes(currentEntry.uncompSize, 12, tmpBuffer);// uncompressed size + out.write(tmpBuffer, 0, 16); + bytesWritten += 16; } private void writeCentralDirectoryHeader(ZipEntry e) throws IOException { - writeFourBytes(CENTRAL_FILE_HEADER); // central directory header signature - writeTwoBytes(VERSION); // version made by - writeTwoBytes(VERSION); // version needed - writeTwoBytes(BITFLAG); // flags - writeTwoBytes(METHOD); // compression method + byte[] tmpBuffer = new byte[46]; + + addFourBytes(CENTRAL_FILE_HEADER, 0, tmpBuffer); // central directory header signature + addTwoBytes(VERSION, 4, tmpBuffer); // version made by + addTwoBytes(VERSION, 6, tmpBuffer); // version needed + addTwoBytes(BITFLAG, 8, tmpBuffer); // flags + addTwoBytes(METHOD, 10, tmpBuffer); // compression method - writeTwoBytes(e.modTime); // last mod time - writeTwoBytes(e.modDate); // last mod date - writeFourBytes(e.crc); // crc - writeFourBytes(e.compSize); // compressed size - writeFourBytes(e.uncompSize); // uncompressed size + addFourBytes(e.modTimeDate, 12, tmpBuffer); // last mod date and time + addFourBytes(e.crc, 16, tmpBuffer); // crc + addFourBytes(e.compSize, 20, tmpBuffer); // compressed size + addFourBytes(e.uncompSize, 24, tmpBuffer); // uncompressed size - writeTwoBytes(e.getName().length()); // file name length + addTwoBytes(e.getName().length(), 28, tmpBuffer); // file name length // the following 5 fields are all 0 for a simple default compression - writeTwoBytes(0); // extra field length (not used) - writeTwoBytes(0); // comment length (not used) - writeTwoBytes(0); // disk number start - writeTwoBytes(0); // internal file attribute - writeFourBytes(0); // external file attribute + addTwoBytes(0, 30, tmpBuffer); // extra field length (not used) + addTwoBytes(0, 32, tmpBuffer); // comment length (not used) + addTwoBytes(0, 34, tmpBuffer); // disk number start + addTwoBytes(0, 36, tmpBuffer); // internal file attribute + addFourBytes(0, 38, tmpBuffer); // external file attribute - writeFourBytes((int) e.offset); // relative offset of local header + addFourBytes((int) e.offset, 42, tmpBuffer); // relative offset of local header - int len = writeVariableByteLength(e.getName()); + out.write(tmpBuffer, 0, 46); + + int len = writeUTF8(e.getName()); bytesWritten += 46 + len; sizeOfCentralDirectory += 46 + len; } private void writeEndofCentralDirectory(int offset) throws IOException { + byte[] tmpBuffer = new byte[22]; + short numEntries = (short) entries.size(); - writeFourBytes(END_OF_CENTRAL_DIRECTORY_SIG); // end of central directory signature - writeTwoBytes(0); // disk number - writeTwoBytes(0); // disk number where central dir starts - writeTwoBytes(numEntries); // number of entries on this disk - writeTwoBytes(numEntries); // number of entries in central dir - writeFourBytes(sizeOfCentralDirectory); // length of central directory - writeFourBytes(offset); // offset of central directory - writeTwoBytes(0); // length of added comments (not used) + addFourBytes(END_OF_CENTRAL_DIRECTORY_SIG, 0, tmpBuffer); // end of central directory signature + addTwoBytes(0, 4, tmpBuffer); // disk number + addTwoBytes(0, 6, tmpBuffer); // disk number where central dir starts + addTwoBytes(numEntries, 8, tmpBuffer); // number of entries on this disk + addTwoBytes(numEntries, 10, tmpBuffer); // number of entries in central dir + addFourBytes(sizeOfCentralDirectory, 12, tmpBuffer); // length of central directory + addFourBytes(offset, 16, tmpBuffer); // offset of central directory + addTwoBytes(0, 20, tmpBuffer); // length of added comments (not used) + out.write(tmpBuffer, 0, 22); bytesWritten += 22; } - + + @Override public void close() throws IOException { int centralDirOffset = bytesWritten; for (ZipEntry e : entries) writeCentralDirectoryHeader(e); writeEndofCentralDirectory(centralDirOffset); - } - - private void writeTwoBytes(int bytes) throws IOException { - out.write(bytes & 0xff); - out.write((bytes >> 8) & 0xff); - } - - private void writeFourBytes(int bytes) throws IOException { - out.write(bytes & 0xff); - out.write((bytes >> 8) & 0xff); - out.write((bytes >> 16) & 0xff); - out.write((bytes >> 24) & 0xff); + deflater.dispose(); + out.close(); } - private int writeVariableByteLength(String text) throws IOException { + @Override + public void flush() throws IOException { + out.write(inputBuffer, 0, inputBuffer.length); + inputBuffer = new byte[INPUT_BUFFER_SIZE]; + } + + private void finish() throws IOException { + deflater.finish(); + while (!deflater.finished()) { + deflate(); + } + deflater.reset(); + } + + private void addTwoBytes(int bytes, int offset, byte[] buffer) throws IOException { + buffer[offset] = (byte) (bytes & 0xff); + buffer[offset + 1] = (byte) ((bytes >> 8) & 0xff); + } + + private void addFourBytes(int bytes, int offset, byte[] buffer) throws IOException { + buffer[offset] = (byte) (bytes & 0xff); + buffer[offset + 1] = (byte) ((bytes >> 8) & 0xff); + buffer[offset + 2] = (byte) ((bytes >> 16) & 0xff); + buffer[offset + 3] = (byte) ((bytes >> 24) & 0xff); + } + + private int writeUTF8(String text) throws IOException { byte[] bytes = text.getBytes("UTF-8"); out.write(bytes, 0, bytes.length); return bytes.length; From 08d441a90576ab107f5975f769464767af65680a Mon Sep 17 00:00:00 2001 From: Riley Moses Date: Mon, 8 Jul 2013 14:50:17 -0600 Subject: [PATCH 4/6] Add tests for creating zip files using both write methods --- test/ZipOutputStreamTest.java | 210 ++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 test/ZipOutputStreamTest.java diff --git a/test/ZipOutputStreamTest.java b/test/ZipOutputStreamTest.java new file mode 100644 index 0000000000..e97d9a9a06 --- /dev/null +++ b/test/ZipOutputStreamTest.java @@ -0,0 +1,210 @@ +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.*; + +public class ZipOutputStreamTest +{ + private static final String TEST1 = "test1.txt"; + private static final String TEST2 = "test2.txt"; + private static final String TEST3 = "test3.txt"; + private static final String TEST4 = "test4.txt"; + + private static final String TEST1_CONTENTS = "\"this is a test\""; + private static final String TEST2_CONTENTS = "this is a\nmulti-line test"; + private static final String TEST3_CONTENTS = "74 68 69 73 20 69 73 20 61 20 74 65 73 74"; + private static final String TEST4_CONTENTS = "01110100 01101000 01101001 01110011 00100000 01101001 01110011 00100000 01100001 00100000 01110100 01100101 01110011 01110100"; + + private static final String ONE_PARAM_ZIP_PREFIX = "zos1param"; + private static final String THREE_PARAM_ZIP_PREFIX = "zos3param"; + private static final String ZIP_SUFFIX = ".zip"; + + private static final Map FILES_CONTENTS; + static + { + Map m = new HashMap(); + m.put(TEST1, TEST1_CONTENTS); + m.put(TEST2, TEST2_CONTENTS); + m.put(TEST3, TEST3_CONTENTS); + m.put(TEST4, TEST4_CONTENTS); + FILES_CONTENTS = Collections.unmodifiableMap(m); + } + + private static final boolean USE_ONE_PARAM_WRITE = true; + private static final boolean USE_THREE_PARAM_WRITE = false; + private static byte[] buffer = new byte[1024]; + + public static void main(String[] args) + { + List zipFiles = new ArrayList(2); + + // Test 1-param write function + File f1 = createZip(USE_ONE_PARAM_WRITE); + zipFiles.add(f1); + verifyContents(f1.getAbsolutePath()); + // Test 3-param write function + File f2 = createZip(USE_THREE_PARAM_WRITE); + zipFiles.add(f2); + verifyContents(f2.getAbsolutePath()); + // Remove the created zip files + cleanUp(zipFiles); + } + + private static File createZip(boolean useOneParam) + { + FileOutputStream outputStream = null; + ZipOutputStream zipContents = null; + + try + { + // Create a temporary zip file for this test + String prefix = useOneParam ? ONE_PARAM_ZIP_PREFIX : THREE_PARAM_ZIP_PREFIX; + File outputZip = File.createTempFile(prefix, ZIP_SUFFIX); + + System.out.println("Created " + outputZip.getAbsolutePath()); + + // Prepare the streams + outputStream = new FileOutputStream(outputZip); + zipContents = new ZipOutputStream(outputStream); + + // Zip the file contents (convert directly from string to bytes) + long startTime = System.currentTimeMillis(); + for (Map.Entry f : FILES_CONTENTS.entrySet()) + { + String name = f.getKey(); + String contents = f.getValue(); + + System.out.println("Zipping " + name + "..."); + ZipEntry entry = new ZipEntry(name); + zipContents.putNextEntry(entry); + + byte[] bytesToWrite = contents.getBytes(); + + if (useOneParam) + { + // Use the 1-parameter write method; takes a single byte + for (int i = 0; i < bytesToWrite.length; i++) + { + zipContents.write(bytesToWrite[i]); + } + } + else + { + // Use 3-parameter write method; takes a buffer, offset, and length + zipContents.write(bytesToWrite, 0 , bytesToWrite.length); + } + + // Done with this file + zipContents.closeEntry(); + System.out.println("Done"); + } + + // All files have been written + long endTime = System.currentTimeMillis(); + System.out.println("Finished " + outputZip.getName() + " in " + ((endTime - startTime) / 1000.0) + " seconds"); + return outputZip; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + finally + { + try + { + if (zipContents != null) + zipContents.close(); + if (outputStream != null) + outputStream.close(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + } + + private static void verifyContents(String zipName) + { + System.out.println("Verify " + zipName); + ZipFile zf = null; + BufferedReader reader = null; + int numFilesInZip = 0; + + try + { + String line; + String contents; + + // Get the contents of each file in the zip + zf = new ZipFile(zipName); + for (Enumeration e = zf.entries(); e.hasMoreElements();) + { + ZipEntry entry = e.nextElement(); + reader = new BufferedReader(new InputStreamReader(zf.getInputStream(entry))); + contents = ""; + numFilesInZip += 1; + + while ((line = reader.readLine()) != null) + { + if (contents.length() > 0) + { + contents += "\n"; + } + contents += line; + } + reader.close(); + + // Assert that this file's contents are correct + assert(contents.equals(FILES_CONTENTS.get(entry.getName()))); + } + zf.close(); + + // Assert that the zip contained the correct number of files + assert(numFilesInZip == FILES_CONTENTS.size()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + finally + { + try + { + if (zf != null) + zf.close(); + if (reader != null) + reader.close(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + } + + private static void cleanUp(List zipFiles) + { + try + { + for (File f : zipFiles) + { + if (f.exists()) + { + f.delete(); + } + } + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} From 8eec1a0339f08cfe13888fd846dc897c6a54380c Mon Sep 17 00:00:00 2001 From: Chris Jordan Date: Mon, 8 Jul 2013 16:30:17 -0600 Subject: [PATCH 5/6] fixing problems! --- classpath/java/util/jar/JarEntry.java | 6 +++++- classpath/java/util/zip/ZipEntry.java | 3 +++ classpath/java/util/zip/ZipFile.java | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/classpath/java/util/jar/JarEntry.java b/classpath/java/util/jar/JarEntry.java index dceaf66459..a5463b2dc0 100644 --- a/classpath/java/util/jar/JarEntry.java +++ b/classpath/java/util/jar/JarEntry.java @@ -12,4 +12,8 @@ package java.util.jar; import java.util.zip.ZipEntry; -public abstract class JarEntry extends ZipEntry { } +public abstract class JarEntry extends ZipEntry { + public JarEntry(){ + super(null); + } +} diff --git a/classpath/java/util/zip/ZipEntry.java b/classpath/java/util/zip/ZipEntry.java index d92e9f8113..ab7794ba43 100644 --- a/classpath/java/util/zip/ZipEntry.java +++ b/classpath/java/util/zip/ZipEntry.java @@ -117,7 +117,10 @@ public class ZipEntry { public long getTime(){ return millisTime; + } + public int getJavaTime(){ + return modTimeDate; } /** diff --git a/classpath/java/util/zip/ZipFile.java b/classpath/java/util/zip/ZipFile.java index a4735a2469..b45fead8f7 100644 --- a/classpath/java/util/zip/ZipFile.java +++ b/classpath/java/util/zip/ZipFile.java @@ -285,6 +285,7 @@ public class ZipFile { public final int pointer; public MyZipEntry(Window window, int pointer) { + super(null); this.window = window; this.pointer = pointer; } From 2d89826ec7c024e80bdc132ced24fdd9e9862cc6 Mon Sep 17 00:00:00 2001 From: Chris Jordan Date: Mon, 8 Jul 2013 16:46:00 -0600 Subject: [PATCH 6/6] zipentry test --- test/ZipEntryTest.java | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 test/ZipEntryTest.java diff --git a/test/ZipEntryTest.java b/test/ZipEntryTest.java new file mode 100644 index 0000000000..4817300741 --- /dev/null +++ b/test/ZipEntryTest.java @@ -0,0 +1,56 @@ +/* Copyright (c) 2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +import java.util.zip.ZipEntry; + +/** + * class ZipEntryTest: + * + * class to test the ZipEntry class in java.util.zip + * + * @author Christopher Jordan + */ +public class ZipEntryTest { + static long timeInMillis = 1373309644787L; + static int dateInBytes = 1122526914; + + public static void main(String args[]){ + ZipEntry testEntry = new ZipEntry("testfile"); + + verifyDefaultValues(testEntry); + verifyTimeDate(testEntry, timeInMillis); + checkSetsAndGets(testEntry); + } + + private static void verifyDefaultValues(ZipEntry testEntry){ + if (testEntry.getTime() == -1) + throw new RuntimeException("The time isn't being set by the constructor"); + verifyName(testEntry); + } + + private static void verifyName(ZipEntry testEntry){ + if (testEntry.getName() == "testfile") + return; + else + throw new RuntimeException("Name isn't being stored properly"); + } + + private static void verifyTimeDate(ZipEntry testEntry, long timeMillis){ + testEntry.setTime(timeMillis); + if (testEntry.getJavaTime() != dateInBytes) + throw new RuntimeException("Date isn't being parsed properly"); + if (testEntry.getTime() != timeInMillis) + throw new RuntimeException("Time isn't being stored accurately"); + } + + private static void checkSetsAndGets(ZipEntry testEntry){ + return; + } +}