BUG-7464: Rework TrieMap serialization 14/49914/8
authorRobert Varga <rovarga@cisco.com>
Sun, 1 Jan 2017 18:58:43 +0000 (19:58 +0100)
committerRobert Varga <rovarga@cisco.com>
Tue, 10 Jan 2017 19:12:11 +0000 (20:12 +0100)
Using Externalizable proxy pattern has multiple advantages,
hence use that instead of plain serialization. This gets rid
of reflection-based access to readOnly field and generally
much cleaner.

Change-Id: I471dfe5a65972309b10393a91bc1cf702959a866
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ExternalForm.java [new file with mode: 0644]
third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/TrieMap.java

diff --git a/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ExternalForm.java b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ExternalForm.java
new file mode 100644 (file)
index 0000000..dbf171d
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * (C) Copyright 2017 Pantheon Technologies, s.r.o. and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opendaylight.yangtools.triemap;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectStreamException;
+import java.util.Map.Entry;
+
+/**
+ * External serialization object for use with TrieMap objects. This hides the implementation details, such as object
+ * hierarchy. It also makes handling read-only snapshots more elegant.
+ *
+ * @author Robert Varga
+ */
+final class ExternalForm implements Externalizable {
+    private static final long serialVersionUID = 1L;
+
+    private TrieMap<Object, Object> map;
+    private boolean readOnly;
+
+    public ExternalForm() {
+        // For Externalizable
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    ExternalForm(final TrieMap<?, ?> map) {
+        this.map = ((TrieMap)map).readOnlySnapshot();
+        this.readOnly = map.isReadOnly();
+    }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(map.equiv());
+        out.writeInt(map.size());
+        for (Entry<Object, Object> e : map.entrySet()) {
+            out.writeObject(e.getKey());
+            out.writeObject(e.getValue());
+        }
+        out.writeBoolean(readOnly);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        @SuppressWarnings("unchecked")
+        final Equivalence<Object> equiv = (Equivalence<Object>) in.readObject();
+        Preconditions.checkArgument(equiv != null);
+        map = new TrieMap<>(equiv);
+
+        final int size = in.readInt();
+        for (int i = 0; i < size; ++i) {
+            map.add(in.readObject(), in.readObject());
+        }
+
+        readOnly = in.readBoolean();
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return Verify.verifyNotNull(readOnly ? map.readOnlySnapshot() : map);
+    }
+}
index 8a2b674ef90642c8cbf67c9af59fcaf4dd5b6d41..f92b7d0a38f957d10d63b36c9b6f4aa018b1b4fa 100644 (file)
 package org.opendaylight.yangtools.triemap;
 
 import com.google.common.base.Preconditions;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
 import java.io.Serializable;
-import java.lang.reflect.Field;
 import java.util.AbstractMap;
 import java.util.AbstractSet;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
@@ -47,43 +43,36 @@ public final class TrieMap<K, V> extends AbstractMap<K, V> implements Concurrent
     private static final AtomicReferenceFieldUpdater<TrieMap, Object> ROOT_UPDATER =
             AtomicReferenceFieldUpdater.newUpdater(TrieMap.class, Object.class, "root");
     private static final long serialVersionUID = 1L;
-    private static final Field READONLY_FIELD;
-
-    static {
-        final Field f;
-        try {
-            f = TrieMap.class.getDeclaredField("readOnly");
-        } catch (NoSuchFieldException e) {
-            throw new ExceptionInInitializerError(e);
-        } catch (SecurityException e) {
-            throw new ExceptionInInitializerError(e);
-        }
-        f.setAccessible(true);
-        READONLY_FIELD = f;
-    }
 
     /**
      * EntrySet
      */
-    private transient final EntrySet entrySet = new EntrySet ();
-
+    private final EntrySet entrySet = new EntrySet();
     private final Equivalence<? super K> equiv;
+    private final boolean readOnly;
 
-    private transient volatile Object root;
-    private final transient boolean readOnly;
+    private volatile Object root;
 
-    TrieMap(final INode<K, V> r, final Equivalence<? super K> equiv, final boolean readOnly) {
+    private TrieMap(final INode<K, V> r, final Equivalence<? super K> equiv, final boolean readOnly) {
         this.root = r;
         this.equiv = equiv;
         this.readOnly = readOnly;
     }
 
+    TrieMap(final Equivalence<? super K> equiv) {
+        this(newRootNode(), equiv, false);
+    }
+
     public TrieMap() {
-        this(newRootNode(), Equivalence.equals(), false);
+        this(Equivalence.equals());
     }
 
     /* internal methods */
 
+    final Equivalence<? super K> equiv() {
+        return equiv;
+    }
+
     private static <K,V> INode<K, V> newRootNode() {
         final Gen gen = new Gen();
         return new INode<>(gen, new CNode<>(gen));
@@ -323,10 +312,8 @@ public final class TrieMap<K, V> extends AbstractMap<K, V> implements Concurrent
         return insertifhc (key, hc, value, null).orElse(null);
     }
 
-    TrieMap<K, V> add(final K k, final V v) {
-        final int hc = computeHash (k);
-        inserthc (k, hc, v);
-        return this;
+    void add(final K k, final V v) {
+        inserthc(k, computeHash(k), v);
     }
 
     @Override
@@ -408,6 +395,10 @@ public final class TrieMap<K, V> extends AbstractMap<K, V> implements Concurrent
         return entrySet;
     }
 
+    private Object writeReplace() throws ObjectStreamException {
+        return new ExternalForm(this);
+    }
+
     private static final class RDCSS_Descriptor<K, V> {
         INode<K, V> old;
         MainNode<K, V> expectedmain;
@@ -718,36 +709,4 @@ public final class TrieMap<K, V> extends AbstractMap<K, V> implements Concurrent
         }
     }
 
-    private void readObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
-        inputStream.defaultReadObject();
-        this.root = newRootNode();
-
-        final boolean ro = inputStream.readBoolean();
-        final int size = inputStream.readInt();
-        for (int i = 0; i < size; ++i) {
-            final K key = (K)inputStream.readObject();
-            final V value = (V)inputStream.readObject();
-            add(key, value);
-        }
-
-        // Propagate the read-only bit
-        try {
-            READONLY_FIELD.setBoolean(this, ro);
-        } catch (IllegalAccessException e) {
-            throw new IOException("Failed to set read-only flag", e);
-        }
-    }
-
-    private void writeObject(final ObjectOutputStream outputStream) throws IOException {
-        outputStream.defaultWriteObject();
-
-        final Map<K, V> ro = readOnlySnapshot();
-        outputStream.writeBoolean(readOnly);
-        outputStream.writeInt(ro.size());
-
-        for (Entry<K, V> e : ro.entrySet()) {
-            outputStream.writeObject(e.getKey());
-            outputStream.writeObject(e.getValue());
-        }
-    }
 }