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