Skip to content

Commit f6b6b8b

Browse files
Add KryoUtils (#119)
Signed-off-by: Thibault Meyer <meyer.thibault@gmail.com>
1 parent e4504a6 commit f6b6b8b

4 files changed

Lines changed: 376 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dev.voidframework.core.kryo;
2+
3+
import com.esotericsoftware.kryo.Kryo;
4+
import com.esotericsoftware.kryo.io.Input;
5+
import com.esotericsoftware.kryo.io.Output;
6+
import com.esotericsoftware.kryo.serializers.ImmutableSerializer;
7+
8+
import java.util.List;
9+
10+
/**
11+
* Kryo serializer for Java {@code List}.
12+
*
13+
* @since 1.11.0
14+
*/
15+
public final class ListSerializer extends ImmutableSerializer<List<Object>> {
16+
17+
@Override
18+
public void write(final Kryo kryo, final Output output, final List<Object> object) {
19+
20+
output.writeInt(object.size(), true);
21+
for (final Object obj : object) {
22+
kryo.writeClassAndObject(output, obj);
23+
}
24+
}
25+
26+
@Override
27+
public List<Object> read(final Kryo kryo, final Input input, final Class<? extends List<Object>> type) {
28+
29+
final int size = input.readInt(true);
30+
31+
final Object[] objectArray = new Object[size];
32+
for (int idx = 0; idx < size; idx += 1) {
33+
objectArray[idx] = kryo.readClassAndObject(input);
34+
}
35+
36+
return List.of(objectArray);
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dev.voidframework.core.kryo;
2+
3+
import com.esotericsoftware.kryo.Kryo;
4+
import com.esotericsoftware.kryo.io.Input;
5+
import com.esotericsoftware.kryo.io.Output;
6+
import com.esotericsoftware.kryo.serializers.ImmutableSerializer;
7+
8+
import java.util.Set;
9+
10+
/**
11+
* Kryo serializer for Java {@code Set}.
12+
*
13+
* @since 1.11.0
14+
*/
15+
public final class SetSerializer extends ImmutableSerializer<Set<Object>> {
16+
17+
@Override
18+
public void write(final Kryo kryo, final Output output, final Set<Object> object) {
19+
20+
output.writeInt(object.size(), true);
21+
for (final Object obj : object) {
22+
kryo.writeClassAndObject(output, obj);
23+
}
24+
}
25+
26+
@Override
27+
public Set<Object> read(final Kryo kryo, final Input input, final Class<? extends Set<Object>> type) {
28+
29+
final int size = input.readInt(true);
30+
31+
final Object[] objectArray = new Object[size];
32+
for (int idx = 0; idx < size; idx += 1) {
33+
objectArray[idx] = kryo.readClassAndObject(input);
34+
}
35+
36+
return Set.of(objectArray);
37+
}
38+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package dev.voidframework.core.utils;
2+
3+
import com.esotericsoftware.kryo.Kryo;
4+
import com.esotericsoftware.kryo.io.ByteBufferOutputStream;
5+
import com.esotericsoftware.kryo.io.Input;
6+
import com.esotericsoftware.kryo.io.Output;
7+
import dev.voidframework.core.kryo.ListSerializer;
8+
import dev.voidframework.core.kryo.SetSerializer;
9+
10+
import java.math.BigDecimal;
11+
import java.math.BigInteger;
12+
import java.util.ArrayList;
13+
import java.util.EnumMap;
14+
import java.util.EnumSet;
15+
import java.util.HashMap;
16+
import java.util.HashSet;
17+
import java.util.LinkedHashMap;
18+
import java.util.LinkedHashSet;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.Set;
22+
23+
/**
24+
* Kryo serializer utility methods.
25+
*
26+
* @since 1.11.0
27+
*/
28+
public final class KryoUtils {
29+
30+
private static final byte[] EMPTY_ARRAY = new byte[0];
31+
private static final Kryo KRYO = new Kryo();
32+
33+
static {
34+
35+
// Configures Kryo
36+
KRYO.setRegistrationRequired(false);
37+
38+
// Registers primitive types
39+
KRYO.register(boolean.class);
40+
KRYO.register(double.class);
41+
KRYO.register(float.class);
42+
KRYO.register(int.class);
43+
KRYO.register(long.class);
44+
45+
// Registers types
46+
KRYO.register(ArrayList.class);
47+
KRYO.register(BigDecimal.class);
48+
KRYO.register(BigInteger.class);
49+
KRYO.register(Boolean.class);
50+
KRYO.register(Class.class);
51+
KRYO.register(Double.class);
52+
KRYO.register(EnumMap.class);
53+
KRYO.register(EnumSet.class);
54+
KRYO.register(Float.class);
55+
KRYO.register(HashMap.class);
56+
KRYO.register(HashSet.class);
57+
KRYO.register(Integer.class);
58+
KRYO.register(LinkedHashMap.class);
59+
KRYO.register(LinkedHashSet.class);
60+
KRYO.register(Long.class);
61+
KRYO.register(Optional.class);
62+
KRYO.register(String.class);
63+
64+
// Adds new serializers (for types with no-args constructor)
65+
KRYO.addDefaultSerializer(List.class, ListSerializer.class);
66+
KRYO.addDefaultSerializer(Set.class, SetSerializer.class);
67+
}
68+
69+
/**
70+
* Default constructor.
71+
*
72+
* @since 1.11.0
73+
*/
74+
private KryoUtils() {
75+
76+
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
77+
}
78+
79+
/**
80+
* Deserialize an object.
81+
*
82+
* @param serializedContent An array of bytes representing a serialized object
83+
* @param classType The object class type
84+
* @param <T> The type of the Java object
85+
* @return Deserialized object
86+
* @since 1.11.0
87+
*/
88+
public static <T> T deserialize(final byte[] serializedContent, final Class<T> classType) {
89+
90+
if (serializedContent == null || serializedContent.length == 0) {
91+
return null;
92+
}
93+
94+
final Input input = new Input(serializedContent);
95+
return KRYO.readObjectOrNull(input, classType);
96+
}
97+
98+
/**
99+
* Serialize an object.
100+
*
101+
* @param object The object to serialize
102+
* @return An array of bytes representing the serialized object
103+
* @since 1.11.0
104+
*/
105+
public static byte[] serialize(final Object object) {
106+
107+
final Output output = new Output(new ByteBufferOutputStream());
108+
final Class<?> classType = object != null ? object.getClass() : Object.class;
109+
110+
KRYO.writeObjectOrNull(output, object, classType);
111+
return output.toBytes();
112+
}
113+
114+
/**
115+
* Deserialize an object.
116+
* This method will return {@code null} if an exception is thrown.
117+
*
118+
* @param serializedContent An array of bytes representing a serialized object
119+
* @param classType The object class type
120+
* @param <T> The type of the Java object
121+
* @return Deserialized object
122+
* @since 1.11.0
123+
*/
124+
public static <T> T deserializeWithoutException(final byte[] serializedContent, final Class<T> classType) {
125+
126+
try {
127+
return deserialize(serializedContent, classType);
128+
} catch (final Exception ignore) {
129+
return null;
130+
}
131+
}
132+
133+
/**
134+
* Serialize an object.
135+
* This method will return an empty array if an exception is thrown.
136+
*
137+
* @param object The object to serialize
138+
* @return An array of bytes representing the serialized object
139+
* @since 1.11.0
140+
*/
141+
public static byte[] serializeWithoutException(final Object object) {
142+
143+
try {
144+
return serialize(object);
145+
} catch (final Exception ignore) {
146+
return EMPTY_ARRAY;
147+
}
148+
}
149+
150+
/**
151+
* Gets the Kryo instance.
152+
*
153+
* @return Kryo instance
154+
* @since 1.11.0
155+
*/
156+
public static Kryo kryo() {
157+
158+
return KRYO;
159+
}
160+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package dev.voidframework.core.utils;
2+
3+
import com.esotericsoftware.kryo.Kryo;
4+
import org.junit.jupiter.api.Assertions;
5+
import org.junit.jupiter.api.MethodOrderer;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.TestMethodOrder;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
11+
12+
import java.lang.reflect.Constructor;
13+
import java.lang.reflect.InvocationTargetException;
14+
import java.math.BigDecimal;
15+
import java.util.List;
16+
import java.util.Optional;
17+
import java.util.Set;
18+
import java.util.stream.Stream;
19+
20+
@TestMethodOrder(MethodOrderer.MethodName.class)
21+
final class KryoUtilsTest {
22+
23+
static Stream<Arguments> getDeserializeArguments() {
24+
return Stream.of(
25+
Arguments.of(new byte[]{1, -14, 20}, Integer.class, 1337),
26+
Arguments.of(new byte[]{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, -28}, String.class, "Hello World"),
27+
Arguments.of(new byte[]{1, 12, 2, 10, 0}, Optional.class, Optional.of(BigDecimal.TEN)),
28+
Arguments.of(new byte[]{1, 12, 3, 8, 0, 0}, Optional.class, Optional.of(BigDecimal.valueOf(2048))),
29+
Arguments.of(new byte[]{1, 2, 3, 65, 112, 112, 108, -27, 3, 66, 97, 110, 97, 110, -31}, List.class, List.of("Apple", "Banana")),
30+
Arguments.of(new byte[]{1, 1, 3, 65, 112, 112, 108, -27}, Set.class, Set.of("Apple")),
31+
Arguments.of(new byte[0], String.class, null),
32+
Arguments.of(new byte[]{0}, Set.class, null));
33+
}
34+
35+
static Stream<Arguments> getDeserializeWithoutExceptionArguments() {
36+
return Stream.of(
37+
Arguments.of(new byte[]{1, -14, 20}, Integer.class, 1337),
38+
Arguments.of(new byte[]{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, -28}, String.class, "Hello World"),
39+
Arguments.of(new byte[]{1, 12, 2, 10, 0}, Optional.class, Optional.of(BigDecimal.TEN)),
40+
Arguments.of(new byte[]{1, 12, 3, 8, 0, 0}, Optional.class, Optional.of(BigDecimal.valueOf(2048))),
41+
Arguments.of(new byte[]{1, 12, 3, 8, 0, 0}, TestDTO.class, null), // "null" because no serializer registered for "TestDTO"
42+
Arguments.of(new byte[]{1, 2, 3, 65, 112, 112, 108, -27, 3, 66, 97, 110, 97, 110, -31}, List.class, List.of("Apple", "Banana")),
43+
Arguments.of(new byte[]{1, 1, 3, 65, 112, 112, 108, -27}, Set.class, Set.of("Apple")),
44+
Arguments.of(new byte[0], String.class, null),
45+
Arguments.of(new byte[]{0}, Set.class, null));
46+
}
47+
48+
static Stream<Arguments> getSerializeArguments() {
49+
return Stream.of(
50+
Arguments.of(1337, new byte[]{1, -14, 20}),
51+
Arguments.of("Hello World", new byte[]{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, -28}),
52+
Arguments.of(Optional.of(BigDecimal.TEN), new byte[]{1, 12, 2, 10, 0}),
53+
Arguments.of(Optional.of(BigDecimal.valueOf(2048)), new byte[]{1, 12, 3, 8, 0, 0}),
54+
Arguments.of(new TestDTO("Clémence"), new byte[]{1, -119, 67, 108, -61, -87, 109, 101, 110, 99, 101}),
55+
Arguments.of(List.of("Apple", "Banana"), new byte[]{1, 2, 3, 65, 112, 112, 108, -27, 3, 66, 97, 110, 97, 110, -31}),
56+
Arguments.of(Set.of("Apple"), new byte[]{1, 1, 3, 65, 112, 112, 108, -27}),
57+
Arguments.of(null, new byte[]{0}));
58+
}
59+
60+
@Test
61+
void constructor() throws NoSuchMethodException {
62+
63+
// Act
64+
final Constructor<KryoUtils> constructor = KryoUtils.class.getDeclaredConstructor();
65+
constructor.setAccessible(true);
66+
67+
final InvocationTargetException exception = Assertions.assertThrows(InvocationTargetException.class, constructor::newInstance);
68+
69+
// Assert
70+
Assertions.assertNotNull(exception.getCause());
71+
Assertions.assertEquals("This is a utility class and cannot be instantiated", exception.getCause().getMessage());
72+
}
73+
74+
@Test
75+
void kryo() {
76+
77+
// Act
78+
final Kryo kryo = KryoUtils.kryo();
79+
80+
// Assert
81+
Assertions.assertNotNull(kryo);
82+
}
83+
84+
@ParameterizedTest
85+
@MethodSource("getDeserializeArguments")
86+
void deserialize(final byte[] toDeserialize, final Class<?> outputClassType, final Object expected) {
87+
88+
// Act
89+
final Object deserializedObject = KryoUtils.deserialize(toDeserialize, outputClassType);
90+
91+
// Assert
92+
Assertions.assertEquals(expected, deserializedObject);
93+
}
94+
95+
@ParameterizedTest
96+
@MethodSource("getDeserializeWithoutExceptionArguments")
97+
void deserializeWithoutException(final byte[] toDeserialize, final Class<?> outputClassType, final Object expected) {
98+
99+
// Act
100+
final Object deserializedObject = KryoUtils.deserializeWithoutException(toDeserialize, outputClassType);
101+
102+
// Assert
103+
Assertions.assertEquals(expected, deserializedObject);
104+
}
105+
106+
@ParameterizedTest
107+
@MethodSource("getSerializeArguments")
108+
void serialize(final Object toSerialize, final byte[] expected) {
109+
110+
// Act
111+
final byte[] serializedContent = KryoUtils.serialize(toSerialize);
112+
113+
// Assert
114+
Assertions.assertArrayEquals(expected, serializedContent);
115+
}
116+
117+
@ParameterizedTest
118+
@MethodSource("getSerializeArguments")
119+
void serializeWithoutException(final Object toSerialize, final byte[] expected) {
120+
121+
// Act
122+
final byte[] serializedContent = KryoUtils.serializeWithoutException(toSerialize);
123+
124+
// Assert
125+
Assertions.assertArrayEquals(expected, serializedContent);
126+
}
127+
128+
/**
129+
* Test DTO.
130+
*/
131+
public static final class TestDTO {
132+
133+
final String name;
134+
135+
public TestDTO(final String name) {
136+
137+
this.name = name;
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)