From a977b02cb1bf3a485601c4a3d239b82674c3041a Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 1 Jan 2017 19:58:43 +0100 Subject: [PATCH 1/1] BUG-7464: Rework TrieMap serialization 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 --- .../yangtools/triemap/ExternalForm.java | 78 ++++++++++++++++++ .../yangtools/triemap/TrieMap.java | 81 +++++-------------- 2 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ExternalForm.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 index 0000000000..dbf171ddfe --- /dev/null +++ b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/ExternalForm.java @@ -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 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 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 equiv = (Equivalence) 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); + } +} diff --git a/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/TrieMap.java b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/TrieMap.java index 8a2b674ef9..f92b7d0a38 100644 --- a/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/TrieMap.java +++ b/third-party/triemap/src/main/java/org/opendaylight/yangtools/triemap/TrieMap.java @@ -16,18 +16,14 @@ 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 extends AbstractMap implements Concurrent private static final AtomicReferenceFieldUpdater 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 equiv; + private final boolean readOnly; - private transient volatile Object root; - private final transient boolean readOnly; + private volatile Object root; - TrieMap(final INode r, final Equivalence equiv, final boolean readOnly) { + private TrieMap(final INode r, final Equivalence equiv, final boolean readOnly) { this.root = r; this.equiv = equiv; this.readOnly = readOnly; } + TrieMap(final Equivalence equiv) { + this(newRootNode(), equiv, false); + } + public TrieMap() { - this(newRootNode(), Equivalence.equals(), false); + this(Equivalence.equals()); } /* internal methods */ + final Equivalence equiv() { + return equiv; + } + private static INode newRootNode() { final Gen gen = new Gen(); return new INode<>(gen, new CNode<>(gen)); @@ -323,10 +312,8 @@ public final class TrieMap extends AbstractMap implements Concurrent return insertifhc (key, hc, value, null).orElse(null); } - TrieMap 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 extends AbstractMap implements Concurrent return entrySet; } + private Object writeReplace() throws ObjectStreamException { + return new ExternalForm(this); + } + private static final class RDCSS_Descriptor { INode old; MainNode expectedmain; @@ -718,36 +709,4 @@ public final class TrieMap extends AbstractMap 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 ro = readOnlySnapshot(); - outputStream.writeBoolean(readOnly); - outputStream.writeInt(ro.size()); - - for (Entry e : ro.entrySet()) { - outputStream.writeObject(e.getKey()); - outputStream.writeObject(e.getValue()); - } - } } -- 2.36.6