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.

Jar.java 35 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. package org.apache.tools.ant.taskdefs;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.InputStreamReader;
  27. import java.io.OutputStreamWriter;
  28. import java.io.PrintWriter;
  29. import java.io.Reader;
  30. import java.io.UnsupportedEncodingException;
  31. import java.util.ArrayList;
  32. import java.util.Collections;
  33. import java.util.Comparator;
  34. import java.util.Enumeration;
  35. import java.util.HashSet;
  36. import java.util.Iterator;
  37. import java.util.List;
  38. import java.util.StringTokenizer;
  39. import java.util.TreeMap;
  40. import java.util.Vector;
  41. import java.util.zip.ZipEntry;
  42. import java.util.zip.ZipFile;
  43. import org.apache.tools.ant.BuildException;
  44. import org.apache.tools.ant.Project;
  45. import org.apache.tools.ant.types.EnumeratedAttribute;
  46. import org.apache.tools.ant.types.Path;
  47. import org.apache.tools.ant.types.ResourceCollection;
  48. import org.apache.tools.ant.types.ZipFileSet;
  49. import org.apache.tools.ant.types.spi.Service;
  50. import org.apache.tools.ant.util.FileUtils;
  51. import org.apache.tools.zip.JarMarker;
  52. import org.apache.tools.zip.ZipExtraField;
  53. import org.apache.tools.zip.ZipOutputStream;
  54. /**
  55. * Creates a JAR archive.
  56. *
  57. * @since Ant 1.1
  58. *
  59. * @ant.task category="packaging"
  60. */
  61. public class Jar extends Zip {
  62. /** The index file name. */
  63. private static final String INDEX_NAME = "META-INF/INDEX.LIST";
  64. /** The manifest file name. */
  65. private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
  66. /**
  67. * List of all known SPI Services
  68. */
  69. private List serviceList = new ArrayList();
  70. /** merged manifests added through addConfiguredManifest */
  71. private Manifest configuredManifest;
  72. /** shadow of the above if upToDate check alters the value */
  73. private Manifest savedConfiguredManifest;
  74. /** merged manifests added through filesets */
  75. private Manifest filesetManifest;
  76. /**
  77. * Manifest of original archive, will be set to null if not in
  78. * update mode.
  79. */
  80. private Manifest originalManifest;
  81. /**
  82. * whether to merge fileset manifests;
  83. * value is true if filesetmanifest is 'merge' or 'mergewithoutmain'
  84. */
  85. private FilesetManifestConfig filesetManifestConfig;
  86. /**
  87. * whether to merge the main section of fileset manifests;
  88. * value is true if filesetmanifest is 'merge'
  89. */
  90. private boolean mergeManifestsMain = true;
  91. /** the manifest specified by the 'manifest' attribute **/
  92. private Manifest manifest;
  93. /** The encoding to use when reading in a manifest file */
  94. private String manifestEncoding;
  95. /**
  96. * The file found from the 'manifest' attribute. This can be
  97. * either the location of a manifest, or the name of a jar added
  98. * through a fileset. If its the name of an added jar, the
  99. * manifest is looked for in META-INF/MANIFEST.MF
  100. */
  101. private File manifestFile;
  102. /** jar index is JDK 1.3+ only */
  103. private boolean index = false;
  104. /**
  105. * whether to really create the archive in createEmptyZip, will
  106. * get set in getResourcesToAdd.
  107. */
  108. private boolean createEmpty = false;
  109. /**
  110. * Stores all files that are in the root of the archive (i.e. that
  111. * have a name that doesn't contain a slash) so they can get
  112. * listed in the index.
  113. *
  114. * Will not be filled unless the user has asked for an index.
  115. *
  116. * @since Ant 1.6
  117. */
  118. private Vector rootEntries;
  119. /**
  120. * Path containing jars that shall be indexed in addition to this archive.
  121. *
  122. * @since Ant 1.6.2
  123. */
  124. private Path indexJars;
  125. /**
  126. * Extra fields needed to make Solaris recognize the archive as a jar file.
  127. *
  128. * @since Ant 1.6.3
  129. */
  130. private static final ZipExtraField[] JAR_MARKER = new ZipExtraField[] {
  131. JarMarker.getInstance()
  132. };
  133. // CheckStyle:VisibilityModifier OFF - bc
  134. protected String emptyBehavior = "create";
  135. // CheckStyle:VisibilityModifier ON
  136. /** constructor */
  137. public Jar() {
  138. super();
  139. archiveType = "jar";
  140. emptyBehavior = "create";
  141. setEncoding("UTF8");
  142. rootEntries = new Vector();
  143. }
  144. /**
  145. * Not used for jar files.
  146. * @param we not used
  147. * @ant.attribute ignore="true"
  148. */
  149. public void setWhenempty(WhenEmpty we) {
  150. log("JARs are never empty, they contain at least a manifest file",
  151. Project.MSG_WARN);
  152. }
  153. /**
  154. * Indicates if a jar file should be created when it would only contain a
  155. * manifest file.
  156. * Possible values are: <code>fail</code> (throw an exception
  157. * and halt the build); <code>skip</code> (do not create
  158. * any archive, but issue a warning); <code>create</code>
  159. * (make an archive with only a manifest file).
  160. * Default is <code>create</code>;
  161. * @param we a <code>WhenEmpty</code> enumerated value
  162. */
  163. public void setWhenmanifestonly(WhenEmpty we) {
  164. emptyBehavior = we.getValue();
  165. }
  166. /**
  167. * Set the destination file.
  168. * @param jarFile the destination file
  169. * @deprecated since 1.5.x.
  170. * Use setDestFile(File) instead.
  171. */
  172. public void setJarfile(File jarFile) {
  173. setDestFile(jarFile);
  174. }
  175. /**
  176. * Set whether or not to create an index list for classes.
  177. * This may speed up classloading in some cases.
  178. * @param flag a <code>boolean</code> value
  179. */
  180. public void setIndex(boolean flag) {
  181. index = flag;
  182. }
  183. /**
  184. * The character encoding to use in the manifest file.
  185. *
  186. * @param manifestEncoding the character encoding
  187. */
  188. public void setManifestEncoding(String manifestEncoding) {
  189. this.manifestEncoding = manifestEncoding;
  190. }
  191. /**
  192. * Allows the manifest for the archive file to be provided inline
  193. * in the build file rather than in an external file.
  194. *
  195. * @param newManifest an embedded manifest element
  196. * @throws ManifestException on error
  197. */
  198. public void addConfiguredManifest(Manifest newManifest)
  199. throws ManifestException {
  200. if (configuredManifest == null) {
  201. configuredManifest = newManifest;
  202. } else {
  203. configuredManifest.merge(newManifest);
  204. }
  205. savedConfiguredManifest = configuredManifest;
  206. }
  207. /**
  208. * The manifest file to use. This can be either the location of a manifest,
  209. * or the name of a jar added through a fileset. If its the name of an added
  210. * jar, the task expects the manifest to be in the jar at META-INF/MANIFEST.MF.
  211. *
  212. * @param manifestFile the manifest file to use.
  213. */
  214. public void setManifest(File manifestFile) {
  215. if (!manifestFile.exists()) {
  216. throw new BuildException("Manifest file: " + manifestFile
  217. + " does not exist.", getLocation());
  218. }
  219. this.manifestFile = manifestFile;
  220. }
  221. private Manifest getManifest(File manifestFile) {
  222. Manifest newManifest = null;
  223. FileInputStream fis = null;
  224. InputStreamReader isr = null;
  225. try {
  226. fis = new FileInputStream(manifestFile);
  227. if (manifestEncoding == null) {
  228. isr = new InputStreamReader(fis);
  229. } else {
  230. isr = new InputStreamReader(fis, manifestEncoding);
  231. }
  232. newManifest = getManifest(isr);
  233. } catch (UnsupportedEncodingException e) {
  234. throw new BuildException("Unsupported encoding while reading manifest: "
  235. + e.getMessage(), e);
  236. } catch (IOException e) {
  237. throw new BuildException("Unable to read manifest file: "
  238. + manifestFile
  239. + " (" + e.getMessage() + ")", e);
  240. } finally {
  241. FileUtils.close(isr);
  242. }
  243. return newManifest;
  244. }
  245. /**
  246. * @return null if jarFile doesn't contain a manifest, the
  247. * manifest otherwise.
  248. * @since Ant 1.5.2
  249. */
  250. private Manifest getManifestFromJar(File jarFile) throws IOException {
  251. ZipFile zf = null;
  252. try {
  253. zf = new ZipFile(jarFile);
  254. // must not use getEntry as "well behaving" applications
  255. // must accept the manifest in any capitalization
  256. Enumeration e = zf.entries();
  257. while (e.hasMoreElements()) {
  258. ZipEntry ze = (ZipEntry) e.nextElement();
  259. if (ze.getName().equalsIgnoreCase(MANIFEST_NAME)) {
  260. InputStreamReader isr =
  261. new InputStreamReader(zf.getInputStream(ze), "UTF-8");
  262. return getManifest(isr);
  263. }
  264. }
  265. return null;
  266. } finally {
  267. if (zf != null) {
  268. try {
  269. zf.close();
  270. } catch (IOException e) {
  271. // XXX - log an error? throw an exception?
  272. }
  273. }
  274. }
  275. }
  276. private Manifest getManifest(Reader r) {
  277. Manifest newManifest = null;
  278. try {
  279. newManifest = new Manifest(r);
  280. } catch (ManifestException e) {
  281. log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR);
  282. throw new BuildException("Invalid Manifest: " + manifestFile,
  283. e, getLocation());
  284. } catch (IOException e) {
  285. throw new BuildException("Unable to read manifest file"
  286. + " (" + e.getMessage() + ")", e);
  287. }
  288. return newManifest;
  289. }
  290. /**
  291. * Behavior when a Manifest is found in a zipfileset or zipgroupfileset file.
  292. * Valid values are "skip", "merge", and "mergewithoutmain".
  293. * "merge" will merge all of manifests together, and merge this into any
  294. * other specified manifests.
  295. * "mergewithoutmain" merges everything but the Main section of the manifests.
  296. * Default value is "skip".
  297. *
  298. * Note: if this attribute's value is not "skip", the created jar will not
  299. * be readable by using java.util.jar.JarInputStream
  300. *
  301. * @param config setting for found manifest behavior.
  302. */
  303. public void setFilesetmanifest(FilesetManifestConfig config) {
  304. filesetManifestConfig = config;
  305. mergeManifestsMain = "merge".equals(config.getValue());
  306. if (filesetManifestConfig != null
  307. && !filesetManifestConfig.getValue().equals("skip")) {
  308. doubleFilePass = true;
  309. }
  310. }
  311. /**
  312. * Adds a zipfileset to include in the META-INF directory.
  313. *
  314. * @param fs zipfileset to add
  315. */
  316. public void addMetainf(ZipFileSet fs) {
  317. // We just set the prefix for this fileset, and pass it up.
  318. fs.setPrefix("META-INF/");
  319. super.addFileset(fs);
  320. }
  321. /**
  322. * Add a path to index jars.
  323. * @param p a path
  324. * @since Ant 1.6.2
  325. */
  326. public void addConfiguredIndexJars(Path p) {
  327. if (indexJars == null) {
  328. indexJars = new Path(getProject());
  329. }
  330. indexJars.append(p);
  331. }
  332. /**
  333. * A nested SPI service element.
  334. * @param service the nested element.
  335. * @since Ant 1.7
  336. */
  337. public void addConfiguredService(Service service) {
  338. // Check if the service is configured correctly
  339. service.check();
  340. serviceList.add(service);
  341. }
  342. /**
  343. * Write SPI Information to JAR
  344. */
  345. private void writeServices(ZipOutputStream zOut) throws IOException {
  346. Iterator serviceIterator;
  347. Service service;
  348. serviceIterator = serviceList.iterator();
  349. while (serviceIterator.hasNext()) {
  350. service = (Service) serviceIterator.next();
  351. //stolen from writeManifest
  352. super.zipFile(service.getAsStream(), zOut,
  353. "META-INF/services/" + service.getType(),
  354. System.currentTimeMillis(), null,
  355. ZipFileSet.DEFAULT_FILE_MODE);
  356. }
  357. }
  358. /**
  359. * Initialize the zip output stream.
  360. * @param zOut the zip output stream
  361. * @throws IOException on I/O errors
  362. * @throws BuildException on other errors
  363. */
  364. protected void initZipOutputStream(ZipOutputStream zOut)
  365. throws IOException, BuildException {
  366. if (!skipWriting) {
  367. Manifest jarManifest = createManifest();
  368. writeManifest(zOut, jarManifest);
  369. writeServices(zOut);
  370. }
  371. }
  372. private Manifest createManifest()
  373. throws BuildException {
  374. try {
  375. Manifest finalManifest = Manifest.getDefaultManifest();
  376. if (manifest == null) {
  377. if (manifestFile != null) {
  378. // if we haven't got the manifest yet, attempt to
  379. // get it now and have manifest be the final merge
  380. manifest = getManifest(manifestFile);
  381. }
  382. }
  383. /*
  384. * Precedence: manifestFile wins over inline manifest,
  385. * over manifests read from the filesets over the original
  386. * manifest.
  387. *
  388. * merge with null argument is a no-op
  389. */
  390. if (isInUpdateMode()) {
  391. finalManifest.merge(originalManifest);
  392. }
  393. finalManifest.merge(filesetManifest);
  394. finalManifest.merge(configuredManifest);
  395. finalManifest.merge(manifest, !mergeManifestsMain);
  396. return finalManifest;
  397. } catch (ManifestException e) {
  398. log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR);
  399. throw new BuildException("Invalid Manifest", e, getLocation());
  400. }
  401. }
  402. private void writeManifest(ZipOutputStream zOut, Manifest manifest)
  403. throws IOException {
  404. for (Enumeration e = manifest.getWarnings();
  405. e.hasMoreElements();) {
  406. log("Manifest warning: " + (String) e.nextElement(),
  407. Project.MSG_WARN);
  408. }
  409. zipDir(null, zOut, "META-INF/", ZipFileSet.DEFAULT_DIR_MODE,
  410. JAR_MARKER);
  411. // time to write the manifest
  412. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  413. OutputStreamWriter osw = new OutputStreamWriter(baos, Manifest.JAR_ENCODING);
  414. PrintWriter writer = new PrintWriter(osw);
  415. manifest.write(writer);
  416. writer.flush();
  417. ByteArrayInputStream bais =
  418. new ByteArrayInputStream(baos.toByteArray());
  419. super.zipFile(bais, zOut, MANIFEST_NAME,
  420. System.currentTimeMillis(), null,
  421. ZipFileSet.DEFAULT_FILE_MODE);
  422. super.initZipOutputStream(zOut);
  423. }
  424. /**
  425. * Finalize the zip output stream.
  426. * This creates an index list if the index attribute is true.
  427. * @param zOut the zip output stream
  428. * @throws IOException on I/O errors
  429. * @throws BuildException on other errors
  430. */
  431. protected void finalizeZipOutputStream(ZipOutputStream zOut)
  432. throws IOException, BuildException {
  433. if (index) {
  434. createIndexList(zOut);
  435. }
  436. }
  437. /**
  438. * Create the index list to speed up classloading.
  439. * This is a JDK 1.3+ specific feature and is enabled by default. See
  440. * <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index">
  441. * the JAR index specification</a> for more details.
  442. *
  443. * @param zOut the zip stream representing the jar being built.
  444. * @throws IOException thrown if there is an error while creating the
  445. * index and adding it to the zip stream.
  446. */
  447. private void createIndexList(ZipOutputStream zOut) throws IOException {
  448. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  449. // encoding must be UTF8 as specified in the specs.
  450. PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos,
  451. "UTF8"));
  452. // version-info blankline
  453. writer.println("JarIndex-Version: 1.0");
  454. writer.println();
  455. // header newline
  456. writer.println(zipFile.getName());
  457. writeIndexLikeList(new ArrayList(addedDirs.keySet()),
  458. rootEntries, writer);
  459. writer.println();
  460. if (indexJars != null) {
  461. Manifest mf = createManifest();
  462. Manifest.Attribute classpath =
  463. mf.getMainSection().getAttribute(Manifest.ATTRIBUTE_CLASSPATH);
  464. String[] cpEntries = null;
  465. if (classpath != null && classpath.getValue() != null) {
  466. StringTokenizer tok = new StringTokenizer(classpath.getValue(),
  467. " ");
  468. cpEntries = new String[tok.countTokens()];
  469. int c = 0;
  470. while (tok.hasMoreTokens()) {
  471. cpEntries[c++] = tok.nextToken();
  472. }
  473. }
  474. String[] indexJarEntries = indexJars.list();
  475. for (int i = 0; i < indexJarEntries.length; i++) {
  476. String name = findJarName(indexJarEntries[i], cpEntries);
  477. if (name != null) {
  478. ArrayList dirs = new ArrayList();
  479. ArrayList files = new ArrayList();
  480. grabFilesAndDirs(indexJarEntries[i], dirs, files);
  481. if (dirs.size() + files.size() > 0) {
  482. writer.println(name);
  483. writeIndexLikeList(dirs, files, writer);
  484. writer.println();
  485. }
  486. }
  487. }
  488. }
  489. writer.flush();
  490. ByteArrayInputStream bais =
  491. new ByteArrayInputStream(baos.toByteArray());
  492. super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), null,
  493. ZipFileSet.DEFAULT_FILE_MODE);
  494. }
  495. /**
  496. * Overridden from Zip class to deal with manifests and index lists.
  497. * @param is the input stream
  498. * @param zOut the zip output stream
  499. * @param vPath the name this entry shall have in the archive
  500. * @param lastModified last modification time for the entry.
  501. * @param fromArchive the original archive we are copying this
  502. * entry from, will be null if we are not copying from an archive.
  503. * @param mode the Unix permissions to set.
  504. * @throws IOException on error
  505. */
  506. protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath,
  507. long lastModified, File fromArchive, int mode)
  508. throws IOException {
  509. if (MANIFEST_NAME.equalsIgnoreCase(vPath)) {
  510. if (!doubleFilePass || (doubleFilePass && skipWriting)) {
  511. filesetManifest(fromArchive, is);
  512. }
  513. } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) {
  514. log("Warning: selected " + archiveType
  515. + " files include a META-INF/INDEX.LIST which will"
  516. + " be replaced by a newly generated one.", Project.MSG_WARN);
  517. } else {
  518. if (index && vPath.indexOf("/") == -1) {
  519. rootEntries.addElement(vPath);
  520. }
  521. super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode);
  522. }
  523. }
  524. private void filesetManifest(File file, InputStream is) throws IOException {
  525. if (manifestFile != null && manifestFile.equals(file)) {
  526. // If this is the same name specified in 'manifest', this
  527. // is the manifest to use
  528. log("Found manifest " + file, Project.MSG_VERBOSE);
  529. try {
  530. if (is != null) {
  531. InputStreamReader isr;
  532. if (manifestEncoding == null) {
  533. isr = new InputStreamReader(is);
  534. } else {
  535. isr = new InputStreamReader(is, manifestEncoding);
  536. }
  537. manifest = getManifest(isr);
  538. } else {
  539. manifest = getManifest(file);
  540. }
  541. } catch (UnsupportedEncodingException e) {
  542. throw new BuildException("Unsupported encoding while reading "
  543. + "manifest: " + e.getMessage(), e);
  544. }
  545. } else if (filesetManifestConfig != null
  546. && !filesetManifestConfig.getValue().equals("skip")) {
  547. // we add this to our group of fileset manifests
  548. log("Found manifest to merge in file " + file,
  549. Project.MSG_VERBOSE);
  550. try {
  551. Manifest newManifest = null;
  552. if (is != null) {
  553. InputStreamReader isr;
  554. if (manifestEncoding == null) {
  555. isr = new InputStreamReader(is);
  556. } else {
  557. isr = new InputStreamReader(is, manifestEncoding);
  558. }
  559. newManifest = getManifest(isr);
  560. } else {
  561. newManifest = getManifest(file);
  562. }
  563. if (filesetManifest == null) {
  564. filesetManifest = newManifest;
  565. } else {
  566. filesetManifest.merge(newManifest);
  567. }
  568. } catch (UnsupportedEncodingException e) {
  569. throw new BuildException("Unsupported encoding while reading "
  570. + "manifest: " + e.getMessage(), e);
  571. } catch (ManifestException e) {
  572. log("Manifest in file " + file + " is invalid: "
  573. + e.getMessage(), Project.MSG_ERR);
  574. throw new BuildException("Invalid Manifest", e, getLocation());
  575. }
  576. } else {
  577. // assuming 'skip' otherwise
  578. // don't warn if skip has been requested explicitly, warn if user
  579. // didn't set the attribute
  580. // Hide warning also as it makes no sense since
  581. // the filesetmanifest attribute itself has been
  582. // hidden
  583. //int logLevel = filesetManifestConfig == null ?
  584. // Project.MSG_WARN : Project.MSG_VERBOSE;
  585. //log("File " + file
  586. // + " includes a META-INF/MANIFEST.MF which will be ignored. "
  587. // + "To include this file, set filesetManifest to a value other "
  588. // + "than 'skip'.", logLevel);
  589. }
  590. }
  591. /**
  592. * Collect the resources that are newer than the corresponding
  593. * entries (or missing) in the original archive.
  594. *
  595. * <p>If we are going to recreate the archive instead of updating
  596. * it, all resources should be considered as new, if a single one
  597. * is. Because of this, subclasses overriding this method must
  598. * call <code>super.getResourcesToAdd</code> and indicate with the
  599. * third arg if they already know that the archive is
  600. * out-of-date.</p>
  601. *
  602. * @param rcs The resource collections to grab resources from
  603. * @param zipFile intended archive file (may or may not exist)
  604. * @param needsUpdate whether we already know that the archive is
  605. * out-of-date. Subclasses overriding this method are supposed to
  606. * set this value correctly in their call to
  607. * super.getResourcesToAdd.
  608. * @return an array of resources to add for each fileset passed in as well
  609. * as a flag that indicates whether the archive is uptodate.
  610. *
  611. * @exception BuildException if it likes
  612. */
  613. protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs,
  614. File zipFile,
  615. boolean needsUpdate)
  616. throws BuildException {
  617. // need to handle manifest as a special check
  618. if (zipFile.exists()) {
  619. // if it doesn't exist, it will get created anyway, don't
  620. // bother with any up-to-date checks.
  621. try {
  622. originalManifest = getManifestFromJar(zipFile);
  623. if (originalManifest == null) {
  624. log("Updating jar since the current jar has no manifest",
  625. Project.MSG_VERBOSE);
  626. needsUpdate = true;
  627. } else {
  628. Manifest mf = createManifest();
  629. if (!mf.equals(originalManifest)) {
  630. log("Updating jar since jar manifest has changed",
  631. Project.MSG_VERBOSE);
  632. needsUpdate = true;
  633. }
  634. }
  635. } catch (Throwable t) {
  636. log("error while reading original manifest in file: "
  637. + zipFile.toString() + t.getMessage(),
  638. Project.MSG_WARN);
  639. needsUpdate = true;
  640. }
  641. } else {
  642. // no existing archive
  643. needsUpdate = true;
  644. }
  645. createEmpty = needsUpdate;
  646. return super.getResourcesToAdd(rcs, zipFile, needsUpdate);
  647. }
  648. /**
  649. * Create an empty jar file.
  650. * @param zipFile the file to create
  651. * @return true for historic reasons
  652. * @throws BuildException on error
  653. */
  654. protected boolean createEmptyZip(File zipFile) throws BuildException {
  655. if (!createEmpty) {
  656. return true;
  657. }
  658. if (emptyBehavior.equals("skip")) {
  659. log("Warning: skipping " + archiveType + " archive "
  660. + zipFile + " because no files were included.",
  661. Project.MSG_WARN);
  662. return true;
  663. } else if (emptyBehavior.equals("fail")) {
  664. throw new BuildException("Cannot create " + archiveType
  665. + " archive " + zipFile
  666. + ": no files were included.",
  667. getLocation());
  668. }
  669. ZipOutputStream zOut = null;
  670. try {
  671. log("Building MANIFEST-only jar: "
  672. + getDestFile().getAbsolutePath());
  673. zOut = new ZipOutputStream(new FileOutputStream(getDestFile()));
  674. zOut.setEncoding(getEncoding());
  675. if (isCompress()) {
  676. zOut.setMethod(ZipOutputStream.DEFLATED);
  677. } else {
  678. zOut.setMethod(ZipOutputStream.STORED);
  679. }
  680. initZipOutputStream(zOut);
  681. finalizeZipOutputStream(zOut);
  682. } catch (IOException ioe) {
  683. throw new BuildException("Could not create almost empty JAR archive"
  684. + " (" + ioe.getMessage() + ")", ioe,
  685. getLocation());
  686. } finally {
  687. // Close the output stream.
  688. FileUtils.close(zOut);
  689. createEmpty = false;
  690. }
  691. return true;
  692. }
  693. /**
  694. * Make sure we don't think we already have a MANIFEST next time this task
  695. * gets executed.
  696. *
  697. * @see Zip#cleanUp
  698. */
  699. protected void cleanUp() {
  700. super.cleanUp();
  701. // we want to save this info if we are going to make another pass
  702. if (!doubleFilePass || (doubleFilePass && !skipWriting)) {
  703. manifest = null;
  704. configuredManifest = savedConfiguredManifest;
  705. filesetManifest = null;
  706. originalManifest = null;
  707. }
  708. rootEntries.removeAllElements();
  709. }
  710. /**
  711. * reset to default values.
  712. *
  713. * @see Zip#reset
  714. *
  715. * @since 1.44, Ant 1.5
  716. */
  717. public void reset() {
  718. super.reset();
  719. emptyBehavior = "create";
  720. configuredManifest = null;
  721. filesetManifestConfig = null;
  722. mergeManifestsMain = false;
  723. manifestFile = null;
  724. index = false;
  725. }
  726. /**
  727. * The manifest config enumerated type.
  728. */
  729. public static class FilesetManifestConfig extends EnumeratedAttribute {
  730. /**
  731. * Get the list of valid strings.
  732. * @return the list of values - "skip", "merge" and "mergewithoutmain"
  733. */
  734. public String[] getValues() {
  735. return new String[] {"skip", "merge", "mergewithoutmain"};
  736. }
  737. }
  738. /**
  739. * Writes the directory entries from the first and the filenames
  740. * from the second list to the given writer, one entry per line.
  741. *
  742. * @param dirs a list of directories
  743. * @param files a list of files
  744. * @param writer the writer to write to
  745. * @throws IOException on error
  746. * @since Ant 1.6.2
  747. */
  748. protected final void writeIndexLikeList(List dirs, List files,
  749. PrintWriter writer)
  750. throws IOException {
  751. // JarIndex is sorting the directories by ascending order.
  752. // it has no value but cosmetic since it will be read into a
  753. // hashtable by the classloader, but we'll do so anyway.
  754. Collections.sort(dirs);
  755. Collections.sort(files);
  756. Iterator iter = dirs.iterator();
  757. while (iter.hasNext()) {
  758. String dir = (String) iter.next();
  759. // try to be smart, not to be fooled by a weird directory name
  760. dir = dir.replace('\\', '/');
  761. if (dir.startsWith("./")) {
  762. dir = dir.substring(2);
  763. }
  764. while (dir.startsWith("/")) {
  765. dir = dir.substring(1);
  766. }
  767. int pos = dir.lastIndexOf('/');
  768. if (pos != -1) {
  769. dir = dir.substring(0, pos);
  770. }
  771. // looks like nothing from META-INF should be added
  772. // and the check is not case insensitive.
  773. // see sun.misc.JarIndex
  774. if (dir.startsWith("META-INF")) {
  775. continue;
  776. }
  777. // name newline
  778. writer.println(dir);
  779. }
  780. iter = files.iterator();
  781. while (iter.hasNext()) {
  782. writer.println(iter.next());
  783. }
  784. }
  785. /**
  786. * try to guess the name of the given file.
  787. *
  788. * <p>If this jar has a classpath attribute in its manifest, we
  789. * can assume that it will only require an index of jars listed
  790. * there. try to find which classpath entry is most likely the
  791. * one the given file name points to.</p>
  792. *
  793. * <p>In the absence of a classpath attribute, assume the other
  794. * files will be placed inside the same directory as this jar and
  795. * use their basename.</p>
  796. *
  797. * <p>if there is a classpath and the given file doesn't match any
  798. * of its entries, return null.</p>
  799. *
  800. * @param fileName the name to look for
  801. * @param classpath the classpath to look in (may be null)
  802. * @return the matching entry, or null if the file is not found
  803. * @since Ant 1.6.2
  804. */
  805. protected static final String findJarName(String fileName,
  806. String[] classpath) {
  807. if (classpath == null) {
  808. return (new File(fileName)).getName();
  809. }
  810. fileName = fileName.replace(File.separatorChar, '/');
  811. TreeMap matches = new TreeMap(new Comparator() {
  812. // longest match comes first
  813. public int compare(Object o1, Object o2) {
  814. if (o1 instanceof String && o2 instanceof String) {
  815. return ((String) o2).length()
  816. - ((String) o1).length();
  817. }
  818. return 0;
  819. }
  820. });
  821. for (int i = 0; i < classpath.length; i++) {
  822. if (fileName.endsWith(classpath[i])) {
  823. matches.put(classpath[i], classpath[i]);
  824. } else {
  825. int slash = classpath[i].indexOf("/");
  826. String candidate = classpath[i];
  827. while (slash > -1) {
  828. candidate = candidate.substring(slash + 1);
  829. if (fileName.endsWith(candidate)) {
  830. matches.put(candidate, classpath[i]);
  831. break;
  832. }
  833. slash = candidate.indexOf("/");
  834. }
  835. }
  836. }
  837. return matches.size() == 0
  838. ? null : (String) matches.get(matches.firstKey());
  839. }
  840. /**
  841. * Grab lists of all root-level files and all directories
  842. * contained in the given archive.
  843. * @param file the zip file to examine
  844. * @param dirs where to place the directories found
  845. * @param files where to place the files found
  846. * @since Ant 1.7
  847. * @throws IOException on error
  848. */
  849. protected static final void grabFilesAndDirs(String file, List dirs,
  850. List files)
  851. throws IOException {
  852. org.apache.tools.zip.ZipFile zf = null;
  853. try {
  854. zf = new org.apache.tools.zip.ZipFile(file, "utf-8");
  855. Enumeration entries = zf.getEntries();
  856. HashSet dirSet = new HashSet();
  857. while (entries.hasMoreElements()) {
  858. org.apache.tools.zip.ZipEntry ze =
  859. (org.apache.tools.zip.ZipEntry) entries.nextElement();
  860. String name = ze.getName();
  861. // META-INF would be skipped anyway, avoid index for
  862. // manifest-only jars.
  863. if (!name.startsWith("META-INF/")) {
  864. if (ze.isDirectory()) {
  865. dirSet.add(name);
  866. } else if (name.indexOf("/") == -1) {
  867. files.add(name);
  868. } else {
  869. // a file, not in the root
  870. // since the jar may be one without directory
  871. // entries, add the parent dir of this file as
  872. // well.
  873. dirSet.add(name.substring(0,
  874. name.lastIndexOf("/") + 1));
  875. }
  876. }
  877. }
  878. dirs.addAll(dirSet);
  879. } finally {
  880. if (zf != null) {
  881. zf.close();
  882. }
  883. }
  884. }
  885. }