1 /* 2 * Copyright (c) 1997, 2015, 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 @SuppressWarnings("deprecation") 302 void write(DataOutputStream os) throws IOException { 303 for (Entry<Object, Object> e : entrySet()) { 304 StringBuffer buffer = new StringBuffer( 305 ((Name) e.getKey()).toString()); 306 buffer.append(": "); 307 308 String value = (String) e.getValue(); 309 if (value != null) { 310 byte[] vb = value.getBytes("UTF8"); 311 value = new String(vb, 0, 0, vb.length); 312 } 313 buffer.append(value); 314 315 buffer.append("\r\n"); 316 Manifest.make72Safe(buffer); 317 os.writeBytes(buffer.toString()); 318 } 319 os.writeBytes("\r\n"); 320 } 321 322 /* 323 * Writes the current attributes to the specified data output stream, 324 * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION 325 * attributes first. 326 * 327 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 328 */ 329 @SuppressWarnings("deprecation") 330 void writeMain(DataOutputStream out) throws IOException 331 { 332 // write out the *-Version header first, if it exists 333 String vername = Name.MANIFEST_VERSION.toString(); 334 String version = getValue(vername); 335 if (version == null) { 336 vername = Name.SIGNATURE_VERSION.toString(); 337 version = getValue(vername); 338 } 339 340 if (version != null) { 341 out.writeBytes(vername+": "+version+"\r\n"); 342 } 343 344 // write out all attributes except for the version 345 // we wrote out earlier 346 for (Entry<Object, Object> e : entrySet()) { 347 String name = ((Name) e.getKey()).toString(); 348 if ((version != null) && !(name.equalsIgnoreCase(vername))) { 349 350 StringBuffer buffer = new StringBuffer(name); 351 buffer.append(": "); 352 353 String value = (String) e.getValue(); 354 if (value != null) { 355 byte[] vb = value.getBytes("UTF8"); 356 value = new String(vb, 0, 0, vb.length); 357 } 358 buffer.append(value); 359 360 buffer.append("\r\n"); 361 Manifest.make72Safe(buffer); 362 out.writeBytes(buffer.toString()); 363 } 364 } 365 out.writeBytes("\r\n"); 366 } 367 368 /* 369 * Reads attributes from the specified input stream. 370 * XXX Need to handle UTF8 values. 371 */ 372 @SuppressWarnings("deprecation") 373 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { 374 String name = null, value = null; 375 byte[] lastline = null; 376 377 int len; 378 while ((len = is.readLine(lbuf)) != -1) { 379 boolean lineContinued = false; 380 if (lbuf[--len] != '\n') { 381 throw new IOException("line too long"); 382 } 383 if (len > 0 && lbuf[len-1] == '\r') { 384 --len; 385 } 386 if (len == 0) { 387 break; 388 } 389 int i = 0; 390 if (lbuf[0] == ' ') { 391 // continuation of previous line 392 if (name == null) { 393 throw new IOException("misplaced continuation line"); 394 } 395 lineContinued = true; 396 byte[] buf = new byte[lastline.length + len - 1]; 397 System.arraycopy(lastline, 0, buf, 0, lastline.length); 398 System.arraycopy(lbuf, 1, buf, lastline.length, 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 PlatformLogger.getLogger("java.util.jar").warning( 425 "Duplicate name in Manifest: " + name 426 + ".\n" 427 + "Ensure that the manifest does not " 428 + "have duplicate entries, and\n" 429 + "that blank lines separate " 430 + "individual sections in both your\n" 431 + "manifest and in the META-INF/MANIFEST.MF " 432 + "entry in the jar file."); 433 } 434 } catch (IllegalArgumentException e) { 435 throw new IOException("invalid header field name: " + name); 436 } 437 } 438 } 439 440 /** 441 * The Attributes.Name class represents an attribute name stored in 442 * this Map. Valid attribute names are case-insensitive, are restricted 443 * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 444 * 70 characters in length. Attribute values can contain any characters 445 * and will be UTF8-encoded when written to the output stream. See the 446 * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a> 447 * for more information about valid attribute names and values. 448 */ 449 public static class Name { 450 private String name; 451 private int hashCode = -1; 452 453 /** 454 * Constructs a new attribute name using the given string name. 455 * 456 * @param name the attribute string name 457 * @exception IllegalArgumentException if the attribute name was 458 * invalid 459 * @exception NullPointerException if the attribute name was null 460 */ 461 public Name(String name) { 462 if (name == null) { 463 throw new NullPointerException("name"); 464 } 465 if (!isValid(name)) { 466 throw new IllegalArgumentException(name); 467 } 468 this.name = name.intern(); 469 } 470 471 private static boolean isValid(String name) { 472 int len = name.length(); 473 if (len > 70 || len == 0) { 474 return false; 475 } 476 for (int i = 0; i < len; i++) { 477 if (!isValid(name.charAt(i))) { 478 return false; 479 } 480 } 481 return true; 482 } 483 484 private static boolean isValid(char c) { 485 return isAlpha(c) || isDigit(c) || c == '_' || c == '-'; 486 } 487 488 private static boolean isAlpha(char c) { 489 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 490 } 491 492 private static boolean isDigit(char c) { 493 return c >= '0' && c <= '9'; 494 } 495 496 /** 497 * Compares this attribute name to another for equality. 498 * @param o the object to compare 499 * @return true if this attribute name is equal to the 500 * specified attribute object 501 */ 502 public boolean equals(Object o) { 503 if (o instanceof Name) { 504 Comparator<String> c = ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER; 505 return c.compare(name, ((Name)o).name) == 0; 506 } else { 507 return false; 508 } 509 } 510 511 /** 512 * Computes the hash value for this attribute name. 513 */ 514 public int hashCode() { 515 if (hashCode == -1) { 516 hashCode = ASCIICaseInsensitiveComparator.lowerCaseHashCode(name); 517 } 518 return hashCode; 519 } 520 521 /** 522 * Returns the attribute name as a String. 523 */ 524 public String toString() { 525 return name; 526 } 527 528 /** 529 * {@code Name} object for {@code Manifest-Version} 530 * manifest attribute. This attribute indicates the version number 531 * of the manifest standard to which a JAR file's manifest conforms. 532 * @see <a href="../../../../technotes/guides/jar/jar.html#JAR_Manifest"> 533 * Manifest and Signature Specification</a> 534 */ 535 public static final Name MANIFEST_VERSION = new Name("Manifest-Version"); 536 537 /** 538 * {@code Name} object for {@code Signature-Version} 539 * manifest attribute used when signing JAR files. 540 * @see <a href="../../../../technotes/guides/jar/jar.html#JAR_Manifest"> 541 * Manifest and Signature Specification</a> 542 */ 543 public static final Name SIGNATURE_VERSION = new Name("Signature-Version"); 544 545 /** 546 * {@code Name} object for {@code Content-Type} 547 * manifest attribute. 548 */ 549 public static final Name CONTENT_TYPE = new Name("Content-Type"); 550 551 /** 552 * {@code Name} object for {@code Class-Path} 553 * manifest attribute. 554 * @see <a href="../../../../technotes/guides/jar/jar.html#classpath"> 555 * JAR file specification</a> 556 */ 557 public static final Name CLASS_PATH = new Name("Class-Path"); 558 559 /** 560 * {@code Name} object for {@code Main-Class} manifest 561 * attribute used for launching applications packaged in JAR files. 562 * The {@code Main-Class} attribute is used in conjunction 563 * with the {@code -jar} command-line option of the 564 * {@code java} application launcher. 565 */ 566 public static final Name MAIN_CLASS = new Name("Main-Class"); 567 568 /** 569 * {@code Name} object for {@code Sealed} manifest attribute 570 * used for sealing. 571 * @see <a href="../../../../technotes/guides/jar/jar.html#sealing"> 572 * Package Sealing</a> 573 */ 574 public static final Name SEALED = new Name("Sealed"); 575 576 /** 577 * {@code Name} object for {@code Extension-List} manifest attribute 578 * used for the extension mechanism that is no longer supported. 579 */ 580 public static final Name EXTENSION_LIST = new Name("Extension-List"); 581 582 /** 583 * {@code Name} object for {@code Extension-Name} manifest attribute. 584 * used for the extension mechanism that is no longer supported. 585 */ 586 public static final Name EXTENSION_NAME = new Name("Extension-Name"); 587 588 /** 589 * {@code Name} object for {@code Extension-Installation} manifest attribute. 590 * 591 * @deprecated Extension mechanism is no longer supported. 592 */ 593 @Deprecated 594 public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation"); 595 596 /** 597 * {@code Name} object for {@code Implementation-Title} 598 * manifest attribute used for package versioning. 599 */ 600 public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title"); 601 602 /** 603 * {@code Name} object for {@code Implementation-Version} 604 * manifest attribute used for package versioning. 605 */ 606 public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version"); 607 608 /** 609 * {@code Name} object for {@code Implementation-Vendor} 610 * manifest attribute used for package versioning. 611 */ 612 public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor"); 613 614 /** 615 * {@code Name} object for {@code Implementation-Vendor-Id} 616 * manifest attribute. 617 * 618 * @deprecated Extension mechanism is no longer supported. 619 */ 620 @Deprecated 621 public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); 622 623 /** 624 * {@code Name} object for {@code Implementation-URL} 625 * manifest attribute. 626 * 627 * @deprecated Extension mechanism is no longer supported. 628 */ 629 @Deprecated 630 public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL"); 631 632 /** 633 * {@code Name} object for {@code Specification-Title} 634 * manifest attribute used for package versioning. 635 */ 636 public static final Name SPECIFICATION_TITLE = new Name("Specification-Title"); 637 638 /** 639 * {@code Name} object for {@code Specification-Version} 640 * manifest attribute used for package versioning. 641 */ 642 public static final Name SPECIFICATION_VERSION = new Name("Specification-Version"); 643 644 /** 645 * {@code Name} object for {@code Specification-Vendor} 646 * manifest attribute used for package versioning. 647 */ 648 public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor"); 649 650 /** 651 * {@code Name} object for {@code Multi-Release} 652 * manifest attribute that indicates this is a multi-release JAR file. 653 * 654 * @since 9 655 */ 656 public static final Name MULTI_RELEASE = new Name("Multi-Release"); 657 } 658 }