166 * @param value the attribute value |
166 * @param value the attribute value |
167 * @return the previous value of the attribute, or null if none |
167 * @return the previous value of the attribute, or null if none |
168 * @exception IllegalArgumentException if the attribute name is invalid |
168 * @exception IllegalArgumentException if the attribute name is invalid |
169 */ |
169 */ |
170 public String putValue(String name, String value) { |
170 public String putValue(String name, String value) { |
171 return (String)put(new Name(name), value); |
171 return (String)put(Name.of(name), value); |
172 } |
172 } |
173 |
173 |
174 /** |
174 /** |
175 * Removes the attribute with the specified name (key) from this Map. |
175 * Removes the attribute with the specified name (key) from this Map. |
176 * Returns the previous attribute value, or null if none. |
176 * Returns the previous attribute value, or null if none. |
369 * Reads attributes from the specified input stream. |
369 * Reads attributes from the specified input stream. |
370 * XXX Need to handle UTF8 values. |
370 * XXX Need to handle UTF8 values. |
371 */ |
371 */ |
372 @SuppressWarnings("deprecation") |
372 @SuppressWarnings("deprecation") |
373 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { |
373 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { |
374 String name = null, value = null; |
374 String name = null, value; |
375 byte[] lastline = null; |
375 byte[] lastline = null; |
376 |
376 |
377 int len; |
377 int len; |
378 while ((len = is.readLine(lbuf)) != -1) { |
378 while ((len = is.readLine(lbuf)) != -1) { |
379 boolean lineContinued = false; |
379 boolean lineContinued = false; |
445 * and will be UTF8-encoded when written to the output stream. See the |
445 * and will be UTF8-encoded when written to the output stream. See the |
446 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> |
446 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> |
447 * for more information about valid attribute names and values. |
447 * for more information about valid attribute names and values. |
448 */ |
448 */ |
449 public static class Name { |
449 public static class Name { |
450 private String name; |
450 private final String name; |
451 private int hashCode = -1; |
451 private final int hashCode; |
|
452 |
|
453 /** |
|
454 * Avoid allocation for common Names |
|
455 */ |
|
456 private static final Map<String, Name> KNOWN_NAMES; |
|
457 |
|
458 static final Name of(String name) { |
|
459 Name n = KNOWN_NAMES.get(name); |
|
460 if (n != null) { |
|
461 return n; |
|
462 } |
|
463 return new Name(name); |
|
464 } |
452 |
465 |
453 /** |
466 /** |
454 * Constructs a new attribute name using the given string name. |
467 * Constructs a new attribute name using the given string name. |
455 * |
468 * |
456 * @param name the attribute string name |
469 * @param name the attribute string name |
457 * @exception IllegalArgumentException if the attribute name was |
470 * @exception IllegalArgumentException if the attribute name was |
458 * invalid |
471 * invalid |
459 * @exception NullPointerException if the attribute name was null |
472 * @exception NullPointerException if the attribute name was null |
460 */ |
473 */ |
461 public Name(String name) { |
474 public Name(String name) { |
462 if (name == null) { |
475 this.hashCode = hash(name); |
463 throw new NullPointerException("name"); |
|
464 } |
|
465 if (!isValid(name)) { |
|
466 throw new IllegalArgumentException(name); |
|
467 } |
|
468 this.name = name.intern(); |
476 this.name = name.intern(); |
469 } |
477 } |
470 |
478 |
471 private static boolean isValid(String name) { |
479 // Checks the string is valid |
|
480 private final int hash(String name) { |
|
481 Objects.requireNonNull(name, "name"); |
472 int len = name.length(); |
482 int len = name.length(); |
473 if (len > 70 || len == 0) { |
483 if (len > 70 || len == 0) { |
474 return false; |
484 throw new IllegalArgumentException(name); |
475 } |
485 } |
|
486 // Calculate hash code case insensitively |
|
487 int h = 0; |
476 for (int i = 0; i < len; i++) { |
488 for (int i = 0; i < len; i++) { |
477 if (!isValid(name.charAt(i))) { |
489 char c = name.charAt(i); |
478 return false; |
490 if (c >= 'a' && c <= 'z') { |
|
491 // hashcode must be identical for upper and lower case |
|
492 h = h * 31 + (c - 0x20); |
|
493 } else if ((c >= 'A' && c <= 'Z' || |
|
494 c >= '0' && c <= '9' || |
|
495 c == '_' || c == '-')) { |
|
496 h = h * 31 + c; |
|
497 } else { |
|
498 throw new IllegalArgumentException(name); |
479 } |
499 } |
480 } |
500 } |
481 return true; |
501 return h; |
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 } |
502 } |
495 |
503 |
496 /** |
504 /** |
497 * Compares this attribute name to another for equality. |
505 * Compares this attribute name to another for equality. |
498 * @param o the object to compare |
506 * @param o the object to compare |
499 * @return true if this attribute name is equal to the |
507 * @return true if this attribute name is equal to the |
500 * specified attribute object |
508 * specified attribute object |
501 */ |
509 */ |
502 public boolean equals(Object o) { |
510 public boolean equals(Object o) { |
|
511 if (this == o) { |
|
512 return true; |
|
513 } |
503 if (o instanceof Name) { |
514 if (o instanceof Name) { |
504 Comparator<String> c = String.CASE_INSENSITIVE_ORDER; |
515 Name other = (Name)o; |
505 return c.compare(name, ((Name)o).name) == 0; |
516 return other.name.equalsIgnoreCase(name); |
506 } else { |
517 } else { |
507 return false; |
518 return false; |
508 } |
519 } |
509 } |
520 } |
510 |
521 |
511 /** |
522 /** |
512 * Computes the hash value for this attribute name. |
523 * Computes the hash value for this attribute name. |
513 */ |
524 */ |
514 public int hashCode() { |
525 public int hashCode() { |
515 if (hashCode == -1) { |
|
516 hashCode = name.toLowerCase(Locale.ROOT).hashCode(); |
|
517 } |
|
518 return hashCode; |
526 return hashCode; |
519 } |
527 } |
520 |
528 |
521 /** |
529 /** |
522 * Returns the attribute name as a String. |
530 * Returns the attribute name as a String. |
652 * manifest attribute that indicates this is a multi-release JAR file. |
660 * manifest attribute that indicates this is a multi-release JAR file. |
653 * |
661 * |
654 * @since 9 |
662 * @since 9 |
655 */ |
663 */ |
656 public static final Name MULTI_RELEASE = new Name("Multi-Release"); |
664 public static final Name MULTI_RELEASE = new Name("Multi-Release"); |
|
665 |
|
666 private static void addName(Map<String, Name> names, Name name) { |
|
667 names.put(name.name, name); |
|
668 } |
|
669 |
|
670 static { |
|
671 var names = new HashMap<String, Name>(64); |
|
672 addName(names, MANIFEST_VERSION); |
|
673 addName(names, SIGNATURE_VERSION); |
|
674 addName(names, CONTENT_TYPE); |
|
675 addName(names, CLASS_PATH); |
|
676 addName(names, MAIN_CLASS); |
|
677 addName(names, SEALED); |
|
678 addName(names, EXTENSION_LIST); |
|
679 addName(names, EXTENSION_NAME); |
|
680 addName(names, IMPLEMENTATION_TITLE); |
|
681 addName(names, IMPLEMENTATION_VERSION); |
|
682 addName(names, IMPLEMENTATION_VENDOR); |
|
683 addName(names, SPECIFICATION_TITLE); |
|
684 addName(names, SPECIFICATION_VERSION); |
|
685 addName(names, SPECIFICATION_VENDOR); |
|
686 addName(names, MULTI_RELEASE); |
|
687 |
|
688 // Common attributes used in MANIFEST.MF et.al; adding these has a |
|
689 // small footprint cost, but is likely to be quickly paid for by |
|
690 // reducing allocation when reading and parsing typical manifests |
|
691 addName(names, new Name("Add-Exports")); |
|
692 addName(names, new Name("Add-Opens")); |
|
693 addName(names, new Name("Ant-Version")); |
|
694 addName(names, new Name("Archiver-Version")); |
|
695 addName(names, new Name("Build-Jdk")); |
|
696 addName(names, new Name("Built-By")); |
|
697 addName(names, new Name("Bnd-LastModified")); |
|
698 addName(names, new Name("Bundle-Description")); |
|
699 addName(names, new Name("Bundle-DocURL")); |
|
700 addName(names, new Name("Bundle-License")); |
|
701 addName(names, new Name("Bundle-ManifestVersion")); |
|
702 addName(names, new Name("Bundle-Name")); |
|
703 addName(names, new Name("Bundle-Vendor")); |
|
704 addName(names, new Name("Bundle-Version")); |
|
705 addName(names, new Name("Bundle-SymbolicName")); |
|
706 addName(names, new Name("Created-By")); |
|
707 addName(names, new Name("Export-Package")); |
|
708 addName(names, new Name("Import-Package")); |
|
709 addName(names, new Name("Name")); |
|
710 addName(names, new Name("SHA1-Digest")); |
|
711 addName(names, new Name("X-Compile-Source-JDK")); |
|
712 addName(names, new Name("X-Compile-Target-JDK")); |
|
713 KNOWN_NAMES = names; |
|
714 } |
657 } |
715 } |
658 } |
716 } |