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