8026344: j.u.c.a *Adder and *Accumulator extend a package private class that is Serializable
authoralanb
Thu, 24 Oct 2013 13:24:32 +0100
changeset 21356 ad2735d41496
parent 21355 c53a16ac1969
child 21357 eb15eae19cd9
8026344: j.u.c.a *Adder and *Accumulator extend a package private class that is Serializable Reviewed-by: rriggs, psandoz, chegar Contributed-by: dl@cs.oswego.edu, alan.bateman@oracle.com
jdk/src/share/classes/java/util/concurrent/atomic/DoubleAccumulator.java
jdk/src/share/classes/java/util/concurrent/atomic/DoubleAdder.java
jdk/src/share/classes/java/util/concurrent/atomic/LongAccumulator.java
jdk/src/share/classes/java/util/concurrent/atomic/LongAdder.java
jdk/test/java/util/concurrent/atomic/Serial.java
--- a/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAccumulator.java	Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAccumulator.java	Thu Oct 24 13:24:32 2013 +0100
@@ -224,18 +224,71 @@
         return (float)get();
     }
 
-    private void writeObject(java.io.ObjectOutputStream s)
-        throws java.io.IOException {
-        s.defaultWriteObject();
-        s.writeDouble(get());
+    /**
+     * Serialization proxy, used to avoid reference to the non-public
+     * Striped64 superclass in serialized forms.
+     * @serial include
+     */
+    private static class SerializationProxy implements Serializable {
+        private static final long serialVersionUID = 7249069246863182397L;
+
+        /**
+         * The current value returned by get().
+         * @serial
+         */
+        private final double value;
+        /**
+         * The function used for updates.
+         * @serial
+         */
+        private final DoubleBinaryOperator function;
+        /**
+         * The identity value
+         * @serial
+         */
+        private final long identity;
+
+        SerializationProxy(DoubleAccumulator a) {
+            function = a.function;
+            identity = a.identity;
+            value = a.get();
+        }
+
+        /**
+         * Returns a {@code DoubleAccumulator} object with initial state
+         * held by this proxy.
+         *
+         * @return a {@code DoubleAccumulator} object with initial state
+         * held by this proxy.
+         */
+        private Object readResolve() {
+            double d = Double.longBitsToDouble(identity);
+            DoubleAccumulator a = new DoubleAccumulator(function, d);
+            a.base = Double.doubleToRawLongBits(value);
+            return a;
+        }
     }
 
+    /**
+     * Returns a
+     * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.DoubleAccumulator.SerializationProxy">
+     * SerializationProxy</a>
+     * representing the state of this instance.
+     *
+     * @return a {@link SerializationProxy}
+     * representing the state of this instance
+     */
+    private Object writeReplace() {
+        return new SerializationProxy(this);
+    }
+
+    /**
+     * @param s the stream
+     * @throws java.io.InvalidObjectException always
+     */
     private void readObject(java.io.ObjectInputStream s)
-        throws java.io.IOException, ClassNotFoundException {
-        s.defaultReadObject();
-        cellsBusy = 0;
-        cells = null;
-        base = Double.doubleToRawLongBits(s.readDouble());
+        throws java.io.InvalidObjectException {
+        throw new java.io.InvalidObjectException("Proxy required");
     }
 
 }
--- a/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAdder.java	Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAdder.java	Thu Oct 24 13:24:32 2013 +0100
@@ -210,18 +210,58 @@
         return (float)sum();
     }
 
-    private void writeObject(java.io.ObjectOutputStream s)
-        throws java.io.IOException {
-        s.defaultWriteObject();
-        s.writeDouble(sum());
+    /**
+     * Serialization proxy, used to avoid reference to the non-public
+     * Striped64 superclass in serialized forms.
+     * @serial include
+     */
+    private static class SerializationProxy implements Serializable {
+        private static final long serialVersionUID = 7249069246863182397L;
+
+        /**
+         * The current value returned by sum().
+         * @serial
+         */
+        private final double value;
+
+        SerializationProxy(DoubleAdder a) {
+            value = a.sum();
+        }
+
+        /**
+         * Returns a {@code DoubleAdder} object with initial state
+         * held by this proxy.
+         *
+         * @return a {@code DoubleAdder} object with initial state
+         * held by this proxy.
+         */
+        private Object readResolve() {
+            DoubleAdder a = new DoubleAdder();
+            a.base = Double.doubleToRawLongBits(value);
+            return a;
+        }
     }
 
+    /**
+     * Returns a
+     * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.DoubleAdder.SerializationProxy">
+     * SerializationProxy</a>
+     * representing the state of this instance.
+     *
+     * @return a {@link SerializationProxy}
+     * representing the state of this instance
+     */
+    private Object writeReplace() {
+        return new SerializationProxy(this);
+    }
+
+    /**
+     * @param s the stream
+     * @throws java.io.InvalidObjectException always
+     */
     private void readObject(java.io.ObjectInputStream s)
-        throws java.io.IOException, ClassNotFoundException {
-        s.defaultReadObject();
-        cellsBusy = 0;
-        cells = null;
-        base = Double.doubleToRawLongBits(s.readDouble());
+        throws java.io.InvalidObjectException {
+        throw new java.io.InvalidObjectException("Proxy required");
     }
 
 }
--- a/jdk/src/share/classes/java/util/concurrent/atomic/LongAccumulator.java	Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/LongAccumulator.java	Thu Oct 24 13:24:32 2013 +0100
@@ -221,18 +221,70 @@
         return (double)get();
     }
 
-    private void writeObject(java.io.ObjectOutputStream s)
-        throws java.io.IOException {
-        s.defaultWriteObject();
-        s.writeLong(get());
+    /**
+     * Serialization proxy, used to avoid reference to the non-public
+     * Striped64 superclass in serialized forms.
+     * @serial include
+     */
+    private static class SerializationProxy implements Serializable {
+        private static final long serialVersionUID = 7249069246863182397L;
+
+        /**
+         * The current value returned by get().
+         * @serial
+         */
+        private final long value;
+        /**
+         * The function used for updates.
+         * @serial
+         */
+        private final LongBinaryOperator function;
+        /**
+         * The identity value
+         * @serial
+         */
+        private final long identity;
+
+        SerializationProxy(LongAccumulator a) {
+            function = a.function;
+            identity = a.identity;
+            value = a.get();
+        }
+
+        /**
+         * Returns a {@code LongAccumulator} object with initial state
+         * held by this proxy.
+         *
+         * @return a {@code LongAccumulator} object with initial state
+         * held by this proxy.
+         */
+        private Object readResolve() {
+            LongAccumulator a = new LongAccumulator(function, identity);
+            a.base = value;
+            return a;
+        }
     }
 
+    /**
+     * Returns a
+     * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.LongAccumulator.SerializationProxy">
+     * SerializationProxy</a>
+     * representing the state of this instance.
+     *
+     * @return a {@link SerializationProxy}
+     * representing the state of this instance
+     */
+    private Object writeReplace() {
+        return new SerializationProxy(this);
+    }
+
+    /**
+     * @param s the stream
+     * @throws java.io.InvalidObjectException always
+     */
     private void readObject(java.io.ObjectInputStream s)
-        throws java.io.IOException, ClassNotFoundException {
-        s.defaultReadObject();
-        cellsBusy = 0;
-        cells = null;
-        base = s.readLong();
+        throws java.io.InvalidObjectException {
+        throw new java.io.InvalidObjectException("Proxy required");
     }
 
 }
--- a/jdk/src/share/classes/java/util/concurrent/atomic/LongAdder.java	Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/LongAdder.java	Thu Oct 24 13:24:32 2013 +0100
@@ -211,18 +211,58 @@
         return (double)sum();
     }
 
-    private void writeObject(java.io.ObjectOutputStream s)
-        throws java.io.IOException {
-        s.defaultWriteObject();
-        s.writeLong(sum());
+    /**
+     * Serialization proxy, used to avoid reference to the non-public
+     * Striped64 superclass in serialized forms.
+     * @serial include
+     */
+    private static class SerializationProxy implements Serializable {
+        private static final long serialVersionUID = 7249069246863182397L;
+
+        /**
+         * The current value returned by sum().
+         * @serial
+         */
+        private final long value;
+
+        SerializationProxy(LongAdder a) {
+            value = a.sum();
+        }
+
+        /**
+         * Return a {@code LongAdder} object with initial state
+         * held by this proxy.
+         *
+         * @return a {@code LongAdder} object with initial state
+         * held by this proxy.
+         */
+        private Object readResolve() {
+            LongAdder a = new LongAdder();
+            a.base = value;
+            return a;
+        }
     }
 
+    /**
+     * Returns a
+     * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.LongAdder.SerializationProxy">
+     * SerializationProxy</a>
+     * representing the state of this instance.
+     *
+     * @return a {@link SerializationProxy}
+     * representing the state of this instance
+     */
+    private Object writeReplace() {
+        return new SerializationProxy(this);
+    }
+
+    /**
+     * @param s the stream
+     * @throws java.io.InvalidObjectException always
+     */
     private void readObject(java.io.ObjectInputStream s)
-        throws java.io.IOException, ClassNotFoundException {
-        s.defaultReadObject();
-        cellsBusy = 0;
-        cells = null;
-        base = s.readLong();
+        throws java.io.InvalidObjectException {
+        throw new java.io.InvalidObjectException("Proxy required");
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/concurrent/atomic/Serial.java	Thu Oct 24 13:24:32 2013 +0100
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2013, 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.
+ */
+
+/* @test
+ * @bug 8026344
+ * @summary Exercise classes in j.u.c.atomic that use serialization proxies
+ */
+
+import java.util.concurrent.atomic.DoubleAdder;
+import java.util.concurrent.atomic.DoubleAccumulator;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.concurrent.atomic.LongAccumulator;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.LongBinaryOperator;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic test to exercise the j.u.c.atomic classes that use serialization
+ * proxies.
+ */
+
+public class Serial {
+
+    public static void main(String[] args) {
+        testDoubleAdder();
+        testDoubleAccumulator();
+        testLongAdder();
+        testLongAccumulator();
+    }
+
+    static void testDoubleAdder() {
+        DoubleAdder a = new DoubleAdder();
+        a.add(20.1d);
+        DoubleAdder result = echo(a);
+        if (result.doubleValue() != a.doubleValue())
+            throw new RuntimeException("Unexpected doubleValue");
+
+        checkSerialClassName(a, "java.util.concurrent.atomic.DoubleAdder$SerializationProxy");
+    }
+
+    static void testDoubleAccumulator() {
+        DoubleBinaryOperator plus = (DoubleBinaryOperator & Serializable) (x, y) -> x + y;
+        DoubleAccumulator a = new DoubleAccumulator(plus, 13.9d);
+        a.accumulate(17.5d);
+        DoubleAccumulator result = echo(a);
+        if (result.get() != a.get())
+            throw new RuntimeException("Unexpected value");
+        a.reset();
+        result.reset();
+        if (result.get() != a.get())
+            throw new RuntimeException("Unexpected value after reset");
+
+        checkSerialClassName(a, "java.util.concurrent.atomic.DoubleAccumulator$SerializationProxy");
+    }
+
+    static void testLongAdder() {
+        LongAdder a = new LongAdder();
+        a.add(45);
+        LongAdder result = echo(a);
+        if (result.longValue() != a.longValue())
+            throw new RuntimeException("Unexpected longValue");
+
+        checkSerialClassName(a, "java.util.concurrent.atomic.LongAdder$SerializationProxy");
+    }
+
+    static void testLongAccumulator() {
+        LongBinaryOperator plus = (LongBinaryOperator & Serializable) (x, y) -> x + y;
+        LongAccumulator a = new LongAccumulator(plus, -2);
+        a.accumulate(34);
+        LongAccumulator result = echo(a);
+        if (result.get() != a.get())
+            throw new RuntimeException("Unexpected value");
+        a.reset();
+        result.reset();
+        if (result.get() != a.get())
+            throw new RuntimeException("Unexpected value after reset");
+
+        checkSerialClassName(a, "java.util.concurrent.atomic.LongAccumulator$SerializationProxy");
+    }
+
+    /**
+     * Serialize the given object, returning the reconstituted object.
+     */
+    @SuppressWarnings("unchecked")
+    static <T extends Serializable> T echo(T obj) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+            oos.writeObject(obj);
+        } catch (IOException e) {
+            throw new RuntimeException("Serialization failed: " + e);
+        }
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        try (ObjectInputStream ois = new ObjectInputStream(in)) {
+            return (T) ois.readObject();
+        } catch (IOException | ClassNotFoundException e) {
+            throw new RuntimeException("Deserialization failed: " + e);
+        }
+    }
+
+    /**
+     * Checks that the given object serializes to the expected class.
+     */
+    static void checkSerialClassName(Serializable obj, String expected) {
+        String cn = serialClassName(obj);
+        if (!cn.equals(expected))
+            throw new RuntimeException(obj.getClass() + " serialized as " + cn
+                + ", expected " + expected);
+    }
+
+    /**
+     * Returns the class name that the given object serializes as.
+     */
+    static String serialClassName(Serializable obj) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+            oos.writeObject(obj);
+        } catch (IOException e) {
+            throw new RuntimeException("Serialization failed: " + e);
+        }
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        try (DataInputStream dis = new DataInputStream(in)) {
+            dis.readShort();      // STREAM_MAGIC
+            dis.readShort();      // STREAM_VERSION
+            dis.readByte();       // TC_OBJECT
+            dis.readByte();       // TC_CLASSDESC
+            return dis.readUTF(); // className
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}