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