1. /*
  2. * Copyright 2002,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.optional.extension;
  18. import java.text.ParseException;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Iterator;
  22. import java.util.Map;
  23. import java.util.jar.Attributes;
  24. import java.util.jar.Manifest;
  25. /**
  26. * <p>Utility class that represents either an available "Optional Package"
  27. * (formerly known as "Standard Extension") as described in the manifest
  28. * of a JAR file, or the requirement for such an optional package.</p>
  29. *
  30. * <p>For more information about optional packages, see the document
  31. * <em>Optional Package Versioning</em> in the documentation bundle for your
  32. * Java2 Standard Edition package, in file
  33. * <code>guide/extensions/versioning.html</code>.</p>
  34. *
  35. * @version $Revision: 1.4.2.5 $ $Date: 2004/04/14 15:42:41 $
  36. */
  37. public final class Specification {
  38. /**
  39. * Manifest Attribute Name object for SPECIFICATION_TITLE.
  40. * @see Attributes.Name#SPECIFICATION_TITLE
  41. */
  42. public static final Attributes.Name SPECIFICATION_TITLE
  43. = Attributes.Name.SPECIFICATION_TITLE;
  44. /**
  45. * Manifest Attribute Name object for SPECIFICATION_VERSION.
  46. * @see Attributes.Name#SPECIFICATION_VERSION
  47. */
  48. public static final Attributes.Name SPECIFICATION_VERSION
  49. = Attributes.Name.SPECIFICATION_VERSION;
  50. /**
  51. * Manifest Attribute Name object for SPECIFICATION_VENDOR.
  52. * @see Attributes.Name#SPECIFICATION_VENDOR
  53. */
  54. public static final Attributes.Name SPECIFICATION_VENDOR
  55. = Attributes.Name.SPECIFICATION_VENDOR;
  56. /**
  57. * Manifest Attribute Name object for IMPLEMENTATION_TITLE.
  58. * @see Attributes.Name#IMPLEMENTATION_TITLE
  59. */
  60. public static final Attributes.Name IMPLEMENTATION_TITLE
  61. = Attributes.Name.IMPLEMENTATION_TITLE;
  62. /**
  63. * Manifest Attribute Name object for IMPLEMENTATION_VERSION.
  64. * @see Attributes.Name#IMPLEMENTATION_VERSION
  65. */
  66. public static final Attributes.Name IMPLEMENTATION_VERSION
  67. = Attributes.Name.IMPLEMENTATION_VERSION;
  68. /**
  69. * Manifest Attribute Name object for IMPLEMENTATION_VENDOR.
  70. * @see Attributes.Name#IMPLEMENTATION_VENDOR
  71. */
  72. public static final Attributes.Name IMPLEMENTATION_VENDOR
  73. = Attributes.Name.IMPLEMENTATION_VENDOR;
  74. /**
  75. * Enum indicating that extension is compatible with other Package
  76. * Specification.
  77. */
  78. public static final Compatibility COMPATIBLE =
  79. new Compatibility("COMPATIBLE");
  80. /**
  81. * Enum indicating that extension requires an upgrade
  82. * of specification to be compatible with other Package Specification.
  83. */
  84. public static final Compatibility REQUIRE_SPECIFICATION_UPGRADE =
  85. new Compatibility("REQUIRE_SPECIFICATION_UPGRADE");
  86. /**
  87. * Enum indicating that extension requires a vendor
  88. * switch to be compatible with other Package Specification.
  89. */
  90. public static final Compatibility REQUIRE_VENDOR_SWITCH =
  91. new Compatibility("REQUIRE_VENDOR_SWITCH");
  92. /**
  93. * Enum indicating that extension requires an upgrade
  94. * of implementation to be compatible with other Package Specification.
  95. */
  96. public static final Compatibility REQUIRE_IMPLEMENTATION_CHANGE =
  97. new Compatibility("REQUIRE_IMPLEMENTATION_CHANGE");
  98. /**
  99. * This enum indicates that an extension is incompatible with
  100. * other Package Specification in ways other than other enums
  101. * indicate. For example, the other Package Specification
  102. * may have a different ID.
  103. */
  104. public static final Compatibility INCOMPATIBLE =
  105. new Compatibility("INCOMPATIBLE");
  106. /**
  107. * The name of the Package Specification.
  108. */
  109. private String specificationTitle;
  110. /**
  111. * The version number (dotted decimal notation) of the specification
  112. * to which this optional package conforms.
  113. */
  114. private DeweyDecimal specificationVersion;
  115. /**
  116. * The name of the company or organization that originated the
  117. * specification to which this specification conforms.
  118. */
  119. private String specificationVendor;
  120. /**
  121. * The title of implementation.
  122. */
  123. private String implementationTitle;
  124. /**
  125. * The name of the company or organization that produced this
  126. * implementation of this specification.
  127. */
  128. private String implementationVendor;
  129. /**
  130. * The version string for implementation. The version string is
  131. * opaque.
  132. */
  133. private String implementationVersion;
  134. /**
  135. * The sections of jar that the specification applies to.
  136. */
  137. private String[] sections;
  138. /**
  139. * Return an array of <code>Package Specification</code> objects.
  140. * If there are no such optional packages, a zero-length array is returned.
  141. *
  142. * @param manifest Manifest to be parsed
  143. * @return the Package Specifications extensions in specified manifest
  144. * @throws ParseException if the attributes of the specifications cannot
  145. * be parsed according to their expected formats.
  146. */
  147. public static Specification[] getSpecifications(final Manifest manifest)
  148. throws ParseException {
  149. if (null == manifest) {
  150. return new Specification[ 0 ];
  151. }
  152. final ArrayList results = new ArrayList();
  153. final Map entries = manifest.getEntries();
  154. final Iterator keys = entries.keySet().iterator();
  155. while (keys.hasNext()) {
  156. final String key = (String) keys.next();
  157. final Attributes attributes = (Attributes) entries.get(key);
  158. final Specification specification
  159. = getSpecification(key, attributes);
  160. if (null != specification) {
  161. results.add(specification);
  162. }
  163. }
  164. final ArrayList trimmedResults = removeDuplicates(results);
  165. return (Specification[]) trimmedResults.toArray(new Specification[0]);
  166. }
  167. /**
  168. * The constructor to create Package Specification object.
  169. * Note that every component is allowed to be specified
  170. * but only the specificationTitle is mandatory.
  171. *
  172. * @param specificationTitle the name of specification.
  173. * @param specificationVersion the specification Version.
  174. * @param specificationVendor the specification Vendor.
  175. * @param implementationTitle the title of implementation.
  176. * @param implementationVersion the implementation Version.
  177. * @param implementationVendor the implementation Vendor.
  178. */
  179. public Specification(final String specificationTitle,
  180. final String specificationVersion,
  181. final String specificationVendor,
  182. final String implementationTitle,
  183. final String implementationVersion,
  184. final String implementationVendor) {
  185. this(specificationTitle, specificationVersion, specificationVendor,
  186. implementationTitle, implementationVersion, implementationVendor,
  187. null);
  188. }
  189. /**
  190. * The constructor to create Package Specification object.
  191. * Note that every component is allowed to be specified
  192. * but only the specificationTitle is mandatory.
  193. *
  194. * @param specificationTitle the name of specification.
  195. * @param specificationVersion the specification Version.
  196. * @param specificationVendor the specification Vendor.
  197. * @param implementationTitle the title of implementation.
  198. * @param implementationVersion the implementation Version.
  199. * @param implementationVendor the implementation Vendor.
  200. * @param sections the sections/packages that Specification applies to.
  201. */
  202. public Specification(final String specificationTitle,
  203. final String specificationVersion,
  204. final String specificationVendor,
  205. final String implementationTitle,
  206. final String implementationVersion,
  207. final String implementationVendor,
  208. final String[] sections) {
  209. this.specificationTitle = specificationTitle;
  210. this.specificationVendor = specificationVendor;
  211. if (null != specificationVersion) {
  212. try {
  213. this.specificationVersion
  214. = new DeweyDecimal(specificationVersion);
  215. } catch (final NumberFormatException nfe) {
  216. final String error = "Bad specification version format '"
  217. + specificationVersion + "' in '" + specificationTitle
  218. + "'. (Reason: " + nfe + ")";
  219. throw new IllegalArgumentException(error);
  220. }
  221. }
  222. this.implementationTitle = implementationTitle;
  223. this.implementationVendor = implementationVendor;
  224. this.implementationVersion = implementationVersion;
  225. if (null == this.specificationTitle) {
  226. throw new NullPointerException("specificationTitle");
  227. }
  228. String[] copy = null;
  229. if (null != sections) {
  230. copy = new String[ sections.length ];
  231. System.arraycopy(sections, 0, copy, 0, sections.length);
  232. }
  233. this.sections = copy;
  234. }
  235. /**
  236. * Get the title of the specification.
  237. *
  238. * @return the title of speciication
  239. */
  240. public String getSpecificationTitle() {
  241. return specificationTitle;
  242. }
  243. /**
  244. * Get the vendor of the specification.
  245. *
  246. * @return the vendor of the specification.
  247. */
  248. public String getSpecificationVendor() {
  249. return specificationVendor;
  250. }
  251. /**
  252. * Get the title of the specification.
  253. *
  254. * @return the title of the specification.
  255. */
  256. public String getImplementationTitle() {
  257. return implementationTitle;
  258. }
  259. /**
  260. * Get the version of the specification.
  261. *
  262. * @return the version of the specification.
  263. */
  264. public DeweyDecimal getSpecificationVersion() {
  265. return specificationVersion;
  266. }
  267. /**
  268. * Get the vendor of the extensions implementation.
  269. *
  270. * @return the vendor of the extensions implementation.
  271. */
  272. public String getImplementationVendor() {
  273. return implementationVendor;
  274. }
  275. /**
  276. * Get the version of the implementation.
  277. *
  278. * @return the version of the implementation.
  279. */
  280. public String getImplementationVersion() {
  281. return implementationVersion;
  282. }
  283. /**
  284. * Return an array containing sections to which specification applies
  285. * or null if relevent to no sections.
  286. *
  287. * @return an array containing sections to which specification applies
  288. * or null if relevent to no sections.
  289. */
  290. public String[] getSections() {
  291. if (null == sections) {
  292. return null;
  293. } else {
  294. final String[] newSections = new String[ sections.length ];
  295. System.arraycopy(sections, 0, newSections, 0, sections.length);
  296. return newSections;
  297. }
  298. }
  299. /**
  300. * Return a Compatibility enum indicating the relationship of this
  301. * <code>Package Specification</code> with the specified
  302. * <code>Extension</code>.
  303. *
  304. * @param other the other specification
  305. * @return the enum indicating the compatibility (or lack thereof)
  306. * of specifed Package Specification
  307. */
  308. public Compatibility getCompatibilityWith(final Specification other) {
  309. // Specification Name must match
  310. if (!specificationTitle.equals(other.getSpecificationTitle())) {
  311. return INCOMPATIBLE;
  312. }
  313. // Available specification version must be >= required
  314. final DeweyDecimal specificationVersion
  315. = other.getSpecificationVersion();
  316. if (null != specificationVersion) {
  317. if (null == specificationVersion
  318. || !isCompatible(specificationVersion, specificationVersion)) {
  319. return REQUIRE_SPECIFICATION_UPGRADE;
  320. }
  321. }
  322. // Implementation Vendor ID must match
  323. final String implementationVendor = other.getImplementationVendor();
  324. if (null != implementationVendor) {
  325. if (null == implementationVendor
  326. || !implementationVendor.equals(implementationVendor)) {
  327. return REQUIRE_VENDOR_SWITCH;
  328. }
  329. }
  330. // Implementation version must be >= required
  331. final String implementationVersion = other.getImplementationVersion();
  332. if (null != implementationVersion) {
  333. if (null == implementationVersion
  334. || !implementationVersion.equals(implementationVersion)) {
  335. return REQUIRE_IMPLEMENTATION_CHANGE;
  336. }
  337. }
  338. // This available optional package satisfies the requirements
  339. return COMPATIBLE;
  340. }
  341. /**
  342. * Return <code>true</code> if the specified <code>package</code>
  343. * is satisfied by this <code>Specification</code>. Otherwise, return
  344. * <code>false</code>.
  345. *
  346. * @param other the specification
  347. * @return true if the specification is compatible with this specification
  348. */
  349. public boolean isCompatibleWith(final Specification other) {
  350. return (COMPATIBLE == getCompatibilityWith(other));
  351. }
  352. /**
  353. * Return a String representation of this object.
  354. *
  355. * @return string representation of object.
  356. */
  357. public String toString() {
  358. final String lineSeparator = System.getProperty("line.separator");
  359. final String brace = ": ";
  360. final StringBuffer sb
  361. = new StringBuffer(SPECIFICATION_TITLE.toString());
  362. sb.append(brace);
  363. sb.append(specificationTitle);
  364. sb.append(lineSeparator);
  365. if (null != specificationVersion) {
  366. sb.append(SPECIFICATION_VERSION);
  367. sb.append(brace);
  368. sb.append(specificationVersion);
  369. sb.append(lineSeparator);
  370. }
  371. if (null != specificationVendor) {
  372. sb.append(SPECIFICATION_VENDOR);
  373. sb.append(brace);
  374. sb.append(specificationVendor);
  375. sb.append(lineSeparator);
  376. }
  377. if (null != implementationTitle) {
  378. sb.append(IMPLEMENTATION_TITLE);
  379. sb.append(brace);
  380. sb.append(implementationTitle);
  381. sb.append(lineSeparator);
  382. }
  383. if (null != implementationVersion) {
  384. sb.append(IMPLEMENTATION_VERSION);
  385. sb.append(brace);
  386. sb.append(implementationVersion);
  387. sb.append(lineSeparator);
  388. }
  389. if (null != implementationVendor) {
  390. sb.append(IMPLEMENTATION_VENDOR);
  391. sb.append(brace);
  392. sb.append(implementationVendor);
  393. sb.append(lineSeparator);
  394. }
  395. return sb.toString();
  396. }
  397. /**
  398. * Return <code>true</code> if the first version number is greater than
  399. * or equal to the second; otherwise return <code>false</code>.
  400. *
  401. * @param first First version number (dotted decimal)
  402. * @param second Second version number (dotted decimal)
  403. */
  404. private boolean isCompatible(final DeweyDecimal first,
  405. final DeweyDecimal second) {
  406. return first.isGreaterThanOrEqual(second);
  407. }
  408. /**
  409. * Combine all specifications objects that are identical except
  410. * for the sections.
  411. *
  412. * <p>Note this is very inefficent and should probably be fixed
  413. * in the future.</p>
  414. *
  415. * @param list the array of results to trim
  416. * @return an array list with all duplicates removed
  417. */
  418. private static ArrayList removeDuplicates(final ArrayList list) {
  419. final ArrayList results = new ArrayList();
  420. final ArrayList sections = new ArrayList();
  421. while (list.size() > 0) {
  422. final Specification specification = (Specification) list.remove(0);
  423. final Iterator iterator = list.iterator();
  424. while (iterator.hasNext()) {
  425. final Specification other = (Specification) iterator.next();
  426. if (isEqual(specification, other)) {
  427. final String[] otherSections = other.getSections();
  428. if (null != sections) {
  429. sections.addAll(Arrays.asList(otherSections));
  430. }
  431. iterator.remove();
  432. }
  433. }
  434. final Specification merged =
  435. mergeInSections(specification, sections);
  436. results.add(merged);
  437. //Reset list of sections
  438. sections.clear();
  439. }
  440. return results;
  441. }
  442. /**
  443. * Test if two specifications are equal except for their sections.
  444. *
  445. * @param specification one specificaiton
  446. * @param other the ohter specification
  447. * @return true if two specifications are equal except for their
  448. * sections, else false
  449. */
  450. private static boolean isEqual(final Specification specification,
  451. final Specification other) {
  452. return
  453. specification.getSpecificationTitle().equals(other.getSpecificationTitle())
  454. && specification.getSpecificationVersion().isEqual(other.getSpecificationVersion())
  455. && specification.getSpecificationVendor().equals(other.getSpecificationVendor())
  456. && specification.getImplementationTitle().equals(other.getImplementationTitle())
  457. && specification.getImplementationVersion().equals(other.getImplementationVersion())
  458. && specification.getImplementationVendor().equals(other.getImplementationVendor());
  459. }
  460. /**
  461. * Merge the specified sections into specified section and return result.
  462. * If no sections to be added then just return original specification.
  463. *
  464. * @param specification the specification
  465. * @param sectionsToAdd the list of sections to merge
  466. * @return the merged specification
  467. */
  468. private static Specification mergeInSections(final Specification specification,
  469. final ArrayList sectionsToAdd) {
  470. if (0 == sectionsToAdd.size()) {
  471. return specification;
  472. } else {
  473. sectionsToAdd.addAll(Arrays.asList(specification.getSections()));
  474. final String[] sections =
  475. (String[]) sectionsToAdd.toArray(new String[sectionsToAdd.size()]);
  476. return new Specification(specification.getSpecificationTitle(),
  477. specification.getSpecificationVersion().toString(),
  478. specification.getSpecificationVendor(),
  479. specification.getImplementationTitle(),
  480. specification.getImplementationVersion(),
  481. specification.getImplementationVendor(),
  482. sections);
  483. }
  484. }
  485. /**
  486. * Trim the supplied string if the string is non-null
  487. *
  488. * @param value the string to trim or null
  489. * @return the trimmed string or null
  490. */
  491. private static String getTrimmedString(final String value) {
  492. if (null == value) {
  493. return null;
  494. } else {
  495. return value.trim();
  496. }
  497. }
  498. /**
  499. * Extract an Package Specification from Attributes.
  500. *
  501. * @param attributes Attributes to searched
  502. * @return the new Specification object, or null
  503. */
  504. private static Specification getSpecification(final String section,
  505. final Attributes attributes)
  506. throws ParseException {
  507. //WARNING: We trim the values of all the attributes because
  508. //Some extension declarations are badly defined (ie have spaces
  509. //after version or vendor)
  510. final String name
  511. = getTrimmedString(attributes.getValue(SPECIFICATION_TITLE));
  512. if (null == name) {
  513. return null;
  514. }
  515. final String specVendor
  516. = getTrimmedString(attributes.getValue(SPECIFICATION_VENDOR));
  517. if (null == specVendor) {
  518. throw new ParseException("Missing " + SPECIFICATION_VENDOR, 0);
  519. }
  520. final String specVersion
  521. = getTrimmedString(attributes.getValue(SPECIFICATION_VERSION));
  522. if (null == specVersion) {
  523. throw new ParseException("Missing " + SPECIFICATION_VERSION, 0);
  524. }
  525. final String impTitle
  526. = getTrimmedString(attributes.getValue(IMPLEMENTATION_TITLE));
  527. if (null == impTitle) {
  528. throw new ParseException("Missing " + IMPLEMENTATION_TITLE, 0);
  529. }
  530. final String impVersion
  531. = getTrimmedString(attributes.getValue(IMPLEMENTATION_VERSION));
  532. if (null == impVersion) {
  533. throw new ParseException("Missing " + IMPLEMENTATION_VERSION, 0);
  534. }
  535. final String impVendor
  536. = getTrimmedString(attributes.getValue(IMPLEMENTATION_VENDOR));
  537. if (null == impVendor) {
  538. throw new ParseException("Missing " + IMPLEMENTATION_VENDOR, 0);
  539. }
  540. return new Specification(name, specVersion, specVendor,
  541. impTitle, impVersion, impVendor,
  542. new String[]{section});
  543. }
  544. }