/** * Copyright (c) 2009 - 2010 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org> * * This file is part of org.appwork.utils.zip * * This software is licensed under the Artistic License 2.0, * see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php * for details */ package org.appwork.utils.zip; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import org.appwork.utils.Files; import org.appwork.utils.logging.Log; /** * @author daniel * */ public class ZipIOReader { private File zipFile = null; private ZipFile zip = null; private ZipIOFile rootFS = null; private boolean autoCreateExtractPath = true; private boolean overwrite = false; private boolean autoCreateSubDirs = true; private byte[] byteArray = null; private int zipEntriesSize = -1; private ZipEntry[] zipEntries = null; public ZipIOReader(final byte[] byteArray) { this.byteArray = byteArray; } /** * open the zipFile for this ZipIOReader * * @param zipFile * the zipFile we want to open * @throws ZipIOException * @throws ZipException * @throws IOException */ public ZipIOReader(final File zipFile) throws ZipIOException, ZipException, IOException { this.zipFile = zipFile; this.openZip(); } /** * closes the ZipFile * * @throws IOException */ public synchronized void close() throws IOException { try { if (this.zip != null) { this.zip.close(); } } finally { this.byteArray = null; this.zip = null; } } /** * extract given ZipEntry to output File * * @param entry * ZipEntry to extract * @param output * File to extract to * @return * @throws ZipIOException * @throws IOException */ public synchronized ArrayList<File> extract(final ZipEntry entry, final File output) throws ZipIOException, IOException { if (entry.isDirectory()) { throw new ZipIOException("Cannot extract a directory", entry); } final ArrayList<File> ret = new ArrayList<File>(); if (output.exists() && output.isDirectory()) { if (this.isOverwrite()) { Files.deleteRecursiv(output); if (output.exists()) { throw new IOException("Cannot extract File to Directory " + output); } } if (output.exists() && output.isDirectory()) { Log.L.finer("Skipped extraction: directory exists: " + output); return ret; } } if (output.exists()) { if (this.isOverwrite()) { output.delete(); if (output.exists()) { throw new IOException("Cannot overwrite File " + output); } } if (output.exists()) { Log.L.finer("Skipped extraction: file exists: " + output); return ret; } } if (!output.getParentFile().exists()) { if (this.isAutoCreateSubDirs()) { output.getParentFile().mkdirs(); ret.add(output.getParentFile()); if (!output.getParentFile().exists()) { throw new IOException("Cannot create folder for File " + output); } } if (!output.getParentFile().exists()) { Log.L.finer("Skipped extraction: cannot create dir: " + output); return ret; } } FileOutputStream stream = null; CheckedInputStream in = null; try { stream = new FileOutputStream(output); final InputStream is = this.getInputStream(entry); in = new CheckedInputStream(is, new CRC32()); final byte[] buffer = new byte[32767]; int len = 0; while ((len = in.read(buffer)) != -1) { stream.write(buffer, 0, len); } if (entry.getCrc() != -1 && entry.getCrc() != in.getChecksum().getValue()) { throw new ZipIOException("CRC32 Failed", entry); } ret.add(output); } finally { try { in.close(); } catch (final Throwable e) { } try { stream.close(); } catch (final Throwable e) { } } return ret; } public synchronized ArrayList<File> extractTo(final File outputDirectory) throws ZipIOException, IOException { if (outputDirectory.exists() && outputDirectory.isFile()) { throw new IOException("cannot extract to a file " + outputDirectory); } if (!outputDirectory.exists() && !(this.autoCreateExtractPath && outputDirectory.mkdirs())) { throw new IOException("could not create outputDirectory " + outputDirectory); } final ArrayList<File> ret = new ArrayList<File>(); for (final ZipEntry entry : this.getZipFiles()) { final File out = new File(outputDirectory, entry.getName()); if (entry.isDirectory()) { if (!out.exists()) { if (this.isAutoCreateSubDirs()) { if (!out.mkdir()) { throw new IOException("could not create outputDirectory " + out); } ret.add(out); } else { Log.L.finer("SKipped creatzion of: " + out); } } } else { ret.addAll(this.extract(entry, out)); } } return ret; } /** * find ZipIOFile that represents the Folder with given path * * @param path * the path we search a ZipIOFile for * @param currentRoot * currentRoot for the search * @return ZipIOFile if path is found, else null */ private ZipIOFile getFolder(final String path, final ZipIOFile currentRoot) { if (path == null || currentRoot == null || !currentRoot.isDirectory()) { return null; } if (currentRoot.getAbsolutePath().equalsIgnoreCase(path)) { return currentRoot; } for (final ZipIOFile tmp : currentRoot.getFiles()) { if (tmp.isDirectory() && tmp.getAbsolutePath().equalsIgnoreCase(path)) { return tmp; } else if (tmp.isDirectory()) { final ZipIOFile ret = this.getFolder(path, tmp); if (ret != null) { return ret; } } } return null; } /** * returns an InputStream for given ZipEntry * * @param entry * ZipEntry we want an InputStream * @return InputStream for given ZipEntry * @throws ZipIOException * @throws IOException */ public synchronized InputStream getInputStream(final ZipEntry entry) throws ZipIOException, IOException { if (entry == null) { throw new ZipIOException("invalid zipEntry"); } if (this.zip != null) { return this.zip.getInputStream(entry); } else { ZipInputStream zis = null; boolean close = true; try { zis = new ZipInputStream(new ByteArrayInputStream(this.byteArray)); ZipEntry ze = null; while ((ze = zis.getNextEntry()) != null) { /* find the entry that matches */ final String name = ze.getName(); if (name.equals(entry.getName())) { final ZipInputStream zis2 = zis; close = false; return new InputStream() { @Override public void close() throws IOException { zis2.close(); } @Override public int read() throws IOException { return zis2.read(); } @Override public int read(final byte b[]) throws IOException { return zis2.read(b); } @Override public int read(final byte b[], final int off, final int len) throws IOException { return zis2.read(b, off, len); } }; } } } catch (final IOException e) { throw new ZipIOException(e.getMessage(), e); } finally { try { if (close) { zis.close(); } } catch (final Throwable e) { } } return null; } } /** * returns the ZipEntry for the given name * * @param fileName * Filename we want a ZipEntry for * @return ZipEntry if filename is found or null if not found * @throws ZipIOException */ public synchronized ZipEntry getZipFile(final String fileName) throws ZipIOException { if (fileName == null) { throw new ZipIOException("invalid fileName"); } if (this.zip != null) { return this.zip.getEntry(fileName); } else { ZipInputStream zis = null; try { zis = new ZipInputStream(new ByteArrayInputStream(this.byteArray)); ZipEntry ze = null; while ((ze = zis.getNextEntry()) != null) { if (ze.getName().equals(fileName)) { return ze; } } return null; } catch (final IOException e) { throw new ZipIOException(e.getMessage(), e); } finally { try { zis.close(); } catch (final Throwable e) { } } } } /** * returns a list of all ZipEntries in this ZipFile * * @return ZipEntry[] of all files in the ZipFile * @throws ZipIOException */ public synchronized ZipEntry[] getZipFiles() throws ZipIOException { if (this.zipEntries != null) { return this.zipEntries; } final ArrayList<ZipEntry> ret = new ArrayList<ZipEntry>(); if (this.zip != null) { final Enumeration<? extends ZipEntry> zipIter = this.zip.entries(); while (zipIter.hasMoreElements()) { ret.add(zipIter.nextElement()); } } else { ZipInputStream zis = null; try { zis = new ZipInputStream(new ByteArrayInputStream(this.byteArray)); ZipEntry ze = null; while ((ze = zis.getNextEntry()) != null) { ret.add(ze); } } catch (final IOException e) { throw new ZipIOException(e.getMessage(), e); } finally { try { zis.close(); } catch (final Throwable e) { } } } this.zipEntries = ret.toArray(new ZipEntry[ret.size()]); return this.zipEntries; } /** * returns a ZipIOFile Filesystem for this ZipFile * * @return ZipIOFile that represents ROOT of the Filesystem * @throws ZipIOException */ public synchronized ZipIOFile getZipIOFileSystem() throws ZipIOException { if (this.rootFS != null) { return this.rootFS; } final ZipEntry[] content = this.getZipFiles(); final ArrayList<ZipIOFile> root = new ArrayList<ZipIOFile>(); for (final ZipEntry file : content) { if (!file.isDirectory() && !file.getName().contains("/")) { /* file is in root */ final ZipIOFile tmp = new ZipIOFile(file.getName(), file, this, null); root.add(tmp); } else if (!file.isDirectory()) { /* file is not in root */ final String parts[] = file.getName().split("/"); /* we begin at root */ ZipIOFile currentParent = null; String path = ""; for (int i = 0; i < parts.length; i++) { if (i == parts.length - 1) { /* the file */ final ZipIOFile tmp = new ZipIOFile(parts[i], file, this, currentParent); currentParent.getFilesInternal().add(tmp); } else { path = path + parts[i] + "/"; ZipIOFile found = null; for (final ZipIOFile tmp : root) { found = this.getFolder(path, tmp); if (found != null) { break; } } if (found != null) { currentParent = found; } else { final ZipIOFile newFolder = new ZipIOFile(parts[i], null, this, currentParent); if (currentParent != null) { currentParent.getFilesInternal().add(newFolder); } else { root.add(newFolder); } currentParent = newFolder; } } } } } this.rootFS = new ZipIOFile("", null, this, null); this.rootFS.getFilesInternal().addAll(root); this.rootFS.getFilesInternal().trimToSize(); this.trimZipIOFiles(this.rootFS); return this.rootFS; } public boolean isAutoCreateExtractPath() { return this.autoCreateExtractPath; } /** * @return */ private boolean isAutoCreateSubDirs() { // TODO Auto-generated method stub return this.autoCreateSubDirs; } /** * @return */ private boolean isOverwrite() { // TODO Auto-generated method stub return this.overwrite; } /** * opens the ZipFile for further use * * @throws ZipIOException * @throws ZipException * @throws IOException */ private synchronized void openZip() throws ZipIOException, ZipException, IOException { if (this.zip != null) { return; } if (this.zipFile == null || this.zipFile.isDirectory() || !this.zipFile.exists()) { throw new ZipIOException("invalid zipFile"); } this.zip = new ZipFile(this.zipFile); } public void setAutoCreateExtractPath(final boolean autoCreateExtractPath) { this.autoCreateExtractPath = autoCreateExtractPath; } public void setAutoCreateSubDirs(final boolean autoCreateSubDirs) { this.autoCreateSubDirs = autoCreateSubDirs; } public void setOverwrite(final boolean overwrite) { this.overwrite = overwrite; } /** * how many ZipEntries does this ZipFile have * * @return * @throws ZipIOException * @throws IOException */ public synchronized int size() throws ZipIOException { if (this.zipEntriesSize != -1) { return this.zipEntriesSize; } if (this.zip != null) { this.zipEntriesSize = this.zip.size(); } else { ZipInputStream zis = null; try { this.zipEntriesSize = 0; zis = new ZipInputStream(new ByteArrayInputStream(this.byteArray)); while (zis.getNextEntry() != null) { this.zipEntriesSize++; } } catch (final IOException e) { throw new ZipIOException(e.getMessage(), e); } finally { try { zis.close(); } catch (final Throwable e) { } } } return this.zipEntriesSize; } /** * trims the ZipIOFiles(reduces memory) * * @param root * ZipIOFile we want to start */ private void trimZipIOFiles(final ZipIOFile root) { if (root == null) { return; } for (final ZipIOFile tmp : root.getFiles()) { if (tmp.isDirectory()) { this.trimZipIOFiles(tmp); } } root.getFilesInternal().trimToSize(); } }