8029891: Deadlock detected in java/lang/ClassLoader/deadlock/GetResource.java

Properties now stores values in an internal ConcurrentHashMap

Reviewed-by: mchung, dholmes, plevart
This commit is contained in:
Brent Christian 2016-05-19 16:25:35 -07:00
parent 6f2785f741
commit 0260528ef9
7 changed files with 702 additions and 48 deletions

View File

@ -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<K,V>
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<K,V>
* @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<K,V>
*/
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) {
@ -1218,12 +1240,30 @@ public class Hashtable<K,V>
}
}
/**
* 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();

View File

@ -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.
*
* <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
@ -127,6 +141,15 @@ class Properties extends Hashtable<Object,Object> {
*/
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.
*/
@ -140,6 +163,9 @@ class Properties extends Hashtable<Object,Object> {
* @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<Object,Object> {
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 @@ class Properties extends Hashtable<Object,Object> {
* @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<Object,Object> {
* @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 @@ class Properties extends Hashtable<Object,Object> {
*/
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 @@ class Properties extends Hashtable<Object,Object> {
*/
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 @@ class Properties extends Hashtable<Object,Object> {
}
/**
* 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 @@ class Properties extends Hashtable<Object,Object> {
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);
}
}
}

View File

@ -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
############################################################################

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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());
}
}
}

View File

@ -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();
}
}