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 44 kB

Addition of ZipFileset facilities. Descibed by the author --- With these patches, Zip (and derivative tasks such as Jar and War) can merge the entries of multiple zip files into a single output zip file. The contents of an input zip file may be selectively extracted based on include/exclude patterns. An included zip file is specified using a <fileset> with a "src" attribute, as in: <target name="jartest"> <jar jarfile="utils.jar"> <fileset src="weblogic.jar" includes="weblogic/utils/" excludes="weblogic/utils/jars/,**/reflect/" /> </jar> </target> In this example, a subset of the "weblogic/utils" directory is extracted from weblogic.jar, into utils.jar. The fileset may also contain "prefix" and "fullpath" attributes (the functionality of PrefixedFileSet has been retained in the new class ZipFileSet). Prefixes apply to directory-based and zip-based filesets. The fullpath attributes applies only to a single file in a directory-based fileset. The War task may extract entries from a zip file for all of its filesets (including the files in "classes" and "lib"). The motivation for this change is: 1) There is significant overlap between "jlink" and "zip", and it seemed better to combine them. 2) "jlink" does not support include/exclude patterns which are extremely useful for writing packaging-type tasks such as Zip/Jar/War. This was my main motivation. 3) By adding this functionality to the base task, it can also be used in derivative tasks such as Jar and War. --- Submitted By: Don Ferguson <don@bea.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268458 13f79535-47bb-0310-9956-ffa450edef68
25 years ago
Addition of ZipFileset facilities. Descibed by the author --- With these patches, Zip (and derivative tasks such as Jar and War) can merge the entries of multiple zip files into a single output zip file. The contents of an input zip file may be selectively extracted based on include/exclude patterns. An included zip file is specified using a <fileset> with a "src" attribute, as in: <target name="jartest"> <jar jarfile="utils.jar"> <fileset src="weblogic.jar" includes="weblogic/utils/" excludes="weblogic/utils/jars/,**/reflect/" /> </jar> </target> In this example, a subset of the "weblogic/utils" directory is extracted from weblogic.jar, into utils.jar. The fileset may also contain "prefix" and "fullpath" attributes (the functionality of PrefixedFileSet has been retained in the new class ZipFileSet). Prefixes apply to directory-based and zip-based filesets. The fullpath attributes applies only to a single file in a directory-based fileset. The War task may extract entries from a zip file for all of its filesets (including the files in "classes" and "lib"). The motivation for this change is: 1) There is significant overlap between "jlink" and "zip", and it seemed better to combine them. 2) "jlink" does not support include/exclude patterns which are extremely useful for writing packaging-type tasks such as Zip/Jar/War. This was my main motivation. 3) By adding this functionality to the base task, it can also be used in derivative tasks such as Jar and War. --- Submitted By: Don Ferguson <don@bea.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268458 13f79535-47bb-0310-9956-ffa450edef68
25 years ago
Addition of ZipFileset facilities. Descibed by the author --- With these patches, Zip (and derivative tasks such as Jar and War) can merge the entries of multiple zip files into a single output zip file. The contents of an input zip file may be selectively extracted based on include/exclude patterns. An included zip file is specified using a <fileset> with a "src" attribute, as in: <target name="jartest"> <jar jarfile="utils.jar"> <fileset src="weblogic.jar" includes="weblogic/utils/" excludes="weblogic/utils/jars/,**/reflect/" /> </jar> </target> In this example, a subset of the "weblogic/utils" directory is extracted from weblogic.jar, into utils.jar. The fileset may also contain "prefix" and "fullpath" attributes (the functionality of PrefixedFileSet has been retained in the new class ZipFileSet). Prefixes apply to directory-based and zip-based filesets. The fullpath attributes applies only to a single file in a directory-based fileset. The War task may extract entries from a zip file for all of its filesets (including the files in "classes" and "lib"). The motivation for this change is: 1) There is significant overlap between "jlink" and "zip", and it seemed better to combine them. 2) "jlink" does not support include/exclude patterns which are extremely useful for writing packaging-type tasks such as Zip/Jar/War. This was my main motivation. 3) By adding this functionality to the base task, it can also be used in derivative tasks such as Jar and War. --- Submitted By: Don Ferguson <don@bea.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268458 13f79535-47bb-0310-9956-ffa450edef68
25 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256
  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. }