Skip to content

Commit 4c11ecb

Browse files
committed
chore(TypeHandlerLibrary)! make Serializer more type-safe
1 parent fbc40c3 commit 4c11ecb

File tree

15 files changed

+210
-248
lines changed

15 files changed

+210
-248
lines changed

engine/src/main/java/org/terasology/engine/entitySystem/metadata/EventLibrary.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2022 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33
package org.terasology.engine.entitySystem.metadata;
44

@@ -49,7 +49,6 @@ public <T extends Event> EventMetadata<T> getMetadata(T object) {
4949
}
5050

5151
@Override
52-
@SuppressWarnings("unchecked")
5352
public EventMetadata<? extends Event> getMetadata(ResourceUrn uri) {
5453
return (EventMetadata<? extends Event>) super.getMetadata(uri);
5554
}

engine/src/main/java/org/terasology/engine/persistence/serializers/ComponentSerializer.java

Lines changed: 86 additions & 86 deletions
Large diffs are not rendered by default.

engine/src/main/java/org/terasology/engine/persistence/serializers/EventSerializer.java

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2022 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

44
package org.terasology.engine.persistence.serializers;
@@ -61,31 +61,29 @@ public void removeIdMapping() {
6161
}
6262

6363
/**
64-
* @param eventData
6564
* @return The event described by the eventData
66-
* @throws org.terasology.engine.persistence.typeHandling.DeserializationException if an error occurs when deserializing
65+
* @throws org.terasology.persistence.typeHandling.DeserializationException if an error occurs when deserializing
6766
*/
6867
public Event deserialize(EntityData.Event eventData) {
69-
Class<? extends Event> eventClass = getEventClass(eventData);
68+
var eventClass = getEventClass(eventData);
7069
if (eventClass != null) {
71-
EventMetadata<?> eventMetadata = eventLibrary.getMetadata(eventClass);
70+
var eventMetadata = eventLibrary.getMetadata(eventClass);
7271
if (!eventMetadata.isConstructable()) {
7372
throw new DeserializationException("Cannot deserialize " + eventMetadata + " - lacks default constructor");
7473
} else {
75-
Event event = eventMetadata.newInstance();
76-
return deserializeOnto(event, eventData, eventMetadata);
74+
return deserializeOnto(eventMetadata.newInstance(), eventData, eventMetadata);
7775
}
7876
} else {
7977
throw new DeserializationException("Unable to deserialize unknown event type: " + eventData.getType());
8078
}
8179
}
8280

8381

84-
private Event deserializeOnto(Event targetEvent, EntityData.Event eventData, EventMetadata<? extends Event> eventMetadata) {
85-
Serializer serializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
82+
private <C extends Event> Event deserializeOnto(C targetEvent, EntityData.Event eventData, EventMetadata<C> eventMetadata) {
83+
Serializer<C> serializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
8684
for (int i = 0; i < eventData.getFieldIds().size(); ++i) {
8785
byte fieldId = eventData.getFieldIds().byteAt(i);
88-
ReplicatedFieldMetadata<?, ?> fieldInfo = eventMetadata.getField(fieldId);
86+
var fieldInfo = eventMetadata.getField(fieldId);
8987
if (fieldInfo == null) {
9088
logger.error("Unable to serialize field {}, out of bounds", fieldId);
9189
continue;
@@ -100,12 +98,11 @@ private Event deserializeOnto(Event targetEvent, EntityData.Event eventData, Eve
10098
/**
10199
* Serializes an event.
102100
*
103-
* @param event
104101
* @return The serialized event
105-
* @throws org.terasology.engine.persistence.typeHandling.SerializationException if an error occurs during serialization
102+
* @throws org.terasology.persistence.typeHandling.SerializationException if an error occurs during serialization
106103
*/
107-
public EntityData.Event serialize(Event event) {
108-
EventMetadata<?> eventMetadata = eventLibrary.getMetadata(event.getClass());
104+
public <E extends Event> EntityData.Event serialize(E event) {
105+
@SuppressWarnings("unchecked") var eventMetadata = eventLibrary.getMetadata((Class<E>) event.getClass());
109106
if (eventMetadata == null) {
110107
throw new SerializationException("Unregistered event type: " + event.getClass());
111108
} else if (!eventMetadata.isConstructable()) {
@@ -115,19 +112,19 @@ public EntityData.Event serialize(Event event) {
115112
EntityData.Event.Builder eventData = EntityData.Event.newBuilder();
116113
serializeEventType(event, eventData);
117114

118-
Serializer eventSerializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
115+
Serializer<E> eventSerializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
119116
ByteString.Output fieldIds = ByteString.newOutput();
120-
for (ReplicatedFieldMetadata field : eventMetadata.getFields()) {
121-
if (field.isReplicated()) {
122-
EntityData.Value serializedValue = ((ProtobufPersistedData) eventSerializer
123-
.serialize(field, event, persistedDataSerializer))
124-
.getValue();
125-
if (serializedValue != null) {
126-
eventData.addFieldValue(serializedValue);
127-
fieldIds.write(field.getId());
128-
}
117+
eventMetadata.getFields().stream()
118+
.filter(ReplicatedFieldMetadata::isReplicated)
119+
.forEach(field -> {
120+
EntityData.Value serializedValue = ((ProtobufPersistedData) eventSerializer
121+
.serialize(field, event, persistedDataSerializer))
122+
.getValue();
123+
if (serializedValue != null) {
124+
eventData.addFieldValue(serializedValue);
125+
fieldIds.write(field.getId());
129126
}
130-
}
127+
});
131128
eventData.setFieldIds(fieldIds.toByteString());
132129

133130
return eventData.build();
@@ -141,16 +138,16 @@ private void serializeEventType(Event event, EntityData.Event.Builder eventData)
141138
/**
142139
* Determines the event class that the serialized event is for.
143140
*
144-
* @param eventData
145141
* @return The event class the given eventData describes, or null if it is unknown.
146142
*/
147-
public Class<? extends Event> getEventClass(EntityData.Event eventData) {
143+
public <E extends Event> Class<E> getEventClass(EntityData.Event eventData) {
148144
if (eventData.hasType()) {
149-
EventMetadata<? extends Event> metadata = null;
145+
EventMetadata<E> metadata = null;
150146
if (!idTable.isEmpty()) {
151-
Class<? extends Event> eventClass = idTable.inverse().get(eventData.getType());
147+
var eventClass = idTable.inverse().get(eventData.getType());
152148
if (eventClass != null) {
153-
metadata = eventLibrary.getMetadata(eventClass);
149+
//noinspection unchecked
150+
metadata = (EventMetadata<E>) eventLibrary.getMetadata(eventClass);
154151
}
155152
}
156153
if (metadata == null) {

subsystems/TypeHandlerLibrary/build.gradle.kts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,13 @@ group = "org.terasology.subsystems"
1313
version = project(":engine").version
1414

1515
dependencies {
16-
implementation("org.slf4j:slf4j-api:1.7.32")
16+
implementation("org.slf4j:slf4j-api:1.7.36")
1717
implementation("net.sf.trove4j:trove4j:3.0.3")
1818

1919
implementation("org.terasology:reflections:0.9.12-MB")
2020
implementation("org.terasology.nui:nui-reflect:3.0.0")
2121
implementation("org.terasology.gestalt:gestalt-module:7.1.0")
2222
implementation("org.terasology.gestalt:gestalt-asset-core:7.1.0")
23-
24-
testRuntimeOnly("org.slf4j:slf4j-simple:1.7.32") {
25-
because("log output during tests")
26-
}
27-
testImplementation(platform("org.junit:junit-bom:5.8.1")) {
28-
// junit-bom will set version numbers for the other org.junit dependencies.
29-
}
30-
testImplementation("org.junit.jupiter:junit-jupiter-api")
31-
testImplementation("org.junit.jupiter:junit-jupiter-params")
32-
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
33-
testImplementation("org.mockito:mockito-inline:3.12.4")
34-
35-
testImplementation("org.mockito:mockito-junit-jupiter:3.12.4")
3623
}
3724

3825
tasks.register<Test>("unitTest") {

subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/Serializer.java

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2022 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33
package org.terasology.persistence.typeHandling;
44

@@ -14,14 +14,14 @@
1414
* A serializer provides low-level serialization support for a type, using a mapping of type handlers for each field of that type.
1515
*
1616
*/
17-
public class Serializer {
17+
public class Serializer<C> {
1818

1919
private static final Logger logger = LoggerFactory.getLogger(Serializer.class);
2020

21-
private ClassMetadata<?, ?> classMetadata;
22-
private Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlers;
21+
private final ClassMetadata<C, ?> classMetadata;
22+
private final Map<FieldMetadata<C, ?>, TypeHandler<?>> fieldHandlers;
2323

24-
public Serializer(ClassMetadata<?, ?> classMetadata, Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlers) {
24+
public Serializer(ClassMetadata<C, ? extends FieldMetadata<C, ?>> classMetadata, Map<FieldMetadata<C, ?>, TypeHandler<?>> fieldHandlers) {
2525
this.fieldHandlers = fieldHandlers;
2626
this.classMetadata = classMetadata;
2727
}
@@ -30,8 +30,8 @@ public Serializer(ClassMetadata<?, ?> classMetadata, Map<FieldMetadata<?, ?>, Ty
3030
* @param field The metadata for a field of the type handled by this serializer.
3131
* @return The TypeHandler for the given field
3232
*/
33-
public TypeHandler<?> getHandlerFor(FieldMetadata<?, ?> field) {
34-
return fieldHandlers.get(field);
33+
public <F> TypeHandler<F> getHandlerFor(FieldMetadata<C, F> field) {
34+
return (TypeHandler<F>) fieldHandlers.get(field);
3535
}
3636

3737
/**
@@ -42,11 +42,10 @@ public TypeHandler<?> getHandlerFor(FieldMetadata<?, ?> field) {
4242
* @param context The current serialization context
4343
* @return The serialized value of the field
4444
*/
45-
@SuppressWarnings("unchecked")
46-
public PersistedData serialize(FieldMetadata<?, ?> field, Object container, PersistedDataSerializer context) {
47-
Object rawValue = field.getValue(container);
45+
public <F> PersistedData serialize(FieldMetadata<C, F> field, C container, PersistedDataSerializer context) {
46+
F rawValue = field.getValue(container);
4847
if (rawValue != null) {
49-
TypeHandler handler = getHandlerFor(field);
48+
TypeHandler<F> handler = getHandlerFor(field);
5049
if (handler != null) {
5150
return handler.serialize(rawValue, context);
5251
}
@@ -62,9 +61,9 @@ public PersistedData serialize(FieldMetadata<?, ?> field, Object container, Pers
6261
* @param rawValue The value to serialize
6362
* @return The serialized value
6463
*/
65-
@SuppressWarnings("unchecked")
66-
public PersistedData serializeValue(FieldMetadata<?, ?> fieldMetadata, Object rawValue, PersistedDataSerializer context) {
67-
return fieldHandlers.get(fieldMetadata).serialize(rawValue, context);
64+
public <F> PersistedData serializeValue(FieldMetadata<C, F> fieldMetadata, F rawValue, PersistedDataSerializer context) {
65+
@SuppressWarnings("unchecked") TypeHandler<F> handler = (TypeHandler<F>) fieldHandlers.get(fieldMetadata);
66+
return handler.serialize(rawValue, context);
6867
}
6968

7069
/**
@@ -74,13 +73,13 @@ public PersistedData serializeValue(FieldMetadata<?, ?> fieldMetadata, Object ra
7473
* @param fieldMetadata The metadata of the field
7574
* @param data The serialized value of the field
7675
*/
77-
public void deserializeOnto(Object target, FieldMetadata<?, ?> fieldMetadata, PersistedData data) {
78-
TypeHandler<?> handler = getHandlerFor(fieldMetadata);
76+
public <F> void deserializeOnto(C target, FieldMetadata<C, F> fieldMetadata, PersistedData data) {
77+
TypeHandler<F> handler = getHandlerFor(fieldMetadata);
7978
if (handler == null) {
8079
logger.error("No type handler for type {} used by {}::{}", fieldMetadata.getType(), target.getClass(), fieldMetadata);
8180
} else {
8281
try {
83-
Object deserializedValue = handler.deserializeOrNull(data);
82+
F deserializedValue = handler.deserializeOrNull(data);
8483
fieldMetadata.setValue(target, deserializedValue);
8584
} catch (DeserializationException e) {
8685
logger.error("Unable to deserialize field '{}' from '{}'", fieldMetadata.getName(), data.toString(), e);
@@ -94,7 +93,7 @@ public void deserializeOnto(Object target, FieldMetadata<?, ?> fieldMetadata, Pe
9493
* @param target The object to deserialize onto
9594
* @param values The collection of values to apply to the object
9695
*/
97-
public void deserializeOnto(Object target, PersistedDataMap values) {
96+
public void deserializeOnto(C target, PersistedDataMap values) {
9897
deserializeOnto(target, values, DeserializeFieldCheck.NullCheck.newInstance());
9998
}
10099

@@ -105,15 +104,16 @@ public void deserializeOnto(Object target, PersistedDataMap values) {
105104
* @param values The collection of values to apply to the object
106105
* @param check A check to filter which fields to deserialize
107106
*/
108-
public void deserializeOnto(Object target, PersistedDataMap values, DeserializeFieldCheck check) {
109-
for (Map.Entry<String, PersistedData> field : values.entrySet()) {
110-
FieldMetadata<?, ?> fieldInfo = classMetadata.getField(field.getKey());
111-
112-
if (fieldInfo != null && check.shouldDeserialize(classMetadata, fieldInfo)) {
113-
deserializeOnto(target, fieldInfo, field.getValue());
114-
} else if (fieldInfo == null) {
115-
logger.warn("Cannot deserialize unknown field '{}' onto '{}'", field.getKey(), classMetadata.getId());
116-
}
107+
public void deserializeOnto(C target, PersistedDataMap values, DeserializeFieldCheck check) {
108+
values.entrySet().forEach(field -> goomp(target, check, field.getKey(), field.getValue()));
109+
}
110+
111+
private void goomp(C target, DeserializeFieldCheck check, String fieldName, PersistedData data) {
112+
var fieldInfo = classMetadata.getField(fieldName);
113+
if (fieldInfo != null && check.shouldDeserialize(classMetadata, fieldInfo)) {
114+
deserializeOnto(target, fieldInfo, data);
115+
} else if (fieldInfo == null) {
116+
logger.warn("Cannot deserialize unknown field '{}' onto '{}'", fieldName, classMetadata.getId());
117117
}
118118
}
119119

@@ -123,7 +123,7 @@ public void deserializeOnto(Object target, PersistedDataMap values, DeserializeF
123123
* @param target The object to deserialize onto
124124
* @param values The collection of values to apply to the object
125125
*/
126-
public void deserializeOnto(Object target, Map<FieldMetadata<?, ?>, PersistedData> values) {
126+
public void deserializeOnto(C target, Map<FieldMetadata<C, ?>, PersistedData> values) {
127127
deserializeOnto(target, values, DeserializeFieldCheck.NullCheck.newInstance());
128128
}
129129

@@ -134,12 +134,12 @@ public void deserializeOnto(Object target, Map<FieldMetadata<?, ?>, PersistedDat
134134
* @param values The collection of values to apply to the object
135135
* @param check A check to filter which fields to deserialize
136136
*/
137-
public void deserializeOnto(Object target, Map<FieldMetadata<?, ?>, PersistedData> values, DeserializeFieldCheck check) {
138-
for (Map.Entry<FieldMetadata<?, ?>, PersistedData> field : values.entrySet()) {
139-
if (check.shouldDeserialize(classMetadata, field.getKey())) {
140-
deserializeOnto(target, field.getKey(), field.getValue());
137+
public void deserializeOnto(C target, Map<FieldMetadata<C, ?>, PersistedData> values, DeserializeFieldCheck check) {
138+
values.forEach((field, data) -> {
139+
if (check.shouldDeserialize(classMetadata, field)) {
140+
deserializeOnto(target, field, data);
141141
}
142-
}
142+
});
143143
}
144144

145145

subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerLibrary.java

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2022 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

44
package org.terasology.persistence.typeHandling;
@@ -57,14 +57,14 @@ public class TypeHandlerLibrary {
5757
private final List<TypeHandlerFactory> typeHandlerFactories = Lists.newArrayList();
5858
private final Map<Type, InstanceCreator<?>> instanceCreators = Maps.newHashMap();
5959
private final Map<TypeInfo<?>, TypeHandler<?>> typeHandlerCache = Maps.newHashMap();
60-
private final Map<ClassMetadata<?, ?>, Serializer> serializerMap = Maps.newHashMap();
60+
private final Map<ClassMetadata<?, ?>, Serializer<?>> serializerMap = Maps.newHashMap();
6161

6262
protected TypeHandlerLibrary(SerializationSandbox sandbox) {
6363
this.sandbox = sandbox;
6464
ConstructorLibrary constructorLibrary = new ConstructorLibrary(instanceCreators);
6565
addTypeHandlerFactory(new ObjectFieldMapTypeHandlerFactory(constructorLibrary));
6666
TypeHandlerLibrary.populateBuiltInHandlers(this);
67-
addTypeHandlerFactory(new CollectionTypeHandlerFactory(constructorLibrary));
67+
addTypeHandlerFactory(new CollectionTypeHandlerFactory());
6868
}
6969

7070
public TypeHandlerLibrary(Reflections reflections) {
@@ -122,11 +122,11 @@ public TypeHandlerLibrary copy() {
122122
* @param type The ClassMetadata for the type of interest
123123
* @return A serializer for serializing/deserializing the type
124124
*/
125-
public Serializer getSerializerFor(ClassMetadata<?, ?> type) {
126-
Serializer serializer = serializerMap.get(type);
125+
public <C> Serializer<C> getSerializerFor(ClassMetadata<C, ? extends FieldMetadata<C, ?>> type) {
126+
@SuppressWarnings("unchecked") Serializer<C> serializer = (Serializer<C>) serializerMap.get(type);
127127
if (serializer == null) {
128-
Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlerMap = getFieldHandlerMap(type);
129-
serializer = new Serializer(type, fieldHandlerMap);
128+
var fieldHandlerMap = getFieldHandlerMap(type);
129+
serializer = new Serializer<>(type, fieldHandlerMap);
130130
serializerMap.put(type, serializer);
131131
}
132132
return serializer;
@@ -209,10 +209,9 @@ public <T> void addInstanceCreator(TypeInfo<T> typeInfo, InstanceCreator<T> inst
209209
* @param type The {@link Type} describing the type for which to retrieve the {@link TypeHandler}.
210210
* @return The {@link TypeHandler} for the specified type, if available.
211211
*/
212-
@SuppressWarnings("unchecked")
213-
public Optional<TypeHandler<?>> getTypeHandler(Type type) {
214-
TypeInfo typeInfo = TypeInfo.of(type);
215-
return (Optional<TypeHandler<?>>) getTypeHandler(typeInfo);
212+
public <T> Optional<TypeHandler<T>> getTypeHandler(Type type) {
213+
TypeInfo<T> typeInfo = TypeInfo.of(type);
214+
return getTypeHandler(typeInfo);
216215
}
217216

218217
/**
@@ -310,17 +309,17 @@ public <T> TypeHandler<T> getBaseTypeHandler(TypeInfo<T> typeInfo) {
310309
return new RuntimeDelegatingTypeHandler<>(delegateHandler, typeInfo, context);
311310
}
312311

313-
private Map<FieldMetadata<?, ?>, TypeHandler> getFieldHandlerMap(ClassMetadata<?, ?> type) {
314-
Map<FieldMetadata<?, ?>, TypeHandler> handlerMap = Maps.newHashMap();
315-
for (FieldMetadata<?, ?> field : type.getFields()) {
316-
Optional<TypeHandler<?>> handler = getTypeHandler(field.getField().getGenericType());
317-
318-
if (handler.isPresent()) {
319-
handlerMap.put(field, handler.get());
320-
} else {
321-
logger.error("Unsupported field: '{}.{}'", type.getId(), field.getName());
322-
}
323-
}
312+
private <C> Map<FieldMetadata<C, ?>, TypeHandler<?>> getFieldHandlerMap(ClassMetadata<C, ? extends FieldMetadata<C, ?>> type) {
313+
Map<FieldMetadata<C, ?>, TypeHandler<?>> handlerMap = new HashMap<>();
314+
type.getFields().forEach(field ->
315+
getFmConsumer(field).ifPresentOrElse(
316+
handler -> handlerMap.put(field, handler),
317+
() -> logger.error("Unsupported field: '{}.{}'", type.getId(), field.getName())
318+
));
324319
return handlerMap;
325320
}
321+
322+
private <T> Optional<TypeHandler<T>> getFmConsumer(FieldMetadata<?, T> field) {
323+
return getTypeHandler(field.getField().getGenericType());
324+
}
326325
}

subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public PersistedData serializeNonNull(T value, PersistedDataSerializer serialize
6464
Type runtimeType = getRuntimeTypeIfMoreSpecific(value);
6565

6666
if (!typeInfo.getType().equals(runtimeType)) {
67-
Optional<TypeHandler<?>> runtimeTypeHandler = typeHandlerLibrary.getTypeHandler(runtimeType);
67+
var runtimeTypeHandler = typeHandlerLibrary.getTypeHandler(runtimeType);
6868

6969
chosenHandler =
7070
(TypeHandler<T>)

0 commit comments

Comments
 (0)