| Attributes.java |
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