1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.ByteArrayInputStream;
  19. import java.io.ByteArrayOutputStream;
  20. import java.io.File;
  21. import java.io.FileInputStream;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.OutputStream;
  26. import java.util.Enumeration;
  27. import java.util.Hashtable;
  28. import java.util.Stack;
  29. import java.util.Vector;
  30. import java.util.zip.CRC32;
  31. import org.apache.tools.ant.BuildException;
  32. import org.apache.tools.ant.DirectoryScanner;
  33. import org.apache.tools.ant.FileScanner;
  34. import org.apache.tools.ant.Project;
  35. import org.apache.tools.ant.types.EnumeratedAttribute;
  36. import org.apache.tools.ant.types.FileSet;
  37. import org.apache.tools.ant.types.PatternSet;
  38. import org.apache.tools.ant.types.Resource;
  39. import org.apache.tools.ant.types.ZipFileSet;
  40. import org.apache.tools.ant.types.ZipScanner;
  41. import org.apache.tools.ant.util.FileNameMapper;
  42. import org.apache.tools.ant.util.FileUtils;
  43. import org.apache.tools.ant.util.GlobPatternMapper;
  44. import org.apache.tools.ant.util.IdentityMapper;
  45. import org.apache.tools.ant.util.MergingMapper;
  46. import org.apache.tools.ant.util.ResourceUtils;
  47. import org.apache.tools.zip.ZipEntry;
  48. import org.apache.tools.zip.ZipFile;
  49. import org.apache.tools.zip.ZipOutputStream;
  50. /**
  51. * Create a Zip file.
  52. *
  53. * @since Ant 1.1
  54. *
  55. * @ant.task category="packaging"
  56. */
  57. public class Zip extends MatchingTask {
  58. protected File zipFile;
  59. // use to scan own archive
  60. private ZipScanner zs;
  61. private File baseDir;
  62. protected Hashtable entries = new Hashtable();
  63. private Vector groupfilesets = new Vector();
  64. private Vector filesetsFromGroupfilesets = new Vector();
  65. protected String duplicate = "add";
  66. private boolean doCompress = true;
  67. private boolean doUpdate = false;
  68. // shadow of the above if the value is altered in execute
  69. private boolean savedDoUpdate = false;
  70. private boolean doFilesonly = false;
  71. protected String archiveType = "zip";
  72. // For directories:
  73. private static final long EMPTY_CRC = new CRC32 ().getValue ();
  74. protected String emptyBehavior = "skip";
  75. private Vector filesets = new Vector ();
  76. protected Hashtable addedDirs = new Hashtable();
  77. private Vector addedFiles = new Vector();
  78. protected boolean doubleFilePass = false;
  79. protected boolean skipWriting = false;
  80. private static FileUtils fileUtils = FileUtils.newFileUtils();
  81. /**
  82. * true when we are adding new files into the Zip file, as opposed
  83. * to adding back the unchanged files
  84. */
  85. private boolean addingNewFiles = false;
  86. /**
  87. * Encoding to use for filenames, defaults to the platform's
  88. * default encoding.
  89. */
  90. private String encoding;
  91. /**
  92. * Whether the original compression of entries coming from a ZIP
  93. * archive should be kept (for example when updating an archive).
  94. *
  95. * @since Ant 1.6
  96. */
  97. private boolean keepCompression = false;
  98. /**
  99. * Whether the file modification times will be rounded up to the
  100. * next even number of seconds.
  101. *
  102. * @since Ant 1.6.2
  103. */
  104. private boolean roundUp = true;
  105. /**
  106. * This is the name/location of where to
  107. * create the .zip file.
  108. *
  109. * @deprecated Use setDestFile(File) instead.
  110. * @ant.attribute ignore="true"
  111. */
  112. public void setZipfile(File zipFile) {
  113. setDestFile(zipFile);
  114. }
  115. /**
  116. * This is the name/location of where to
  117. * create the file.
  118. * @since Ant 1.5
  119. * @deprecated Use setDestFile(File) instead
  120. * @ant.attribute ignore="true"
  121. */
  122. public void setFile(File file) {
  123. setDestFile(file);
  124. }
  125. /**
  126. * The file to create; required.
  127. * @since Ant 1.5
  128. * @param destFile The new destination File
  129. */
  130. public void setDestFile(File destFile) {
  131. this.zipFile = destFile;
  132. }
  133. /**
  134. * The file to create.
  135. * @since Ant 1.5.2
  136. */
  137. public File getDestFile() {
  138. return zipFile;
  139. }
  140. /**
  141. * Directory from which to archive files; optional.
  142. */
  143. public void setBasedir(File baseDir) {
  144. this.baseDir = baseDir;
  145. }
  146. /**
  147. * Whether we want to compress the files or only store them;
  148. * optional, default=true;
  149. */
  150. public void setCompress(boolean c) {
  151. doCompress = c;
  152. }
  153. /**
  154. * Whether we want to compress the files or only store them;
  155. *
  156. * @since Ant 1.5.2
  157. */
  158. public boolean isCompress() {
  159. return doCompress;
  160. }
  161. /**
  162. * If true, emulate Sun's jar utility by not adding parent directories;
  163. * optional, defaults to false.
  164. */
  165. public void setFilesonly(boolean f) {
  166. doFilesonly = f;
  167. }
  168. /**
  169. * If true, updates an existing file, otherwise overwrite
  170. * any existing one; optional defaults to false.
  171. */
  172. public void setUpdate(boolean c) {
  173. doUpdate = c;
  174. savedDoUpdate = c;
  175. }
  176. /**
  177. * Are we updating an existing archive?
  178. */
  179. public boolean isInUpdateMode() {
  180. return doUpdate;
  181. }
  182. /**
  183. * Adds a set of files.
  184. */
  185. public void addFileset(FileSet set) {
  186. filesets.addElement(set);
  187. }
  188. /**
  189. * Adds a set of files that can be
  190. * read from an archive and be given a prefix/fullpath.
  191. */
  192. public void addZipfileset(ZipFileSet set) {
  193. filesets.addElement(set);
  194. }
  195. /**
  196. * Adds a group of zip files.
  197. */
  198. public void addZipGroupFileset(FileSet set) {
  199. groupfilesets.addElement(set);
  200. }
  201. /**
  202. * Sets behavior for when a duplicate file is about to be added -
  203. * one of <code>keep</code>, <code>skip</code> or <code>overwrite</code>.
  204. * Possible values are: <code>keep</code> (keep both
  205. * of the files); <code>skip</code> (keep the first version
  206. * of the file found); <code>overwrite</code> overwrite the file
  207. * with the new file
  208. * Default for zip tasks is <code>keep</code>
  209. */
  210. public void setDuplicate(Duplicate df) {
  211. duplicate = df.getValue();
  212. }
  213. /**
  214. * Possible behaviors when there are no matching files for the task:
  215. * "fail", "skip", or "create".
  216. */
  217. public static class WhenEmpty extends EnumeratedAttribute {
  218. public String[] getValues() {
  219. return new String[] {"fail", "skip", "create"};
  220. }
  221. }
  222. /**
  223. * Sets behavior of the task when no files match.
  224. * Possible values are: <code>fail</code> (throw an exception
  225. * and halt the build); <code>skip</code> (do not create
  226. * any archive, but issue a warning); <code>create</code>
  227. * (make an archive with no entries).
  228. * Default for zip tasks is <code>skip</code>
  229. * for jar tasks, <code>create</code>.
  230. */
  231. public void setWhenempty(WhenEmpty we) {
  232. emptyBehavior = we.getValue();
  233. }
  234. /**
  235. * Encoding to use for filenames, defaults to the platform's
  236. * default encoding.
  237. *
  238. * <p>For a list of possible values see <a
  239. * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.</p>
  240. */
  241. public void setEncoding(String encoding) {
  242. this.encoding = encoding;
  243. }
  244. /**
  245. * Encoding to use for filenames.
  246. *
  247. * @since Ant 1.5.2
  248. */
  249. public String getEncoding() {
  250. return encoding;
  251. }
  252. /**
  253. * Whether the original compression of entries coming from a ZIP
  254. * archive should be kept (for example when updating an archive).
  255. *
  256. * @since Ant 1.6
  257. */
  258. public void setKeepCompression(boolean keep) {
  259. keepCompression = keep;
  260. }
  261. /**
  262. * Whether the file modification times will be rounded up to the
  263. * next even number of seconds.
  264. *
  265. * <p>Zip archives store file modification times with a
  266. * granularity of two seconds, so the times will either be rounded
  267. * up or down. If you round down, the archive will always seem
  268. * out-of-date when you rerun the task, so the default is to round
  269. * up. Rounding up may lead to a different type of problems like
  270. * JSPs inside a web archive that seem to be slightly more recent
  271. * than precompiled pages, rendering precompilation useless.</p>
  272. *
  273. * @since Ant 1.6.2
  274. */
  275. public void setRoundUp(boolean r) {
  276. roundUp = r;
  277. }
  278. /**
  279. * validate and build
  280. */
  281. public void execute() throws BuildException {
  282. if (doubleFilePass) {
  283. skipWriting = true;
  284. executeMain();
  285. skipWriting = false;
  286. executeMain();
  287. } else {
  288. executeMain();
  289. }
  290. }
  291. public void executeMain() throws BuildException {
  292. if (baseDir == null && filesets.size() == 0
  293. && groupfilesets.size() == 0 && "zip".equals(archiveType)) {
  294. throw new BuildException("basedir attribute must be set, "
  295. + "or at least "
  296. + "one fileset must be given!");
  297. }
  298. if (zipFile == null) {
  299. throw new BuildException("You must specify the "
  300. + archiveType + " file to create!");
  301. }
  302. if (zipFile.exists() && !zipFile.isFile()) {
  303. throw new BuildException(zipFile + " is not a file.");
  304. }
  305. if (zipFile.exists() && !zipFile.canWrite()) {
  306. throw new BuildException(zipFile + " is read-only.");
  307. }
  308. // Renamed version of original file, if it exists
  309. File renamedFile = null;
  310. // Whether or not an actual update is required -
  311. // we don't need to update if the original file doesn't exist
  312. addingNewFiles = true;
  313. if (doUpdate && !zipFile.exists()) {
  314. doUpdate = false;
  315. log("ignoring update attribute as " + archiveType
  316. + " doesn't exist.", Project.MSG_DEBUG);
  317. }
  318. // Add the files found in groupfileset to fileset
  319. for (int i = 0; i < groupfilesets.size(); i++) {
  320. log("Processing groupfileset ", Project.MSG_VERBOSE);
  321. FileSet fs = (FileSet) groupfilesets.elementAt(i);
  322. FileScanner scanner = fs.getDirectoryScanner(getProject());
  323. String[] files = scanner.getIncludedFiles();
  324. File basedir = scanner.getBasedir();
  325. for (int j = 0; j < files.length; j++) {
  326. log("Adding file " + files[j] + " to fileset",
  327. Project.MSG_VERBOSE);
  328. ZipFileSet zf = new ZipFileSet();
  329. zf.setProject(getProject());
  330. zf.setSrc(new File(basedir, files[j]));
  331. filesets.addElement(zf);
  332. filesetsFromGroupfilesets.addElement(zf);
  333. }
  334. }
  335. // collect filesets to pass them to getResourcesToAdd
  336. Vector vfss = new Vector();
  337. if (baseDir != null) {
  338. FileSet fs = (FileSet) getImplicitFileSet().clone();
  339. fs.setDir(baseDir);
  340. vfss.addElement(fs);
  341. }
  342. for (int i = 0; i < filesets.size(); i++) {
  343. FileSet fs = (FileSet) filesets.elementAt(i);
  344. vfss.addElement(fs);
  345. }
  346. FileSet[] fss = new FileSet[vfss.size()];
  347. vfss.copyInto(fss);
  348. boolean success = false;
  349. try {
  350. // can also handle empty archives
  351. ArchiveState state = getResourcesToAdd(fss, zipFile, false);
  352. // quick exit if the target is up to date
  353. if (!state.isOutOfDate()) {
  354. return;
  355. }
  356. Resource[][] addThem = state.getResourcesToAdd();
  357. if (doUpdate) {
  358. renamedFile =
  359. fileUtils.createTempFile("zip", ".tmp",
  360. fileUtils.getParentFile(zipFile));
  361. renamedFile.deleteOnExit();
  362. try {
  363. fileUtils.rename(zipFile, renamedFile);
  364. } catch (SecurityException e) {
  365. throw new BuildException(
  366. "Not allowed to rename old file ("
  367. + zipFile.getAbsolutePath()
  368. + ") to temporary file");
  369. } catch (IOException e) {
  370. throw new BuildException(
  371. "Unable to rename old file ("
  372. + zipFile.getAbsolutePath()
  373. + ") to temporary file");
  374. }
  375. }
  376. String action = doUpdate ? "Updating " : "Building ";
  377. log(action + archiveType + ": " + zipFile.getAbsolutePath());
  378. ZipOutputStream zOut = null;
  379. try {
  380. if (!skipWriting) {
  381. zOut = new ZipOutputStream(zipFile);
  382. zOut.setEncoding(encoding);
  383. if (doCompress) {
  384. zOut.setMethod(ZipOutputStream.DEFLATED);
  385. } else {
  386. zOut.setMethod(ZipOutputStream.STORED);
  387. }
  388. }
  389. initZipOutputStream(zOut);
  390. // Add the explicit filesets to the archive.
  391. for (int i = 0; i < fss.length; i++) {
  392. if (addThem[i].length != 0) {
  393. addResources(fss[i], addThem[i], zOut);
  394. }
  395. }
  396. if (doUpdate) {
  397. addingNewFiles = false;
  398. ZipFileSet oldFiles = new ZipFileSet();
  399. oldFiles.setProject(getProject());
  400. oldFiles.setSrc(renamedFile);
  401. for (int i = 0; i < addedFiles.size(); i++) {
  402. PatternSet.NameEntry ne = oldFiles.createExclude();
  403. ne.setName((String) addedFiles.elementAt(i));
  404. }
  405. DirectoryScanner ds =
  406. oldFiles.getDirectoryScanner(getProject());
  407. ((ZipScanner) ds).setEncoding(encoding);
  408. String[] f = ds.getIncludedFiles();
  409. Resource[] r = new Resource[f.length];
  410. for (int i = 0; i < f.length; i++) {
  411. r[i] = ds.getResource(f[i]);
  412. }
  413. if (!doFilesonly) {
  414. String[] d = ds.getIncludedDirectories();
  415. Resource[] dr = new Resource[d.length];
  416. for (int i = 0; i < d.length; i++) {
  417. dr[i] = ds.getResource(d[i]);
  418. }
  419. Resource[] tmp = r;
  420. r = new Resource[tmp.length + dr.length];
  421. System.arraycopy(dr, 0, r, 0, dr.length);
  422. System.arraycopy(tmp, 0, r, dr.length, tmp.length);
  423. }
  424. addResources(oldFiles, r, zOut);
  425. }
  426. finalizeZipOutputStream(zOut);
  427. // If we've been successful on an update, delete the
  428. // temporary file
  429. if (doUpdate) {
  430. if (!renamedFile.delete()) {
  431. log ("Warning: unable to delete temporary file "
  432. + renamedFile.getName(), Project.MSG_WARN);
  433. }
  434. }
  435. success = true;
  436. } finally {
  437. // Close the output stream.
  438. try {
  439. if (zOut != null) {
  440. zOut.close();
  441. }
  442. } catch (IOException ex) {
  443. // If we're in this finally clause because of an
  444. // exception, we don't really care if there's an
  445. // exception when closing the stream. E.g. if it
  446. // throws "ZIP file must have at least one entry",
  447. // because an exception happened before we added
  448. // any files, then we must swallow this
  449. // exception. Otherwise, the error that's reported
  450. // will be the close() error, which is not the
  451. // real cause of the problem.
  452. if (success) {
  453. throw ex;
  454. }
  455. }
  456. }
  457. } catch (IOException ioe) {
  458. String msg = "Problem creating " + archiveType + ": "
  459. + ioe.getMessage();
  460. // delete a bogus ZIP file (but only if it's not the original one)
  461. if ((!doUpdate || renamedFile != null) && !zipFile.delete()) {
  462. msg += " (and the archive is probably corrupt but I could not "
  463. + "delete it)";
  464. }
  465. if (doUpdate && renamedFile != null) {
  466. try {
  467. fileUtils.rename(renamedFile, zipFile);
  468. } catch (IOException e) {
  469. msg += " (and I couldn't rename the temporary file "
  470. + renamedFile.getName() + " back)";
  471. }
  472. }
  473. throw new BuildException(msg, ioe, getLocation());
  474. } finally {
  475. cleanUp();
  476. }
  477. }
  478. /**
  479. * Indicates if the task is adding new files into the archive as opposed to
  480. * copying back unchanged files from the backup copy
  481. */
  482. protected final boolean isAddingNewFiles() {
  483. return addingNewFiles;
  484. }
  485. /**
  486. * Add the given resources.
  487. *
  488. * @param fileset may give additional information like fullpath or
  489. * permissions.
  490. * @param resources the resources to add
  491. * @param zOut the stream to write to
  492. *
  493. * @since Ant 1.5.2
  494. */
  495. protected final void addResources(FileSet fileset, Resource[] resources,
  496. ZipOutputStream zOut)
  497. throws IOException {
  498. String prefix = "";
  499. String fullpath = "";
  500. int dirMode = ZipFileSet.DEFAULT_DIR_MODE;
  501. int fileMode = ZipFileSet.DEFAULT_FILE_MODE;
  502. ZipFileSet zfs = null;
  503. if (fileset instanceof ZipFileSet) {
  504. zfs = (ZipFileSet) fileset;
  505. prefix = zfs.getPrefix(getProject());
  506. fullpath = zfs.getFullpath(getProject());
  507. dirMode = zfs.getDirMode(getProject());
  508. fileMode = zfs.getFileMode(getProject());
  509. }
  510. if (prefix.length() > 0 && fullpath.length() > 0) {
  511. throw new BuildException("Both prefix and fullpath attributes must"
  512. + " not be set on the same fileset.");
  513. }
  514. if (resources.length != 1 && fullpath.length() > 0) {
  515. throw new BuildException("fullpath attribute may only be specified"
  516. + " for filesets that specify a single"
  517. + " file.");
  518. }
  519. if (prefix.length() > 0) {
  520. if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
  521. prefix += "/";
  522. }
  523. addParentDirs(null, prefix, zOut, "", dirMode);
  524. }
  525. ZipFile zf = null;
  526. try {
  527. boolean dealingWithFiles = false;
  528. File base = null;
  529. if (zfs == null || zfs.getSrc(getProject()) == null) {
  530. dealingWithFiles = true;
  531. base = fileset.getDir(getProject());
  532. } else {
  533. zf = new ZipFile(zfs.getSrc(getProject()), encoding);
  534. }
  535. for (int i = 0; i < resources.length; i++) {
  536. String name = null;
  537. if (fullpath.length() > 0) {
  538. name = fullpath;
  539. } else {
  540. name = resources[i].getName();
  541. }
  542. name = name.replace(File.separatorChar, '/');
  543. if ("".equals(name)) {
  544. continue;
  545. }
  546. if (resources[i].isDirectory() && !name.endsWith("/")) {
  547. name = name + "/";
  548. }
  549. if (!doFilesonly && !dealingWithFiles
  550. && resources[i].isDirectory()
  551. && !zfs.hasDirModeBeenSet()) {
  552. int nextToLastSlash = name.lastIndexOf("/",
  553. name.length() - 2);
  554. if (nextToLastSlash != -1) {
  555. addParentDirs(base, name.substring(0,
  556. nextToLastSlash + 1),
  557. zOut, prefix, dirMode);
  558. }
  559. ZipEntry ze = zf.getEntry(resources[i].getName());
  560. addParentDirs(base, name, zOut, prefix, ze.getUnixMode());
  561. } else {
  562. addParentDirs(base, name, zOut, prefix, dirMode);
  563. }
  564. if (!resources[i].isDirectory() && dealingWithFiles) {
  565. File f = fileUtils.resolveFile(base,
  566. resources[i].getName());
  567. zipFile(f, zOut, prefix + name, fileMode);
  568. } else if (!resources[i].isDirectory()) {
  569. ZipEntry ze = zf.getEntry(resources[i].getName());
  570. if (ze != null) {
  571. boolean oldCompress = doCompress;
  572. if (keepCompression) {
  573. doCompress = (ze.getMethod() == ZipEntry.DEFLATED);
  574. }
  575. try {
  576. zipFile(zf.getInputStream(ze), zOut, prefix + name,
  577. ze.getTime(), zfs.getSrc(getProject()),
  578. zfs.hasFileModeBeenSet() ? fileMode
  579. : ze.getUnixMode());
  580. } finally {
  581. doCompress = oldCompress;
  582. }
  583. }
  584. }
  585. }
  586. } finally {
  587. if (zf != null) {
  588. zf.close();
  589. }
  590. }
  591. }
  592. /**
  593. * method for subclasses to override
  594. */
  595. protected void initZipOutputStream(ZipOutputStream zOut)
  596. throws IOException, BuildException {
  597. }
  598. /**
  599. * method for subclasses to override
  600. */
  601. protected void finalizeZipOutputStream(ZipOutputStream zOut)
  602. throws IOException, BuildException {
  603. }
  604. /**
  605. * Create an empty zip file
  606. *
  607. * @return true for historic reasons
  608. */
  609. protected boolean createEmptyZip(File zipFile) throws BuildException {
  610. // In this case using java.util.zip will not work
  611. // because it does not permit a zero-entry archive.
  612. // Must create it manually.
  613. log("Note: creating empty " + archiveType + " archive " + zipFile,
  614. Project.MSG_INFO);
  615. OutputStream os = null;
  616. try {
  617. os = new FileOutputStream(zipFile);
  618. // Cf. PKZIP specification.
  619. byte[] empty = new byte[22];
  620. empty[0] = 80; // P
  621. empty[1] = 75; // K
  622. empty[2] = 5;
  623. empty[3] = 6;
  624. // remainder zeros
  625. os.write(empty);
  626. } catch (IOException ioe) {
  627. throw new BuildException("Could not create empty ZIP archive "
  628. + "(" + ioe.getMessage() + ")", ioe,
  629. getLocation());
  630. } finally {
  631. if (os != null) {
  632. try {
  633. os.close();
  634. } catch (IOException e) {
  635. //ignore
  636. }
  637. }
  638. }
  639. return true;
  640. }
  641. /**
  642. * @since Ant 1.5.2
  643. */
  644. private synchronized ZipScanner getZipScanner() {
  645. if (zs == null) {
  646. zs = new ZipScanner();
  647. zs.setEncoding(encoding);
  648. zs.setSrc(zipFile);
  649. }
  650. return zs;
  651. }
  652. /**
  653. * Collect the resources that are newer than the corresponding
  654. * entries (or missing) in the original archive.
  655. *
  656. * <p>If we are going to recreate the archive instead of updating
  657. * it, all resources should be considered as new, if a single one
  658. * is. Because of this, subclasses overriding this method must
  659. * call <code>super.getResourcesToAdd</code> and indicate with the
  660. * third arg if they already know that the archive is
  661. * out-of-date.</p>
  662. *
  663. * @param filesets The filesets to grab resources from
  664. * @param zipFile intended archive file (may or may not exist)
  665. * @param needsUpdate whether we already know that the archive is
  666. * out-of-date. Subclasses overriding this method are supposed to
  667. * set this value correctly in their call to
  668. * super.getResourcesToAdd.
  669. * @return an array of resources to add for each fileset passed in as well
  670. * as a flag that indicates whether the archive is uptodate.
  671. *
  672. * @exception BuildException if it likes
  673. */
  674. protected ArchiveState getResourcesToAdd(FileSet[] filesets,
  675. File zipFile,
  676. boolean needsUpdate)
  677. throws BuildException {
  678. Resource[][] initialResources = grabResources(filesets);
  679. if (isEmpty(initialResources)) {
  680. if (needsUpdate && doUpdate) {
  681. /*
  682. * This is a rather hairy case.
  683. *
  684. * One of our subclasses knows that we need to update the
  685. * archive, but at the same time, there are no resources
  686. * known to us that would need to be added. Only the
  687. * subclass seems to know what's going on.
  688. *
  689. * This happens if <jar> detects that the manifest has changed,
  690. * for example. The manifest is not part of any resources
  691. * because of our support for inline <manifest>s.
  692. *
  693. * If we invoke createEmptyZip like Ant 1.5.2 did,
  694. * we'll loose all stuff that has been in the original
  695. * archive (bugzilla report 17780).
  696. */
  697. return new ArchiveState(true, initialResources);
  698. }
  699. if (emptyBehavior.equals("skip")) {
  700. if (doUpdate) {
  701. log(archiveType + " archive " + zipFile
  702. + " not updated because no new files were included.",
  703. Project.MSG_VERBOSE);
  704. } else {
  705. log("Warning: skipping " + archiveType + " archive "
  706. + zipFile + " because no files were included.",
  707. Project.MSG_WARN);
  708. }
  709. } else if (emptyBehavior.equals("fail")) {
  710. throw new BuildException("Cannot create " + archiveType
  711. + " archive " + zipFile
  712. + ": no files were included.",
  713. getLocation());
  714. } else {
  715. // Create.
  716. createEmptyZip(zipFile);
  717. }
  718. return new ArchiveState(needsUpdate, initialResources);
  719. }
  720. // initialResources is not empty
  721. if (!zipFile.exists()) {
  722. return new ArchiveState(true, initialResources);
  723. }
  724. if (needsUpdate && !doUpdate) {
  725. // we are recreating the archive, need all resources
  726. return new ArchiveState(true, initialResources);
  727. }
  728. Resource[][] newerResources = new Resource[filesets.length][];
  729. for (int i = 0; i < filesets.length; i++) {
  730. if (!(fileset instanceof ZipFileSet)
  731. || ((ZipFileSet) fileset).getSrc(getProject()) == null) {
  732. File base = filesets[i].getDir(getProject());
  733. for (int j = 0; j < initialResources[i].length; j++) {
  734. File resourceAsFile =
  735. fileUtils.resolveFile(base,
  736. initialResources[i][j].getName());
  737. if (resourceAsFile.equals(zipFile)) {
  738. throw new BuildException("A zip file cannot include "
  739. + "itself", getLocation());
  740. }
  741. }
  742. }
  743. }
  744. for (int i = 0; i < filesets.length; i++) {
  745. if (initialResources[i].length == 0) {
  746. newerResources[i] = new Resource[] {};
  747. continue;
  748. }
  749. FileNameMapper myMapper = new IdentityMapper();
  750. if (filesets[i] instanceof ZipFileSet) {
  751. ZipFileSet zfs = (ZipFileSet) filesets[i];
  752. if (zfs.getFullpath(getProject()) != null
  753. && !zfs.getFullpath(getProject()).equals("")) {
  754. // in this case all files from origin map to
  755. // the fullPath attribute of the zipfileset at
  756. // destination
  757. MergingMapper fm = new MergingMapper();
  758. fm.setTo(zfs.getFullpath(getProject()));
  759. myMapper = fm;
  760. } else if (zfs.getPrefix(getProject()) != null
  761. && !zfs.getPrefix(getProject()).equals("")) {
  762. GlobPatternMapper gm = new GlobPatternMapper();
  763. gm.setFrom("*");
  764. String prefix = zfs.getPrefix(getProject());
  765. if (!prefix.endsWith("/") && !prefix.endsWith("\\")) {
  766. prefix += "/";
  767. }
  768. gm.setTo(prefix + "*");
  769. myMapper = gm;
  770. }
  771. }
  772. Resource[] resources = initialResources[i];
  773. if (doFilesonly) {
  774. resources = selectFileResources(resources);
  775. }
  776. newerResources[i] =
  777. ResourceUtils.selectOutOfDateSources(this,
  778. resources,
  779. myMapper,
  780. getZipScanner());
  781. needsUpdate = needsUpdate || (newerResources[i].length > 0);
  782. if (needsUpdate && !doUpdate) {
  783. // we will return initialResources anyway, no reason
  784. // to scan further.
  785. break;
  786. }
  787. }
  788. if (needsUpdate && !doUpdate) {
  789. // we are recreating the archive, need all resources
  790. return new ArchiveState(true, initialResources);
  791. }
  792. return new ArchiveState(needsUpdate, newerResources);
  793. }
  794. /**
  795. * Fetch all included and not excluded resources from the sets.
  796. *
  797. * <p>Included directories will precede included files.</p>
  798. *
  799. * @since Ant 1.5.2
  800. */
  801. protected Resource[][] grabResources(FileSet[] filesets) {
  802. Resource[][] result = new Resource[filesets.length][];
  803. for (int i = 0; i < filesets.length; i++) {
  804. boolean skipEmptyNames = true;
  805. if (filesets[i] instanceof ZipFileSet) {
  806. ZipFileSet zfs = (ZipFileSet) filesets[i];
  807. skipEmptyNames = zfs.getPrefix(getProject()).equals("")
  808. && zfs.getFullpath(getProject()).equals("");
  809. }
  810. DirectoryScanner rs =
  811. filesets[i].getDirectoryScanner(getProject());
  812. if (rs instanceof ZipScanner) {
  813. ((ZipScanner) rs).setEncoding(encoding);
  814. }
  815. Vector resources = new Vector();
  816. String[] directories = rs.getIncludedDirectories();
  817. for (int j = 0; j < directories.length; j++) {
  818. if (!"".equals(directories[0]) || !skipEmptyNames) {
  819. resources.addElement(rs.getResource(directories[j]));
  820. }
  821. }
  822. String[] files = rs.getIncludedFiles();
  823. for (int j = 0; j < files.length; j++) {
  824. if (!"".equals(files[0]) || !skipEmptyNames) {
  825. resources.addElement(rs.getResource(files[j]));
  826. }
  827. }
  828. result[i] = new Resource[resources.size()];
  829. resources.copyInto(result[i]);
  830. }
  831. return result;
  832. }
  833. /**
  834. * @since Ant 1.5.2
  835. */
  836. protected void zipDir(File dir, ZipOutputStream zOut, String vPath,
  837. int mode)
  838. throws IOException {
  839. if (addedDirs.get(vPath) != null) {
  840. // don't add directories we've already added.
  841. // no warning if we try, it is harmless in and of itself
  842. return;
  843. }
  844. log("adding directory " + vPath, Project.MSG_VERBOSE);
  845. addedDirs.put(vPath, vPath);
  846. if (!skipWriting) {
  847. ZipEntry ze = new ZipEntry (vPath);
  848. if (dir != null && dir.exists()) {
  849. // ZIPs store time with a granularity of 2 seconds, round up
  850. ze.setTime(dir.lastModified() + (roundUp ? 1999 : 0));
  851. } else {
  852. // ZIPs store time with a granularity of 2 seconds, round up
  853. ze.setTime(System.currentTimeMillis() + (roundUp ? 1999 : 0));
  854. }
  855. ze.setSize (0);
  856. ze.setMethod (ZipEntry.STORED);
  857. // This is faintly ridiculous:
  858. ze.setCrc (EMPTY_CRC);
  859. ze.setUnixMode(mode);
  860. zOut.putNextEntry (ze);
  861. }
  862. }
  863. /**
  864. * Adds a new entry to the archive, takes care of duplicates as well.
  865. *
  866. * @param in the stream to read data for the entry from.
  867. * @param zOut the stream to write to.
  868. * @param vPath the name this entry shall have in the archive.
  869. * @param lastModified last modification time for the entry.
  870. * @param fromArchive the original archive we are copying this
  871. * entry from, will be null if we are not copying from an archive.
  872. * @param mode the Unix permissions to set.
  873. *
  874. * @since Ant 1.5.2
  875. */
  876. protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,
  877. long lastModified, File fromArchive, int mode)
  878. throws IOException {
  879. if (entries.contains(vPath)) {
  880. if (duplicate.equals("preserve")) {
  881. log(vPath + " already added, skipping", Project.MSG_INFO);
  882. return;
  883. } else if (duplicate.equals("fail")) {
  884. throw new BuildException("Duplicate file " + vPath
  885. + " was found and the duplicate "
  886. + "attribute is 'fail'.");
  887. } else {
  888. // duplicate equal to add, so we continue
  889. log("duplicate file " + vPath
  890. + " found, adding.", Project.MSG_VERBOSE);
  891. }
  892. } else {
  893. log("adding entry " + vPath, Project.MSG_VERBOSE);
  894. }
  895. entries.put(vPath, vPath);
  896. if (!skipWriting) {
  897. ZipEntry ze = new ZipEntry(vPath);
  898. ze.setTime(lastModified);
  899. ze.setMethod(doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED);
  900. /*
  901. * ZipOutputStream.putNextEntry expects the ZipEntry to
  902. * know its size and the CRC sum before you start writing
  903. * the data when using STORED mode - unless it is seekable.
  904. *
  905. * This forces us to process the data twice.
  906. */
  907. if (!zOut.isSeekable() && !doCompress) {
  908. long size = 0;
  909. CRC32 cal = new CRC32();
  910. if (!in.markSupported()) {
  911. // Store data into a byte[]
  912. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  913. byte[] buffer = new byte[8 * 1024];
  914. int count = 0;
  915. do {
  916. size += count;
  917. cal.update(buffer, 0, count);
  918. bos.write(buffer, 0, count);
  919. count = in.read(buffer, 0, buffer.length);
  920. } while (count != -1);
  921. in = new ByteArrayInputStream(bos.toByteArray());
  922. } else {
  923. in.mark(Integer.MAX_VALUE);
  924. byte[] buffer = new byte[8 * 1024];
  925. int count = 0;
  926. do {
  927. size += count;
  928. cal.update(buffer, 0, count);
  929. count = in.read(buffer, 0, buffer.length);
  930. } while (count != -1);
  931. in.reset();
  932. }
  933. ze.setSize(size);
  934. ze.setCrc(cal.getValue());
  935. }
  936. ze.setUnixMode(mode);
  937. zOut.putNextEntry(ze);
  938. byte[] buffer = new byte[8 * 1024];
  939. int count = 0;
  940. do {
  941. if (count != 0) {
  942. zOut.write(buffer, 0, count);
  943. }
  944. count = in.read(buffer, 0, buffer.length);
  945. } while (count != -1);
  946. }
  947. addedFiles.addElement(vPath);
  948. }
  949. /**
  950. * Method that gets called when adding from java.io.File instances.
  951. *
  952. * <p>This implementation delegates to the six-arg version.</p>
  953. *
  954. * @param file the file to add to the archive
  955. * @param zOut the stream to write to
  956. * @param vPath the name this entry shall have in the archive
  957. * @param mode the Unix permissions to set.
  958. *
  959. * @since Ant 1.5.2
  960. */
  961. protected void zipFile(File file, ZipOutputStream zOut, String vPath,
  962. int mode)
  963. throws IOException {
  964. if (file.equals(zipFile)) {
  965. throw new BuildException("A zip file cannot include itself",
  966. getLocation());
  967. }
  968. FileInputStream fIn = new FileInputStream(file);
  969. try {
  970. // ZIPs store time with a granularity of 2 seconds, round up
  971. zipFile(fIn, zOut, vPath,
  972. file.lastModified() + (roundUp ? 1999 : 0),
  973. null, mode);
  974. } finally {
  975. fIn.close();
  976. }
  977. }
  978. /**
  979. * Ensure all parent dirs of a given entry have been added.
  980. *
  981. * @since Ant 1.5.2
  982. */
  983. protected final void addParentDirs(File baseDir, String entry,
  984. ZipOutputStream zOut, String prefix,
  985. int dirMode)
  986. throws IOException {
  987. if (!doFilesonly) {
  988. Stack directories = new Stack();
  989. int slashPos = entry.length();
  990. while ((slashPos = entry.lastIndexOf('/', slashPos - 1)) != -1) {
  991. String dir = entry.substring(0, slashPos + 1);
  992. if (addedDirs.get(prefix + dir) != null) {
  993. break;
  994. }
  995. directories.push(dir);
  996. }
  997. while (!directories.isEmpty()) {
  998. String dir = (String) directories.pop();
  999. File f = null;
  1000. if (baseDir != null) {
  1001. f = new File(baseDir, dir);
  1002. } else {
  1003. f = new File(dir);
  1004. }
  1005. zipDir(f, zOut, prefix + dir, dirMode);
  1006. }
  1007. }
  1008. }
  1009. /**
  1010. * Do any clean up necessary to allow this instance to be used again.
  1011. *
  1012. * <p>When we get here, the Zip file has been closed and all we
  1013. * need to do is to reset some globals.</p>
  1014. *
  1015. * <p>This method will only reset globals that have been changed
  1016. * during execute(), it will not alter the attributes or nested
  1017. * child elements. If you want to reset the instance so that you
  1018. * can later zip a completely different set of files, you must use
  1019. * the reset method.</p>
  1020. *
  1021. * @see #reset
  1022. */
  1023. protected void cleanUp() {
  1024. addedDirs.clear();
  1025. addedFiles.removeAllElements();
  1026. entries.clear();
  1027. addingNewFiles = false;
  1028. doUpdate = savedDoUpdate;
  1029. Enumeration e = filesetsFromGroupfilesets.elements();
  1030. while (e.hasMoreElements()) {
  1031. ZipFileSet zf = (ZipFileSet) e.nextElement();
  1032. filesets.removeElement(zf);
  1033. }
  1034. filesetsFromGroupfilesets.removeAllElements();
  1035. }
  1036. /**
  1037. * Makes this instance reset all attributes to their default
  1038. * values and forget all children.
  1039. *
  1040. * @since Ant 1.5
  1041. *
  1042. * @see #cleanUp
  1043. */
  1044. public void reset() {
  1045. filesets.removeAllElements();
  1046. zipFile = null;
  1047. baseDir = null;
  1048. groupfilesets.removeAllElements();
  1049. duplicate = "add";
  1050. archiveType = "zip";
  1051. doCompress = true;
  1052. emptyBehavior = "skip";
  1053. doUpdate = false;
  1054. doFilesonly = false;
  1055. encoding = null;
  1056. }
  1057. /**
  1058. * @return true if all individual arrays are empty
  1059. *
  1060. * @since Ant 1.5.2
  1061. */
  1062. protected static final boolean isEmpty(Resource[][] r) {
  1063. for (int i = 0; i < r.length; i++) {
  1064. if (r[i].length > 0) {
  1065. return false;
  1066. }
  1067. }
  1068. return true;
  1069. }
  1070. /**
  1071. * Drops all non-file resources from the given array.
  1072. *
  1073. * @since Ant 1.6
  1074. */
  1075. protected Resource[] selectFileResources(Resource[] orig) {
  1076. if (orig.length == 0) {
  1077. return orig;
  1078. }
  1079. Vector v = new Vector(orig.length);
  1080. for (int i = 0; i < orig.length; i++) {
  1081. if (!orig[i].isDirectory()) {
  1082. v.addElement(orig[i]);
  1083. } else {
  1084. log("Ignoring directory " + orig[i].getName()
  1085. + " as only files will be added.", Project.MSG_VERBOSE);
  1086. }
  1087. }
  1088. if (v.size() != orig.length) {
  1089. Resource[] r = new Resource[v.size()];
  1090. v.copyInto(r);
  1091. return r;
  1092. }
  1093. return orig;
  1094. }
  1095. /**
  1096. * Possible behaviors when a duplicate file is added:
  1097. * "add", "preserve" or "fail"
  1098. */
  1099. public static class Duplicate extends EnumeratedAttribute {
  1100. public String[] getValues() {
  1101. return new String[] {"add", "preserve", "fail"};
  1102. }
  1103. }
  1104. /**
  1105. * Holds the up-to-date status and the out-of-date resources of
  1106. * the original archive.
  1107. *
  1108. * @since Ant 1.5.3
  1109. */
  1110. public static class ArchiveState {
  1111. private boolean outOfDate;
  1112. private Resource[][] resourcesToAdd;
  1113. ArchiveState(boolean state, Resource[][] r) {
  1114. outOfDate = state;
  1115. resourcesToAdd = r;
  1116. }
  1117. public boolean isOutOfDate() {
  1118. return outOfDate;
  1119. }
  1120. public Resource[][] getResourcesToAdd() {
  1121. return resourcesToAdd;
  1122. }
  1123. }
  1124. }