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