View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package java.util.jar;
27  
28  import java.io.DataInputStream;
29  import java.io.DataOutputStream;
30  import java.io.IOException;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.Collection;
35  import java.util.AbstractSet;
36  import java.util.Iterator;
37  import sun.util.logging.PlatformLogger;
38  import java.util.Comparator;
39  import 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   */
54  public 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<Map.Entry<Object, Object>> it = entrySet().iterator();
300         while (it.hasNext()) {
301             Map.Entry<Object, Object> e = 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<Map.Entry<Object, Object>> it = entrySet().iterator();
344         while (it.hasNext()) {
345             Map.Entry<Object, Object> e = 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<String> 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-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 }