Attributes.java revision 1d958dba8b5c9589e3375446045e1672eee0e1b1
1/*
2 * Copyright (c) 1997, 2006, 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.DataInputStream;
29import java.io.DataOutputStream;
30import java.io.IOException;
31import java.util.HashMap;
32import java.util.Map;
33import java.util.Set;
34import java.util.Collection;
35import java.util.AbstractSet;
36import java.util.Iterator;
37import sun.util.logging.PlatformLogger;
38import java.util.Comparator;
39import sun.misc.ASCIICaseInsensitiveComparator;
40
41/**
42 * The Attributes class maps Manifest attribute names to associated string
43 * values. Valid attribute names are case-insensitive, are restricted to
44 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
45 * characters in length. Attribute values can contain any characters and
46 * will be UTF8-encoded when written to the output stream.  See the
47 * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a>
48 * for more information about valid attribute names and values.
49 *
50 * @author  David Connelly
51 * @see     Manifest
52 * @since   1.2
53 */
54public class Attributes implements Map<Object,Object>, Cloneable {
55    /**
56     * The attribute name-value mappings.
57     */
58    protected Map<Object,Object> map;
59
60    /**
61     * Constructs a new, empty Attributes object with default size.
62     */
63    public Attributes() {
64        this(11);
65    }
66
67    /**
68     * Constructs a new, empty Attributes object with the specified
69     * initial size.
70     *
71     * @param size the initial number of attributes
72     */
73    public Attributes(int size) {
74        map = new HashMap(size);
75    }
76
77    /**
78     * Constructs a new Attributes object with the same attribute name-value
79     * mappings as in the specified Attributes.
80     *
81     * @param attr the specified Attributes
82     */
83    public Attributes(Attributes attr) {
84        map = new HashMap(attr);
85    }
86
87
88    /**
89     * Returns the value of the specified attribute name, or null if the
90     * attribute name was not found.
91     *
92     * @param name the attribute name
93     * @return the value of the specified attribute name, or null if
94     *         not found.
95     */
96    public Object get(Object name) {
97        return map.get(name);
98    }
99
100    /**
101     * Returns the value of the specified attribute name, specified as
102     * a string, or null if the attribute was not found. The attribute
103     * name is case-insensitive.
104     * <p>
105     * This method is defined as:
106     * <pre>
107     *      return (String)get(new Attributes.Name((String)name));
108     * </pre>
109     *
110     * @param name the attribute name as a string
111     * @return the String value of the specified attribute name, or null if
112     *         not found.
113     * @throws IllegalArgumentException if the attribute name is invalid
114     */
115    public String getValue(String name) {
116        return (String)get(new Attributes.Name(name));
117    }
118
119    /**
120     * Returns the value of the specified Attributes.Name, or null if the
121     * attribute was not found.
122     * <p>
123     * This method is defined as:
124     * <pre>
125     *     return (String)get(name);
126     * </pre>
127     *
128     * @param name the Attributes.Name object
129     * @return the String value of the specified Attribute.Name, or null if
130     *         not found.
131     */
132    public String getValue(Name name) {
133        return (String)get(name);
134    }
135
136    /**
137     * Associates the specified value with the specified attribute name
138     * (key) in this Map. If the Map previously contained a mapping for
139     * the attribute name, the old value is replaced.
140     *
141     * @param name the attribute name
142     * @param value the attribute value
143     * @return the previous value of the attribute, or null if none
144     * @exception ClassCastException if the name is not a Attributes.Name
145     *            or the value is not a String
146     */
147    public Object put(Object name, Object value) {
148        return map.put((Attributes.Name)name, (String)value);
149    }
150
151    /**
152     * Associates the specified value with the specified attribute name,
153     * specified as a String. The attributes name is case-insensitive.
154     * If the Map previously contained a mapping for the attribute name,
155     * the old value is replaced.
156     * <p>
157     * This method is defined as:
158     * <pre>
159     *      return (String)put(new Attributes.Name(name), value);
160     * </pre>
161     *
162     * @param name the attribute name as a string
163     * @param value the attribute value
164     * @return the previous value of the attribute, or null if none
165     * @exception IllegalArgumentException if the attribute name is invalid
166     */
167    public String putValue(String name, String value) {
168        return (String)put(new Name(name), value);
169    }
170
171    /**
172     * Removes the attribute with the specified name (key) from this Map.
173     * Returns the previous attribute value, or null if none.
174     *
175     * @param name attribute name
176     * @return the previous value of the attribute, or null if none
177     */
178    public Object remove(Object name) {
179        return map.remove(name);
180    }
181
182    /**
183     * Returns true if this Map maps one or more attribute names (keys)
184     * to the specified value.
185     *
186     * @param value the attribute value
187     * @return true if this Map maps one or more attribute names to
188     *         the specified value
189     */
190    public boolean containsValue(Object value) {
191        return map.containsValue(value);
192    }
193
194    /**
195     * Returns true if this Map contains the specified attribute name (key).
196     *
197     * @param name the attribute name
198     * @return true if this Map contains the specified attribute name
199     */
200    public boolean containsKey(Object name) {
201        return map.containsKey(name);
202    }
203
204    /**
205     * Copies all of the attribute name-value mappings from the specified
206     * Attributes to this Map. Duplicate mappings will be replaced.
207     *
208     * @param attr the Attributes to be stored in this map
209     * @exception ClassCastException if attr is not an Attributes
210     */
211    public void putAll(Map<?,?> attr) {
212        // ## javac bug?
213        if (!Attributes.class.isInstance(attr))
214            throw new ClassCastException();
215        for (Map.Entry<?,?> me : (attr).entrySet())
216            put(me.getKey(), me.getValue());
217    }
218
219    /**
220     * Removes all attributes from this Map.
221     */
222    public void clear() {
223        map.clear();
224    }
225
226    /**
227     * Returns the number of attributes in this Map.
228     */
229    public int size() {
230        return map.size();
231    }
232
233    /**
234     * Returns true if this Map contains no attributes.
235     */
236    public boolean isEmpty() {
237        return map.isEmpty();
238    }
239
240    /**
241     * Returns a Set view of the attribute names (keys) contained in this Map.
242     */
243    public Set<Object> keySet() {
244        return map.keySet();
245    }
246
247    /**
248     * Returns a Collection view of the attribute values contained in this Map.
249     */
250    public Collection<Object> values() {
251        return map.values();
252    }
253
254    /**
255     * Returns a Collection view of the attribute name-value mappings
256     * contained in this Map.
257     */
258    public Set<Map.Entry<Object,Object>> entrySet() {
259        return map.entrySet();
260    }
261
262    /**
263     * Compares the specified Attributes object with this Map for equality.
264     * Returns true if the given object is also an instance of Attributes
265     * and the two Attributes objects represent the same mappings.
266     *
267     * @param o the Object to be compared
268     * @return true if the specified Object is equal to this Map
269     */
270    public boolean equals(Object o) {
271        return map.equals(o);
272    }
273
274    /**
275     * Returns the hash code value for this Map.
276     */
277    public int hashCode() {
278        return map.hashCode();
279    }
280
281    /**
282     * Returns a copy of the Attributes, implemented as follows:
283     * <pre>
284     *     public Object clone() { return new Attributes(this); }
285     * </pre>
286     * Since the attribute names and values are themselves immutable,
287     * the Attributes returned can be safely modified without affecting
288     * the original.
289     */
290    public Object clone() {
291        return new Attributes(this);
292    }
293
294    /*
295     * Writes the current attributes to the specified data output stream.
296     * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
297     */
298     void write(DataOutputStream os) throws IOException {
299        Iterator it = entrySet().iterator();
300        while (it.hasNext()) {
301            Map.Entry e = (Map.Entry)it.next();
302            StringBuffer buffer = new StringBuffer(
303                                        ((Name)e.getKey()).toString());
304            buffer.append(": ");
305
306            String value = (String)e.getValue();
307            if (value != null) {
308                byte[] vb = value.getBytes("UTF8");
309                value = new String(vb, 0, 0, vb.length);
310            }
311            buffer.append(value);
312
313            buffer.append("\r\n");
314            Manifest.make72Safe(buffer);
315            os.writeBytes(buffer.toString());
316        }
317        os.writeBytes("\r\n");
318    }
319
320    /*
321     * Writes the current attributes to the specified data output stream,
322     * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
323     * attributes first.
324     *
325     * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
326     */
327    void writeMain(DataOutputStream out) throws IOException
328    {
329        // write out the *-Version header first, if it exists
330        String vername = Name.MANIFEST_VERSION.toString();
331        String version = getValue(vername);
332        if (version == null) {
333            vername = Name.SIGNATURE_VERSION.toString();
334            version = getValue(vername);
335        }
336
337        if (version != null) {
338            out.writeBytes(vername+": "+version+"\r\n");
339        }
340
341        // write out all attributes except for the version
342        // we wrote out earlier
343        Iterator it = entrySet().iterator();
344        while (it.hasNext()) {
345            Map.Entry e = (Map.Entry)it.next();
346            String name = ((Name)e.getKey()).toString();
347            if ((version != null) && ! (name.equalsIgnoreCase(vername))) {
348
349                StringBuffer buffer = new StringBuffer(name);
350                buffer.append(": ");
351
352                String value = (String)e.getValue();
353                if (value != null) {
354                    byte[] vb = value.getBytes("UTF8");
355                    value = new String(vb, 0, 0, vb.length);
356                }
357                buffer.append(value);
358
359                buffer.append("\r\n");
360                Manifest.make72Safe(buffer);
361                out.writeBytes(buffer.toString());
362            }
363        }
364        out.writeBytes("\r\n");
365    }
366
367    /*
368     * Reads attributes from the specified input stream.
369     * XXX Need to handle UTF8 values.
370     */
371    void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
372        String name = null, value = null;
373        byte[] lastline = null;
374
375        int len;
376        while ((len = is.readLine(lbuf)) != -1) {
377            boolean lineContinued = false;
378            if (lbuf[--len] != '\n') {
379                throw new IOException("line too long");
380            }
381            if (len > 0 && lbuf[len-1] == '\r') {
382                --len;
383            }
384            if (len == 0) {
385                break;
386            }
387            int i = 0;
388            if (lbuf[0] == ' ') {
389                // continuation of previous line
390                if (name == null) {
391                    throw new IOException("misplaced continuation line");
392                }
393                lineContinued = true;
394                byte[] buf = new byte[lastline.length + len - 1];
395                System.arraycopy(lastline, 0, buf, 0, lastline.length);
396                System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
397                if (is.peek() == ' ') {
398                    lastline = buf;
399                    continue;
400                }
401                value = new String(buf, 0, buf.length, "UTF8");
402                lastline = null;
403            } else {
404                while (lbuf[i++] != ':') {
405                    if (i >= len) {
406                        throw new IOException("invalid header field");
407                    }
408                }
409                if (lbuf[i++] != ' ') {
410                    throw new IOException("invalid header field");
411                }
412                name = new String(lbuf, 0, 0, i - 2);
413                if (is.peek() == ' ') {
414                    lastline = new byte[len - i];
415                    System.arraycopy(lbuf, i, lastline, 0, len - i);
416                    continue;
417                }
418                value = new String(lbuf, i, len - i, "UTF8");
419            }
420            try {
421                if ((putValue(name, value) != null) && (!lineContinued)) {
422                    PlatformLogger.getLogger("java.util.jar").warning(
423                                     "Duplicate name in Manifest: " + name
424                                     + ".\n"
425                                     + "Ensure that the manifest does not "
426                                     + "have duplicate entries, and\n"
427                                     + "that blank lines separate "
428                                     + "individual sections in both your\n"
429                                     + "manifest and in the META-INF/MANIFEST.MF "
430                                     + "entry in the jar file.");
431                }
432            } catch (IllegalArgumentException e) {
433                throw new IOException("invalid header field name: " + name);
434            }
435        }
436    }
437
438    /**
439     * The Attributes.Name class represents an attribute name stored in
440     * this Map. Valid attribute names are case-insensitive, are restricted
441     * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
442     * 70 characters in length. Attribute values can contain any characters
443     * and will be UTF8-encoded when written to the output stream.  See the
444     * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a>
445     * for more information about valid attribute names and values.
446     */
447    public static class Name {
448        private String name;
449        private int hashCode = -1;
450
451        /**
452         * Constructs a new attribute name using the given string name.
453         *
454         * @param name the attribute string name
455         * @exception IllegalArgumentException if the attribute name was
456         *            invalid
457         * @exception NullPointerException if the attribute name was null
458         */
459        public Name(String name) {
460            if (name == null) {
461                throw new NullPointerException("name");
462            }
463            if (!isValid(name)) {
464                throw new IllegalArgumentException(name);
465            }
466            this.name = name.intern();
467        }
468
469        private static boolean isValid(String name) {
470            int len = name.length();
471            if (len > 70 || len == 0) {
472                return false;
473            }
474            for (int i = 0; i < len; i++) {
475                if (!isValid(name.charAt(i))) {
476                    return false;
477                }
478            }
479            return true;
480        }
481
482        private static boolean isValid(char c) {
483            return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
484        }
485
486        private static boolean isAlpha(char c) {
487            return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
488        }
489
490        private static boolean isDigit(char c) {
491            return c >= '0' && c <= '9';
492        }
493
494        /**
495         * Compares this attribute name to another for equality.
496         * @param o the object to compare
497         * @return true if this attribute name is equal to the
498         *         specified attribute object
499         */
500        public boolean equals(Object o) {
501            if (o instanceof Name) {
502                Comparator c = ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER;
503                return c.compare(name, ((Name)o).name) == 0;
504            } else {
505                return false;
506            }
507        }
508
509        /**
510         * Computes the hash value for this attribute name.
511         */
512        public int hashCode() {
513            if (hashCode == -1) {
514                hashCode = ASCIICaseInsensitiveComparator.lowerCaseHashCode(name);
515            }
516            return hashCode;
517        }
518
519        /**
520         * Returns the attribute name as a String.
521         */
522        public String toString() {
523            return name;
524        }
525
526        /**
527         * <code>Name</code> object for <code>Manifest-Version</code>
528         * manifest attribute. This attribute indicates the version number
529         * of the manifest standard to which a JAR file's manifest conforms.
530         * @see <a href="../../../../technotes/guides/jar/jar.html#JAR Manifest">
531         *      Manifest and Signature Specification</a>
532         */
533        public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
534
535        /**
536         * <code>Name</code> object for <code>Signature-Version</code>
537         * manifest attribute used when signing JAR files.
538         * @see <a href="../../../../technotes/guides/jar/jar.html#JAR Manifest">
539         *      Manifest and Signature Specification</a>
540         */
541        public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
542
543        /**
544         * <code>Name</code> object for <code>Content-Type</code>
545         * manifest attribute.
546         */
547        public static final Name CONTENT_TYPE = new Name("Content-Type");
548
549        /**
550         * <code>Name</code> object for <code>Class-Path</code>
551         * manifest attribute. Bundled extensions can use this attribute
552         * to find other JAR files containing needed classes.
553         * @see <a href="../../../../technotes/guides/extensions/spec.html#bundled">
554         *      Extensions Specification</a>
555         */
556        public static final Name CLASS_PATH = new Name("Class-Path");
557
558        /**
559         * <code>Name</code> object for <code>Main-Class</code> manifest
560         * attribute used for launching applications packaged in JAR files.
561         * The <code>Main-Class</code> attribute is used in conjunction
562         * with the <code>-jar</code> command-line option of the
563         * <tt>java</tt> application launcher.
564         */
565        public static final Name MAIN_CLASS = new Name("Main-Class");
566
567        /**
568         * <code>Name</code> object for <code>Sealed</code> manifest attribute
569         * used for sealing.
570         * @see <a href="../../../../technotes/guides/extensions/spec.html#sealing">
571         *      Extension Sealing</a>
572         */
573        public static final Name SEALED = new Name("Sealed");
574
575       /**
576         * <code>Name</code> object for <code>Extension-List</code> manifest attribute
577         * used for declaring dependencies on installed extensions.
578         * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
579         *      Installed extension dependency</a>
580         */
581        public static final Name EXTENSION_LIST = new Name("Extension-List");
582
583        /**
584         * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
585         * used for declaring dependencies on installed extensions.
586         * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
587         *      Installed extension dependency</a>
588         */
589        public static final Name EXTENSION_NAME = new Name("Extension-Name");
590
591        /**
592         * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
593         * used for declaring dependencies on installed extensions.
594         * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
595         *      Installed extension dependency</a>
596         */
597        public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
598
599        /**
600         * <code>Name</code> object for <code>Implementation-Title</code>
601         * manifest attribute used for package versioning.
602         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
603         *      Java Product Versioning Specification</a>
604         */
605        public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
606
607        /**
608         * <code>Name</code> object for <code>Implementation-Version</code>
609         * manifest attribute used for package versioning.
610         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
611         *      Java Product Versioning Specification</a>
612         */
613        public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
614
615        /**
616         * <code>Name</code> object for <code>Implementation-Vendor</code>
617         * manifest attribute used for package versioning.
618         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
619         *      Java Product Versioning Specification</a>
620         */
621        public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
622
623        /**
624         * <code>Name</code> object for <code>Implementation-Vendor-Id</code>
625         * manifest attribute used for package versioning.
626         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
627         *      Java Product Versioning Specification</a>
628         */
629        public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
630
631       /**
632         * <code>Name</code> object for <code>Implementation-Vendor-URL</code>
633         * manifest attribute used for package versioning.
634         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
635         *      Java Product Versioning Specification</a>
636         */
637        public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
638
639        /**
640         * <code>Name</code> object for <code>Specification-Title</code>
641         * manifest attribute used for package versioning.
642         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
643         *      Java Product Versioning Specification</a>
644         */
645        public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
646
647        /**
648         * <code>Name</code> object for <code>Specification-Version</code>
649         * manifest attribute used for package versioning.
650         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
651         *      Java Product Versioning Specification</a>
652         */
653        public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
654
655        /**
656         * <code>Name</code> object for <code>Specification-Vendor</code>
657         * manifest attribute used for package versioning.
658         * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
659         *      Java Product Versioning Specification</a>
660         */
661        public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
662
663        /**
664         * @hide
665         */
666        public static final Name NAME = new Name("Name");
667    }
668}
669