--- old/src/java.base/share/classes/java/util/Hashtable.java 2015-05-01 10:10:39.000000000 -0700 +++ new/src/java.base/share/classes/java/util/Hashtable.java 2015-05-01 10:10:38.000000000 -0700 @@ -230,6 +230,14 @@ } /** + * A constructor chained from {@link Properties} keeps Hashtable fields + * uninitialized since they are not used. + * + * @param dummy a dummy parameter + */ + Hashtable(Void dummy) {} + + /** * Returns the number of keys in this hashtable. * * @return the number of keys in this hashtable. @@ -549,18 +557,23 @@ * @return a clone of the hashtable */ public synchronized Object clone() { + Hashtable t = cloneHashtable(); + t.table = new Entry[table.length]; + for (int i = table.length ; i-- > 0 ; ) { + t.table[i] = (table[i] != null) + ? (Entry) table[i].clone() : null; + } + t.keySet = null; + t.entrySet = null; + t.values = null; + t.modCount = 0; + return t; + } + + /** Calls super.clone() */ + final Hashtable cloneHashtable() { try { - Hashtable t = (Hashtable)super.clone(); - t.table = new Entry[table.length]; - for (int i = table.length ; i-- > 0 ; ) { - t.table[i] = (table[i] != null) - ? (Entry) table[i].clone() : null; - } - t.keySet = null; - t.entrySet = null; - t.values = null; - t.modCount = 0; - return t; + return (Hashtable)super.clone(); } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); @@ -1189,6 +1202,11 @@ */ private void writeObject(java.io.ObjectOutputStream s) throws IOException { + writeHashtable(s); + } + + void writeHashtable(java.io.ObjectOutputStream s) + throws IOException { Entry entryStack = null; synchronized (this) { @@ -1219,11 +1237,26 @@ } /** + * Write out the simulated threshold, loadfactor + * Called by Properties + */ + final void defaultWriteHashtable(java.io.ObjectOutputStream s, int length, + float loadFactor) throws IOException { + this.threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1); + this.loadFactor = loadFactor; + s.defaultWriteObject(); + } + + /** * Reconstitute the Hashtable from a stream (i.e., deserialize it). */ private void readObject(java.io.ObjectInputStream s) - throws IOException, ClassNotFoundException - { + throws IOException, ClassNotFoundException { + readHashtable(s); + } + + void readHashtable(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException { // Read in the threshold and loadFactor s.defaultReadObject(); --- old/src/java.base/share/classes/java/util/Properties.java 2015-05-01 10:10:42.000000000 -0700 +++ new/src/java.base/share/classes/java/util/Properties.java 2015-05-01 10:10:42.000000000 -0700 @@ -34,6 +34,12 @@ import java.io.Writer; import java.io.OutputStreamWriter; import java.io.BufferedWriter; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import jdk.internal.util.xml.PropertiesDefaultHandler; @@ -144,6 +150,9 @@ * @param defaults the defaults. */ public Properties(Properties defaults) { + // use package-private constructor to + // initialize unused fields with dummy values + super((Void) null); this.defaults = defaults; } @@ -160,7 +169,7 @@ * @see #getProperty * @since 1.2 */ - public synchronized Object setProperty(String key, String value) { + public Object setProperty(String key, String value) { return put(key, value); } @@ -312,7 +321,7 @@ * @throws NullPointerException if {@code reader} is null. * @since 1.6 */ - public synchronized void load(Reader reader) throws IOException { + public void load(Reader reader) throws IOException { Objects.requireNonNull(reader, "reader parameter is null"); load0(new LineReader(reader)); } @@ -338,7 +347,7 @@ * @throws NullPointerException if {@code inStream} is null. * @since 1.2 */ - public synchronized void load(InputStream inStream) throws IOException { + public void load(InputStream inStream) throws IOException { Objects.requireNonNull(inStream, "inStream parameter is null"); load0(new LineReader(inStream)); } @@ -829,18 +838,16 @@ } bw.write("#" + new Date().toString()); bw.newLine(); - synchronized (this) { - for (Enumeration e = keys(); e.hasMoreElements();) { - String key = (String)e.nextElement(); - String val = (String)get(key); - key = saveConvert(key, true, escUnicode); - /* No need to escape embedded and trailing spaces for value, hence - * pass false to flag. - */ - val = saveConvert(val, false, escUnicode); - bw.write(key + "=" + val); - bw.newLine(); - } + for (Map.Entry e : entrySet()) { + String key = (String)e.getKey(); + String val = (String)e.getValue(); + key = saveConvert(key, true, escUnicode); + /* No need to escape embedded and trailing spaces for value, hence + * pass false to flag. + */ + val = saveConvert(val, false, escUnicode); + bw.write(key + "=" + val); + bw.newLine(); } bw.flush(); } @@ -876,7 +883,7 @@ * Encoding in Entities * @since 1.5 */ - public synchronized void loadFromXML(InputStream in) + public void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException { Objects.requireNonNull(in); @@ -971,7 +978,7 @@ * @see #defaults */ public String getProperty(String key) { - Object oval = super.get(key); + Object oval = map.get(key); String sval = (oval instanceof String) ? (String)oval : null; return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; } @@ -1009,7 +1016,7 @@ * @see #stringPropertyNames */ public Enumeration propertyNames() { - Hashtable h = new Hashtable<>(); + Hashtable h = new Hashtable<>(); enumerate(h); return h.keys(); } @@ -1033,7 +1040,7 @@ * @since 1.6 */ public Set stringPropertyNames() { - Hashtable h = new Hashtable<>(); + Map h = new HashMap<>(); enumerateStringProperties(h); return h.keySet(); } @@ -1048,11 +1055,11 @@ */ public void list(PrintStream out) { out.println("-- listing properties --"); - Hashtable h = new Hashtable<>(); + Map h = new HashMap<>(); enumerate(h); - for (Enumeration e = h.keys() ; e.hasMoreElements() ;) { - String key = e.nextElement(); - String val = (String)h.get(key); + for (Map.Entry e : h.entrySet()) { + String key = e.getKey(); + String val = (String)e.getValue(); if (val.length() > 40) { val = val.substring(0, 37) + "..."; } @@ -1076,11 +1083,11 @@ */ public void list(PrintWriter out) { out.println("-- listing properties --"); - Hashtable h = new Hashtable<>(); + Map h = new HashMap<>(); enumerate(h); - for (Enumeration e = h.keys() ; e.hasMoreElements() ;) { - String key = e.nextElement(); - String val = (String)h.get(key); + for (Map.Entry e : h.entrySet()) { + String key = e.getKey(); + String val = (String)e.getValue(); if (val.length() > 40) { val = val.substring(0, 37) + "..."; } @@ -1089,33 +1096,33 @@ } /** - * Enumerates all key/value pairs in the specified hashtable. - * @param h the hashtable + * Enumerates all key/value pairs in the specified Map. + * @param h the Map * @throws ClassCastException if any of the property keys * is not of String type. */ - private synchronized void enumerate(Hashtable h) { + private void enumerate(Map h) { if (defaults != null) { defaults.enumerate(h); } - for (Enumeration e = keys() ; e.hasMoreElements() ;) { - String key = (String)e.nextElement(); - h.put(key, get(key)); + for (Map.Entry e : entrySet()) { + String key = (String)e.getKey(); + h.put(key, e.getValue()); } } /** - * Enumerates all key/value pairs in the specified hashtable + * Enumerates all key/value pairs in the specified Map * and omits the property if the key or value is not a string. - * @param h the hashtable + * @param h the Map */ - private synchronized void enumerateStringProperties(Hashtable h) { + private void enumerateStringProperties(Map h) { if (defaults != null) { defaults.enumerateStringProperties(h); } - for (Enumeration e = keys() ; e.hasMoreElements() ;) { - Object k = e.nextElement(); - Object v = get(k); + for (Map.Entry e : entrySet()) { + Object k = e.getKey(); + Object v = e.getValue(); if (k instanceof String && v instanceof String) { h.put((String) k, (String) v); } @@ -1134,4 +1141,229 @@ private static final char[] hexDigit = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; -} + + // + // Hashtable methods overridden and delegated to a ConcurrentHashMap instance + + private transient ConcurrentHashMap map = + new ConcurrentHashMap<>(8); + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Enumeration keys() { + return map.keys(); + } + + @Override + public Enumeration elements() { + return map.elements(); + } + + @Override + public boolean contains(Object value) { + return map.contains(value); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public Object get(Object key) { + return map.get(key); + } + + @Override + public Object put(Object key, Object value) { + return map.put(key, value); + } + + @Override + public Object remove(Object key) { + return map.remove(key); + } + + @Override + public void putAll(Map t) { + map.putAll(t); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public String toString() { + return map.toString(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public boolean equals(Object o) { + return map.equals(o); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public Object getOrDefault(Object key, Object defaultValue) { + return map.getOrDefault(key, defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + map.forEach(action); + } + + @Override + public void replaceAll(BiFunction function) { + map.replaceAll(function); + } + + @Override + public Object putIfAbsent(Object key, Object value) { + return map.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return map.remove(key, value); + } + + @Override + public boolean replace(Object key, Object oldValue, Object newValue) { + return map.replace(key, oldValue, newValue); + } + + @Override + public Object replace(Object key, Object value) { + return map.replace(key, value); + } + + @Override + public Object computeIfAbsent(Object key, + Function mappingFunction) { + return map.computeIfAbsent(key, mappingFunction); + } + + @Override + public Object computeIfPresent(Object key, + BiFunction remappingFunction) { + return map.computeIfPresent(key, remappingFunction); + } + + @Override + public Object compute(Object key, + BiFunction remappingFunction) { + return map.compute(key, remappingFunction); + } + + @Override + public Object merge(Object key, Object value, + BiFunction remappingFunction) { + return map.merge(key, value, remappingFunction); + } + + // + // Special Hashtable methods + + @Override + protected void rehash() { + // no-op + } + + @Override + public Object clone() { + Properties clone = (Properties) cloneHashtable(); + clone.map = new ConcurrentHashMap<>(map); + return clone; + } + + // + // Hashtable serialization overrides + // (these should emit and consume Hashtable-compatible stream) + + @Override + void writeHashtable(ObjectOutputStream s) throws IOException { + List entryStack = new ArrayList<>(map.size() * 2); // an estimate + + for (Map.Entry entry : map.entrySet()) { + entryStack.add(entry.getValue()); + entryStack.add(entry.getKey()); + } + + // Write out the simulated threshold, loadfactor + float loadFactor = 0.75f; + int count = entryStack.size() / 2; + int length = (int)(count / loadFactor) + (count / 20) + 3; + if (length > count && (length & 1) == 0) { + length--; + } + synchronized (map) { // in case of multiple concurrent serializations + defaultWriteHashtable(s, length, loadFactor); + } + + // Write out simulated length and real count of elements + s.writeInt(length); + s.writeInt(count); + + // Write out the key/value objects from the stacked entries + for (int i = entryStack.size() - 1; i >= 0; i--) { + s.writeObject(entryStack.get(i)); + } + } + + @Override + void readHashtable(ObjectInputStream s) throws IOException, + ClassNotFoundException { + // Read in the threshold and loadfactor + s.defaultReadObject(); + + // Read the original length of the array and number of elements + int origlength = s.readInt(); + int elements = s.readInt(); + + // create CHM of appropriate capacity + map = new ConcurrentHashMap<>(elements); + + // Read all the key/value objects + for (; elements > 0; elements--) { + Object key = s.readObject(); + Object value = s.readObject(); + map.put(key, value); + } + } +} --- old/test/ProblemList.txt 2015-05-01 10:10:45.000000000 -0700 +++ new/test/ProblemList.txt 2015-05-01 10:10:45.000000000 -0700 @@ -120,9 +120,6 @@ # jdk_lang -# 8029891 -java/lang/ClassLoader/deadlock/GetResource.java generic-all - ############################################################################ # jdk_instrument --- /dev/null 2015-05-01 10:10:48.000000000 -0700 +++ new/test/java/util/Properties/CheckOverrides.java 2015-05-01 10:10:47.000000000 -0700 @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/* + * @test + * @bug 8029891 + * @summary Test that the Properties class overrides all public+protected + * methods of all ancestor classes and interfaces + * @run main CheckOverrides + * @author bchristi + */ +public class CheckOverrides { + private static List pMethods; + + private static boolean isMethodOfInterest(Method method) { + int mods = method.getModifiers(); + return !Modifier.isStatic(mods) && + (Modifier.isPublic(mods) || Modifier.isProtected(mods)); + } + + public static void main(String[] args) { + // Create list of all non-static, public/protected Properties methods + Class propsClass = Properties.class; + Method[] pMethodArray = propsClass.getDeclaredMethods(); + pMethods = new ArrayList<>(pMethodArray.length); + + for (Method method : pMethodArray ) { + if (isMethodOfInterest(method)) { + pMethods.add(new SameMethodNoClass(method)); + } + } + System.out.println(pMethods.size() + " non-static, public/protected" + + " methods in " + propsClass); + + // Assemble list of all interfaces and classes to check + List classesToCheck = new ArrayList<>(); + classesToCheck.addAll(Arrays.asList(propsClass.getInterfaces())); + + Class superClass = propsClass.getSuperclass(); + while (!superClass.equals(Object.class)) { + classesToCheck.add(superClass); + classesToCheck.addAll(Arrays.asList(superClass.getInterfaces())); + superClass = superClass.getSuperclass(); + } + + boolean passed = true; + for (Class toCheck : classesToCheck) { + passed = checkClass(toCheck) && passed; + } + if (!passed) { + throw new RuntimeException("Test failed"); + } + System.out.println("Test passed"); + } + + /** + * Check Properties for methods in the given class. + * Return true if all found, false otherwise + */ + private static boolean checkClass(Class clazz) { + boolean passed = true; + // For each non-static, public/protected method, confirm + // that it's in the list. + Method[] methods = clazz.getDeclaredMethods(); + int numPubProcMethods = 0; + + for (Method method : methods) { + if (isMethodOfInterest(method)) { + numPubProcMethods++; + boolean contains = pMethods.contains(new + SameMethodNoClass(method)); + if (!contains) { + passed = false; + System.out.println("Properties does not contain: " + method); + System.out.println(" from: " + clazz); + } + } + } + System.out.println("Checked " + numPubProcMethods + " public/protected " + + "methods in " + clazz); + return passed; + } + + /* + * Wrapper to compare Methods, ignoring declaring Class + */ + private static class SameMethodNoClass { + public Method method; + public SameMethodNoClass(Method method) { this.method = method; } + + @Override + public boolean equals(Object other) { + if (other instanceof SameMethodNoClass) { + SameMethodNoClass smnc = (SameMethodNoClass)other; + + return this.method.getName().equals(smnc.method.getName()) && + this.method.getReturnType().equals(smnc.method.getReturnType()) && + Arrays.equals(this.method.getParameterTypes(), + smnc.method.getParameterTypes()) && + Arrays.equals(this.method.getExceptionTypes(), + smnc.method.getExceptionTypes()); + } + return false; + } + @Override + public int hashCode() { return method.hashCode(); } + @Override + public String toString() { return method.toString(); } + } +} --- /dev/null 2015-05-01 10:10:51.000000000 -0700 +++ new/test/java/util/Properties/PropertiesSerialization.java 2015-05-01 10:10:50.000000000 -0700 @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Base64; +import java.util.Properties; + +/** + * @test + * @bug 8029891 + * @summary tests the compatibility of Properties serial form + * @run main PropertiesSerialization read + * @author bchristi + * + * To update this test in case the serial form of Properties changes, run this + * test with the 'write' flag, and copy the resulting output back into this + * file, replacing the existing String declaration(s). + */ +public class PropertiesSerialization { + private static final Properties TEST_PROPS; + static { + TEST_PROPS = new Properties(); + TEST_PROPS.setProperty("one", "two"); + TEST_PROPS.setProperty("buckle", "shoe"); + TEST_PROPS.setProperty("three", "four"); + TEST_PROPS.setProperty("shut", "door"); + } + + /** + * Base64 encoded string for Properties object + * Java version: 1.8.0 + **/ + private static final String TEST_SER_BASE64 = + "rO0ABXNyABRqYXZhLnV0aWwuUHJvcGVydGllczkS0HpwNj6YAgABTAAIZGVmYXVs" + + "dHN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHIAE2phdmEudXRpbC5IYXNodGFi" + + "bGUTuw8lIUrkuAMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAI" + + "dwgAAAALAAAABHQAA29uZXQAA3R3b3QABHNodXR0AARkb29ydAAGYnVja2xldAAE" + + "c2hvZXQABXRocmVldAAEZm91cnhw"; + + public static void main(String[] args) throws IOException, + ClassNotFoundException { + if (args.length == 0) { + System.err.println("Run with 'read' or 'write'"); + System.err.println(" read mode: normal test mode."); + System.err.println(" Confirms that serial stream can"); + System.err.println(" be deserialized as expected."); + System.err.println(" write mode: meant for updating the test,"); + System.err.println(" should the serial form change."); + System.err.println(" Test output should be pasted"); + System.err.println(" back into the test source."); + return; + } + + Properties deserializedObject; + if ("read".equals(args[0])) { + ByteArrayInputStream bais = new + ByteArrayInputStream(Base64.getDecoder().decode(TEST_SER_BASE64)); + try (ObjectInputStream ois = new ObjectInputStream(bais)) { + deserializedObject = (Properties) ois.readObject(); + } + if (!TEST_PROPS.equals(deserializedObject)) { + throw new RuntimeException("deserializedObject not equals()"); + } + System.out.println("Test passed"); + } else if ("write".equals(args[0])) { + System.out.println("\nTo update the test, paste the following back " + + "into the test code:\n"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(TEST_PROPS); + oos.flush(); + oos.close(); + + byte[] byteArray = baos.toByteArray(); + // Check that the Properties deserializes correctly + ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ObjectInputStream ois = new ObjectInputStream(bais); + Properties deser = (Properties)ois.readObject(); + if (!TEST_PROPS.equals(deser)) { + throw new RuntimeException("write: Deserialized != original"); + } + + // Now get a Base64 string representation of the serialized bytes. + final String base64 = Base64.getEncoder().encodeToString(byteArray); + // Check that we can deserialize the Base64 string we just computed. + ByteArrayInputStream bais2 = + new ByteArrayInputStream(Base64.getDecoder().decode(base64)); + ObjectInputStream ois2 = new ObjectInputStream(bais2); + Properties deser2 = (Properties)ois2.readObject(); + if (!TEST_PROPS.equals(deser2)) { + throw new RuntimeException("write: Deserialized base64 != " + + "original"); + } + System.out.println(dumpBase64SerialStream(base64)); + } + } + + private static final String INDENT = " "; + /* Based on: + * java/util/logging/HigherResolutionTimeStamps/SerializeLogRecored.java + */ + private static String dumpBase64SerialStream(String base64) { + // Generates the Java Pseudo code that can be cut & pasted into + // this test (see Jdk8SerializedLog and Jdk9SerializedLog below) + final StringBuilder sb = new StringBuilder(); + sb.append(INDENT).append(" /**").append('\n'); + sb.append(INDENT).append(" * Base64 encoded string for Properties object\n"); + sb.append(INDENT).append(" * Java version: ").append(System.getProperty("java.version")).append('\n'); + sb.append(INDENT).append(" **/").append('\n'); + sb.append(INDENT).append(" private static final String TEST_SER_BASE64 = ").append("\n").append(INDENT).append(" "); + final int last = base64.length() - 1; + for (int i=0; i