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