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