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