From 0260528ef92c4cdfd14198639b78e8e6d0c81613 Mon Sep 17 00:00:00 2001 From: Brent Christian Date: Thu, 19 May 2016 16:25:35 -0700 Subject: [PATCH] 8029891: Deadlock detected in java/lang/ClassLoader/deadlock/GetResource.java Properties now stores values in an internal ConcurrentHashMap Reviewed-by: mchung, dholmes, plevart --- .../share/classes/java/util/Hashtable.java | 68 +++- .../share/classes/java/util/Properties.java | 357 ++++++++++++++++-- jdk/test/ProblemList.txt | 2 - .../ClassLoader/deadlock/GetResource.java | 8 +- .../java/util/Properties/CheckOverrides.java | 106 ++++++ .../util/Properties/CheckUnsynchronized.java | 61 +++ .../Properties/PropertiesSerialization.java | 148 ++++++++ 7 files changed, 702 insertions(+), 48 deletions(-) create mode 100644 jdk/test/java/util/Properties/CheckOverrides.java create mode 100644 jdk/test/java/util/Properties/CheckUnsynchronized.java create mode 100644 jdk/test/java/util/Properties/PropertiesSerialization.java diff --git a/jdk/src/java.base/share/classes/java/util/Hashtable.java b/jdk/src/java.base/share/classes/java/util/Hashtable.java index 6ad2f3a933c..b541ca12a06 100644 --- a/jdk/src/java.base/share/classes/java/util/Hashtable.java +++ b/jdk/src/java.base/share/classes/java/util/Hashtable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2016, 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 @@ -229,6 +229,14 @@ public class Hashtable putAll(t); } + /** + * 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. * @@ -549,18 +557,23 @@ public class Hashtable * @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,15 @@ public class Hashtable */ private void writeObject(java.io.ObjectOutputStream s) throws IOException { + writeHashtable(s); + } + + /** + * Perform serialization of the Hashtable to an ObjectOutputStream. + * The Properties class overrides this method. + */ + void writeHashtable(java.io.ObjectOutputStream s) + throws IOException { Entry entryStack = null; synchronized (this) { @@ -1218,12 +1240,30 @@ public class Hashtable } } + /** + * Called by Properties to write out a simulated threshold and loadfactor. + */ + 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); + } + + /** + * Perform deserialization of the Hashtable from an ObjectInputStream. + * The Properties class overrides this method. + */ + void readHashtable(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException { // Read in the threshold and loadFactor s.defaultReadObject(); diff --git a/jdk/src/java.base/share/classes/java/util/Properties.java b/jdk/src/java.base/share/classes/java/util/Properties.java index 1eddbab3740..c2b93d250bf 100644 --- a/jdk/src/java.base/share/classes/java/util/Properties.java +++ b/jdk/src/java.base/share/classes/java/util/Properties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2016, 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 @@ -34,6 +34,13 @@ import java.io.Reader; import java.io.Writer; import java.io.OutputStreamWriter; import java.io.BufferedWriter; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StreamCorruptedException; +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; @@ -60,6 +67,13 @@ import jdk.internal.util.xml.PropertiesDefaultHandler; * object that contains a non-{@code String} key. * *

+ * The iterators returned by the {@code iterator} method of this class's + * "collection views" (that is, {@code entrySet()}, {@code keySet()}, and + * {@code values()}) may not fail-fast (unlike the Hashtable implementation). + * These iterators are guaranteed to traverse elements as they existed upon + * construction exactly once, and may (but are not guaranteed to) reflect any + * modifications subsequent to construction. + *

* The {@link #load(java.io.Reader) load(Reader)} {@code /} * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)} * methods load and store properties from and to a character based stream @@ -127,6 +141,15 @@ class Properties extends Hashtable { */ protected Properties defaults; + /** + * Properties does not store values in its inherited Hashtable, but instead + * in an internal ConcurrentHashMap. Synchronization is omitted from + * simple read operations. Writes and bulk operations remain synchronized, + * as in Hashtable. + */ + private transient ConcurrentHashMap map = + new ConcurrentHashMap<>(8); + /** * Creates an empty property list with no default values. */ @@ -140,6 +163,9 @@ class Properties extends Hashtable { * @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; } @@ -826,9 +852,9 @@ class Properties extends Hashtable { 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); + 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. @@ -967,7 +993,7 @@ class Properties extends Hashtable { * @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; } @@ -1029,7 +1055,7 @@ class Properties extends Hashtable { * @since 1.6 */ public Set stringPropertyNames() { - Hashtable h = new Hashtable<>(); + Map h = new HashMap<>(); enumerateStringProperties(h); return h.keySet(); } @@ -1044,11 +1070,11 @@ class Properties extends Hashtable { */ 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) + "..."; } @@ -1072,11 +1098,11 @@ class Properties extends Hashtable { */ 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) + "..."; } @@ -1085,33 +1111,33 @@ class Properties extends Hashtable { } /** - * Enumerates all key/value pairs in the specified hashtable. - * @param h the hashtable + * Enumerates all key/value pairs into 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 into 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); } @@ -1130,4 +1156,283 @@ class Properties extends Hashtable { 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 + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Enumeration keys() { + // CHM.keys() returns Iterator w/ remove() - instead wrap keySet() + return Collections.enumeration(map.keySet()); + } + + @Override + public Enumeration elements() { + // CHM.elements() returns Iterator w/ remove() - instead wrap values() + return Collections.enumeration(map.values()); + } + + @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 synchronized Object put(Object key, Object value) { + return map.put(key, value); + } + + @Override + public synchronized Object remove(Object key) { + return map.remove(key); + } + + @Override + public synchronized void putAll(Map t) { + map.putAll(t); + } + + @Override + public synchronized void clear() { + map.clear(); + } + + @Override + public synchronized String toString() { + return map.toString(); + } + + @Override + public Set keySet() { + return Collections.synchronizedSet(map.keySet(), this); + } + + @Override + public Collection values() { + return Collections.synchronizedCollection(map.values(), this); + } + + @Override + public Set> entrySet() { + return Collections.synchronizedSet(new EntrySet(map.entrySet()), this); + } + + /* + * Properties.entrySet() should not support add/addAll, however + * ConcurrentHashMap.entrySet() provides add/addAll. This class wraps the + * Set returned from CHM, changing add/addAll to throw UOE. + */ + private static class EntrySet implements Set> { + private Set> entrySet; + + private EntrySet(Set> entrySet) { + this.entrySet = entrySet; + } + + @Override public int size() { return entrySet.size(); } + @Override public boolean isEmpty() { return entrySet.isEmpty(); } + @Override public boolean contains(Object o) { return entrySet.contains(o); } + @Override public Object[] toArray() { return entrySet.toArray(); } + @Override public T[] toArray(T[] a) { return entrySet.toArray(a); } + @Override public void clear() { entrySet.clear(); } + @Override public boolean remove(Object o) { return entrySet.remove(o); } + + @Override + public boolean add(Map.Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + return entrySet.containsAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return entrySet.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return entrySet.retainAll(c); + } + + @Override + public Iterator> iterator() { + return entrySet.iterator(); + } + } + + @Override + public synchronized boolean equals(Object o) { + return map.equals(o); + } + + @Override + public synchronized int hashCode() { + return map.hashCode(); + } + + @Override + public Object getOrDefault(Object key, Object defaultValue) { + return map.getOrDefault(key, defaultValue); + } + + @Override + public synchronized void forEach(BiConsumer action) { + map.forEach(action); + } + + @Override + public synchronized void replaceAll(BiFunction function) { + map.replaceAll(function); + } + + @Override + public synchronized Object putIfAbsent(Object key, Object value) { + return map.putIfAbsent(key, value); + } + + @Override + public synchronized boolean remove(Object key, Object value) { + return map.remove(key, value); + } + + /** @hidden */ + @Override + public synchronized boolean replace(Object key, Object oldValue, Object newValue) { + return map.replace(key, oldValue, newValue); + } + + @Override + public synchronized Object replace(Object key, Object value) { + return map.replace(key, value); + } + + @Override + public synchronized Object computeIfAbsent(Object key, + Function mappingFunction) { + return map.computeIfAbsent(key, mappingFunction); + } + + @Override + public synchronized Object computeIfPresent(Object key, + BiFunction remappingFunction) { + return map.computeIfPresent(key, remappingFunction); + } + + @Override + public synchronized Object compute(Object key, + BiFunction remappingFunction) { + return map.compute(key, remappingFunction); + } + + @Override + public synchronized 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 synchronized 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(); + + // Validate # of elements + if (elements < 0) { + throw new StreamCorruptedException("Illegal # of Elements: " + elements); + } + + // 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); + } + } } diff --git a/jdk/test/ProblemList.txt b/jdk/test/ProblemList.txt index a3c3a99ded4..2e080ae84fe 100644 --- a/jdk/test/ProblemList.txt +++ b/jdk/test/ProblemList.txt @@ -126,8 +126,6 @@ java/beans/Introspector/8132566/OverrideUserDefPropertyInfoTest.java 8132565 gen # jdk_lang -java/lang/ClassLoader/deadlock/GetResource.java 8029891 generic-all - java/lang/StringCoding/CheckEncodings.sh 7008363 generic-all ############################################################################ diff --git a/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java b/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java index a1a1b4e1730..26fbe3dd586 100644 --- a/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java +++ b/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2016 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 @@ -28,7 +28,7 @@ import java.io.IOException; import java.net.URL; /* @test - * @bug 6977738 + * @bug 6977738 8029891 * @summary Test ClassLoader.getResource() that should not deadlock # if another thread is holding the system properties object * @@ -70,10 +70,6 @@ public class GetResource { go.await(); // wait until t1 holds the lock of the system properties URL u1 = Thread.currentThread().getContextClassLoader().getResource("unknownresource"); - URL u2 = Thread.currentThread().getContextClassLoader().getResource("sun/util/resources/CalendarData.class"); - if (u2 == null) { - throw new RuntimeException("Test failed: resource not found"); - } done.await(); } catch (InterruptedException e) { throw new RuntimeException(e); diff --git a/jdk/test/java/util/Properties/CheckOverrides.java b/jdk/test/java/util/Properties/CheckOverrides.java new file mode 100644 index 00000000000..327143a66cb --- /dev/null +++ b/jdk/test/java/util/Properties/CheckOverrides.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016, 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.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/* + * @test + * @bug 8029891 + * @summary Test that the Properties class overrides all public+protected + * methods of all ancestor classes and interfaces + * @run main CheckOverrides + */ +public class CheckOverrides { + + public static void main(String[] args) { + Set pMethodSignatures = + Stream.of(Properties.class.getDeclaredMethods()) + .filter(CheckOverrides::isMethodOfInterest) + .map(MethodSignature::new) + .collect(Collectors.toSet()); + + Map unoverriddenMethods = new HashMap<>(); + for (Class superclass = Properties.class.getSuperclass(); + superclass != Object.class; + superclass = superclass.getSuperclass()) { + Stream.of(superclass.getDeclaredMethods()) + .filter(CheckOverrides::isMethodOfInterest) + .forEach(m -> unoverriddenMethods.putIfAbsent(new MethodSignature(m), m)); + } + unoverriddenMethods.keySet().removeAll(pMethodSignatures); + + if (!unoverriddenMethods.isEmpty()) { + throw new RuntimeException( + "The following methods should be overridden by Properties class:\n" + + unoverriddenMethods.values().stream() + .map(Method::toString) + .collect(Collectors.joining("\n ", " ", "\n")) + ); + } + } + + static boolean isMethodOfInterest(Method method) { + int mods = method.getModifiers(); + return !Modifier.isStatic(mods) && + (Modifier.isPublic(mods) || Modifier.isProtected(mods)); + } + + static class MethodSignature { + final Class returnType; + final String name; + final Class[] parameterTypes; + + MethodSignature(Method method) { + this(method.getReturnType(), method.getName(), method.getParameterTypes()); + } + + private MethodSignature(Class returnType, String name, Class[] parameterTypes) { + this.returnType = returnType; + this.name = name; + this.parameterTypes = parameterTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MethodSignature that = (MethodSignature) o; + if (!returnType.equals(that.returnType)) return false; + if (!name.equals(that.name)) return false; + return Arrays.equals(parameterTypes, that.parameterTypes); + } + + @Override + public int hashCode() { + int result = returnType.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + Arrays.hashCode(parameterTypes); + return result; + } + } +} + diff --git a/jdk/test/java/util/Properties/CheckUnsynchronized.java b/jdk/test/java/util/Properties/CheckUnsynchronized.java new file mode 100644 index 00000000000..1b32cca0827 --- /dev/null +++ b/jdk/test/java/util/Properties/CheckUnsynchronized.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016, 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.util.Enumeration; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; + +/* + * @test + * @bug 8029891 + * @summary Test Properties methods that do not synchronize any more + * @run main CheckUnsynchronized + */ +public class CheckUnsynchronized { + public static void main(String[] args) { + Properties props = new Properties(); + synchronized (props) { + props.setProperty("key", "value"); + System.out.println("contains(value)? " + + CompletableFuture.supplyAsync(() -> props.contains("value")).join()); + System.out.println("containsKey(key)? " + + CompletableFuture.supplyAsync(() -> props.containsKey("key")).join()); + System.out.println("containsValue(value)? " + + CompletableFuture.supplyAsync(() -> props.containsValue("value")).join()); + Enumeration elems = + CompletableFuture.supplyAsync(() -> props.elements()).join(); + System.out.println("first value from elements(): " + elems.nextElement()); + System.out.println("value from get(): " + + CompletableFuture.supplyAsync(() -> props.getProperty("key")).join()); + System.out.println("getOrDefault(\"missing\"): " + + CompletableFuture.supplyAsync(() -> props.getOrDefault("missing", "default")).join()); + System.out.println("isEmpty()? " + + CompletableFuture.supplyAsync(() -> props.isEmpty()).join()); + Enumeration keys = + CompletableFuture.supplyAsync(() -> props.keys()).join(); + System.out.println("first key from keys(): " + keys.nextElement()); + System.out.println("size(): " + + CompletableFuture.supplyAsync(() -> props.size()).join()); + } + } +} diff --git a/jdk/test/java/util/Properties/PropertiesSerialization.java b/jdk/test/java/util/Properties/PropertiesSerialization.java new file mode 100644 index 00000000000..2fcf1dc810a --- /dev/null +++ b/jdk/test/java/util/Properties/PropertiesSerialization.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2016, 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 + * + * 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