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