1. /*
  2. * @(#)Attributes.java 1.45 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.util.jar;
  8. import java.io.DataInputStream;
  9. import java.io.DataOutputStream;
  10. import java.io.IOException;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. import java.util.Set;
  14. import java.util.Collection;
  15. import java.util.AbstractSet;
  16. import java.util.Iterator;
  17. import java.util.logging.Logger;
  18. /**
  19. * The Attributes class maps Manifest attribute names to associated string
  20. * values. Valid attribute names are case-insensitive, are restricted to
  21. * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
  22. * characters in length. Attribute values can contain any characters and
  23. * will be UTF8-encoded when written to the output stream. See the
  24. * <a href="../../../../guide/jar/jar.html">JAR File Specification</a>
  25. * for more information about valid attribute names and values.
  26. *
  27. * @author David Connelly
  28. * @version 1.45, 01/23/03
  29. * @see Manifest
  30. * @since 1.2
  31. */
  32. public class Attributes implements Map, Cloneable {
  33. /**
  34. * The attribute name-value mappings.
  35. */
  36. protected Map map;
  37. /**
  38. * Constructs a new, empty Attributes object with default size.
  39. */
  40. public Attributes() {
  41. this(11);
  42. }
  43. /**
  44. * Constructs a new, empty Attributes object with the specified
  45. * initial size.
  46. *
  47. * @param size the initial number of attributes
  48. */
  49. public Attributes(int size) {
  50. map = new HashMap(size);
  51. }
  52. /**
  53. * Constructs a new Attributes object with the same attribute name-value
  54. * mappings as in the specified Attributes.
  55. *
  56. * @param attr the specified Attributes
  57. */
  58. public Attributes(Attributes attr) {
  59. map = new HashMap(attr);
  60. }
  61. /**
  62. * Returns the value of the specified attribute name, or null if the
  63. * attribute name was not found.
  64. *
  65. * @param name the attribute name
  66. * @return the value of the specified attribute name, or null if
  67. * not found.
  68. */
  69. public Object get(Object name) {
  70. return map.get(name);
  71. }
  72. /**
  73. * Returns the value of the specified attribute name, specified as
  74. * a string, or null if the attribute was not found. The attribute
  75. * name is case-insensitive.
  76. * <p>
  77. * This method is defined as:
  78. * <pre>
  79. * return (String)get(new Attributes.Name((String)name));
  80. * </pre>
  81. *
  82. * @param name the attribute name as a string
  83. * @return the String value of the specified attribute name, or null if
  84. * not found.
  85. * @throws IllegalArgumentException if the attribute name is invalid
  86. */
  87. public String getValue(String name) {
  88. return (String)get(new Attributes.Name((String)name));
  89. }
  90. /**
  91. * Returns the value of the specified Attributes.Name, or null if the
  92. * attribute was not found.
  93. * <p>
  94. * This method is defined as:
  95. * <pre>
  96. * return (String)get(name);
  97. * </pre>
  98. *
  99. * @param name the Attributes.Name object
  100. * @return the String value of the specified Attribute.Name, or null if
  101. * not found.
  102. */
  103. public String getValue(Name name) {
  104. return (String)get(name);
  105. }
  106. /**
  107. * Associates the specified value with the specified attribute name
  108. * (key) in this Map. If the Map previously contained a mapping for
  109. * the attribute name, the old value is replaced.
  110. *
  111. * @param name the attribute name
  112. * @param value the attribute value
  113. * @return the previous value of the attribute, or null if none
  114. * @exception ClassCastException if the name is not a Attributes.Name
  115. * or the value is not a String
  116. */
  117. public Object put(Object name, Object value) {
  118. return map.put((Attributes.Name)name, (String)value);
  119. }
  120. /**
  121. * Associates the specified value with the specified attribute name,
  122. * specified as a String. The attributes name is case-insensitive.
  123. * If the Map previously contained a mapping for the attribute name,
  124. * the old value is replaced.
  125. * <p>
  126. * This method is defined as:
  127. * <pre>
  128. * return (String)put(new Attributes.Name(name), value);
  129. * </pre>
  130. *
  131. * @param name the attribute name as a string
  132. * @param value the attribute value
  133. * @return the previous value of the attribute, or null if none
  134. * @exception IllegalArgumentException if the attribute name is invalid
  135. */
  136. public String putValue(String name, String value) {
  137. return (String)put(new Name(name), value);
  138. }
  139. /**
  140. * Removes the attribute with the specified name (key) from this Map.
  141. * Returns the previous attribute value, or null if none.
  142. *
  143. * @param name attribute name
  144. * @return the previous value of the attribute, or null if none
  145. */
  146. public Object remove(Object name) {
  147. return map.remove(name);
  148. }
  149. /**
  150. * Returns true if this Map maps one or more attribute names (keys)
  151. * to the specified value.
  152. *
  153. * @param value the attribute value
  154. * @return true if this Map maps one or more attribute names to
  155. * the specified value
  156. */
  157. public boolean containsValue(Object value) {
  158. return map.containsValue(value);
  159. }
  160. /**
  161. * Returns true if this Map contains the specified attribute name (key).
  162. *
  163. * @param name the attribute name
  164. * @return true if this Map contains the specified attribute name
  165. */
  166. public boolean containsKey(Object name) {
  167. return map.containsKey(name);
  168. }
  169. /**
  170. * Copies all of the attribute name-value mappings from the specified
  171. * Attributes to this Map. Duplicate mappings will be replaced.
  172. *
  173. * @param attr the Attributes to be stored in this map
  174. * @exception ClassCastException if attr is not an Attributes
  175. */
  176. public void putAll(Map attr) {
  177. map.putAll((Attributes)attr);
  178. }
  179. /**
  180. * Removes all attributes from this Map.
  181. */
  182. public void clear() {
  183. map.clear();
  184. }
  185. /**
  186. * Returns the number of attributes in this Map.
  187. */
  188. public int size() {
  189. return map.size();
  190. }
  191. /**
  192. * Returns true if this Map contains no attributes.
  193. */
  194. public boolean isEmpty() {
  195. return map.isEmpty();
  196. }
  197. /**
  198. * Returns a Set view of the attribute names (keys) contained in this Map.
  199. */
  200. public Set keySet() {
  201. return map.keySet();
  202. }
  203. /**
  204. * Returns a Collection view of the attribute values contained in this Map.
  205. */
  206. public Collection values() {
  207. return map.values();
  208. }
  209. /**
  210. * Returns a Collection view of the attribute name-value mappings
  211. * contained in this Map.
  212. */
  213. public Set entrySet() {
  214. return map.entrySet();
  215. }
  216. /**
  217. * Compares the specified Attributes object with this Map for equality.
  218. * Returns true if the given object is also an instance of Attributes
  219. * and the two Attributes objects represent the same mappings.
  220. *
  221. * @param o the Object to be compared
  222. * @return true if the specified Object is equal to this Map
  223. */
  224. public boolean equals(Object o) {
  225. return map.equals(o);
  226. }
  227. /**
  228. * Returns the hash code value for this Map.
  229. */
  230. public int hashCode() {
  231. return map.hashCode();
  232. }
  233. /**
  234. * Returns a copy of the Attributes, implemented as follows:
  235. * <pre>
  236. * public Object clone() { return new Attributes(this); }
  237. * </pre>
  238. * Since the attribute names and values are themselves immutable,
  239. * the Attributes returned can be safely modified without affecting
  240. * the original.
  241. */
  242. public Object clone() {
  243. return new Attributes(this);
  244. }
  245. /*
  246. * Writes the current attributes to the specified data output stream.
  247. * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
  248. */
  249. void write(DataOutputStream os) throws IOException {
  250. Iterator it = entrySet().iterator();
  251. while (it.hasNext()) {
  252. Map.Entry e = (Map.Entry)it.next();
  253. StringBuffer buffer = new StringBuffer(
  254. ((Name)e.getKey()).toString());
  255. buffer.append(": ");
  256. String value = (String)e.getValue();
  257. if (value != null) {
  258. byte[] vb = value.getBytes("UTF8");
  259. value = new String(vb, 0, 0, vb.length);
  260. }
  261. buffer.append(value);
  262. buffer.append("\r\n");
  263. Manifest.make72Safe(buffer);
  264. os.writeBytes(buffer.toString());
  265. }
  266. os.writeBytes("\r\n");
  267. }
  268. /*
  269. * Writes the current attributes to the specified data output stream,
  270. * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
  271. * attributes first.
  272. *
  273. * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
  274. */
  275. void writeMain(DataOutputStream out) throws IOException
  276. {
  277. // write out the *-Version header first, if it exists
  278. String vername = Name.MANIFEST_VERSION.toString();
  279. String version = getValue(vername);
  280. if (version == null) {
  281. vername = Name.SIGNATURE_VERSION.toString();
  282. version = getValue(vername);
  283. }
  284. if (version != null) {
  285. out.writeBytes(vername+": "+version+"\r\n");
  286. }
  287. // write out all attributes except for the version
  288. // we wrote out earlier
  289. Iterator it = entrySet().iterator();
  290. while (it.hasNext()) {
  291. Map.Entry e = (Map.Entry)it.next();
  292. String name = ((Name)e.getKey()).toString();
  293. if ((version != null) && ! (name.equalsIgnoreCase(vername))) {
  294. StringBuffer buffer = new StringBuffer(name);
  295. buffer.append(": ");
  296. String value = (String)e.getValue();
  297. if (value != null) {
  298. byte[] vb = value.getBytes("UTF8");
  299. value = new String(vb, 0, 0, vb.length);
  300. }
  301. buffer.append(value);
  302. buffer.append("\r\n");
  303. Manifest.make72Safe(buffer);
  304. out.writeBytes(buffer.toString());
  305. }
  306. }
  307. out.writeBytes("\r\n");
  308. }
  309. /*
  310. * Reads attributes from the specified input stream.
  311. * XXX Need to handle UTF8 values.
  312. */
  313. void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
  314. String name = null, value = null;
  315. byte[] lastline = null;
  316. int len;
  317. while ((len = is.readLine(lbuf)) != -1) {
  318. boolean lineContinued = false;
  319. if (lbuf[--len] != '\n') {
  320. throw new IOException("line too long");
  321. }
  322. if (len > 0 && lbuf[len-1] == '\r') {
  323. --len;
  324. }
  325. if (len == 0) {
  326. break;
  327. }
  328. int i = 0;
  329. if (lbuf[0] == ' ') {
  330. // continuation of previous line
  331. if (name == null) {
  332. throw new IOException("misplaced continuation line");
  333. }
  334. lineContinued = true;
  335. byte[] buf = new byte[lastline.length + len - 1];
  336. System.arraycopy(lastline, 0, buf, 0, lastline.length);
  337. System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
  338. if (is.peek() == ' ') {
  339. lastline = buf;
  340. continue;
  341. }
  342. value = new String(buf, 0, buf.length, "UTF8");
  343. lastline = null;
  344. } else {
  345. while (lbuf[i++] != ':') {
  346. if (i >= len) {
  347. throw new IOException("invalid header field");
  348. }
  349. }
  350. if (lbuf[i++] != ' ') {
  351. throw new IOException("invalid header field");
  352. }
  353. name = new String(lbuf, 0, 0, i - 2);
  354. if (is.peek() == ' ') {
  355. lastline = new byte[len - i];
  356. System.arraycopy(lbuf, i, lastline, 0, len - i);
  357. continue;
  358. }
  359. value = new String(lbuf, i, len - i, "UTF8");
  360. }
  361. try {
  362. if ((putValue(name, value) != null) && (!lineContinued)) {
  363. Logger.getLogger("java.util.jar").warning(
  364. "Duplicate name in Manifest: " + name);
  365. }
  366. } catch (IllegalArgumentException e) {
  367. throw new IOException("invalid header field name: " + name);
  368. }
  369. }
  370. }
  371. /**
  372. * The Attributes.Name class represents an attribute name stored in
  373. * this Map. Valid attribute names are case-insensitive, are restricted
  374. * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
  375. * 70 characters in length. Attribute values can contain any characters
  376. * and will be UTF8-encoded when written to the output stream. See the
  377. * <a href="../../../../guide/jar/jar.html">JAR File Specification</a>
  378. * for more information about valid attribute names and values.
  379. */
  380. public static class Name {
  381. private String name;
  382. private int hashCode = -1;
  383. /**
  384. * Constructs a new attribute name using the given string name.
  385. *
  386. * @param name the attribute string name
  387. * @exception IllegalArgumentException if the attribute name was
  388. * invalid
  389. * @exception NullPointerException if the attribute name was null
  390. */
  391. public Name(String name) {
  392. if (name == null) {
  393. throw new NullPointerException("name");
  394. }
  395. if (!isValid(name)) {
  396. throw new IllegalArgumentException(name);
  397. }
  398. this.name = name.intern();
  399. }
  400. private static boolean isValid(String name) {
  401. int len = name.length();
  402. if (len > 70 || len == 0) {
  403. return false;
  404. }
  405. for (int i = 0; i < len; i++) {
  406. if (!isValid(name.charAt(i))) {
  407. return false;
  408. }
  409. }
  410. return true;
  411. }
  412. private static boolean isValid(char c) {
  413. return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
  414. }
  415. private static boolean isAlpha(char c) {
  416. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
  417. }
  418. private static boolean isDigit(char c) {
  419. return c >= '0' && c <= '9';
  420. }
  421. /**
  422. * Compares this attribute name to another for equality.
  423. * @param o the object to compare
  424. * @return true if this attribute name is equal to the
  425. * specified attribute object
  426. */
  427. public boolean equals(Object o) {
  428. if (o instanceof Name) {
  429. return name.equalsIgnoreCase(((Name)o).name);
  430. } else {
  431. return false;
  432. }
  433. }
  434. /**
  435. * Computes the hash value for this attribute name.
  436. */
  437. public int hashCode() {
  438. if (hashCode == -1) {
  439. hashCode = name.toLowerCase().hashCode();
  440. }
  441. return hashCode;
  442. }
  443. /**
  444. * Returns the attribute name as a String.
  445. */
  446. public String toString() {
  447. return name;
  448. }
  449. /**
  450. * <code>Name</code> object for <code>Manifest-Version</code>
  451. * manifest attribute. This attribute indicates the version number
  452. * of the manifest standard to which a JAR file's manifest conforms.
  453. * @see <a href="../../../../guide/jar/jar.html#JAR Manifest">
  454. * Manifest and Signature Specification</a>
  455. */
  456. public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
  457. /**
  458. * <code>Name</code> object for <code>Signature-Version</code>
  459. * manifest attribute used when signing JAR files.
  460. * @see <a href="../../../../guide/jar/jar.html#JAR Manifest">
  461. * Manifest and Signature Specification</a>
  462. */
  463. public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
  464. /**
  465. * <code>Name</code> object for <code>Content-Type</code>
  466. * manifest attribute.
  467. */
  468. public static final Name CONTENT_TYPE = new Name("Content-Type");
  469. /**
  470. * <code>Name</code> object for <code>Class-Path</code>
  471. * manifest attribute. Bundled extensions can use this attribute
  472. * to find other JAR files containing needed classes.
  473. * @see <a href="../../../../guide/extensions/spec.html#bundled">
  474. * Extensions Specification</a>
  475. */
  476. public static final Name CLASS_PATH = new Name("Class-Path");
  477. /**
  478. * <code>Name</code> object for <code>Main-Class</code> manifest
  479. * attribute used for launching applications packaged in JAR files.
  480. * The <code>Main-Class</code> attribute is used in conjunction
  481. * with the <code>-jar</code> command-line option of the
  482. * <tt>java</tt> application launcher.
  483. */
  484. public static final Name MAIN_CLASS = new Name("Main-Class");
  485. /**
  486. * <code>Name</code> object for <code>Sealed</code> manifest attribute
  487. * used for sealing.
  488. * @see <a href="../../../../guide/extensions/spec.html#sealing">
  489. * Extension Sealing</a>
  490. */
  491. public static final Name SEALED = new Name("Sealed");
  492. /**
  493. * <code>Name</code> object for <code>Extension-List</code> manifest attribute
  494. * used for declaring dependencies on installed extensions.
  495. * @see <a href="../../../../guide/extensions/spec.html#dependnecy">
  496. * Installed extension dependency</a>
  497. */
  498. public static final Name EXTENSION_LIST = new Name("Extension-List");
  499. /**
  500. * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
  501. * used for declaring dependencies on installed extensions.
  502. * @see <a href="../../../../guide/extensions/spec.html#dependency">
  503. * Installed extension dependency</a>
  504. */
  505. public static final Name EXTENSION_NAME = new Name("Extension-Name");
  506. /**
  507. * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
  508. * used for declaring dependencies on installed extensions.
  509. * @see <a href="../../../../guide/extensions/spec.html#dependency">
  510. * Installed extension dependency</a>
  511. */
  512. public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
  513. /**
  514. * <code>Name</code> object for <code>Implementation-Title</code>
  515. * manifest attribute used for package versioning.
  516. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  517. * Java Product Versioning Specification</a>
  518. */
  519. public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
  520. /**
  521. * <code>Name</code> object for <code>Implementation-Version</code>
  522. * manifest attribute used for package versioning.
  523. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  524. * Java Product Versioning Specification</a>
  525. */
  526. public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
  527. /**
  528. * <code>Name</code> object for <code>Implementation-Vendor</code>
  529. * manifest attribute used for package versioning.
  530. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  531. * Java Product Versioning Specification</a>
  532. */
  533. public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
  534. /**
  535. * <code>Name</code> object for <code>Implementation-Vendor-Id</code>
  536. * manifest attribute used for package versioning.
  537. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  538. * Java Product Versioning Specification</a>
  539. */
  540. public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
  541. /**
  542. * <code>Name</code> object for <code>Implementation-Vendor-URL</code>
  543. * manifest attribute used for package versioning.
  544. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  545. * Java Product Versioning Specification</a>
  546. */
  547. public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
  548. /**
  549. * <code>Name</code> object for <code>Specification-Title</code>
  550. * manifest attribute used for package versioning.
  551. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  552. * Java Product Versioning Specification</a>
  553. */
  554. public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
  555. /**
  556. * <code>Name</code> object for <code>Specification-Version</code>
  557. * manifest attribute used for package versioning.
  558. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  559. * Java Product Versioning Specification</a>
  560. */
  561. public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
  562. /**
  563. * <code>Name</code> object for <code>Specification-Vendor</code>
  564. * manifest attribute used for package versioning.
  565. * @see <a href="../../../../guide/versioning/spec/VersioningSpecification.html#PackageVersioning">
  566. * Java Product Versioning Specification</a>
  567. */
  568. public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
  569. }
  570. }