1 /*
   2  * Copyright (c) 1997, 2018, 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.DataOutputStream;
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.security.AccessController;
  32 import java.security.PrivilegedAction;
  33 import java.security.Security;
  34 import java.util.Collection;
  35 import java.util.HashMap;
  36 import java.util.LinkedHashMap;
  37 import java.util.Map;
  38 import java.util.Objects;
  39 import java.util.Set;
  40 
  41 import sun.util.logging.PlatformLogger;
  42 
  43 /**
  44  * The Attributes class maps Manifest attribute names to associated string
  45  * values. Valid attribute names are case-insensitive, are restricted to
  46  * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
  47  * characters in length. There must be a colon and a SPACE after the name;
  48  * the combined length will not exceed 72 characters.
  49  * Attribute values can contain any characters and
  50  * will be UTF8-encoded when written to the output stream.  See the
  51  * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
  52  * for more information about valid attribute names and values.
  53  *
  54  * <p>This map and its views have a predictable iteration order, namely the
  55  * order that keys were inserted into the map, as with {@link LinkedHashMap}.
  56  *
  57  * @author  David Connelly
  58  * @see     Manifest
  59  * @since   1.2
  60  */
  61 public class Attributes implements Map<Object,Object>, Cloneable {
  62     /**
  63      * The attribute name-value mappings.
  64      */
  65     protected Map<Object,Object> map;
  66 
  67     /**
  68      * Security or system property which specifies categories of
  69      * (potentially sensitive) information that may be included
  70      * in exception text. This class only defines one category:
  71      * "jarpath" which represents the path of a jar file
  72      * relating to an IO exception.
  73      * The property value is a comma separated list of
  74      * case insignificant category names.
  75      */
  76     private static final String enhancedTextPropname = "jdk.includeInExceptions";
  77 
  78     private static final boolean jarPathInExceptionText = initTextProp();
  79 
  80     private static boolean initTextProp() {
  81         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
  82             public Boolean run() {
  83                 String val = System.getProperty(enhancedTextPropname);
  84                 if (val == null) {
  85                     val = Security.getProperty(enhancedTextPropname);
  86                     if (val == null)
  87                         return false;
  88                 }
  89                 String[] tokens = val.split(",");
  90                 for (String token : tokens) {
  91                     if (token.equalsIgnoreCase("jarpath"))
  92                         return true;
  93                 }
  94                 return false;
  95             }
  96         });
  97     }
  98 
  99 
 100     /**
 101      * Constructs a new, empty Attributes object with default size.
 102      */
 103     public Attributes() {
 104         this(11);
 105     }
 106 
 107     /**
 108      * Constructs a new, empty Attributes object with the specified
 109      * initial size.
 110      *
 111      * @param size the initial number of attributes
 112      */
 113     public Attributes(int size) {
 114         map = new LinkedHashMap<>(size);
 115     }
 116 
 117     /**
 118      * Constructs a new Attributes object with the same attribute name-value
 119      * mappings as in the specified Attributes.
 120      *
 121      * @param attr the specified Attributes
 122      */
 123     public Attributes(Attributes attr) {
 124         map = new LinkedHashMap<>(attr);
 125     }
 126 
 127 
 128     /**
 129      * Returns the value of the specified attribute name, or null if the
 130      * attribute name was not found.
 131      *
 132      * @param name the attribute name
 133      * @return the value of the specified attribute name, or null if
 134      *         not found.
 135      */
 136     public Object get(Object name) {
 137         return map.get(name);
 138     }
 139 
 140     /**
 141      * Returns the value of the specified attribute name, specified as
 142      * a string, or null if the attribute was not found. The attribute
 143      * name is case-insensitive.
 144      * <p>
 145      * This method is defined as:
 146      * <pre>
 147      *      return (String)get(new Attributes.Name((String)name));
 148      * </pre>
 149      *
 150      * @param name the attribute name as a string
 151      * @return the String value of the specified attribute name, or null if
 152      *         not found.
 153      * @throws IllegalArgumentException if the attribute name is invalid
 154      */
 155     public String getValue(String name) {
 156         return (String)get(Name.of(name));
 157     }
 158 
 159     /**
 160      * Returns the value of the specified Attributes.Name, or null if the
 161      * attribute was not found.
 162      * <p>
 163      * This method is defined as:
 164      * <pre>
 165      *     return (String)get(name);
 166      * </pre>
 167      *
 168      * @param name the Attributes.Name object
 169      * @return the String value of the specified Attribute.Name, or null if
 170      *         not found.
 171      */
 172     public String getValue(Name name) {
 173         return (String)get(name);
 174     }
 175 
 176     /**
 177      * Associates the specified value with the specified attribute name
 178      * (key) in this Map. If the Map previously contained a mapping for
 179      * the attribute name, the old value is replaced.
 180      *
 181      * @param name the attribute name
 182      * @param value the attribute value
 183      * @return the previous value of the attribute, or null if none
 184      * @exception ClassCastException if the name is not a Attributes.Name
 185      *            or the value is not a String
 186      */
 187     public Object put(Object name, Object value) {
 188         return map.put((Attributes.Name)name, (String)value);
 189     }
 190 
 191     /**
 192      * Associates the specified value with the specified attribute name,
 193      * specified as a String. The attributes name is case-insensitive.
 194      * If the Map previously contained a mapping for the attribute name,
 195      * the old value is replaced.
 196      * <p>
 197      * This method is defined as:
 198      * <pre>
 199      *      return (String)put(new Attributes.Name(name), value);
 200      * </pre>
 201      *
 202      * @param name the attribute name as a string
 203      * @param value the attribute value
 204      * @return the previous value of the attribute, or null if none
 205      * @exception IllegalArgumentException if the attribute name is invalid
 206      */
 207     public String putValue(String name, String value) {
 208         return (String)put(Name.of(name), value);
 209     }
 210 
 211     /**
 212      * Removes the attribute with the specified name (key) from this Map.
 213      * Returns the previous attribute value, or null if none.
 214      *
 215      * @param name attribute name
 216      * @return the previous value of the attribute, or null if none
 217      */
 218     public Object remove(Object name) {
 219         return map.remove(name);
 220     }
 221 
 222     /**
 223      * Returns true if this Map maps one or more attribute names (keys)
 224      * to the specified value.
 225      *
 226      * @param value the attribute value
 227      * @return true if this Map maps one or more attribute names to
 228      *         the specified value
 229      */
 230     public boolean containsValue(Object value) {
 231         return map.containsValue(value);
 232     }
 233 
 234     /**
 235      * Returns true if this Map contains the specified attribute name (key).
 236      *
 237      * @param name the attribute name
 238      * @return true if this Map contains the specified attribute name
 239      */
 240     public boolean containsKey(Object name) {
 241         return map.containsKey(name);
 242     }
 243 
 244     /**
 245      * Copies all of the attribute name-value mappings from the specified
 246      * Attributes to this Map. Duplicate mappings will be replaced.
 247      *
 248      * @param attr the Attributes to be stored in this map
 249      * @exception ClassCastException if attr is not an Attributes
 250      */
 251     public void putAll(Map<?,?> attr) {
 252         // ## javac bug?
 253         if (!Attributes.class.isInstance(attr))
 254             throw new ClassCastException();
 255         for (Map.Entry<?,?> me : (attr).entrySet())
 256             put(me.getKey(), me.getValue());
 257     }
 258 
 259     /**
 260      * Removes all attributes from this Map.
 261      */
 262     public void clear() {
 263         map.clear();
 264     }
 265 
 266     /**
 267      * Returns the number of attributes in this Map.
 268      */
 269     public int size() {
 270         return map.size();
 271     }
 272 
 273     /**
 274      * Returns true if this Map contains no attributes.
 275      */
 276     public boolean isEmpty() {
 277         return map.isEmpty();
 278     }
 279 
 280     /**
 281      * Returns a Set view of the attribute names (keys) contained in this Map.
 282      */
 283     public Set<Object> keySet() {
 284         return map.keySet();
 285     }
 286 
 287     /**
 288      * Returns a Collection view of the attribute values contained in this Map.
 289      */
 290     public Collection<Object> values() {
 291         return map.values();
 292     }
 293 
 294     /**
 295      * Returns a Collection view of the attribute name-value mappings
 296      * contained in this Map.
 297      */
 298     public Set<Map.Entry<Object,Object>> entrySet() {
 299         return map.entrySet();
 300     }
 301 
 302     /**
 303      * Compares the specified Attributes object with this Map for equality.
 304      * Returns true if the given object is also an instance of Attributes
 305      * and the two Attributes objects represent the same mappings.
 306      *
 307      * @param o the Object to be compared
 308      * @return true if the specified Object is equal to this Map
 309      */
 310     public boolean equals(Object o) {
 311         return map.equals(o);
 312     }
 313 
 314     /**
 315      * Returns the hash code value for this Map.
 316      */
 317     public int hashCode() {
 318         return map.hashCode();
 319     }
 320 
 321     /**
 322      * Returns a copy of the Attributes, implemented as follows:
 323      * <pre>
 324      *     public Object clone() { return new Attributes(this); }
 325      * </pre>
 326      * Since the attribute names and values are themselves immutable,
 327      * the Attributes returned can be safely modified without affecting
 328      * the original.
 329      */
 330     public Object clone() {
 331         return new Attributes(this);
 332     }
 333 
 334     /*
 335      * Writes the current attributes to the specified data output stream.
 336      * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
 337      */
 338      @SuppressWarnings("deprecation")
 339      void write(DataOutputStream os) throws IOException {
 340          for (Entry<Object, Object> e : entrySet()) {
 341              StringBuffer buffer = new StringBuffer(
 342                                          ((Name) e.getKey()).toString());
 343              buffer.append(": ");
 344 
 345              String value = (String) e.getValue();
 346              if (value != null) {
 347                  byte[] vb = value.getBytes("UTF8");
 348                  value = new String(vb, 0, 0, vb.length);
 349              }
 350              buffer.append(value);
 351 
 352              Manifest.make72Safe(buffer);
 353              buffer.append("\r\n");
 354              os.writeBytes(buffer.toString());
 355          }
 356         os.writeBytes("\r\n");
 357     }
 358 
 359     /*
 360      * Writes the current attributes to the specified data output stream,
 361      * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
 362      * attributes first.
 363      *
 364      * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
 365      */
 366     @SuppressWarnings("deprecation")
 367     void writeMain(DataOutputStream out) throws IOException
 368     {
 369         // write out the *-Version header first, if it exists
 370         String vername = Name.MANIFEST_VERSION.toString();
 371         String version = getValue(vername);
 372         if (version == null) {
 373             vername = Name.SIGNATURE_VERSION.toString();
 374             version = getValue(vername);
 375         }
 376 
 377         if (version != null) {
 378             out.writeBytes(vername+": "+version+"\r\n");
 379         }
 380 
 381         // write out all attributes except for the version
 382         // we wrote out earlier
 383         for (Entry<Object, Object> e : entrySet()) {
 384             String name = ((Name) e.getKey()).toString();
 385             if ((version != null) && !(name.equalsIgnoreCase(vername))) {
 386 
 387                 StringBuffer buffer = new StringBuffer(name);
 388                 buffer.append(": ");
 389 
 390                 String value = (String) e.getValue();
 391                 if (value != null) {
 392                     byte[] vb = value.getBytes("UTF8");
 393                     value = new String(vb, 0, 0, vb.length);
 394                 }
 395                 buffer.append(value);
 396 
 397                 Manifest.make72Safe(buffer);
 398                 buffer.append("\r\n");
 399                 out.writeBytes(buffer.toString());
 400             }
 401         }
 402         out.writeBytes("\r\n");
 403     }
 404 
 405     /*
 406      * Reads attributes from the specified input stream.
 407      * XXX Need to handle UTF8 values.
 408      */
 409     void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
 410         read(is, lbuf, null, 0);
 411     }
 412 
 413     @SuppressWarnings("deprecation")
 414     int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int offset) throws IOException {
 415         String name = null, value;
 416         byte[] lastline = null;
 417         int lineNumber = offset;
 418 
 419         int len;
 420         while ((len = is.readLine(lbuf)) != -1) {
 421             boolean lineContinued = false;
 422             byte c = lbuf[--len];
 423             lineNumber++;
 424 
 425             if (c != '\n' && c != '\r') {
 426                 throw new IOException("line too long (" + getErrorPosition(filename, lineNumber) + ")");
 427             }
 428             if (len > 0 && lbuf[len-1] == '\r') {
 429                 --len;
 430             }
 431             if (len == 0) {
 432                 break;
 433             }
 434             int i = 0;
 435             if (lbuf[0] == ' ') {
 436                 // continuation of previous line
 437                 if (name == null) {
 438                     throw new IOException("misplaced continuation line (" + getErrorPosition(filename, lineNumber) + ")");
 439                 }
 440                 lineContinued = true;
 441                 byte[] buf = new byte[lastline.length + len - 1];
 442                 System.arraycopy(lastline, 0, buf, 0, lastline.length);
 443                 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
 444                 if (is.peek() == ' ') {
 445                     lastline = buf;
 446                     continue;
 447                 }
 448                 value = new String(buf, 0, buf.length, "UTF8");
 449                 lastline = null;
 450             } else {
 451                 while (lbuf[i++] != ':') {
 452                     if (i >= len) {
 453                         throw new IOException("invalid header field (" + getErrorPosition(filename, lineNumber) + ")");
 454                     }
 455                 }
 456                 if (lbuf[i++] != ' ') {
 457                     throw new IOException("invalid header field (" + getErrorPosition(filename, lineNumber) + ")");
 458                 }
 459                 name = new String(lbuf, 0, 0, i - 2);
 460                 if (is.peek() == ' ') {
 461                     lastline = new byte[len - i];
 462                     System.arraycopy(lbuf, i, lastline, 0, len - i);
 463                     continue;
 464                 }
 465                 value = new String(lbuf, i, len - i, "UTF8");
 466             }
 467             try {
 468                 if ((putValue(name, value) != null) && (!lineContinued)) {
 469                     PlatformLogger.getLogger("java.util.jar").warning(
 470                                      "Duplicate name in Manifest: " + name
 471                                      + ".\n"
 472                                      + "Ensure that the manifest does not "
 473                                      + "have duplicate entries, and\n"
 474                                      + "that blank lines separate "
 475                                      + "individual sections in both your\n"
 476                                      + "manifest and in the META-INF/MANIFEST.MF "
 477                                      + "entry in the jar file.");
 478                 }
 479             } catch (IllegalArgumentException e) {
 480                 throw new IOException("invalid header field name: " + name + " (" + getErrorPosition(filename, lineNumber) + ")");
 481             }
 482         }
 483         return lineNumber;
 484     }
 485 
 486     static String getErrorPosition(String filename, final int lineNumber) {
 487         if (filename == null || !jarPathInExceptionText) {
 488             return "line " + lineNumber;
 489         }
 490 
 491         final File file = new File(filename);
 492         return AccessController.doPrivileged(new PrivilegedAction<String>() {
 493             public String run() {
 494                 return file.getAbsolutePath() + ":" + lineNumber;
 495             }
 496         });
 497     }
 498 
 499     /**
 500      * The Attributes.Name class represents an attribute name stored in
 501      * this Map. Valid attribute names are case-insensitive, are restricted
 502      * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
 503      * 70 characters in length. Attribute values can contain any characters
 504      * and will be UTF8-encoded when written to the output stream.  See the
 505      * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
 506      * for more information about valid attribute names and values.
 507      */
 508     public static class Name {
 509         private final String name;
 510         private final int hashCode;
 511 
 512         /**
 513          * Avoid allocation for common Names
 514          */
 515         private static final Map<String, Name> KNOWN_NAMES;
 516 
 517         static final Name of(String name) {
 518             Name n = KNOWN_NAMES.get(name);
 519             if (n != null) {
 520                 return n;
 521             }
 522             return new Name(name);
 523         }
 524 
 525         /**
 526          * Constructs a new attribute name using the given string name.
 527          *
 528          * @param name the attribute string name
 529          * @exception IllegalArgumentException if the attribute name was
 530          *            invalid
 531          * @exception NullPointerException if the attribute name was null
 532          */
 533         public Name(String name) {
 534             this.hashCode = hash(name);
 535             this.name = name.intern();
 536         }
 537 
 538         // Checks the string is valid
 539         private final int hash(String name) {
 540             Objects.requireNonNull(name, "name");
 541             int len = name.length();
 542             if (len > 70 || len == 0) {
 543                 throw new IllegalArgumentException(name);
 544             }
 545             // Calculate hash code case insensitively
 546             int h = 0;
 547             for (int i = 0; i < len; i++) {
 548                 char c = name.charAt(i);
 549                 if (c >= 'a' && c <= 'z') {
 550                     // hashcode must be identical for upper and lower case
 551                     h = h * 31 + (c - 0x20);
 552                 } else if ((c >= 'A' && c <= 'Z' ||
 553                         c >= '0' && c <= '9' ||
 554                         c == '_' || c == '-')) {
 555                     h = h * 31 + c;
 556                 } else {
 557                     throw new IllegalArgumentException(name);
 558                 }
 559             }
 560             return h;
 561         }
 562 
 563         /**
 564          * Compares this attribute name to another for equality.
 565          * @param o the object to compare
 566          * @return true if this attribute name is equal to the
 567          *         specified attribute object
 568          */
 569         public boolean equals(Object o) {
 570             if (this == o) {
 571                 return true;
 572             }
 573             if (o instanceof Name) {
 574                 Name other = (Name)o;
 575                 return other.name.equalsIgnoreCase(name);
 576             } else {
 577                 return false;
 578             }
 579         }
 580 
 581         /**
 582          * Computes the hash value for this attribute name.
 583          */
 584         public int hashCode() {
 585             return hashCode;
 586         }
 587 
 588         /**
 589          * Returns the attribute name as a String.
 590          */
 591         public String toString() {
 592             return name;
 593         }
 594 
 595         /**
 596          * {@code Name} object for {@code Manifest-Version}
 597          * manifest attribute. This attribute indicates the version number
 598          * of the manifest standard to which a JAR file's manifest conforms.
 599          * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest">
 600          *      Manifest and Signature Specification</a>
 601          */
 602         public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
 603 
 604         /**
 605          * {@code Name} object for {@code Signature-Version}
 606          * manifest attribute used when signing JAR files.
 607          * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest">
 608          *      Manifest and Signature Specification</a>
 609          */
 610         public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
 611 
 612         /**
 613          * {@code Name} object for {@code Content-Type}
 614          * manifest attribute.
 615          */
 616         public static final Name CONTENT_TYPE = new Name("Content-Type");
 617 
 618         /**
 619          * {@code Name} object for {@code Class-Path}
 620          * manifest attribute.
 621          * @see <a href="{@docRoot}/../specs/jar/jar.html#classpath">
 622          *      JAR file specification</a>
 623          */
 624         public static final Name CLASS_PATH = new Name("Class-Path");
 625 
 626         /**
 627          * {@code Name} object for {@code Main-Class} manifest
 628          * attribute used for launching applications packaged in JAR files.
 629          * The {@code Main-Class} attribute is used in conjunction
 630          * with the {@code -jar} command-line option of the
 631          * {@code java} application launcher.
 632          */
 633         public static final Name MAIN_CLASS = new Name("Main-Class");
 634 
 635         /**
 636          * {@code Name} object for {@code Sealed} manifest attribute
 637          * used for sealing.
 638          * @see <a href="{@docRoot}/../specs/jar/jar.html#package-sealing">
 639          *      Package Sealing</a>
 640          */
 641         public static final Name SEALED = new Name("Sealed");
 642 
 643         /**
 644          * {@code Name} object for {@code Extension-List} manifest attribute
 645          * used for the extension mechanism that is no longer supported.
 646          */
 647         public static final Name EXTENSION_LIST = new Name("Extension-List");
 648 
 649         /**
 650          * {@code Name} object for {@code Extension-Name} manifest attribute.
 651          * used for the extension mechanism that is no longer supported.
 652          */
 653         public static final Name EXTENSION_NAME = new Name("Extension-Name");
 654 
 655         /**
 656          * {@code Name} object for {@code Extension-Installation} manifest attribute.
 657          *
 658          * @deprecated Extension mechanism is no longer supported.
 659          */
 660         @Deprecated
 661         public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
 662 
 663         /**
 664          * {@code Name} object for {@code Implementation-Title}
 665          * manifest attribute used for package versioning.
 666          */
 667         public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
 668 
 669         /**
 670          * {@code Name} object for {@code Implementation-Version}
 671          * manifest attribute used for package versioning.
 672          */
 673         public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
 674 
 675         /**
 676          * {@code Name} object for {@code Implementation-Vendor}
 677          * manifest attribute used for package versioning.
 678          */
 679         public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
 680 
 681         /**
 682          * {@code Name} object for {@code Implementation-Vendor-Id}
 683          * manifest attribute.
 684          *
 685          * @deprecated Extension mechanism is no longer supported.
 686          */
 687         @Deprecated
 688         public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
 689 
 690         /**
 691          * {@code Name} object for {@code Implementation-URL}
 692          * manifest attribute.
 693          *
 694          * @deprecated Extension mechanism is no longer supported.
 695          */
 696         @Deprecated
 697         public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
 698 
 699         /**
 700          * {@code Name} object for {@code Specification-Title}
 701          * manifest attribute used for package versioning.
 702          */
 703         public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
 704 
 705         /**
 706          * {@code Name} object for {@code Specification-Version}
 707          * manifest attribute used for package versioning.
 708          */
 709         public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
 710 
 711         /**
 712          * {@code Name} object for {@code Specification-Vendor}
 713          * manifest attribute used for package versioning.
 714          */
 715         public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
 716 
 717         /**
 718          * {@code Name} object for {@code Multi-Release}
 719          * manifest attribute that indicates this is a multi-release JAR file.
 720          *
 721          * @since   9
 722          */
 723         public static final Name MULTI_RELEASE = new Name("Multi-Release");
 724 
 725         private static void addName(Map<String, Name> names, Name name) {
 726             names.put(name.name, name);
 727         }
 728 
 729         static {
 730             var names = new HashMap<String, Name>(64);
 731             addName(names, MANIFEST_VERSION);
 732             addName(names, SIGNATURE_VERSION);
 733             addName(names, CONTENT_TYPE);
 734             addName(names, CLASS_PATH);
 735             addName(names, MAIN_CLASS);
 736             addName(names, SEALED);
 737             addName(names, EXTENSION_LIST);
 738             addName(names, EXTENSION_NAME);
 739             addName(names, IMPLEMENTATION_TITLE);
 740             addName(names, IMPLEMENTATION_VERSION);
 741             addName(names, IMPLEMENTATION_VENDOR);
 742             addName(names, SPECIFICATION_TITLE);
 743             addName(names, SPECIFICATION_VERSION);
 744             addName(names, SPECIFICATION_VENDOR);
 745             addName(names, MULTI_RELEASE);
 746 
 747             // Common attributes used in MANIFEST.MF et.al; adding these has a
 748             // small footprint cost, but is likely to be quickly paid for by
 749             // reducing allocation when reading and parsing typical manifests
 750             addName(names, new Name("Add-Exports"));
 751             addName(names, new Name("Add-Opens"));
 752             addName(names, new Name("Ant-Version"));
 753             addName(names, new Name("Archiver-Version"));
 754             addName(names, new Name("Build-Jdk"));
 755             addName(names, new Name("Built-By"));
 756             addName(names, new Name("Bnd-LastModified"));
 757             addName(names, new Name("Bundle-Description"));
 758             addName(names, new Name("Bundle-DocURL"));
 759             addName(names, new Name("Bundle-License"));
 760             addName(names, new Name("Bundle-ManifestVersion"));
 761             addName(names, new Name("Bundle-Name"));
 762             addName(names, new Name("Bundle-Vendor"));
 763             addName(names, new Name("Bundle-Version"));
 764             addName(names, new Name("Bundle-SymbolicName"));
 765             addName(names, new Name("Created-By"));
 766             addName(names, new Name("Export-Package"));
 767             addName(names, new Name("Import-Package"));
 768             addName(names, new Name("Name"));
 769             addName(names, new Name("SHA1-Digest"));
 770             addName(names, new Name("X-Compile-Source-JDK"));
 771             addName(names, new Name("X-Compile-Target-JDK"));
 772             KNOWN_NAMES = names;
 773         }
 774     }
 775 }