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

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