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

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