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/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/ZipEntry.java b/classpath/java/util/zip/ZipEntry.java index f52da8f4ca..ab7794ba43 100644 --- a/classpath/java/util/zip/ZipEntry.java +++ b/classpath/java/util/zip/ZipEntry.java @@ -10,12 +10,205 @@ package java.util.zip; -public abstract class ZipEntry { - public abstract String getName(); - public abstract int getCompressedSize(); - public abstract int getSize(); +/** + * 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 + * + * "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; + 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; + setTime(System.currentTimeMillis()); + } + + //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 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; + } + + public int getJavaTime(){ + return modTimeDate; + } + + /** + * 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 + * 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 Christopher Jordan + * @author Aaron Davis + **/ + private static int computeDOSDateTime(long currentTime){ + 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; + + //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; + + //Minutes + int minBits = 0x3f & (modCalendar.get(MINUTE));; + timeBits = timeBits ^ minBits; + timeBits = timeBits << 5; + + //Seconds + int secBits = 0x1f & (modCalendar.get(SECOND)); + secBits = secBits >> 1; //Divide by 2 + timeBits = timeBits ^ secBits; + + //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 + int storeDate = ((dateBits << 16) ^ (timeBits)); + return storeDate; + } + + //Methods to set and get the uncompressed size of the entry + public void setSize(int size){ + if (size < 0){ + return; + } + else + uncompSize = size; + } + + public int getSize() { + return uncompSize; + } + + //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; + } } 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; } diff --git a/classpath/java/util/zip/ZipOutputStream.java b/classpath/java/util/zip/ZipOutputStream.java new file mode 100644 index 0000000000..02702218da --- /dev/null +++ b/classpath/java/util/zip/ZipOutputStream.java @@ -0,0 +1,246 @@ +/* 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; +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; + + +/** + * 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 David Chau + * @author Aaron Davis + * @author Christopher Jordan + * @author Riley Moses + * + */ +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 static final int INPUT_BUFFER_SIZE = 1024; + + private List entries; + private CRC32 crc = new CRC32(); + 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 + + // 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(); + } + + public void putNextEntry(ZipEntry e) throws IOException { + e.offset = bytesWritten; + currentEntry = e; + entries.add(e); + writeLocalHeader(e); + } + + public void closeEntry() throws IOException { + // write remainder of buffer if partially full + if (bufferIndex != 0) { + write(inputBuffer, 0, bufferIndex); + bufferIndex = 0; + } + + finish(); + + currentEntry.crc = (int) crc.getValue(); + crc.reset(); + writeDataDescriptor(currentEntry); + } + + @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(); + + deflater.setInput(b, offset, length); + while (deflater.getRemaining() > 0) + deflate(); + } + + @Override + public void write(int b) throws IOException { + inputBuffer[bufferIndex] = (byte)(b & 0xff); + bufferIndex += 1; + if (bufferIndex == 1024) { + write(inputBuffer, 0, bufferIndex); + bufferIndex = 0; + } + } + + 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 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 { + 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 + + 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 + + addTwoBytes(e.getName().length(), 28, tmpBuffer); // file name length + + // the following 5 fields are all 0 for a simple default compression + 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 + + addFourBytes((int) e.offset, 42, tmpBuffer); // relative offset of local header + + 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(); + 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); + deflater.dispose(); + out.close(); + } + + @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; + } +} \ No newline at end of file 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; + } +} 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); + } + } +}