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