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