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