PageRenderTime 49ms CodeModel.GetById 10ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 1ms

/maven-amps-plugin/src/main/java/com/atlassian/maven/plugins/amps/util/ZipUtils.java

https://bitbucket.org/mmeinhold/amps
Java | 384 lines | 277 code | 37 blank | 70 comment | 33 complexity | 0efc01f222ebdc35b2e19fa671e2f5b0 MD5 | raw file
  1package com.atlassian.maven.plugins.amps.util;
  2
  3import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
  4import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
  5import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
  6import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
  7import org.apache.commons.compress.archivers.zip.ZipFile;
  8import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
  9import org.apache.commons.io.IOUtils;
 10import org.apache.commons.lang.StringUtils;
 11
 12import com.google.common.collect.Lists;
 13
 14import java.io.*;
 15import java.util.Enumeration;
 16import java.util.List;
 17import java.util.zip.ZipEntry;
 18import java.util.zip.ZipException;
 19
 20public class ZipUtils
 21{
 22
 23    /**
 24     * Ungzips and extracts the specified tar.gz file into the specified directory.
 25     * @param targz the tar.gz file to use
 26     * @param destDir the directory to contain the extracted contents
 27     * @throws IOException
 28     */
 29    public static void untargz(final File targz, final String destDir) throws IOException
 30    {
 31        untargz(targz, destDir, 0);
 32    }
 33
 34    /**
 35     * Ungzips and extracts the specified tar.gz file into the specified directory, trimming
 36     * the specified number of leading path segments from the extraction path.
 37     * @param targz the tar.gz file to use
 38     * @param destDir the directory to contain the extracted contents
 39     * @param leadingPathSegmentsToTrim the number of leading path segments to remove from the
 40     *                                  extracted path
 41     * @throws IOException
 42     */
 43    public static void untargz(final File targz, final String destDir, int leadingPathSegmentsToTrim) throws IOException
 44    {
 45        FileInputStream fin = new FileInputStream(targz);
 46        GzipCompressorInputStream gzIn = new GzipCompressorInputStream(fin);
 47        TarArchiveInputStream tarIn = new TarArchiveInputStream(gzIn);
 48
 49        try
 50        {
 51            while (true)
 52            {
 53                TarArchiveEntry entry = tarIn.getNextTarEntry();
 54                if (entry == null) {
 55                    // tar file exhausted
 56                    break;
 57                }
 58
 59                File entryFile = new File(destDir + File.separator +
 60                        trimPathSegments(entry.getName(), leadingPathSegmentsToTrim));
 61
 62                if (entry.isDirectory())
 63                {
 64                    entryFile.mkdirs();
 65                    continue;
 66                }
 67
 68                if (!entryFile.getParentFile().exists())
 69                {
 70                    entryFile.getParentFile().mkdirs();
 71                }
 72
 73                FileOutputStream fos = null;
 74                try
 75                {
 76                    fos = new FileOutputStream(entryFile);
 77                    IOUtils.copy(tarIn, fos);
 78
 79                    // check for user-executable bit on entry and apply to file
 80                    if ((entry.getMode() & 0100) != 0) {
 81                        entryFile.setExecutable(true);
 82                    }
 83                }
 84                finally
 85                {
 86                    IOUtils.closeQuietly(fos);
 87                }
 88            }
 89        }
 90        finally
 91        {
 92            IOUtils.closeQuietly(fin);
 93            IOUtils.closeQuietly(tarIn);
 94            IOUtils.closeQuietly(gzIn);
 95        }
 96    }
 97
 98    public static void unzip(final File zipFile, final String destDir) throws IOException
 99    {
100        unzip(zipFile, destDir, 0);
101    }
102
103    /**
104     * Unzips a file
105     *
106     * @param zipFile
107     *            the Zip file
108     * @param destDir
109     *            the destination folder
110     * @param leadingPathSegmentsToTrim
111     *            number of root folders to skip. Example: If all files are in generated-resources/home/*,
112     *            then you may want to skip 2 folders.
113     * @throws IOException
114     */
115    public static void unzip(final File zipFile, final String destDir, int leadingPathSegmentsToTrim) throws IOException
116    {
117        final ZipFile zip = new ZipFile(zipFile);
118        try
119        {
120            final Enumeration<? extends ZipArchiveEntry> entries = zip.getEntries();
121            while (entries.hasMoreElements())
122            {
123                final ZipArchiveEntry zipEntry = entries.nextElement();
124                String zipPath = trimPathSegments(zipEntry.getName(), leadingPathSegmentsToTrim);
125                final File file = new File(destDir + "/" + zipPath);
126                if (zipEntry.isDirectory())
127                {
128                    file.mkdirs();
129                    continue;
130                }
131                // make sure our parent exists in case zipentries are out of order
132                if (!file.getParentFile().exists())
133                {
134                    file.getParentFile().mkdirs();
135                }
136
137                InputStream is = null;
138                OutputStream fos = null;
139                try
140                {
141                    is = zip.getInputStream(zipEntry);
142                    fos = new FileOutputStream(file);
143                    IOUtils.copy(is, fos);
144
145                    // check for user-executable bit on entry and apply to file
146                    if ((zipEntry.getUnixMode() & 0100) != 0)
147                    {
148                        file.setExecutable(true);
149                    }
150                }
151                finally
152                {
153                    IOUtils.closeQuietly(is);
154                    IOUtils.closeQuietly(fos);
155                }
156                file.setLastModified(zipEntry.getTime());
157            }
158        }
159        finally
160        {
161            try
162            {
163                zip.close();
164            }
165            catch (IOException e)
166            {
167                // ignore
168            }
169        }
170    }
171
172    /**
173     * Count the number of nested root folders. A root folder is a folder which contains 0 or 1 file or folder.
174     *
175     * Example: A zip with only "generated-resources/home/database.log" has 2 root folders.
176     *
177     * @param zip the zip file
178     * @return the number of root folders.
179     */
180    public static int countNestingLevel(File zip) throws ZipException, IOException
181    {
182        ZipFile zipFile = null;
183        try
184        {
185            zipFile = new ZipFile(zip);
186            List<String> filenames = toList(zipFile.getEntries());
187            return countNestingLevel(filenames);
188        }
189        finally
190        {
191            if (zipFile != null)
192            {
193                try
194                {
195                    zipFile.close();
196                }
197                catch (IOException e)
198                {
199                    // ignore
200                }
201            }
202        }
203    }
204
205    /**
206     * Count the number of nested root directories in the filenames.
207     *
208     * A root directory is a directory that has no sibling.
209     * @param filenames the list of filenames, using / as a separator. Must be a mutable copy,
210     * as it will be modified.
211     */
212    static int countNestingLevel(List<String> filenames)
213    {
214        String prefix = StringUtils.getCommonPrefix(filenames.toArray(new String[filenames.size()]));
215        if (!prefix.endsWith("/"))
216        {
217            prefix = prefix.substring(0, prefix.lastIndexOf("/") + 1);
218        }
219
220        // The first prefix may be wrong, example:
221        // root/ <- to be discarded
222        // root/nested/ <- to be discarded
223        // root/nested/folder1/file.txt <- the root "root/nested/" will be detected properly
224        // root/nested/folder2/file.txt
225        if (filenames.remove(prefix))
226        {
227            return countNestingLevel(filenames);
228        }
229
230        // The client can't use these filenames anymore.
231        filenames.clear();
232        return StringUtils.countMatches(prefix, "/");
233    }
234
235    private static List<String> toList(final Enumeration<? extends ZipEntry> entries)
236    {
237        List<String> filenamesList = Lists.newArrayList();
238        while (entries.hasMoreElements())
239        {
240            final ZipEntry zipEntry = entries.nextElement();
241            filenamesList.add(zipEntry.getName());
242        }
243        return filenamesList;
244    }
245
246    /**
247     * @param prefix the prefix. If empty, uses the srcDir's name. That means you can't create a zip with no
248     * root folder.
249     */
250    public static void zipDir(final File zipFile, final File srcDir, final String prefix) throws IOException
251    {
252        ZipArchiveOutputStream out = new ZipArchiveOutputStream(new FileOutputStream(zipFile));
253        try
254        {
255            addZipPrefixes(srcDir, out, prefix);
256            addZipDir(srcDir, out, prefix);
257        }
258        finally
259        {
260            // Complete the ZIP file
261            IOUtils.closeQuietly(out);
262        }
263    }
264
265    private static void addZipPrefixes(File dirObj, ZipArchiveOutputStream out, String prefix) throws IOException
266    {
267        // need to manually add the prefix folders
268        String entryPrefix = ensurePrefixWithSlash(dirObj, prefix);
269
270        String[] prefixes = entryPrefix.split("/");
271        String lastPrefix = "";
272        for (int i = 0; i < prefixes.length; i++)
273        {
274            ZipArchiveEntry entry = new ZipArchiveEntry(lastPrefix + prefixes[i] + "/");
275            out.putArchiveEntry(entry);
276            out.closeArchiveEntry();
277
278            lastPrefix = prefixes[i] + "/";
279        }
280    }
281
282    private static void addZipDir(File dirObj, ZipArchiveOutputStream out, String prefix) throws IOException
283    {
284        File[] files = dirObj.listFiles();
285        byte[] tmpBuf = new byte[1024];
286        File currentFile;
287        String entryPrefix = ensurePrefixWithSlash(dirObj, prefix);
288        String entryName = "";
289
290        for (int i = 0; i < files.length; i++)
291        {
292            currentFile = files[i];
293            if (currentFile.isDirectory())
294            {
295                entryName = entryPrefix + currentFile.getName() + "/";
296
297                // need to manually add folders so entries are in order
298                ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
299                out.putArchiveEntry(entry);
300                out.closeArchiveEntry();
301
302                // add the files in the folder
303                addZipDir(currentFile, out, entryName);
304            }
305            else if (currentFile.isFile())
306            {
307
308                entryName = entryPrefix + currentFile.getName();
309                FileInputStream in = new FileInputStream(currentFile.getAbsolutePath());
310                try
311                {
312                    ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
313                    out.putArchiveEntry(entry);
314                    if (currentFile.canExecute())
315                    {
316                        entry.setUnixMode(0700);
317                    }
318                    // Transfer from the file to the ZIP file
319                    int len;
320                    while ((len = in.read(tmpBuf)) > 0)
321                    {
322                        out.write(tmpBuf, 0, len);
323                    }
324
325                    // Complete the entry
326                    out.closeArchiveEntry();
327                }
328                finally
329                {
330                    IOUtils.closeQuietly(in);
331                }
332            }
333        }
334    }
335
336    /**
337     * Make sure 'prefix' is in format 'entry/' or, by default, 'rootDir/'
338     * (not '', '/', '/entry', or 'entry').
339     */
340    private static String ensurePrefixWithSlash(File rootDir, String prefix)
341    {
342        String entryPrefix = prefix;
343
344        if (StringUtils.isNotBlank(entryPrefix) && !entryPrefix.equals("/"))
345        {
346            // strip leading '/'
347            if (entryPrefix.charAt(0) == '/')
348            {
349                entryPrefix = entryPrefix.substring(1);
350            }
351            // ensure trailing '/'
352            if (entryPrefix.charAt(entryPrefix.length() - 1) != '/')
353            {
354                entryPrefix = entryPrefix + "/";
355            }
356        }
357        else
358        {
359            entryPrefix = rootDir.getName() + "/";
360        }
361
362        return entryPrefix;
363    }
364
365    private static String trimPathSegments(String zipPath, final int trimLeadingPathSegments)
366    {
367        int startIndex = 0;
368        for (int i = 0; i < trimLeadingPathSegments; i++)
369        {
370            int nextSlash = zipPath.indexOf("/", startIndex);
371            if (nextSlash == -1)
372            {
373                break;
374            }
375            else
376            {
377                startIndex = nextSlash + 1;
378            }
379        }
380
381        return zipPath.substring(startIndex);
382    }
383
384}