/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.core.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.rhq.core.util.stream.StreamUtil;
/**
* A set of utility methods for working with zip files.
*
* @author Ian Springer
*/
public abstract class ZipUtil {
/**
* Zips up the given file or directory and stores the zip file at <code>zipFile</code>.
* Note that zipping up a directory and if the output zip file is to be located in or under
* the directory being zipped, an exception will be thrown - you must output the zip file
* in another location outside of the directory being zipped.
*
* @param fileOrDirToZip what to zip up
* @param zipFile where to store the zip file
* @throws IOException
*/
public static void zipFileOrDirectory(File fileOrDirToZip, File zipFile) throws IOException {
if (fileOrDirToZip.isDirectory()) {
if (zipFile.getParentFile().getAbsolutePath().startsWith(fileOrDirToZip.getAbsolutePath())) {
// if we allowed this, we could go in an infinite loop zipping up the every growing zip file
throw new IOException("Cannot write the zip file [" + zipFile.getAbsolutePath()
+ "] in or under the same directory being zipped [" + fileOrDirToZip.getAbsolutePath() + "]");
}
}
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(new FileOutputStream(zipFile));
zipFileOrDirectory(fileOrDirToZip, zos);
} finally {
if (zos != null) {
zos.close();
}
}
}
public static void zipFileOrDirectory(File fileOrDirToZip, ZipOutputStream zos) throws IOException {
if (fileOrDirToZip.isDirectory()) {
File[] files = fileOrDirToZip.listFiles();
for (File f : files) {
zipFileOrDirectory(f, zos); // recurse
}
} else {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileOrDirToZip);
ZipEntry zipEntry = new ZipEntry(fileOrDirToZip.getPath());
zos.putNextEntry(zipEntry);
StreamUtil.copy(fis, zos, false);
} finally {
if (fis != null) {
fis.close();
}
}
}
}
/**
* Unzips the content of the given zip file to the specified directory.
*
* @param zipFile the zip file to unzip
* @param destDir root directory where the zip files will be extracted
*
* @throws IOException if any errors occur during the reading or writing
*/
public static void unzipFile(File zipFile, File destDir) throws IOException {
destDir.mkdirs();
InputStream is = new BufferedInputStream(new FileInputStream(zipFile));
ZipUtil.unzipFile(is, destDir);
}
/**
* Writes the zip content out to the specified directory. The input stream should contain a valid
* ZIP archive that will be extracted. NOTE: zipContent will be closed by this method.
*
* @param zipContent stream containing the content to write; should be formatted as a ZIP file
* @param outputDir root directory where the zip files will be extracted
*
* @throws IOException if any errors occur during the reading or writing
*/
public static void unzipFile(InputStream zipContent, File outputDir) throws IOException {
try {
// First step is to create the output directory into which to unzip the content stream
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new RuntimeException("Output directory already exists, but is a file, not a directory. File: "
+ outputDir.getAbsolutePath());
}
if (!outputDir.exists()) {
boolean directoryMade = outputDir.mkdirs();
if (!directoryMade) {
throw new RuntimeException("Could not create output directory for unzipped artifact: "
+ outputDir.getAbsolutePath());
}
}
ZipInputStream zis = new ZipInputStream(zipContent);
ZipEntry e;
while ((e = zis.getNextEntry()) != null) {
String entryFileName = e.getName();
File entryFile = new File(outputDir, entryFileName);
if (e.isDirectory()) {
entryFile.mkdirs();
} else {
// JBNADM-3287 - For each file, make sure the directory it is to reside in is created.
String parentDirectory = entryFile.getParent();
File parentDirectoryFile = new File(parentDirectory);
parentDirectoryFile.mkdirs();
FileOutputStream fos = new FileOutputStream(entryFile);
try {
BufferedOutputStream outputStream = new BufferedOutputStream(fos);
try {
StreamUtil.copy(zis, outputStream, false);
} finally {
outputStream.close();
}
} finally {
fos.close();
}
}
}
} finally {
zipContent.close();
}
}
/**
* Walks the entries of a zip file, allowing a listener to "visit" each node and perform tasks on
* the zip entry.
*
* @param zipFile the zip file to walk
* @param visitor the object that will be notified for each entry in the zip file
*
* @throws Exception if any errors occur during the reading or visiting
*/
public static void walkZipFile(File zipFile, ZipEntryVisitor visitor) throws Exception {
FileInputStream fis = new FileInputStream(zipFile);
try {
InputStream zipContent = new BufferedInputStream(fis);
try {
walkZip(zipContent, visitor, false);
} finally {
zipContent.close();
}
} finally {
fis.close();
}
}
/**
* Walks the provided zip file stream. Does NOT close it afterwards.
*
* @param zipFileStream the stream of zip file contents
* @param visitor the visitor to call on each zip entry
* @param readFully true if the whole zip file should be read from the stream even if visitor bailed out, false to
* quit reading as soon as the visitor bails out.
* @throws Exception
* @since 4.13
*/
public static void walkZip(InputStream zipFileStream, ZipEntryVisitor visitor, boolean readFully) throws Exception {
ZipInputStream zis = new ZipInputStream(zipFileStream);
ZipEntry e;
boolean doVisit = true;
while ((e = zis.getNextEntry()) != null) {
doVisit = doVisit && visitor.visit(e, zis);
if (!readFully && !doVisit) {
break;
}
}
}
// prevent instantiation
private ZipUtil() {
}
/**
* Used by {@link ZipUtil#walkZipFile(File, ZipEntryVisitor)} to visit zip entries.
*/
public static interface ZipEntryVisitor {
/**
* Visits a specific zip file entry. Implementations can read the entry content from the given stream but
* must <b>not</b> close the stream - the caller of this method will handle the lifecycle of the stream.
*
* @param entry the entry being visited
* @param stream the stream containing the zip content
* @return the visitor should return <code>true</code> if everything is OK and processing of the zip content
* should continue; returning <code>false</code> will tell the walker to abort further traversing
* of the zip content.
* @throws Exception if the visitation failed for some reason - this will abort further walking of the zip content
*/
boolean visit(ZipEntry entry, ZipInputStream stream) throws Exception;
}
}