src/java.base/share/classes/java/util/jar/Attributes.java
changeset 49850 e286fa159ff1
parent 49111 1b33025ae610
child 50413 1234ff7199c7
--- a/src/java.base/share/classes/java/util/jar/Attributes.java	Mon Apr 23 12:16:09 2018 +0200
+++ b/src/java.base/share/classes/java/util/jar/Attributes.java	Mon Apr 23 13:32:00 2018 +0200
@@ -28,10 +28,10 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.util.Collection;
-import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import sun.util.logging.PlatformLogger;
@@ -116,7 +116,7 @@
      * @throws IllegalArgumentException if the attribute name is invalid
      */
     public String getValue(String name) {
-        return (String)get(new Attributes.Name(name));
+        return (String)get(Name.of(name));
     }
 
     /**
@@ -168,7 +168,7 @@
      * @exception IllegalArgumentException if the attribute name is invalid
      */
     public String putValue(String name, String value) {
-        return (String)put(new Name(name), value);
+        return (String)put(Name.of(name), value);
     }
 
     /**
@@ -371,7 +371,7 @@
      */
     @SuppressWarnings("deprecation")
     void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
-        String name = null, value = null;
+        String name = null, value;
         byte[] lastline = null;
 
         int len;
@@ -447,8 +447,21 @@
      * for more information about valid attribute names and values.
      */
     public static class Name {
-        private String name;
-        private int hashCode = -1;
+        private final String name;
+        private final int hashCode;
+
+        /**
+         * Avoid allocation for common Names
+         */
+        private static final Map<String, Name> KNOWN_NAMES;
+
+        static final Name of(String name) {
+            Name n = KNOWN_NAMES.get(name);
+            if (n != null) {
+                return n;
+            }
+            return new Name(name);
+        }
 
         /**
          * Constructs a new attribute name using the given string name.
@@ -459,38 +472,33 @@
          * @exception NullPointerException if the attribute name was null
          */
         public Name(String name) {
-            if (name == null) {
-                throw new NullPointerException("name");
-            }
-            if (!isValid(name)) {
-                throw new IllegalArgumentException(name);
-            }
+            this.hashCode = hash(name);
             this.name = name.intern();
         }
 
-        private static boolean isValid(String name) {
+        // Checks the string is valid
+        private final int hash(String name) {
+            Objects.requireNonNull(name, "name");
             int len = name.length();
             if (len > 70 || len == 0) {
-                return false;
+                throw new IllegalArgumentException(name);
             }
+            // Calculate hash code case insensitively
+            int h = 0;
             for (int i = 0; i < len; i++) {
-                if (!isValid(name.charAt(i))) {
-                    return false;
+                char c = name.charAt(i);
+                if (c >= 'a' && c <= 'z') {
+                    // hashcode must be identical for upper and lower case
+                    h = h * 31 + (c - 0x20);
+                } else if ((c >= 'A' && c <= 'Z' ||
+                        c >= '0' && c <= '9' ||
+                        c == '_' || c == '-')) {
+                    h = h * 31 + c;
+                } else {
+                    throw new IllegalArgumentException(name);
                 }
             }
-            return true;
-        }
-
-        private static boolean isValid(char c) {
-            return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
-        }
-
-        private static boolean isAlpha(char c) {
-            return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
-        }
-
-        private static boolean isDigit(char c) {
-            return c >= '0' && c <= '9';
+            return h;
         }
 
         /**
@@ -500,9 +508,12 @@
          *         specified attribute object
          */
         public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
             if (o instanceof Name) {
-                Comparator<String> c = String.CASE_INSENSITIVE_ORDER;
-                return c.compare(name, ((Name)o).name) == 0;
+                Name other = (Name)o;
+                return other.name.equalsIgnoreCase(name);
             } else {
                 return false;
             }
@@ -512,9 +523,6 @@
          * Computes the hash value for this attribute name.
          */
         public int hashCode() {
-            if (hashCode == -1) {
-                hashCode = name.toLowerCase(Locale.ROOT).hashCode();
-            }
             return hashCode;
         }
 
@@ -573,7 +581,7 @@
          */
         public static final Name SEALED = new Name("Sealed");
 
-       /**
+        /**
          * {@code Name} object for {@code Extension-List} manifest attribute
          * used for the extension mechanism that is no longer supported.
          */
@@ -620,7 +628,7 @@
         @Deprecated
         public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
 
-       /**
+        /**
          * {@code Name} object for {@code Implementation-URL}
          * manifest attribute.
          *
@@ -654,5 +662,55 @@
          * @since   9
          */
         public static final Name MULTI_RELEASE = new Name("Multi-Release");
+
+        private static void addName(Map<String, Name> names, Name name) {
+            names.put(name.name, name);
+        }
+
+        static {
+            var names = new HashMap<String, Name>(64);
+            addName(names, MANIFEST_VERSION);
+            addName(names, SIGNATURE_VERSION);
+            addName(names, CONTENT_TYPE);
+            addName(names, CLASS_PATH);
+            addName(names, MAIN_CLASS);
+            addName(names, SEALED);
+            addName(names, EXTENSION_LIST);
+            addName(names, EXTENSION_NAME);
+            addName(names, IMPLEMENTATION_TITLE);
+            addName(names, IMPLEMENTATION_VERSION);
+            addName(names, IMPLEMENTATION_VENDOR);
+            addName(names, SPECIFICATION_TITLE);
+            addName(names, SPECIFICATION_VERSION);
+            addName(names, SPECIFICATION_VENDOR);
+            addName(names, MULTI_RELEASE);
+
+            // Common attributes used in MANIFEST.MF et.al; adding these has a
+            // small footprint cost, but is likely to be quickly paid for by
+            // reducing allocation when reading and parsing typical manifests
+            addName(names, new Name("Add-Exports"));
+            addName(names, new Name("Add-Opens"));
+            addName(names, new Name("Ant-Version"));
+            addName(names, new Name("Archiver-Version"));
+            addName(names, new Name("Build-Jdk"));
+            addName(names, new Name("Built-By"));
+            addName(names, new Name("Bnd-LastModified"));
+            addName(names, new Name("Bundle-Description"));
+            addName(names, new Name("Bundle-DocURL"));
+            addName(names, new Name("Bundle-License"));
+            addName(names, new Name("Bundle-ManifestVersion"));
+            addName(names, new Name("Bundle-Name"));
+            addName(names, new Name("Bundle-Vendor"));
+            addName(names, new Name("Bundle-Version"));
+            addName(names, new Name("Bundle-SymbolicName"));
+            addName(names, new Name("Created-By"));
+            addName(names, new Name("Export-Package"));
+            addName(names, new Name("Import-Package"));
+            addName(names, new Name("Name"));
+            addName(names, new Name("SHA1-Digest"));
+            addName(names, new Name("X-Compile-Source-JDK"));
+            addName(names, new Name("X-Compile-Target-JDK"));
+            KNOWN_NAMES = names;
+        }
     }
 }