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

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