You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Zip.java 20 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 1999 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Ant", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant.taskdefs;
  55. import java.io.*;
  56. import java.util.Enumeration;
  57. import java.util.Hashtable;
  58. import java.util.Stack;
  59. import java.util.StringTokenizer;
  60. import java.util.Vector;
  61. import java.util.zip.*;
  62. import org.apache.tools.ant.*;
  63. import org.apache.tools.ant.types.*;
  64. import org.apache.tools.ant.util.*;
  65. /**
  66. * Create a ZIP archive.
  67. *
  68. * @author James Davidson <a href="mailto:duncan@x180.com">duncan@x180.com</a>
  69. * @author Jon S. Stevens <a href="mailto:jon@clearink.com">jon@clearink.com</a>
  70. * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
  71. */
  72. public class Zip extends MatchingTask {
  73. private File zipFile;
  74. private File baseDir;
  75. private boolean doCompress = true;
  76. protected String archiveType = "zip";
  77. // For directories:
  78. private static long emptyCrc = new CRC32 ().getValue ();
  79. protected String emptyBehavior = "skip";
  80. private Vector filesets = new Vector ();
  81. private Hashtable addedDirs = new Hashtable();
  82. /**
  83. * This is the name/location of where to
  84. * create the .zip file.
  85. */
  86. public void setZipfile(File zipFile) {
  87. this.zipFile = zipFile;
  88. }
  89. /**
  90. * This is the base directory to look in for
  91. * things to zip.
  92. */
  93. public void setBasedir(File baseDir) {
  94. this.baseDir = baseDir;
  95. }
  96. /**
  97. * Sets whether we want to compress the files or only store them.
  98. */
  99. public void setCompress(boolean c) {
  100. doCompress = c;
  101. }
  102. /**
  103. * Adds a set of files (nested fileset attribute).
  104. */
  105. public void addFileset(PrefixedFileSet set) {
  106. filesets.addElement(set);
  107. }
  108. /**
  109. * Adds a set of files (nested fileset attribute).
  110. */
  111. public void addPrefixedfileset(PrefixedFileSet set) {
  112. addFileset(set);
  113. }
  114. /**
  115. * FileSet with an additional prefix attribute to specify the
  116. * location we want to move the files to (inside the archive).
  117. * Or, if this FileSet represents only a single file, then the
  118. * fullpath attribute can be set, which specifies the full path
  119. * that the file should have when it is placed in the archive.
  120. */
  121. public static class PrefixedFileSet extends FileSet {
  122. private String prefix = "";
  123. private String fullpath = "";
  124. public void setPrefix(String loc) {
  125. prefix = loc;
  126. }
  127. public String getPrefix() {return prefix;}
  128. public void setFullpath(String loc) {
  129. fullpath = loc;
  130. }
  131. public String getFullpath() {return fullpath;}
  132. }
  133. /**
  134. * Sets behavior of the task when no files match.
  135. * Possible values are: <code>fail</code> (throw an exception
  136. * and halt the build); <code>skip</code> (do not create
  137. * any archive, but issue a warning); <code>create</code>
  138. * (make an archive with no entries).
  139. * Default for zip tasks is <code>skip</code>;
  140. * for jar tasks, <code>create</code>.
  141. */
  142. public void setWhenempty(String we) throws BuildException {
  143. we = we.toLowerCase();
  144. // XXX could instead be using EnumeratedAttribute, but this works
  145. if (!"fail".equals(we) && !"skip".equals(we) && !"create".equals(we))
  146. throw new BuildException("Unrecognized whenempty attribute: " + we);
  147. emptyBehavior = we;
  148. }
  149. public void execute() throws BuildException {
  150. if (baseDir == null && filesets.size() == 0 && "zip".equals(archiveType)) {
  151. throw new BuildException( "basedir attribute must be set, or at least " +
  152. "one fileset or prefixedfileset must be given!" );
  153. }
  154. if (zipFile == null) {
  155. throw new BuildException("You must specify the " + archiveType + " file to create!");
  156. }
  157. // Create the scanners to pass to isUpToDate().
  158. Vector dss = new Vector ();
  159. if (baseDir != null)
  160. dss.addElement(getDirectoryScanner(baseDir));
  161. for (int i=0; i<filesets.size(); i++) {
  162. FileSet fs = (FileSet) filesets.elementAt(i);
  163. dss.addElement (fs.getDirectoryScanner(project));
  164. }
  165. int dssSize = dss.size();
  166. FileScanner[] scanners = new FileScanner[dssSize];
  167. dss.copyInto(scanners);
  168. // quick exit if the target is up to date
  169. // can also handle empty archives
  170. if (isUpToDate(scanners, zipFile)) return;
  171. log("Building "+ archiveType +": "+ zipFile.getAbsolutePath());
  172. try {
  173. boolean success = false;
  174. ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream(zipFile));
  175. try {
  176. if (doCompress) {
  177. zOut.setMethod(ZipOutputStream.DEFLATED);
  178. } else {
  179. zOut.setMethod(ZipOutputStream.STORED);
  180. }
  181. initZipOutputStream(zOut);
  182. // Add the implicit fileset to the archive.
  183. if (baseDir != null)
  184. addFiles(getDirectoryScanner(baseDir), zOut, "", "");
  185. // Add the explicit filesets to the archive.
  186. addFiles(filesets, zOut);
  187. success = true;
  188. } finally {
  189. // Close the output stream.
  190. try {
  191. if (zOut != null)
  192. zOut.close ();
  193. } catch(IOException ex) {
  194. // If we're in this finally clause because of an exception, we don't
  195. // really care if there's an exception when closing the stream. E.g. if it
  196. // throws "ZIP file must have at least one entry", because an exception happened
  197. // before we added any files, then we must swallow this exception. Otherwise,
  198. // the error that's reported will be the close() error, which is not the real
  199. // cause of the problem.
  200. if (success)
  201. throw ex;
  202. }
  203. }
  204. } catch (IOException ioe) {
  205. String msg = "Problem creating " + archiveType + ": " + ioe.getMessage();
  206. // delete a bogus ZIP file
  207. if (!zipFile.delete()) {
  208. msg += " (and the archive is probably corrupt but I could not delete it)";
  209. }
  210. throw new BuildException(msg, ioe, location);
  211. } finally {
  212. cleanUp();
  213. }
  214. }
  215. /**
  216. * Add all files of the given FileScanner to the ZipOutputStream
  217. * prependig the given prefix to each filename.
  218. *
  219. * <p>Ensure parent directories have been added as well.
  220. */
  221. protected void addFiles(FileScanner scanner, ZipOutputStream zOut,
  222. String prefix, String fullpath) throws IOException {
  223. if (prefix.length() > 0 && fullpath.length() > 0)
  224. throw new BuildException("Both prefix and fullpath attributes may not be set on the same fileset.");
  225. File thisBaseDir = scanner.getBasedir();
  226. // directories that matched include patterns
  227. String[] dirs = scanner.getIncludedDirectories();
  228. if (dirs.length > 0 && fullpath.length() > 0)
  229. throw new BuildException("fullpath attribute may only be specified for filesets that specify a single file.");
  230. for (int i = 0; i < dirs.length; i++) {
  231. String name = dirs[i].replace(File.separatorChar,'/');
  232. if (!name.endsWith("/")) {
  233. name += "/";
  234. }
  235. addParentDirs(thisBaseDir, name, zOut, prefix);
  236. }
  237. // files that matched include patterns
  238. String[] files = scanner.getIncludedFiles();
  239. if (files.length > 1 && fullpath.length() > 0)
  240. throw new BuildException("fullpath attribute may only be specified for filesets that specify a single file.");
  241. for (int i = 0; i < files.length; i++) {
  242. File f = new File(thisBaseDir, files[i]);
  243. if (fullpath.length() > 0)
  244. {
  245. // Add this file at the specified location.
  246. addParentDirs(null, fullpath, zOut, "");
  247. zipFile(f, zOut, fullpath);
  248. }
  249. else
  250. {
  251. // Add this file with the specified prefix.
  252. String name = files[i].replace(File.separatorChar,'/');
  253. addParentDirs(thisBaseDir, name, zOut, prefix);
  254. zipFile(f, zOut, prefix+name);
  255. }
  256. }
  257. }
  258. protected void initZipOutputStream(ZipOutputStream zOut)
  259. throws IOException, BuildException
  260. {
  261. }
  262. /**
  263. * Check whether the archive is up-to-date; and handle behavior for empty archives.
  264. * @param scanners list of prepared scanners containing files to archive
  265. * @param zipFile intended archive file (may or may not exist)
  266. * @return true if nothing need be done (may have done something already); false if
  267. * archive creation should proceed
  268. * @exception BuildException if it likes
  269. */
  270. protected boolean isUpToDate(FileScanner[] scanners, File zipFile) throws BuildException
  271. {
  272. String[][] fileNames = grabFileNames(scanners);
  273. File[] files = grabFiles(scanners, fileNames);
  274. if (files.length == 0) {
  275. if (emptyBehavior.equals("skip")) {
  276. log("Warning: skipping "+archiveType+" archive " + zipFile +
  277. " because no files were included.", Project.MSG_WARN);
  278. return true;
  279. } else if (emptyBehavior.equals("fail")) {
  280. throw new BuildException("Cannot create "+archiveType+" archive " + zipFile +
  281. ": no files were included.", location);
  282. } else {
  283. // Create.
  284. if (zipFile.exists()) return true;
  285. // In this case using java.util.zip will not work
  286. // because it does not permit a zero-entry archive.
  287. // Must create it manually.
  288. log("Note: creating empty "+archiveType+" archive " + zipFile, Project.MSG_INFO);
  289. try {
  290. OutputStream os = new FileOutputStream(zipFile);
  291. try {
  292. // Cf. PKZIP specification.
  293. byte[] empty = new byte[22];
  294. empty[0] = 80; // P
  295. empty[1] = 75; // K
  296. empty[2] = 5;
  297. empty[3] = 6;
  298. // remainder zeros
  299. os.write(empty);
  300. } finally {
  301. os.close();
  302. }
  303. } catch (IOException ioe) {
  304. throw new BuildException("Could not create empty ZIP archive", ioe, location);
  305. }
  306. return true;
  307. }
  308. } else {
  309. if (!zipFile.exists()) return false;
  310. SourceFileScanner sfs = new SourceFileScanner(this);
  311. MergingMapper mm = new MergingMapper();
  312. mm.setTo(zipFile.getAbsolutePath());
  313. for (int i=0; i<scanners.length; i++) {
  314. if (sfs.restrict(fileNames[i], scanners[i].getBasedir(), null,
  315. mm).length > 0) {
  316. return false;
  317. }
  318. }
  319. return true;
  320. }
  321. }
  322. protected static File[] grabFiles(FileScanner[] scanners) {
  323. return grabFiles(scanners, grabFileNames(scanners));
  324. }
  325. protected static File[] grabFiles(FileScanner[] scanners,
  326. String[][] fileNames) {
  327. Vector files = new Vector();
  328. for (int i = 0; i < fileNames.length; i++) {
  329. File thisBaseDir = scanners[i].getBasedir();
  330. for (int j = 0; j < fileNames[i].length; j++)
  331. files.addElement(new File(thisBaseDir, fileNames[i][j]));
  332. }
  333. File[] toret = new File[files.size()];
  334. files.copyInto(toret);
  335. return toret;
  336. }
  337. protected static String[][] grabFileNames(FileScanner[] scanners) {
  338. String[][] result = new String[scanners.length][];
  339. for (int i=0; i<scanners.length; i++) {
  340. String[] files = scanners[i].getIncludedFiles();
  341. String[] dirs = scanners[i].getIncludedDirectories();
  342. result[i] = new String[files.length + dirs.length];
  343. System.arraycopy(files, 0, result[i], 0, files.length);
  344. System.arraycopy(dirs, 0, result[i], files.length, dirs.length);
  345. }
  346. return result;
  347. }
  348. protected void zipDir(File dir, ZipOutputStream zOut, String vPath)
  349. throws IOException
  350. {
  351. if (addedDirs.get(vPath) != null) {
  352. // don't add directories we've already added.
  353. // no warning if we try, it is harmless in and of itself
  354. return;
  355. }
  356. addedDirs.put(vPath, vPath);
  357. ZipEntry ze = new ZipEntry (vPath);
  358. if (dir != null) ze.setTime (dir.lastModified ());
  359. ze.setSize (0);
  360. ze.setMethod (ZipEntry.STORED);
  361. // This is faintly ridiculous:
  362. ze.setCrc (emptyCrc);
  363. zOut.putNextEntry (ze);
  364. }
  365. protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,
  366. long lastModified)
  367. throws IOException
  368. {
  369. ZipEntry ze = new ZipEntry(vPath);
  370. ze.setTime(lastModified);
  371. /*
  372. * XXX ZipOutputStream.putEntry expects the ZipEntry to know its
  373. * size and the CRC sum before you start writing the data when using
  374. * STORED mode.
  375. *
  376. * This forces us to process the data twice.
  377. *
  378. * I couldn't find any documentation on this, just found out by try
  379. * and error.
  380. */
  381. if (!doCompress) {
  382. long size = 0;
  383. CRC32 cal = new CRC32();
  384. if (!in.markSupported()) {
  385. // Store data into a byte[]
  386. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  387. byte[] buffer = new byte[8 * 1024];
  388. int count = 0;
  389. do {
  390. size += count;
  391. cal.update(buffer, 0, count);
  392. bos.write(buffer, 0, count);
  393. count = in.read(buffer, 0, buffer.length);
  394. } while (count != -1);
  395. in = new ByteArrayInputStream(bos.toByteArray());
  396. } else {
  397. in.mark(Integer.MAX_VALUE);
  398. byte[] buffer = new byte[8 * 1024];
  399. int count = 0;
  400. do {
  401. size += count;
  402. cal.update(buffer, 0, count);
  403. count = in.read(buffer, 0, buffer.length);
  404. } while (count != -1);
  405. in.reset();
  406. }
  407. ze.setSize(size);
  408. ze.setCrc(cal.getValue());
  409. }
  410. zOut.putNextEntry(ze);
  411. byte[] buffer = new byte[8 * 1024];
  412. int count = 0;
  413. do {
  414. zOut.write(buffer, 0, count);
  415. count = in.read(buffer, 0, buffer.length);
  416. } while (count != -1);
  417. }
  418. protected void zipFile(File file, ZipOutputStream zOut, String vPath)
  419. throws IOException
  420. {
  421. FileInputStream fIn = new FileInputStream(file);
  422. try {
  423. zipFile(fIn, zOut, vPath, file.lastModified());
  424. } finally {
  425. fIn.close();
  426. }
  427. }
  428. /**
  429. * Ensure all parent dirs of a given entry have been added.
  430. */
  431. protected void addParentDirs(File baseDir, String entry,
  432. ZipOutputStream zOut, String prefix)
  433. throws IOException {
  434. Stack directories = new Stack();
  435. int slashPos = entry.length();
  436. while ((slashPos = entry.lastIndexOf((int)'/', slashPos-1)) != -1) {
  437. String dir = entry.substring(0, slashPos+1);
  438. if (addedDirs.get(prefix+dir) != null) {
  439. break;
  440. }
  441. directories.push(dir);
  442. }
  443. while (!directories.isEmpty()) {
  444. String dir = (String) directories.pop();
  445. File f = null;
  446. if (baseDir != null) {
  447. f = new File(baseDir, dir);
  448. } else {
  449. f = new File(dir);
  450. }
  451. zipDir(f, zOut, prefix+dir);
  452. }
  453. }
  454. /**
  455. * Iterate over the given Vector of prefixedfilesets and add
  456. * all files to the ZipOutputStream using the given prefix.
  457. */
  458. protected void addFiles(Vector filesets, ZipOutputStream zOut)
  459. throws IOException {
  460. // Add each fileset in the Vector.
  461. for (int i = 0; i<filesets.size(); i++) {
  462. PrefixedFileSet fs = (PrefixedFileSet) filesets.elementAt(i);
  463. DirectoryScanner ds = fs.getDirectoryScanner(project);
  464. String prefix = fs.getPrefix();
  465. if (prefix.length() > 0
  466. && !prefix.endsWith("/")
  467. && !prefix.endsWith("\\")) {
  468. prefix += "/";
  469. }
  470. String fullpath = fs.getFullpath();
  471. // Need to manually add either fullpath's parent directory, or
  472. // the prefix directory, to the archive.
  473. if (prefix.length() > 0) {
  474. addParentDirs(null, prefix, zOut, "");
  475. zipDir(null, zOut, prefix);
  476. } else if (fullpath.length() > 0) {
  477. addParentDirs(null, fullpath, zOut, "");
  478. }
  479. // Add the fileset.
  480. addFiles(ds, zOut, prefix, fullpath);
  481. }
  482. }
  483. /**
  484. * Do any clean up necessary to allow this instance to be used again.
  485. *
  486. * <p>When we get here, the Zip file has been closed and all we
  487. * need to do is to reset some globals.</p>
  488. */
  489. protected void cleanUp() {}
  490. }