001 /*
002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.util.jar;
027
028 import java.io.DataInputStream;
029 import java.io.DataOutputStream;
030 import java.io.IOException;
031 import java.util.HashMap;
032 import java.util.Map;
033 import java.util.Set;
034 import java.util.Collection;
035 import java.util.AbstractSet;
036 import java.util.Iterator;
037 import java.util.logging.Logger;
038 import java.util.Comparator;
039 import sun.misc.ASCIICaseInsensitiveComparator;
040
041 /**
042 * The Attributes class maps Manifest attribute names to associated string
043 * values. Valid attribute names are case-insensitive, are restricted to
044 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
045 * characters in length. Attribute values can contain any characters and
046 * will be UTF8-encoded when written to the output stream. See the
047 * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a>
048 * for more information about valid attribute names and values.
049 *
050 * @author David Connelly
051 * @version 1.60, 05/05/07
052 * @see Manifest
053 * @since 1.2
054 */
055 public class Attributes implements Map<Object, Object>, Cloneable {
056 /**
057 * The attribute name-value mappings.
058 */
059 protected Map<Object, Object> map;
060
061 /**
062 * Constructs a new, empty Attributes object with default size.
063 */
064 public Attributes() {
065 this (11);
066 }
067
068 /**
069 * Constructs a new, empty Attributes object with the specified
070 * initial size.
071 *
072 * @param size the initial number of attributes
073 */
074 public Attributes(int size) {
075 map = new HashMap(size);
076 }
077
078 /**
079 * Constructs a new Attributes object with the same attribute name-value
080 * mappings as in the specified Attributes.
081 *
082 * @param attr the specified Attributes
083 */
084 public Attributes(Attributes attr) {
085 map = new HashMap(attr);
086 }
087
088 /**
089 * Returns the value of the specified attribute name, or null if the
090 * attribute name was not found.
091 *
092 * @param name the attribute name
093 * @return the value of the specified attribute name, or null if
094 * not found.
095 */
096 public Object get(Object name) {
097 return map.get(name);
098 }
099
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((String) 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(((Name) e.getKey())
303 .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 // write out the *-Version header first, if it exists
329 String vername = Name.MANIFEST_VERSION.toString();
330 String version = getValue(vername);
331 if (version == null) {
332 vername = Name.SIGNATURE_VERSION.toString();
333 version = getValue(vername);
334 }
335
336 if (version != null) {
337 out.writeBytes(vername + ": " + version + "\r\n");
338 }
339
340 // write out all attributes except for the version
341 // we wrote out earlier
342 Iterator it = entrySet().iterator();
343 while (it.hasNext()) {
344 Map.Entry e = (Map.Entry) it.next();
345 String name = ((Name) e.getKey()).toString();
346 if ((version != null) && !(name.equalsIgnoreCase(vername))) {
347
348 StringBuffer buffer = new StringBuffer(name);
349 buffer.append(": ");
350
351 String value = (String) e.getValue();
352 if (value != null) {
353 byte[] vb = value.getBytes("UTF8");
354 value = new String(vb, 0, 0, vb.length);
355 }
356 buffer.append(value);
357
358 buffer.append("\r\n");
359 Manifest.make72Safe(buffer);
360 out.writeBytes(buffer.toString());
361 }
362 }
363 out.writeBytes("\r\n");
364 }
365
366 /*
367 * Reads attributes from the specified input stream.
368 * XXX Need to handle UTF8 values.
369 */
370 void read(Manifest.FastInputStream is, byte[] lbuf)
371 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
397 .arraycopy(lbuf, 1, buf, lastline.length,
398 len - 1);
399 if (is.peek() == ' ') {
400 lastline = buf;
401 continue;
402 }
403 value = new String(buf, 0, buf.length, "UTF8");
404 lastline = null;
405 } else {
406 while (lbuf[i++] != ':') {
407 if (i >= len) {
408 throw new IOException("invalid header field");
409 }
410 }
411 if (lbuf[i++] != ' ') {
412 throw new IOException("invalid header field");
413 }
414 name = new String(lbuf, 0, 0, i - 2);
415 if (is.peek() == ' ') {
416 lastline = new byte[len - i];
417 System.arraycopy(lbuf, i, lastline, 0, len - i);
418 continue;
419 }
420 value = new String(lbuf, i, len - i, "UTF8");
421 }
422 try {
423 if ((putValue(name, value) != null) && (!lineContinued)) {
424 Logger
425 .getLogger("java.util.jar")
426 .warning(
427 "Duplicate name in Manifest: "
428 + name
429 + ".\n"
430 + "Ensure that the manifest does not "
431 + "have duplicate entries, and\n"
432 + "that blank lines separate "
433 + "individual sections in both your\n"
434 + "manifest and in the META-INF/MANIFEST.MF "
435 + "entry in the jar file.");
436 }
437 } catch (IllegalArgumentException e) {
438 throw new IOException("invalid header field name: "
439 + name);
440 }
441 }
442 }
443
444 /**
445 * The Attributes.Name class represents an attribute name stored in
446 * this Map. Valid attribute names are case-insensitive, are restricted
447 * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
448 * 70 characters in length. Attribute values can contain any characters
449 * and will be UTF8-encoded when written to the output stream. See the
450 * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a>
451 * for more information about valid attribute names and values.
452 */
453 public static class Name {
454 private String name;
455 private int hashCode = -1;
456
457 /**
458 * Constructs a new attribute name using the given string name.
459 *
460 * @param name the attribute string name
461 * @exception IllegalArgumentException if the attribute name was
462 * invalid
463 * @exception NullPointerException if the attribute name was null
464 */
465 public Name(String name) {
466 if (name == null) {
467 throw new NullPointerException("name");
468 }
469 if (!isValid(name)) {
470 throw new IllegalArgumentException(name);
471 }
472 this .name = name.intern();
473 }
474
475 private static boolean isValid(String name) {
476 int len = name.length();
477 if (len > 70 || len == 0) {
478 return false;
479 }
480 for (int i = 0; i < len; i++) {
481 if (!isValid(name.charAt(i))) {
482 return false;
483 }
484 }
485 return true;
486 }
487
488 private static boolean isValid(char c) {
489 return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
490 }
491
492 private static boolean isAlpha(char c) {
493 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
494 }
495
496 private static boolean isDigit(char c) {
497 return c >= '0' && c <= '9';
498 }
499
500 /**
501 * Compares this attribute name to another for equality.
502 * @param o the object to compare
503 * @return true if this attribute name is equal to the
504 * specified attribute object
505 */
506 public boolean equals(Object o) {
507 if (o instanceof Name) {
508 Comparator c = ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER;
509 return c.compare(name, ((Name) o).name) == 0;
510 } else {
511 return false;
512 }
513 }
514
515 /**
516 * Computes the hash value for this attribute name.
517 */
518 public int hashCode() {
519 if (hashCode == -1) {
520 hashCode = ASCIICaseInsensitiveComparator
521 .lowerCaseHashCode(name);
522 }
523 return hashCode;
524 }
525
526 /**
527 * Returns the attribute name as a String.
528 */
529 public String toString() {
530 return name;
531 }
532
533 /**
534 * <code>Name</code> object for <code>Manifest-Version</code>
535 * manifest attribute. This attribute indicates the version number
536 * of the manifest standard to which a JAR file's manifest conforms.
537 * @see <a href="../../../../technotes/guides/jar/jar.html#JAR Manifest">
538 * Manifest and Signature Specification</a>
539 */
540 public static final Name MANIFEST_VERSION = new Name(
541 "Manifest-Version");
542
543 /**
544 * <code>Name</code> object for <code>Signature-Version</code>
545 * manifest attribute used when signing JAR files.
546 * @see <a href="../../../../technotes/guides/jar/jar.html#JAR Manifest">
547 * Manifest and Signature Specification</a>
548 */
549 public static final Name SIGNATURE_VERSION = new Name(
550 "Signature-Version");
551
552 /**
553 * <code>Name</code> object for <code>Content-Type</code>
554 * manifest attribute.
555 */
556 public static final Name CONTENT_TYPE = new Name("Content-Type");
557
558 /**
559 * <code>Name</code> object for <code>Class-Path</code>
560 * manifest attribute. Bundled extensions can use this attribute
561 * to find other JAR files containing needed classes.
562 * @see <a href="../../../../technotes/guides/extensions/spec.html#bundled">
563 * Extensions Specification</a>
564 */
565 public static final Name CLASS_PATH = new Name("Class-Path");
566
567 /**
568 * <code>Name</code> object for <code>Main-Class</code> manifest
569 * attribute used for launching applications packaged in JAR files.
570 * The <code>Main-Class</code> attribute is used in conjunction
571 * with the <code>-jar</code> command-line option of the
572 * <tt>java</tt> application launcher.
573 */
574 public static final Name MAIN_CLASS = new Name("Main-Class");
575
576 /**
577 * <code>Name</code> object for <code>Sealed</code> manifest attribute
578 * used for sealing.
579 * @see <a href="../../../../technotes/guides/extensions/spec.html#sealing">
580 * Extension Sealing</a>
581 */
582 public static final Name SEALED = new Name("Sealed");
583
584 /**
585 * <code>Name</code> object for <code>Extension-List</code> manifest attribute
586 * used for declaring dependencies on installed extensions.
587 * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
588 * Installed extension dependency</a>
589 */
590 public static final Name EXTENSION_LIST = new Name(
591 "Extension-List");
592
593 /**
594 * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
595 * used for declaring dependencies on installed extensions.
596 * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
597 * Installed extension dependency</a>
598 */
599 public static final Name EXTENSION_NAME = new Name(
600 "Extension-Name");
601
602 /**
603 * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
604 * used for declaring dependencies on installed extensions.
605 * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
606 * Installed extension dependency</a>
607 */
608 public static final Name EXTENSION_INSTALLATION = new Name(
609 "Extension-Installation");
610
611 /**
612 * <code>Name</code> object for <code>Implementation-Title</code>
613 * manifest attribute used for package versioning.
614 * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
615 * Java Product Versioning Specification</a>
616 */
617 public static final Name IMPLEMENTATION_TITLE = new Name(
618 "Implementation-Title");
619
620 /**
621 * <code>Name</code> object for <code>Implementation-Version</code>
622 * manifest attribute used for package versioning.
623 * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
624 * Java Product Versioning Specification</a>
625 */
626 public static final Name IMPLEMENTATION_VERSION = new Name(
627 "Implementation-Version");
628
629 /**
630 * <code>Name</code> object for <code>Implementation-Vendor</code>
631 * manifest attribute used for package versioning.
632 * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
633 * Java Product Versioning Specification</a>
634 */
635 public static final Name IMPLEMENTATION_VENDOR = new Name(
636 "Implementation-Vendor");
637
638 /**
639 * <code>Name</code> object for <code>Implementation-Vendor-Id</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 IMPLEMENTATION_VENDOR_ID = new Name(
645 "Implementation-Vendor-Id");
646
647 /**
648 * <code>Name</code> object for <code>Implementation-Vendor-URL</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 IMPLEMENTATION_URL = new Name(
654 "Implementation-URL");
655
656 /**
657 * <code>Name</code> object for <code>Specification-Title</code>
658 * manifest attribute used for package versioning.
659 * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
660 * Java Product Versioning Specification</a>
661 */
662 public static final Name SPECIFICATION_TITLE = new Name(
663 "Specification-Title");
664
665 /**
666 * <code>Name</code> object for <code>Specification-Version</code>
667 * manifest attribute used for package versioning.
668 * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
669 * Java Product Versioning Specification</a>
670 */
671 public static final Name SPECIFICATION_VERSION = new Name(
672 "Specification-Version");
673
674 /**
675 * <code>Name</code> object for <code>Specification-Vendor</code>
676 * manifest attribute used for package versioning.
677 * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
678 * Java Product Versioning Specification</a>
679 */
680 public static final Name SPECIFICATION_VENDOR = new Name(
681 "Specification-Vendor");
682 }
683 }
|