Manifest.java revision 03d2687dfc9b84bb16ea2b5f6a85da539696b30c
1/*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.util.jar;
27
28import java.io.FilterInputStream;
29import java.io.DataOutputStream;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.io.IOException;
33import java.util.Map;
34import java.util.HashMap;
35import 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="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/jar/jar.html">
43 * Manifest format specification</a>.
44 *
45 * @author  David Connelly
46 * @see     Attributes
47 * @since   1.2
48 */
49public class Manifest implements Cloneable {
50    // manifest main attributes
51    private Attributes attr = new Attributes();
52
53    // manifest entries
54    private Map<String, Attributes> 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 occurred
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<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
152        while (it.hasNext()) {
153            Map.Entry<String, Attributes> e = it.next();
154            StringBuffer buffer = new StringBuffer("Name: ");
155            String value = 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            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 Byte.toUnsignedInt(buf[pos++]);
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            if (pos == count)
404                return -1; // nothing left in buffer
405            return buf[pos];
406        }
407
408        public int readLine(byte[] b) throws IOException {
409            return readLine(b, 0, b.length);
410        }
411
412        public long skip(long n) throws IOException {
413            if (n <= 0) {
414                return 0;
415            }
416            long avail = count - pos;
417            if (avail <= 0) {
418                return in.skip(n);
419            }
420            if (n > avail) {
421                n = avail;
422            }
423            pos += n;
424            return n;
425        }
426
427        public int available() throws IOException {
428            return (count - pos) + in.available();
429        }
430
431        public void close() throws IOException {
432            if (in != null) {
433                in.close();
434                in = null;
435                buf = null;
436            }
437        }
438
439        private void fill() throws IOException {
440            count = pos = 0;
441            int n = in.read(buf, 0, buf.length);
442            if (n > 0) {
443                count = n;
444            }
445        }
446    }
447}
448