/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

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