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