001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.utils;
021
022import java.io.BufferedInputStream;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.util.Enumeration;
030import java.util.zip.ZipEntry;
031import java.util.zip.ZipFile;
032import java.util.zip.ZipInputStream;
033import java.util.zip.ZipOutputStream;
034import org.apache.commons.io.FileUtils;
035import org.apache.commons.io.IOUtils;
036
037/**
038 * @since 1.10
039 */
040public final class ZipUtils {
041
042  private static final String ERROR_CREATING_DIRECTORY = "Error creating directory: ";
043
044  private ZipUtils() {
045    // only static methods
046  }
047
048  /**
049   * Unzip a file into a directory. The directory is created if it does not exist.
050   *
051   * @return the target directory
052   */
053  public static File unzip(File zip, File toDir) throws IOException {
054    unzip(zip, toDir, TrueZipEntryFilter.INSTANCE);
055    return toDir;
056  }
057
058  public static File unzip(InputStream zip, File toDir) throws IOException {
059    unzip(zip, toDir, TrueZipEntryFilter.INSTANCE);
060    return toDir;
061  }
062
063  private enum TrueZipEntryFilter implements ZipEntryFilter {
064    INSTANCE;
065    @Override
066    public boolean accept(ZipEntry entry) {
067      return true;
068    }
069  }
070
071  public static File unzip(InputStream stream, File toDir, ZipEntryFilter filter) throws IOException {
072    if (!toDir.exists()) {
073      FileUtils.forceMkdir(toDir);
074    }
075
076    ZipInputStream zipStream = new ZipInputStream(stream);
077    try {
078      ZipEntry entry;
079      while ((entry = zipStream.getNextEntry()) != null) {
080        if (filter.accept(entry)) {
081          File to = new File(toDir, entry.getName());
082          if (entry.isDirectory()) {
083            throwExceptionIfDirectoryIsNotCreatable(to);
084          } else {
085            File parent = to.getParentFile();
086            throwExceptionIfDirectoryIsNotCreatable(parent);
087            copy(zipStream, to);
088          }
089        }
090      }
091      return toDir;
092
093    } finally {
094      zipStream.close();
095    }
096  }
097
098  private static void throwExceptionIfDirectoryIsNotCreatable(File to) throws IOException {
099    if (!to.exists() && !to.mkdirs()) {
100      throw new IOException(ERROR_CREATING_DIRECTORY + to);
101    }
102  }
103
104  public static File unzip(File zip, File toDir, ZipEntryFilter filter) throws IOException {
105    if (!toDir.exists()) {
106      FileUtils.forceMkdir(toDir);
107    }
108
109    ZipFile zipFile = new ZipFile(zip);
110    try {
111      Enumeration<? extends ZipEntry> entries = zipFile.entries();
112      while (entries.hasMoreElements()) {
113        ZipEntry entry = entries.nextElement();
114        if (filter.accept(entry)) {
115          File to = new File(toDir, entry.getName());
116          if (entry.isDirectory()) {
117            throwExceptionIfDirectoryIsNotCreatable(to);
118          } else {
119            File parent = to.getParentFile();
120            throwExceptionIfDirectoryIsNotCreatable(parent);
121            copy(zipFile, entry, to);
122          }
123        }
124      }
125      return toDir;
126
127    } finally {
128      zipFile.close();
129    }
130  }
131
132  private static void copy(ZipInputStream zipStream, File to) throws IOException {
133    FileOutputStream fos = null;
134    try {
135      fos = new FileOutputStream(to);
136      IOUtils.copy(zipStream, fos);
137    } finally {
138      IOUtils.closeQuietly(fos);
139    }
140  }
141
142  private static void copy(ZipFile zipFile, ZipEntry entry, File to) throws IOException {
143    FileOutputStream fos = new FileOutputStream(to);
144    InputStream input = null;
145    try {
146      input = zipFile.getInputStream(entry);
147      IOUtils.copy(input, fos);
148    } finally {
149      IOUtils.closeQuietly(input);
150      IOUtils.closeQuietly(fos);
151    }
152  }
153
154  public static void zipDir(File dir, File zip) throws IOException {
155    OutputStream out = null;
156    ZipOutputStream zout = null;
157    try {
158      out = FileUtils.openOutputStream(zip);
159      zout = new ZipOutputStream(out);
160      doZipDir(dir, zout);
161
162    } finally {
163      IOUtils.closeQuietly(zout);
164      IOUtils.closeQuietly(out);
165    }
166  }
167
168  private static void doZip(String entryName, InputStream in, ZipOutputStream out) throws IOException {
169    ZipEntry entry = new ZipEntry(entryName);
170    out.putNextEntry(entry);
171    IOUtils.copy(in, out);
172    out.closeEntry();
173  }
174
175  private static void doZip(String entryName, File file, ZipOutputStream out) throws IOException {
176    if (file.isDirectory()) {
177      entryName += "/";
178      ZipEntry entry = new ZipEntry(entryName);
179      out.putNextEntry(entry);
180      out.closeEntry();
181      File[] files = file.listFiles();
182      // java.io.File#listFiles() returns null if object is a directory (not possible here) or if
183      // an I/O error occurs (weird!)
184      if (files == null) {
185        throw new IllegalStateException("Fail to list files of directory " + file.getAbsolutePath());
186      }
187      for (File f : files) {
188        doZip(entryName + f.getName(), f, out);
189      }
190
191    } else {
192      InputStream in = null;
193      try {
194        in = new BufferedInputStream(new FileInputStream(file));
195        doZip(entryName, in, out);
196      } finally {
197        IOUtils.closeQuietly(in);
198      }
199    }
200  }
201
202  private static void doZipDir(File dir, ZipOutputStream out) throws IOException {
203    File[] children = dir.listFiles();
204    if (children == null) {
205      throw new IllegalStateException("Fail to list files of directory " + dir.getAbsolutePath());
206    }
207    for (File child : children) {
208      doZip(child.getName(), child, out);
209    }
210  }
211
212  public interface ZipEntryFilter {
213    boolean accept(ZipEntry entry);
214  }
215
216}