From 81975530ecf0444ac4f50c2ffe0db55c7e059892 Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Wed, 4 Sep 2019 19:05:31 +0300 Subject: [PATCH] Transfer pre-serialized objects --- src/one/nio/serial/DataStream.java | 5 ++ src/one/nio/serial/DeserializeStream.java | 20 ++++++ src/one/nio/serial/Repository.java | 5 ++ src/one/nio/serial/SerializationContext.java | 2 +- src/one/nio/serial/SerializeStream.java | 17 +++++ src/one/nio/serial/SerializedWrapper.java | 62 ++++++++++++++++ .../serial/SerializedWrapperSerializer.java | 72 +++++++++++++++++++ test/one/nio/serial/SerializationTest.java | 39 ++++++++++ 8 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/one/nio/serial/SerializedWrapper.java create mode 100644 src/one/nio/serial/SerializedWrapperSerializer.java diff --git a/src/one/nio/serial/DataStream.java b/src/one/nio/serial/DataStream.java index 9a6ead4..ecfc274 100755 --- a/src/one/nio/serial/DataStream.java +++ b/src/one/nio/serial/DataStream.java @@ -19,6 +19,7 @@ import one.nio.mem.DirectMemory; import one.nio.util.Utf8; +import java.io.Closeable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; @@ -332,6 +333,10 @@ public void register(Object obj) { // Nothing to do } + public Closeable newScope() { + return null; + } + protected long alloc(int size) throws IOException { long currentOffset = offset; if ((offset = currentOffset + size) > limit) { diff --git a/src/one/nio/serial/DeserializeStream.java b/src/one/nio/serial/DeserializeStream.java index 2faae29..8d9c515 100755 --- a/src/one/nio/serial/DeserializeStream.java +++ b/src/one/nio/serial/DeserializeStream.java @@ -16,6 +16,7 @@ package one.nio.serial; +import java.io.Closeable; import java.io.IOException; import java.util.Arrays; @@ -81,4 +82,23 @@ public void close() { public void register(Object obj) { context[contextSize] = obj; } + + @Override + public Closeable newScope() { + return new Closeable() { + private final Object[] prevContext = context; + private final int prevContextSize = contextSize; + + { + context = new Object[INITIAL_CAPACITY]; + contextSize = 0; + } + + @Override + public void close() { + context = prevContext; + contextSize = prevContextSize; + } + }; + } } diff --git a/src/one/nio/serial/Repository.java b/src/one/nio/serial/Repository.java index 2091ad1..9371fbf 100755 --- a/src/one/nio/serial/Repository.java +++ b/src/one/nio/serial/Repository.java @@ -130,6 +130,7 @@ public class Repository { addBootstrap(new RemoteCallSerializer()); addBootstrap(new SerializerSerializer(MethodSerializer.class)); addBootstrap(new HttpRequestSerializer()); + addBootstrap(new SerializedWrapperSerializer()); classMap.put(int.class, classMap.get(Integer.class)); classMap.put(long.class, classMap.get(Long.class)); @@ -356,6 +357,10 @@ private static Serializer generateFor(Class cls) { } else { serializer = new ExternalizableSerializer(cls); } + } else if (SerializedWrapper.class.isAssignableFrom(cls)) { + serializer = classMap.get(SerializedWrapper.class); + classMap.put(cls, serializer); + return serializer; } else if (Collection.class.isAssignableFrom(cls) && !hasOptions(cls, FIELD_SERIALIZATION)) { serializer = new CollectionSerializer(cls); } else if (Map.class.isAssignableFrom(cls) && !hasOptions(cls, FIELD_SERIALIZATION)) { diff --git a/src/one/nio/serial/SerializationContext.java b/src/one/nio/serial/SerializationContext.java index 5e7088e..701d33b 100755 --- a/src/one/nio/serial/SerializationContext.java +++ b/src/one/nio/serial/SerializationContext.java @@ -18,7 +18,7 @@ import java.util.Arrays; -class SerializationContext { +public class SerializationContext { private static final int INITIAL_CAPACITY = 64; private Object first; diff --git a/src/one/nio/serial/SerializeStream.java b/src/one/nio/serial/SerializeStream.java index c1f281a..9c2998d 100755 --- a/src/one/nio/serial/SerializeStream.java +++ b/src/one/nio/serial/SerializeStream.java @@ -16,6 +16,7 @@ package one.nio.serial; +import java.io.Closeable; import java.io.IOException; public class SerializeStream extends DataStream { @@ -60,4 +61,20 @@ public void writeObject(Object obj) throws IOException { public void close() { context = null; } + + @Override + public Closeable newScope() { + return new Closeable() { + private final SerializationContext prevContext = context; + + { + context = new SerializationContext(); + } + + @Override + public void close() { + context = prevContext; + } + }; + } } diff --git a/src/one/nio/serial/SerializedWrapper.java b/src/one/nio/serial/SerializedWrapper.java new file mode 100644 index 0000000..d65f891 --- /dev/null +++ b/src/one/nio/serial/SerializedWrapper.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Odnoklassniki Ltd, Mail.Ru Group + * + * 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 one.nio.serial; + +import one.nio.util.Hash; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; + +/** + * A wrapper for a preserialized object. Helps to avoid double serialization. + * The wrapper automatically unpacks the original object during deserialization. + */ +public class SerializedWrapper implements Serializable { + private final byte[] serialized; + + public SerializedWrapper(byte[] serialized) { + this.serialized = serialized; + } + + public byte[] getSerialized() { + return serialized; + } + + @Override + public int hashCode() { + return Hash.xxhash(serialized, 0, serialized.length); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SerializedWrapper)) { + return false; + } + + return Arrays.equals(serialized, ((SerializedWrapper) obj).serialized); + } + + public static SerializedWrapper wrap(T object) throws IOException { + return new SerializedWrapper<>(Serializer.serialize(object)); + } + + @SuppressWarnings("unchecked") + public T unwrap() throws IOException, ClassNotFoundException { + return (T) Serializer.deserialize(serialized); + } +} diff --git a/src/one/nio/serial/SerializedWrapperSerializer.java b/src/one/nio/serial/SerializedWrapperSerializer.java new file mode 100644 index 0000000..0361cf4 --- /dev/null +++ b/src/one/nio/serial/SerializedWrapperSerializer.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Odnoklassniki Ltd, Mail.Ru Group + * + * 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 one.nio.serial; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Asymmetric serializer that writes a preserialized object as byte[], + * but reads an original object + */ +class SerializedWrapperSerializer extends Serializer { + + SerializedWrapperSerializer() { + super(SerializedWrapper.class); + } + + @Override + public void calcSize(Object obj, CalcSizeStream css) throws IOException { + css.add(getSerialized(obj).length); + } + + @Override + public void write(Object obj, DataStream out) throws IOException { + out.write(getSerialized(obj)); + } + + @Override + public Object read(DataStream in) throws IOException, ClassNotFoundException { + Object result; + try (Closeable blankScope = in.newScope()) { + result = in.readObject(); + } + in.register(result); + return result; + } + + @Override + public void skip(DataStream in) throws IOException, ClassNotFoundException { + try (Closeable blankScope = in.newScope()) { + in.readObject(); + } + } + + @Override + public void toJson(Object obj, StringBuilder builder) throws IOException { + Json.appendBinary(builder, getSerialized(obj)); + } + + @Override + public Object fromJson(JsonReader in) throws IOException, ClassNotFoundException { + return Serializer.deserialize(in.readBinary()); + } + + private static byte[] getSerialized(Object obj) { + return ((SerializedWrapper) obj).getSerialized(); + } +} diff --git a/test/one/nio/serial/SerializationTest.java b/test/one/nio/serial/SerializationTest.java index e9aeb95..3ab8678 100755 --- a/test/one/nio/serial/SerializationTest.java +++ b/test/one/nio/serial/SerializationTest.java @@ -32,7 +32,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; public class SerializationTest { @@ -434,4 +437,40 @@ public void testHierarchy() throws IOException, ClassNotFoundException { public void testChatSample() throws IOException, ClassNotFoundException { checkSerialize(Sample.createChat()); } + + static class MySerializedWrapper extends SerializedWrapper { + + public MySerializedWrapper(byte[] serialized) { + super(serialized); + } + + public static MySerializedWrapper wrap(Object obj) throws IOException { + return new MySerializedWrapper(Serializer.serialize(obj)); + } + } + + @Test + public void testSerializedWrapper() throws IOException, ClassNotFoundException { + Object original = Sample.createChat(); + + SerializedWrapper wrapper1 = SerializedWrapper.wrap(original); + MySerializedWrapper wrapper2 = MySerializedWrapper.wrap(original); + assertEquals(wrapper1, wrapper2); + assertEquals(wrapper1.hashCode(), wrapper2.hashCode()); + + byte[] serializedOriginal = Serializer.serialize(original); + byte[] serializedWrapper = Serializer.serialize(wrapper2); + assertEquals(serializedOriginal.length + 1, serializedWrapper.length); + assertArrayEquals(serializedOriginal, Arrays.copyOfRange(serializedWrapper, 1, serializedWrapper.length)); + + Object deserialized = clone(wrapper1); + assertEquals(original, deserialized); + + Object[] array = {original, wrapper1, original, wrapper2}; + Object[] deserializedArray = (Object[]) clone(array); + assertSame(deserializedArray[0], deserializedArray[2]); + assertNotSame(deserializedArray[1], deserializedArray[3]); + assertEquals(deserializedArray[0], deserializedArray[1]); + assertEquals(deserializedArray[0], deserializedArray[3]); + } }