Jedna ze základních vlastností Javy je práce se ZIP archívy, ať už jsou to knihovny tříd a spustitelné JARy, webové aplikace (war), nebo třeba JEE bumbrlíčci (ear). Není tedy divu, že i přímo v základním API je implementována práce s těmito archívy. Slouží k tomu třídy v balíku java.util.zip a nejzajímavější z nich jsou ZipOutputStream a ZipInputStream.
Příkladem budiž vytvoření zipu:
//Vytvorime Zip
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("javlog.zip"));
//V Zipu chceme mit jeden textovy soubor
zos.putNextEntry(new ZipEntry("priznani.txt"));
//naplnime obsah textoveho souboru
zos.write("Máme rádi Javu!".getBytes());
//zavrem entry (priznani.txt)
zos.closeEntry();
//zavrem stream
zos.close();
a jeho rozbalení:
ZipInputStream zis = new ZipInputStream(new FileInputStream("javlog.zip"));
ZipEntry zipEntry;
//budem predpokladat, ze v ZIPu mame jen textove soubory a tak je vypisem do konzole
while ((zipEntry = zis.getNextEntry()) != null) {
System.out.println("Soubor: " + zipEntry.getName());
BufferedReader br = new BufferedReader(new InputStreamReader(zis));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
System.out.println();
zis.closeEntry();
}
zis.close();
Jak je vidět, není tenhle přístup úplně přímočarý. Tím pádem je i větší prostor pro zavlečení nějaké chybky. Víte například, jak rozlišíte při vytváření ZIPu prázdný soubor od prázdného adresáře?
Tento základní přístup se často hodí v případě, kdy obsah, který chcete do zipu vložit, nemáte ve file systému, ale např. ho generujete přímo ve webové aplikaci. Pak daný ZipOutputStream vytvoříte nad OutputStreamem servletu a naládujete do něj data přímo z paměti nebo z databáze.
To co vývojář aplikace často potřebuje, je možnost jednoduše zazipovat část souborového systému (složku a všechno co je v ní), a nebo ze ZIPu zase všechno někam rozbalit.
Samozřejmě, že se dají na internetu najít knihovny, které tuto funkcionalitu poskytují, ale částo jsou kanón na vrabce (opravdu potřebujete aby knihovna uměla i bzip2, 7z, arj a cab?), nebo naopak příliš jednoduché a nezvládnou operaci typu – sbal mi část filesystému a k tomu přidej tenhle vygenerovaný (v paměti) PDF report.
Takže by se v těchto případech hodilo malé rozšíření Zip*Stream tříd. Aby na jednu stranu bylo možné jednoduše pracovat přímo se souborovým systémem a na druhou, aby nebyl programátor ochuzen o to, co mu ZipStreamy umožňují. Hodily by se nám tedy metody:
void ZipOutputStream.put(File fileOrFolderToZip};
void ZipInputStream.unzip(File folderWhereToStoreUnzippedFiles);
A protože originální Zip*Stream třídy nejsou finální, nic nám nebrání v rozšíření.
Má implementace přidává ještě dodatečné parametry, aby bylo použití univerzálnější.
cz.cacek.javlog.zip.ZipOutputStream extends java.util.zip.ZipOutputStream:
/**
* Adds file or directory (with its content) to this {@link ZipOutputStream}.
* If the given {@link File} doesn't exist, it does nothing and returns.
* The path of the given {@link File} in the result zip is determined only
* from the file name
* and from the provided basePath. It doesn't take the file path into consideration
* (but the folder structure under the given folder is preserved).
*
* @param file
* {@link File} (normal or directory) to add to the stream
* (must not be null)
* @param basePath
* base path (path prefix) for added {@link File} (may be null)
* @param contentOnly
* determines if the folder name (in case the file
* parameter is directory) should be added to the zip path. If
* true is provided only the folder content is added and the
* folder itself is not included.
* @throws IOException
*/
public void put(final File file, final String basePath, boolean contentOnly) throws IOException;
cz.cacek.javlog.zip.ZipInputStream extends java.util.zip.ZipInputStream:
/**
* Unzips the stream to the given base folder. If the baseFolder parameter
* is null, the default (working) directory is used instead. If the
* baseFolder doesn't exist it is created.
*
* @param baseFolder
* base folder to unpack (may be null).
* @param allowAbsolutePath
* flag which says if handling of absolute paths is allowed in
* the zip. If the flag is
* true and ZipEntry contains absolute path, the baseFolder
* parameter is not used and File is created from the absolute
* path in the ZipEntry.
* @throws IllegalArgumentException
* base folder can't be created or file denoted by baseFolder
* parameter already exists,
* but it's not a directory.
* @throws IOException
*/
public void unzip(final File baseFolder, final boolean allowAbsolutePath) throws IllegalArgumentException, IOException;
A použití? Chceme například rozbalit war soubor do nějaké složky, upravit obsah a zase ho zabalit:
final File baseFolder = new File("temp-webapp"); //folder to unpack the zip content (will be created)
final File warFile = new File("myapp.war"); //file to unpack
//unpack ZIP
ZipInputStream zis = new ZipInputStream(new FileInputStream(warFile));
zis.unzip(baseFolder, false);
zis.close();
//TODO make some magic with files in the temp-webapp folder
new FileOutputStream(new File(baseFolder, "newEmptyFile.xxx")).close();
//repack the ZIP (WAR) file
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(warFile));
zos.put(baseFolder, null, true); //pack only the content of the folder - 3rd parameter is true
zos.close();
//TODO remove the baseFolder
//delete(baseFolder);
Toť vše, ani to nebolelo.
Upravené ZipStreamy připravené k použití najdete v tomto jaru (3 třídy + zdrojáky), nebo v zazipovaném Eclipse projektu i s JUnit testy.
A co používáte k práci s ZIP archívy vy? Podělte se v komentářích.