Return-Path: X-Original-To: apmail-commons-commits-archive@minotaur.apache.org Delivered-To: apmail-commons-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 5F58A102A7 for ; Thu, 18 Dec 2014 20:52:01 +0000 (UTC) Received: (qmail 65429 invoked by uid 500); 18 Dec 2014 20:52:01 -0000 Delivered-To: apmail-commons-commits-archive@commons.apache.org Received: (qmail 65353 invoked by uid 500); 18 Dec 2014 20:52:01 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 65342 invoked by uid 99); 18 Dec 2014 20:52:01 -0000 Received: from eris.apache.org (HELO hades.apache.org) (140.211.11.105) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 18 Dec 2014 20:52:01 +0000 Received: from hades.apache.org (localhost [127.0.0.1]) by hades.apache.org (ASF Mail Server at hades.apache.org) with ESMTP id 64BD8AC08CC; Thu, 18 Dec 2014 20:51:59 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1646531 - in /commons/proper/compress/trunk: ./ src/main/java/org/apache/commons/compress/archivers/zip/ src/test/java/org/apache/commons/compress/ src/test/java/org/apache/commons/compress/archivers/ Date: Thu, 18 Dec 2014 20:51:57 -0000 To: commits@commons.apache.org From: krosenvold@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20141218205200.64BD8AC08CC@hades.apache.org> Author: krosenvold Date: Thu Dec 18 20:51:57 2014 New Revision: 1646531 URL: http://svn.apache.org/r1646531 Log: COMPRESS-295 Add support for transferring a zip entry from one zip file to another Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryPredicate.java Modified: commons/proper/compress/trunk/.gitignore commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/AbstractTestCase.java commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java Modified: commons/proper/compress/trunk/.gitignore URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/.gitignore?rev=1646531&r1=1646530&r2=1646531&view=diff ============================================================================== --- commons/proper/compress/trunk/.gitignore (original) +++ commons/proper/compress/trunk/.gitignore Thu Dec 18 20:51:57 2014 @@ -2,4 +2,6 @@ target .project .classpath .settings +.idea +*.iml *~ Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryPredicate.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryPredicate.java?rev=1646531&view=auto ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryPredicate.java (added) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryPredicate.java Thu Dec 18 20:51:57 2014 @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.commons.compress.archivers.zip; + +/** + * A predicate to test if a #ZipArchiveEntry matches a criteria. + * Some day this can extend java.util.function.Predicate + */ +public interface ZipArchiveEntryPredicate { + /** + * Indicate if the given entry should be included in the operation + * @param zipArchiveEntry the entry to test + * @return true if the entry should be included + */ + boolean test(ZipArchiveEntry zipArchiveEntry); +} Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java?rev=1646531&r1=1646530&r2=1646531&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java Thu Dec 18 20:51:57 2014 @@ -20,9 +20,11 @@ package org.apache.commons.compress.arch import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -432,27 +434,36 @@ public class ZipArchiveOutputStream exte */ @Override public void closeArchiveEntry() throws IOException { - if (finished) { - throw new IOException("Stream has already been finished"); - } - - if (entry == null) { - throw new IOException("No current entry to close"); - } - - if (!entry.hasWritten) { - write(EMPTY, 0, 0); - } + preClose(); flushDeflater(); - final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); long bytesWritten = written - entry.dataStart; long realCrc = crc.getValue(); crc.reset(); + doCloseEntry(realCrc, bytesWritten); + } + + /** + * Writes all necessary data for this entry. + * + * @throws IOException on error + * @throws Zip64RequiredException if the entry's uncompressed or + * compressed size exceeds 4 GByte and {@link #setUseZip64} + * is {@link Zip64Mode#Never}. + */ + private void closeCopiedEntry() throws IOException { + preClose(); + long realCrc = entry.entry.getCrc(); + entry.bytesRead = entry.entry.getSize(); + doCloseEntry(realCrc, entry.entry.getCompressedSize()); + } + + private void doCloseEntry(long realCrc, long bytesWritten) throws IOException { + final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); final boolean actuallyNeedsZip64 = - handleSizesAndCrc(bytesWritten, realCrc, effectiveMode); + handleSizesAndCrc(bytesWritten, realCrc, effectiveMode); if (raf != null) { rewriteSizesAndCrc(actuallyNeedsZip64); @@ -462,6 +473,37 @@ public class ZipArchiveOutputStream exte entry = null; } + private void preClose() throws IOException { + if (finished) { + throw new IOException("Stream has already been finished"); + } + + if (entry == null) { + throw new IOException("No current entry to close"); + } + + if (!entry.hasWritten) { + write(EMPTY, 0, 0); + } + } + + /** + * Adds an archive entry with a raw input stream. + * + * The entry is put and closed immediately. + * + * @param entry The archive entry to add + * @param rawStream The raw input stream of a different entry. May be compressed/encrypted. + * @throws IOException If copying fails + */ + public void addRawArchiveEntry(ZipArchiveEntry entry, InputStream rawStream) + throws IOException { + ZipArchiveEntry ae = new ZipArchiveEntry((java.util.zip.ZipEntry)entry); + putArchiveEntry(ae); + copyFromZipInputStream(rawStream); + closeCopiedEntry(); + } + /** * Ensures all bytes sent to the deflater are written to the stream. */ @@ -768,6 +810,25 @@ public class ZipArchiveOutputStream exte count(length); } + private void copyFromZipInputStream(InputStream src) throws IOException { + if (entry == null) { + throw new IllegalStateException("No current entry"); + } + ZipUtil.checkRequestedFeatures(entry.entry); + entry.hasWritten = true; + byte[] tmpBuf = new byte[4096]; + int length = src.read( tmpBuf ); + while ( length >= 0 ) + { + writeOut( tmpBuf, 0, length ); + written += length; + crc.update( tmpBuf, 0, length ); + + count( length ); + length = src.read( tmpBuf ); + } + } + /** * write implementation for DEFLATED entries. */ Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java?rev=1646531&r1=1646530&r2=1646531&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java Thu Dec 18 20:51:57 2014 @@ -346,6 +346,44 @@ public class ZipFile implements Closeabl } /** + * Expose the raw stream of the archive entry (compressed form) + *

+ * This method does not relate to how/if we understand the payload in the + * stream, since we really only intend to move it on to somewhere else. + * + * @param ze The entry to get the stream for + * @return The raw input stream containing (possibly) compressed data. + */ + private InputStream getRawInputStream(ZipArchiveEntry ze) { + if (!(ze instanceof Entry)) { + return null; + } + OffsetEntry offsetEntry = ((Entry) ze).getOffsetEntry(); + long start = offsetEntry.dataOffset; + return new BoundedInputStream(start, ze.getCompressedSize()); + } + + + /** + * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream. + * Compression and all other attributes will be as in this file. + * This method transfers entries based on the central directory of the zip file. + * + * @param target The zipArchiveOutputStream to write the entries to + * @param predicate A predicate that selects which entries to write + */ + public void copyRawEntries(ZipArchiveOutputStream target, ZipArchiveEntryPredicate predicate) + throws IOException { + Enumeration src = getEntriesInPhysicalOrder(); + while (src.hasMoreElements()) { + ZipArchiveEntry entry = src.nextElement(); + if (predicate.test( entry)) { + target.addRawArchiveEntry(entry, getRawInputStream(entry)); + } + } + } + + /** * Returns an InputStream for reading the contents of the given entry. * * @param ze the entry to get the stream for. Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/AbstractTestCase.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/AbstractTestCase.java?rev=1646531&r1=1646530&r2=1646531&view=diff ============================================================================== --- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/AbstractTestCase.java (original) +++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/AbstractTestCase.java Thu Dec 18 20:51:57 2014 @@ -392,8 +392,7 @@ public abstract class AbstractTestCase e * element of the two element array). */ protected File[] createTempDirAndFile() throws IOException { - File tmpDir = mkdir("testdir"); - tmpDir.deleteOnExit(); + File tmpDir = createTempDir(); File tmpFile = File.createTempFile("testfile", "", tmpDir); tmpFile.deleteOnExit(); FileOutputStream fos = new FileOutputStream(tmpFile); @@ -405,6 +404,12 @@ public abstract class AbstractTestCase e } } + protected File createTempDir() throws IOException { + File tmpDir = mkdir("testdir"); + tmpDir.deleteOnExit(); + return tmpDir; + } + protected void closeQuietly(Closeable closeable){ if (closeable != null) { try { Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java?rev=1646531&r1=1646530&r2=1646531&view=diff ============================================================================== --- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java (original) +++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java Thu Dec 18 20:51:57 2014 @@ -18,22 +18,14 @@ */ package org.apache.commons.compress.archivers; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.util.ArrayList; import java.util.List; import org.apache.commons.compress.AbstractTestCase; -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; -import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; -import org.apache.commons.compress.archivers.zip.ZipFile; -import org.apache.commons.compress.archivers.zip.ZipMethod; +import org.apache.commons.compress.archivers.zip.*; import org.apache.commons.compress.utils.IOUtils; +import org.junit.Assert; public final class ZipTestCase extends AbstractTestCase { /** @@ -293,6 +285,82 @@ public final class ZipTestCase extends A rmdir(tmp[0]); } } + String first_payload = "ABBA"; + String second_payload = "AAAAAAAAAAAA"; + ZipArchiveEntryPredicate allFilesPredicate = new ZipArchiveEntryPredicate() { + public boolean test(ZipArchiveEntry zipArchiveEntry) { + return true; + } + }; + + + public void testCopyRawEntriesFromFile + () + throws IOException { + + File[] tmp = createTempDirAndFile(); + File reference = createReferenceFile(tmp[0]); + + File a1 = File.createTempFile("src1.", ".zip", tmp[0]); + createFirstEntry(new ZipArchiveOutputStream(a1)).close(); + + File a2 = File.createTempFile("src2.", ".zip", tmp[0]); + createSecondEntry(new ZipArchiveOutputStream(a2)).close(); + + ZipFile zf1 = new ZipFile(a1); + ZipFile zf2 = new ZipFile(a2); + File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]); + ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult); + zf1.copyRawEntries(zos2, allFilesPredicate); + zf2.copyRawEntries(zos2, allFilesPredicate); + zos2.close(); + assertSameFileContents(reference, fileResult); + zf1.close(); + zf2.close(); + } + + private File createReferenceFile(File directory) throws IOException { + File reference = File.createTempFile("expected.", ".zip", directory); + ZipArchiveOutputStream zos = new ZipArchiveOutputStream(reference); + createFirstEntry(zos); + createSecondEntry(zos); + zos.close(); + return reference; + } + + private ZipArchiveOutputStream createFirstEntry(ZipArchiveOutputStream zos) throws IOException { + createArchiveEntry(first_payload, zos, "file1.txt"); + return zos; + } + + private ZipArchiveOutputStream createSecondEntry(ZipArchiveOutputStream zos) throws IOException { + createArchiveEntry(second_payload, zos, "file2.txt"); + return zos; + } + + + private void assertSameFileContents(File expectedFile, File actualFile) throws IOException { + int size = (int) Math.max(expectedFile.length(), actualFile.length()); + byte[] expected = new byte[size]; + byte[] actual = new byte[size]; + final FileInputStream expectedIs = new FileInputStream(expectedFile); + final FileInputStream actualIs = new FileInputStream(actualFile); + IOUtils.readFully(expectedIs, expected); + IOUtils.readFully(actualIs, actual); + expectedIs.close(); + actualIs.close(); + Assert.assertArrayEquals(expected, actual); + } + + + private void createArchiveEntry(String payload, ZipArchiveOutputStream zos, String name) + throws IOException { + ZipArchiveEntry in = new ZipArchiveEntry(name); + zos.putArchiveEntry(in); + + zos.write(payload.getBytes()); + zos.closeArchiveEntry(); + } public void testFileEntryFromFile() throws Exception { File[] tmp = createTempDirAndFile();