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