blob: 27a2aba8ef98596120d6a1c06e847932b6210ceb [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package java.util.jar;
27
28import java.io.DataInputStream;
29import java.io.DataOutputStream;
30import java.io.IOException;
31import java.util.HashMap;
32import java.util.Map;
33import java.util.Set;
34import java.util.Collection;
35import java.util.AbstractSet;
36import java.util.Iterator;
37import java.util.logging.Logger;
38import java.util.Comparator;
39import 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 */
54public 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 it = entrySet().iterator();
300 while (it.hasNext()) {
301 Map.Entry e = (Map.Entry)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 it = entrySet().iterator();
344 while (it.hasNext()) {
345 Map.Entry e = (Map.Entry)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 Logger.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 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-Vendor-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}