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