8029891: Deadlock detected in java/lang/ClassLoader/deadlock/GetResource.java
Summary: Properties now stores values in an internal ConcurrentHashMap
Reviewed-by: mchung, dholmes, plevart
--- a/jdk/src/java.base/share/classes/java/util/Hashtable.java Thu May 19 16:05:33 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/util/Hashtable.java Thu May 19 16:25:35 2016 -0700
@@ -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
@@ -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,15 @@
*/
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<Object, Object> entryStack = null;
synchronized (this) {
@@ -1219,11 +1241,29 @@
}
/**
+ * 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();
--- a/jdk/src/java.base/share/classes/java/util/Properties.java Thu May 19 16:05:33 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/util/Properties.java Thu May 19 16:25:35 2016 -0700
@@ -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.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 @@
* object that contains a non-{@code String} key.
*
* <p>
+ * 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.
+ * <p>
* 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
@@ -128,6 +142,15 @@
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<Object, Object> map =
+ new ConcurrentHashMap<>(8);
+
+ /**
* Creates an empty property list with no default values.
*/
public Properties() {
@@ -140,6 +163,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;
}
@@ -826,9 +852,9 @@
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<Object, Object> 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 @@
* @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 @@
* @since 1.6
*/
public Set<String> stringPropertyNames() {
- Hashtable<String, String> h = new Hashtable<>();
+ Map<String, String> h = new HashMap<>();
enumerateStringProperties(h);
return h.keySet();
}
@@ -1044,11 +1070,11 @@
*/
public void list(PrintStream out) {
out.println("-- listing properties --");
- Hashtable<String,Object> h = new Hashtable<>();
+ Map<String, Object> h = new HashMap<>();
enumerate(h);
- for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
- String key = e.nextElement();
- String val = (String)h.get(key);
+ for (Map.Entry<String, Object> 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 @@
*/
public void list(PrintWriter out) {
out.println("-- listing properties --");
- Hashtable<String,Object> h = new Hashtable<>();
+ Map<String, Object> h = new HashMap<>();
enumerate(h);
- for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
- String key = e.nextElement();
- String val = (String)h.get(key);
+ for (Map.Entry<String, Object> 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 @@
}
/**
- * 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<String,Object> h) {
+ private void enumerate(Map<String, Object> 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<Object, Object> 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<String, String> h) {
+ private void enumerateStringProperties(Map<String, String> h) {
if (defaults != null) {
defaults.enumerateStringProperties(h);
}
- for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
- Object k = e.nextElement();
- Object v = get(k);
+ for (Map.Entry<Object, Object> 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 @@
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<Object> keys() {
+ // CHM.keys() returns Iterator w/ remove() - instead wrap keySet()
+ return Collections.enumeration(map.keySet());
+ }
+
+ @Override
+ public Enumeration<Object> 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<Object> keySet() {
+ return Collections.synchronizedSet(map.keySet(), this);
+ }
+
+ @Override
+ public Collection<Object> values() {
+ return Collections.synchronizedCollection(map.values(), this);
+ }
+
+ @Override
+ public Set<Map.Entry<Object, Object>> 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<Map.Entry<Object, Object>> {
+ private Set<Map.Entry<Object,Object>> entrySet;
+
+ private EntrySet(Set<Map.Entry<Object, Object>> 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> 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<Object, Object> e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends Map.Entry<Object, Object>> 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<Map.Entry<Object, Object>> 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<? super Object, ? super Object> action) {
+ map.forEach(action);
+ }
+
+ @Override
+ public synchronized void replaceAll(BiFunction<? super Object, ? super Object, ?> 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<? super Object, ?> mappingFunction) {
+ return map.computeIfAbsent(key, mappingFunction);
+ }
+
+ @Override
+ public synchronized Object computeIfPresent(Object key,
+ BiFunction<? super Object, ? super Object, ?> remappingFunction) {
+ return map.computeIfPresent(key, remappingFunction);
+ }
+
+ @Override
+ public synchronized Object compute(Object key,
+ BiFunction<? super Object, ? super Object, ?> remappingFunction) {
+ return map.compute(key, remappingFunction);
+ }
+
+ @Override
+ public synchronized Object merge(Object key, Object value,
+ BiFunction<? super Object, ? super Object, ?> 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<Object> entryStack = new ArrayList<>(map.size() * 2); // an estimate
+
+ for (Map.Entry<Object, Object> 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);
+ }
+ }
}
--- a/jdk/test/ProblemList.txt Thu May 19 16:05:33 2016 -0700
+++ b/jdk/test/ProblemList.txt Thu May 19 16:25:35 2016 -0700
@@ -126,8 +126,6 @@
# jdk_lang
-java/lang/ClassLoader/deadlock/GetResource.java 8029891 generic-all
-
java/lang/StringCoding/CheckEncodings.sh 7008363 generic-all
############################################################################
--- a/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java Thu May 19 16:05:33 2016 -0700
+++ b/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java Thu May 19 16:25:35 2016 -0700
@@ -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.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 @@
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);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Properties/CheckOverrides.java Thu May 19 16:25:35 2016 -0700
@@ -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<MethodSignature> pMethodSignatures =
+ Stream.of(Properties.class.getDeclaredMethods())
+ .filter(CheckOverrides::isMethodOfInterest)
+ .map(MethodSignature::new)
+ .collect(Collectors.toSet());
+
+ Map<MethodSignature, Method> 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;
+ }
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Properties/CheckUnsynchronized.java Thu May 19 16:25:35 2016 -0700
@@ -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<Object> 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<Object> keys =
+ CompletableFuture.supplyAsync(() -> props.keys()).join();
+ System.out.println("first key from keys(): " + keys.nextElement());
+ System.out.println("size(): " +
+ CompletableFuture.supplyAsync(() -> props.size()).join());
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Properties/PropertiesSerialization.java Thu May 19 16:25:35 2016 -0700
@@ -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<base64.length();i++) {
+ if (i%64 == 0) sb.append("\"");
+ sb.append(base64.charAt(i));
+ if (i%64 == 63 || i == last) {
+ sb.append("\"");
+ if (i == last) sb.append(";\n");
+ else sb.append("\n").append(INDENT).append(" + ");
+ }
+ }
+ return sb.toString();
+ }
+}