1. /*
  2. * @(#)Manifest.java 1.32 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package java.util.jar;
  11. import java.io.FilterInputStream;
  12. import java.io.DataOutputStream;
  13. import java.io.InputStream;
  14. import java.io.OutputStream;
  15. import java.io.IOException;
  16. import java.util.Map;
  17. import java.util.HashMap;
  18. import java.util.Iterator;
  19. /**
  20. * The Manifest class is used to maintain Manifest entry names and their
  21. * associated Attributes. There are main Manifest Attributes as well as
  22. * per-entry Attributes. Documentation on the Manifest format can be
  23. * found at :
  24. * <blockquote><pre>
  25. * http://java.sun.com/products/jdk/1.2/docs/guide/jar/manifest.html
  26. * </pre></blockquote>
  27. *
  28. * @author David Connelly
  29. * @version 1.32, 02/02/00
  30. * @see Attributes
  31. * @since 1.2
  32. */
  33. public class Manifest implements Cloneable {
  34. // manifest main attributes
  35. private Attributes attr = new Attributes();
  36. // manifest entries
  37. private Map entries = new HashMap();
  38. /**
  39. * Constructs a new, empty Manifest.
  40. */
  41. public Manifest() {
  42. }
  43. /**
  44. * Constructs a new Manifest from the specified input stream.
  45. *
  46. * @param is the input stream containing manifest data
  47. * @throws IOException if an I/O error has occured
  48. */
  49. public Manifest(InputStream is) throws IOException {
  50. read(is);
  51. }
  52. /**
  53. * Constructs a new Manifest that is a copy of the specified Manifest.
  54. *
  55. * @param man the Manifest to copy
  56. */
  57. public Manifest(Manifest man) {
  58. attr.putAll(man.getMainAttributes());
  59. entries.putAll(man.getEntries());
  60. }
  61. /**
  62. * Returns the main Attributes for the Manifest.
  63. * @return the main Attributes for the Manifest
  64. */
  65. public Attributes getMainAttributes() {
  66. return attr;
  67. }
  68. /**
  69. * Returns a Map of the entries contained in this Manifest. Each entry
  70. * is represented by a String name (key) and associated Attributes (value).
  71. *
  72. * @return a Map of the entries contained in this Manifest
  73. */
  74. public Map getEntries() {
  75. return entries;
  76. }
  77. /**
  78. * Returns the Attributes for the specified entry name.
  79. * This method is defined as:
  80. * <pre>
  81. * return (Attributes)getEntries().get(name)
  82. * </pre>
  83. * @return the Attributes for the specified entry name
  84. */
  85. public Attributes getAttributes(String name) {
  86. return (Attributes)getEntries().get(name);
  87. }
  88. /**
  89. * Clears the main Attributes as well as the entries in this Manifest.
  90. */
  91. public void clear() {
  92. attr.clear();
  93. entries.clear();
  94. }
  95. /**
  96. * Writes the Manifest to the specified OutputStream.
  97. *
  98. * @param out the output stream
  99. * @exception IOException if an I/O error has occurred
  100. */
  101. public void write(OutputStream out) throws IOException {
  102. DataOutputStream dos = new DataOutputStream(out);
  103. // Write out the main attributes for the manifest
  104. attr.writeMain(dos);
  105. // Now write out the pre-entry attributes
  106. Iterator it = entries.entrySet().iterator();
  107. while (it.hasNext()) {
  108. Map.Entry e = (Map.Entry)it.next();
  109. StringBuffer buffer = new StringBuffer("Name: ");
  110. buffer.append((String)e.getKey());
  111. buffer.append("\r\n");
  112. make72Safe(buffer);
  113. dos.writeBytes(buffer.toString());
  114. ((Attributes)e.getValue()).write(dos);
  115. }
  116. dos.flush();
  117. }
  118. /**
  119. * Adds line breaks to enforce a maximum 72 bytes per line.
  120. */
  121. static void make72Safe(StringBuffer line) {
  122. int length = line.length();
  123. if (length > 72) {
  124. int index = 70;
  125. while (index - 1 < length) {
  126. line.insert(index, "\r\n ");
  127. index += 72;
  128. length += 3;
  129. }
  130. }
  131. return;
  132. }
  133. /**
  134. * Reads the Manifest from the specified InputStream. The entry
  135. * names and attributes read will be merged in with the current
  136. * manifest entries.
  137. *
  138. * @param is the input stream
  139. * @exception IOException if an I/O error has occurred
  140. */
  141. public void read(InputStream is) throws IOException {
  142. // Buffered input stream for reading manifest data
  143. FastInputStream fis = new FastInputStream(is);
  144. // Line buffer
  145. byte[] lbuf = new byte[512];
  146. // Read the main attributes for the manifest
  147. attr.read(fis, lbuf);
  148. // Total number of entries, attributes read
  149. int ecount = 0, acount = 0;
  150. // Average size of entry attributes
  151. int asize = 2;
  152. // Now parse the manifest entries
  153. int len;
  154. String name = null;
  155. boolean skipEmptyLines = true;
  156. while ((len = fis.readLine(lbuf)) != -1) {
  157. if (lbuf[--len] != '\n') {
  158. throw new IOException("manifest line too long");
  159. }
  160. if (len > 0 && lbuf[len-1] == '\r') {
  161. --len;
  162. }
  163. if (len == 0 && skipEmptyLines) {
  164. continue;
  165. }
  166. skipEmptyLines = false;
  167. if (name == null) {
  168. name = parseName(lbuf, len);
  169. if (name == null) {
  170. throw new IOException("invalid manifest format");
  171. }
  172. } else {
  173. // continuation line
  174. name = name + new String(lbuf, 0, 1, len-1);
  175. }
  176. if (fis.peek() == ' ') {
  177. // name is wrapped
  178. continue;
  179. }
  180. Attributes attr = getAttributes(name);
  181. if (attr == null) {
  182. attr = new Attributes(asize);
  183. entries.put(name, attr);
  184. }
  185. attr.read(fis, lbuf);
  186. ecount++;
  187. acount += attr.size();
  188. //XXX: Fix for when the average is 0. When it is 0,
  189. // you get an Attributes object with an initial
  190. // capacity of 0, which tickles a bug in HashMap.
  191. asize = Math.max(2, acount / ecount);
  192. name = null;
  193. skipEmptyLines = true;
  194. }
  195. }
  196. private String parseName(byte[] lbuf, int len) {
  197. if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
  198. toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
  199. lbuf[4] == ':' && lbuf[5] == ' ') {
  200. return new String(lbuf, 0, 6, len - 6);
  201. }
  202. return null;
  203. }
  204. private int toLower(int c) {
  205. return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
  206. }
  207. /**
  208. * Returns true if the specified Object is also a Manifest and has
  209. * the same main Attributes and entries.
  210. *
  211. * @param o the object to be compared
  212. * @return true if the specified Object is also a Manifest and has
  213. * the same main Attributes and entries
  214. */
  215. public boolean equals(Object o) {
  216. if (o instanceof Manifest) {
  217. Manifest m = (Manifest)o;
  218. return attr.equals(m.getMainAttributes()) &&
  219. entries.equals(m.getEntries());
  220. } else {
  221. return false;
  222. }
  223. }
  224. /**
  225. * Returns the hash code for this Manifest.
  226. */
  227. public int hashCode() {
  228. return attr.hashCode() + entries.hashCode();
  229. }
  230. /**
  231. * Returns a shallow copy of this Manifest, implemented as follows:
  232. * <pre>
  233. * public Object clone() { return new Manifest(this); }
  234. * </pre>
  235. * @return a shallow copy of this Manifest
  236. */
  237. public Object clone() {
  238. return new Manifest(this);
  239. }
  240. /*
  241. * A fast buffered input stream for parsing manifest files.
  242. */
  243. static class FastInputStream extends FilterInputStream {
  244. private byte buf[];
  245. private int count = 0;
  246. private int pos = 0;
  247. FastInputStream(InputStream in) {
  248. this(in, 8192);
  249. }
  250. FastInputStream(InputStream in, int size) {
  251. super(in);
  252. buf = new byte[size];
  253. }
  254. public int read() throws IOException {
  255. if (pos >= count) {
  256. fill();
  257. if (pos >= count) {
  258. return -1;
  259. }
  260. }
  261. return buf[pos++] & 0xff;
  262. }
  263. public int read(byte[] b, int off, int len) throws IOException {
  264. int avail = count - pos;
  265. if (avail <= 0) {
  266. if (len >= buf.length) {
  267. return in.read(b, off, len);
  268. }
  269. fill();
  270. avail = count - pos;
  271. if (avail <= 0) {
  272. return -1;
  273. }
  274. }
  275. if (len > avail) {
  276. len = avail;
  277. }
  278. System.arraycopy(buf, pos, b, off, len);
  279. pos += len;
  280. return len;
  281. }
  282. /*
  283. * Reads 'len' bytes from the input stream, or until an end-of-line
  284. * is reached. Returns the number of bytes read.
  285. */
  286. public int readLine(byte[] b, int off, int len) throws IOException {
  287. byte[] tbuf = this.buf;
  288. int total = 0;
  289. while (total < len) {
  290. int avail = count - pos;
  291. if (avail <= 0) {
  292. fill();
  293. avail = count - pos;
  294. if (avail <= 0) {
  295. return -1;
  296. }
  297. }
  298. int n = len - total;
  299. if (n > avail) {
  300. n = avail;
  301. }
  302. int tpos = pos;
  303. int maxpos = tpos + n;
  304. while (tpos < maxpos && tbuf[tpos++] != '\n') ;
  305. n = tpos - pos;
  306. System.arraycopy(tbuf, pos, b, off, n);
  307. off += n;
  308. total += n;
  309. pos = tpos;
  310. if (tbuf[tpos-1] == '\n') {
  311. break;
  312. }
  313. }
  314. return total;
  315. }
  316. public byte peek() throws IOException {
  317. if (pos == count)
  318. fill();
  319. return buf[pos];
  320. }
  321. public int readLine(byte[] b) throws IOException {
  322. return readLine(b, 0, b.length);
  323. }
  324. public long skip(long n) throws IOException {
  325. if (n <= 0) {
  326. return 0;
  327. }
  328. long avail = count - pos;
  329. if (avail <= 0) {
  330. return in.skip(n);
  331. }
  332. if (n > avail) {
  333. n = avail;
  334. }
  335. pos += n;
  336. return n;
  337. }
  338. public int available() throws IOException {
  339. return (count - pos) + in.available();
  340. }
  341. public void close() throws IOException {
  342. if (in != null) {
  343. in.close();
  344. in = null;
  345. buf = null;
  346. }
  347. }
  348. private void fill() throws IOException {
  349. count = pos = 0;
  350. int n = in.read(buf, 0, buf.length);
  351. if (n > 0) {
  352. count = n;
  353. }
  354. }
  355. }
  356. }