• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
27 package java.util.jar;
28 
29 import java.io.DataInputStream;
30 import java.io.DataOutputStream;
31 import java.io.IOException;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.Collection;
36 import java.util.AbstractSet;
37 import java.util.Iterator;
38 import sun.util.logging.PlatformLogger;
39 import java.util.Comparator;
40 import 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  */
55 public 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      */
Attributes()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      */
Attributes(int size)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      */
Attributes(Attributes attr)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      */
get(Object name)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      */
getValue(String name)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      */
getValue(Name name)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      */
put(Object name, Object value)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      */
putValue(String name, String value)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      */
remove(Object name)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      */
containsValue(Object value)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      */
containsKey(Object name)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      */
putAll(Map<?,?> attr)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      */
clear()223     public void clear() {
224         map.clear();
225     }
226 
227     /**
228      * Returns the number of attributes in this Map.
229      */
size()230     public int size() {
231         return map.size();
232     }
233 
234     /**
235      * Returns true if this Map contains no attributes.
236      */
isEmpty()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      */
keySet()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      */
values()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      */
entrySet()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      */
equals(Object o)271     public boolean equals(Object o) {
272         return map.equals(o);
273     }
274 
275     /**
276      * Returns the hash code value for this Map.
277      */
hashCode()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      */
clone()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      */
write(DataOutputStream os)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      */
writeMain(DataOutputStream out)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      */
read(Manifest.FastInputStream is, byte[] lbuf)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          */
Name(String name)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 
isValid(String name)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 
isValid(char c)483         private static boolean isValid(char c) {
484             return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
485         }
486 
isAlpha(char c)487         private static boolean isAlpha(char c) {
488             return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
489         }
490 
isDigit(char c)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          */
equals(Object o)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          */
hashCode()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          */
toString()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