001: /*
002: * @(#)Manifest.java 1.41 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.util.jar;
029:
030: import java.io.FilterInputStream;
031: import java.io.DataOutputStream;
032: import java.io.InputStream;
033: import java.io.OutputStream;
034: import java.io.IOException;
035: import java.util.Map;
036: import java.util.HashMap;
037: import java.util.Iterator;
038:
039: /**
040: * The Manifest class is used to maintain Manifest entry names and their
041: * associated Attributes. There are main Manifest Attributes as well as
042: * per-entry Attributes. For information on the Manifest format, please
043: * see the
044: * <a href="../../../../guide/jar/jar.html">
045: * Manifest format specification</a>.
046: *
047: * @author David Connelly
048: * @version 1.33, 05/03/00
049: * @see Attributes
050: * @since 1.2
051: */
052: public class Manifest implements Cloneable {
053: // manifest main attributes
054: private Attributes attr = new Attributes();
055:
056: // manifest entries
057: private Map entries = new HashMap();
058:
059: /**
060: * Constructs a new, empty Manifest.
061: */
062: public Manifest() {
063: }
064:
065: /**
066: * Constructs a new Manifest from the specified input stream.
067: *
068: * @param is the input stream containing manifest data
069: * @throws IOException if an I/O error has occured
070: */
071: public Manifest(InputStream is) throws IOException {
072: read(is);
073: }
074:
075: /**
076: * Constructs a new Manifest that is a copy of the specified Manifest.
077: *
078: * @param man the Manifest to copy
079: */
080: public Manifest(Manifest man) {
081: attr.putAll(man.getMainAttributes());
082: entries.putAll(man.getEntries());
083: }
084:
085: /**
086: * Returns the main Attributes for the Manifest.
087: * @return the main Attributes for the Manifest
088: */
089: public Attributes getMainAttributes() {
090: return attr;
091: }
092:
093: /**
094: * Returns a Map of the entries contained in this Manifest. Each entry
095: * is represented by a String name (key) and associated Attributes (value).
096: *
097: * @return a Map of the entries contained in this Manifest
098: */
099: public Map 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: *
110: * @param name entry name
111: * @return the Attributes for the specified entry name
112: */
113: public Attributes getAttributes(String name) {
114: return (Attributes) getEntries().get(name);
115: }
116:
117: /**
118: * Clears the main Attributes as well as the entries in this Manifest.
119: */
120: public void clear() {
121: attr.clear();
122: entries.clear();
123: }
124:
125: /**
126: * Writes the Manifest to the specified OutputStream.
127: * Attributes.Name.MANIFEST_VERSION must be set in
128: * MainAttributes prior to invoking this method.
129: *
130: * @param out the output stream
131: * @exception IOException if an I/O error has occurred
132: * @see #getMainAttributes
133: */
134: public void write(OutputStream out) throws IOException {
135: DataOutputStream dos = new DataOutputStream(out);
136: // Write out the main attributes for the manifest
137: attr.writeMain(dos);
138: // Now write out the pre-entry attributes
139: Iterator it = entries.entrySet().iterator();
140: while (it.hasNext()) {
141: Map.Entry e = (Map.Entry) it.next();
142: StringBuffer buffer = new StringBuffer("Name: ");
143: String value = (String) e.getKey();
144: if (value != null) {
145: byte[] vb = value.getBytes("UTF8");
146: value = new String(vb, 0, vb.length);
147: }
148: buffer.append(value);
149: buffer.append("\r\n");
150: make72Safe(buffer);
151: dos.writeBytes(buffer.toString());
152: ((Attributes) e.getValue()).write(dos);
153: }
154: dos.flush();
155: }
156:
157: /**
158: * Adds line breaks to enforce a maximum 72 bytes per line.
159: */
160: static void make72Safe(StringBuffer line) {
161: int length = line.length();
162: if (length > 72) {
163: int index = 70;
164: while (index < length - 2) {
165: line.insert(index, "\r\n ");
166: index += 72;
167: length += 3;
168: }
169: }
170: return;
171: }
172:
173: /**
174: * Reads the Manifest from the specified InputStream. The entry
175: * names and attributes read will be merged in with the current
176: * manifest entries.
177: *
178: * @param is the input stream
179: * @exception IOException if an I/O error has occurred
180: */
181: public void read(InputStream is) throws IOException {
182: // Buffered input stream for reading manifest data
183: FastInputStream fis = new FastInputStream(is);
184: // Line buffer
185: byte[] lbuf = new byte[512];
186: // Read the main attributes for the manifest
187: attr.read(fis, lbuf);
188: // Total number of entries, attributes read
189: int ecount = 0, acount = 0;
190: // Average size of entry attributes
191: int asize = 2;
192: // Now parse the manifest entries
193: int len;
194: String name = null;
195: boolean skipEmptyLines = true;
196: byte[] lastline = null;
197:
198: while ((len = fis.readLine(lbuf)) != -1) {
199: if (lbuf[--len] != '\n') {
200: throw new IOException("manifest line too long");
201: }
202: if (len > 0 && lbuf[len - 1] == '\r') {
203: --len;
204: }
205: if (len == 0 && skipEmptyLines) {
206: continue;
207: }
208: skipEmptyLines = false;
209:
210: if (name == null) {
211: name = parseName(lbuf, len);
212: if (name == null) {
213: throw new IOException("invalid manifest format");
214: }
215: if (fis.peek() == ' ') {
216: // name is wrapped
217: lastline = new byte[len - 6];
218: System.arraycopy(lbuf, 6, lastline, 0, len - 6);
219: continue;
220: }
221: } else {
222: // continuation line
223: byte[] buf = new byte[lastline.length + len - 1];
224: System.arraycopy(lastline, 0, buf, 0, lastline.length);
225: System
226: .arraycopy(lbuf, 1, buf, lastline.length,
227: len - 1);
228: if (fis.peek() == ' ') {
229: // name is wrapped
230: lastline = buf;
231: continue;
232: }
233: name = new String(buf, 0, buf.length, "UTF8");
234: lastline = null;
235: }
236: Attributes attr = getAttributes(name);
237: if (attr == null) {
238: attr = new Attributes(asize);
239: entries.put(name, attr);
240: }
241: attr.read(fis, lbuf);
242: ecount++;
243: acount += attr.size();
244: // Fix for when the average is 0. When it is 0,
245: // you get an Attributes object with an initial
246: // capacity of 0, which tickles a bug in HashMap.
247: asize = Math.max(2, acount / ecount);
248:
249: name = null;
250: skipEmptyLines = true;
251: }
252: }
253:
254: private String parseName(byte[] lbuf, int len) {
255: if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a'
256: && toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e'
257: && lbuf[4] == ':' && lbuf[5] == ' ') {
258: try {
259: return new String(lbuf, 6, len - 6, "UTF8");
260: } catch (Exception e) {
261: }
262: }
263: return null;
264: }
265:
266: private int toLower(int c) {
267: return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
268: }
269:
270: /**
271: * Returns true if the specified Object is also a Manifest and has
272: * the same main Attributes and entries.
273: *
274: * @param o the object to be compared
275: * @return true if the specified Object is also a Manifest and has
276: * the same main Attributes and entries
277: */
278: public boolean equals(Object o) {
279: if (o instanceof Manifest) {
280: Manifest m = (Manifest) o;
281: return attr.equals(m.getMainAttributes())
282: && entries.equals(m.getEntries());
283: } else {
284: return false;
285: }
286: }
287:
288: /**
289: * Returns the hash code for this Manifest.
290: */
291: public int hashCode() {
292: return attr.hashCode() + entries.hashCode();
293: }
294:
295: /**
296: * Returns a shallow copy of this Manifest. The shallow copy is
297: * implemented as follows:
298: * <pre>
299: * public Object clone() { return new Manifest(this); }
300: * </pre>
301: * @return a shallow copy of this Manifest
302: */
303: public Object clone() {
304: return new Manifest(this );
305: }
306:
307: /*
308: * A fast buffered input stream for parsing manifest files.
309: */
310: static class FastInputStream extends FilterInputStream {
311: private byte buf[];
312: private int count = 0;
313: private int pos = 0;
314:
315: FastInputStream(InputStream in) {
316: this (in, 8192);
317: }
318:
319: FastInputStream(InputStream in, int size) {
320: super (in);
321: buf = new byte[size];
322: }
323:
324: public int read() throws IOException {
325: if (pos >= count) {
326: fill();
327: if (pos >= count) {
328: return -1;
329: }
330: }
331: return buf[pos++] & 0xff;
332: }
333:
334: public int read(byte[] b, int off, int len) throws IOException {
335: int avail = count - pos;
336: if (avail <= 0) {
337: if (len >= buf.length) {
338: return in.read(b, off, len);
339: }
340: fill();
341: avail = count - pos;
342: if (avail <= 0) {
343: return -1;
344: }
345: }
346: if (len > avail) {
347: len = avail;
348: }
349: System.arraycopy(buf, pos, b, off, len);
350: pos += len;
351: return len;
352: }
353:
354: /*
355: * Reads 'len' bytes from the input stream, or until an end-of-line
356: * is reached. Returns the number of bytes read.
357: */
358: public int readLine(byte[] b, int off, int len)
359: throws IOException {
360: byte[] tbuf = this .buf;
361: int total = 0;
362: while (total < len) {
363: int avail = count - pos;
364: if (avail <= 0) {
365: fill();
366: avail = count - pos;
367: if (avail <= 0) {
368: return -1;
369: }
370: }
371: int n = len - total;
372: if (n > avail) {
373: n = avail;
374: }
375: int tpos = pos;
376: int maxpos = tpos + n;
377: while (tpos < maxpos && tbuf[tpos++] != '\n')
378: ;
379: n = tpos - pos;
380: System.arraycopy(tbuf, pos, b, off, n);
381: off += n;
382: total += n;
383: pos = tpos;
384: if (tbuf[tpos - 1] == '\n') {
385: break;
386: }
387: }
388: return total;
389: }
390:
391: public byte peek() throws IOException {
392: if (pos == count)
393: fill();
394: return buf[pos];
395: }
396:
397: public int readLine(byte[] b) throws IOException {
398: return readLine(b, 0, b.length);
399: }
400:
401: public long skip(long n) throws IOException {
402: if (n <= 0) {
403: return 0;
404: }
405: long avail = count - pos;
406: if (avail <= 0) {
407: return in.skip(n);
408: }
409: if (n > avail) {
410: n = avail;
411: }
412: pos += n;
413: return n;
414: }
415:
416: public int available() throws IOException {
417: return (count - pos) + in.available();
418: }
419:
420: public void close() throws IOException {
421: if (in != null) {
422: in.close();
423: in = null;
424: buf = null;
425: }
426: }
427:
428: private void fill() throws IOException {
429: count = pos = 0;
430: int n = in.read(buf, 0, buf.length);
431: if (n > 0) {
432: count = n;
433: }
434: }
435: }
436: }
|