Skip to content

Commit

Permalink
chore(TypeHandlerLibrary)! make Serializer more type-safe
Browse files Browse the repository at this point in the history
  • Loading branch information
keturn committed Jun 15, 2022
1 parent fbc40c3 commit 518450c
Show file tree
Hide file tree
Showing 14 changed files with 124 additions and 162 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The Terasology Foundation
// Copyright 2022 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.engine.entitySystem.metadata;

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

@Override
@SuppressWarnings("unchecked")
public EventMetadata<? extends Event> getMetadata(ResourceUrn uri) {
return (EventMetadata<? extends Event>) super.getMetadata(uri);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The Terasology Foundation
// Copyright 2022 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0

package org.terasology.engine.persistence.serializers;
Expand Down Expand Up @@ -61,31 +61,29 @@ public void removeIdMapping() {
}

/**
* @param eventData
* @return The event described by the eventData
* @throws org.terasology.engine.persistence.typeHandling.DeserializationException if an error occurs when deserializing
* @throws org.terasology.persistence.typeHandling.DeserializationException if an error occurs when deserializing
*/
public Event deserialize(EntityData.Event eventData) {
Class<? extends Event> eventClass = getEventClass(eventData);
var eventClass = getEventClass(eventData);
if (eventClass != null) {
EventMetadata<?> eventMetadata = eventLibrary.getMetadata(eventClass);
var eventMetadata = eventLibrary.getMetadata(eventClass);
if (!eventMetadata.isConstructable()) {
throw new DeserializationException("Cannot deserialize " + eventMetadata + " - lacks default constructor");
} else {
Event event = eventMetadata.newInstance();
return deserializeOnto(event, eventData, eventMetadata);
return deserializeOnto(eventMetadata.newInstance(), eventData, eventMetadata);
}
} else {
throw new DeserializationException("Unable to deserialize unknown event type: " + eventData.getType());
}
}


private Event deserializeOnto(Event targetEvent, EntityData.Event eventData, EventMetadata<? extends Event> eventMetadata) {
Serializer serializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
private <C extends Event> Event deserializeOnto(C targetEvent, EntityData.Event eventData, EventMetadata<C> eventMetadata) {
Serializer<C> serializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
for (int i = 0; i < eventData.getFieldIds().size(); ++i) {
byte fieldId = eventData.getFieldIds().byteAt(i);
ReplicatedFieldMetadata<?, ?> fieldInfo = eventMetadata.getField(fieldId);
var fieldInfo = eventMetadata.getField(fieldId);
if (fieldInfo == null) {
logger.error("Unable to serialize field {}, out of bounds", fieldId);
continue;
Expand All @@ -100,12 +98,11 @@ private Event deserializeOnto(Event targetEvent, EntityData.Event eventData, Eve
/**
* Serializes an event.
*
* @param event
* @return The serialized event
* @throws org.terasology.engine.persistence.typeHandling.SerializationException if an error occurs during serialization
* @throws org.terasology.persistence.typeHandling.SerializationException if an error occurs during serialization
*/
public EntityData.Event serialize(Event event) {
EventMetadata<?> eventMetadata = eventLibrary.getMetadata(event.getClass());
public <E extends Event> EntityData.Event serialize(E event) {
@SuppressWarnings("unchecked") var eventMetadata = eventLibrary.getMetadata((Class<E>) event.getClass());
if (eventMetadata == null) {
throw new SerializationException("Unregistered event type: " + event.getClass());
} else if (!eventMetadata.isConstructable()) {
Expand All @@ -115,19 +112,19 @@ public EntityData.Event serialize(Event event) {
EntityData.Event.Builder eventData = EntityData.Event.newBuilder();
serializeEventType(event, eventData);

Serializer eventSerializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
Serializer<E> eventSerializer = typeHandlerLibrary.getSerializerFor(eventMetadata);
ByteString.Output fieldIds = ByteString.newOutput();
for (ReplicatedFieldMetadata field : eventMetadata.getFields()) {
if (field.isReplicated()) {
EntityData.Value serializedValue = ((ProtobufPersistedData) eventSerializer
.serialize(field, event, persistedDataSerializer))
.getValue();
if (serializedValue != null) {
eventData.addFieldValue(serializedValue);
fieldIds.write(field.getId());
}
eventMetadata.getFields().stream()
.filter(ReplicatedFieldMetadata::isReplicated)
.forEach(field -> {
EntityData.Value serializedValue = ((ProtobufPersistedData) eventSerializer
.serialize(field, event, persistedDataSerializer))
.getValue();
if (serializedValue != null) {
eventData.addFieldValue(serializedValue);
fieldIds.write(field.getId());
}
}
});
eventData.setFieldIds(fieldIds.toByteString());

return eventData.build();
Expand All @@ -141,16 +138,16 @@ private void serializeEventType(Event event, EntityData.Event.Builder eventData)
/**
* Determines the event class that the serialized event is for.
*
* @param eventData
* @return The event class the given eventData describes, or null if it is unknown.
*/
public Class<? extends Event> getEventClass(EntityData.Event eventData) {
public <E extends Event> Class<E> getEventClass(EntityData.Event eventData) {
if (eventData.hasType()) {
EventMetadata<? extends Event> metadata = null;
EventMetadata<E> metadata = null;
if (!idTable.isEmpty()) {
Class<? extends Event> eventClass = idTable.inverse().get(eventData.getType());
var eventClass = idTable.inverse().get(eventData.getType());
if (eventClass != null) {
metadata = eventLibrary.getMetadata(eventClass);
//noinspection unchecked
metadata = (EventMetadata<E>) eventLibrary.getMetadata(eventClass);
}
}
if (metadata == null) {
Expand Down
15 changes: 1 addition & 14 deletions subsystems/TypeHandlerLibrary/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,13 @@ group = "org.terasology.subsystems"
version = project(":engine").version

dependencies {
implementation("org.slf4j:slf4j-api:1.7.32")
implementation("org.slf4j:slf4j-api:1.7.36")
implementation("net.sf.trove4j:trove4j:3.0.3")

implementation("org.terasology:reflections:0.9.12-MB")
implementation("org.terasology.nui:nui-reflect:3.0.0")
implementation("org.terasology.gestalt:gestalt-module:7.1.0")
implementation("org.terasology.gestalt:gestalt-asset-core:7.1.0")

testRuntimeOnly("org.slf4j:slf4j-simple:1.7.32") {
because("log output during tests")
}
testImplementation(platform("org.junit:junit-bom:5.8.1")) {
// junit-bom will set version numbers for the other org.junit dependencies.
}
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.mockito:mockito-inline:3.12.4")

testImplementation("org.mockito:mockito-junit-jupiter:3.12.4")
}

tasks.register<Test>("unitTest") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The Terasology Foundation
// Copyright 2022 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.persistence.typeHandling;

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

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

private ClassMetadata<?, ?> classMetadata;
private Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlers;
private final ClassMetadata<C, ?> classMetadata;
private final Map<FieldMetadata<C, ?>, TypeHandler<?>> fieldHandlers;

public Serializer(ClassMetadata<?, ?> classMetadata, Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlers) {
public Serializer(ClassMetadata<C, ? extends FieldMetadata<C, ?>> classMetadata, Map<FieldMetadata<C, ?>, TypeHandler<?>> fieldHandlers) {
this.fieldHandlers = fieldHandlers;
this.classMetadata = classMetadata;
}
Expand All @@ -30,8 +30,8 @@ public Serializer(ClassMetadata<?, ?> classMetadata, Map<FieldMetadata<?, ?>, Ty
* @param field The metadata for a field of the type handled by this serializer.
* @return The TypeHandler for the given field
*/
public TypeHandler<?> getHandlerFor(FieldMetadata<?, ?> field) {
return fieldHandlers.get(field);
public <F> TypeHandler<F> getHandlerFor(FieldMetadata<C, F> field) {
return (TypeHandler<F>) fieldHandlers.get(field);
}

/**
Expand All @@ -42,11 +42,10 @@ public TypeHandler<?> getHandlerFor(FieldMetadata<?, ?> field) {
* @param context The current serialization context
* @return The serialized value of the field
*/
@SuppressWarnings("unchecked")
public PersistedData serialize(FieldMetadata<?, ?> field, Object container, PersistedDataSerializer context) {
Object rawValue = field.getValue(container);
public <F> PersistedData serialize(FieldMetadata<C, F> field, C container, PersistedDataSerializer context) {
F rawValue = field.getValue(container);
if (rawValue != null) {
TypeHandler handler = getHandlerFor(field);
TypeHandler<F> handler = getHandlerFor(field);
if (handler != null) {
return handler.serialize(rawValue, context);
}
Expand All @@ -62,9 +61,9 @@ public PersistedData serialize(FieldMetadata<?, ?> field, Object container, Pers
* @param rawValue The value to serialize
* @return The serialized value
*/
@SuppressWarnings("unchecked")
public PersistedData serializeValue(FieldMetadata<?, ?> fieldMetadata, Object rawValue, PersistedDataSerializer context) {
return fieldHandlers.get(fieldMetadata).serialize(rawValue, context);
public <F> PersistedData serializeValue(FieldMetadata<C, F> fieldMetadata, F rawValue, PersistedDataSerializer context) {
@SuppressWarnings("unchecked") TypeHandler<F> handler = (TypeHandler<F>) fieldHandlers.get(fieldMetadata);
return handler.serialize(rawValue, context);
}

/**
Expand All @@ -74,13 +73,13 @@ public PersistedData serializeValue(FieldMetadata<?, ?> fieldMetadata, Object ra
* @param fieldMetadata The metadata of the field
* @param data The serialized value of the field
*/
public void deserializeOnto(Object target, FieldMetadata<?, ?> fieldMetadata, PersistedData data) {
TypeHandler<?> handler = getHandlerFor(fieldMetadata);
public <F> void deserializeOnto(C target, FieldMetadata<C, F> fieldMetadata, PersistedData data) {
TypeHandler<F> handler = getHandlerFor(fieldMetadata);
if (handler == null) {
logger.error("No type handler for type {} used by {}::{}", fieldMetadata.getType(), target.getClass(), fieldMetadata);
} else {
try {
Object deserializedValue = handler.deserializeOrNull(data);
F deserializedValue = handler.deserializeOrNull(data);
fieldMetadata.setValue(target, deserializedValue);
} catch (DeserializationException e) {
logger.error("Unable to deserialize field '{}' from '{}'", fieldMetadata.getName(), data.toString(), e);
Expand All @@ -94,7 +93,7 @@ public void deserializeOnto(Object target, FieldMetadata<?, ?> fieldMetadata, Pe
* @param target The object to deserialize onto
* @param values The collection of values to apply to the object
*/
public void deserializeOnto(Object target, PersistedDataMap values) {
public void deserializeOnto(C target, PersistedDataMap values) {
deserializeOnto(target, values, DeserializeFieldCheck.NullCheck.newInstance());
}

Expand All @@ -105,15 +104,16 @@ public void deserializeOnto(Object target, PersistedDataMap values) {
* @param values The collection of values to apply to the object
* @param check A check to filter which fields to deserialize
*/
public void deserializeOnto(Object target, PersistedDataMap values, DeserializeFieldCheck check) {
for (Map.Entry<String, PersistedData> field : values.entrySet()) {
FieldMetadata<?, ?> fieldInfo = classMetadata.getField(field.getKey());

if (fieldInfo != null && check.shouldDeserialize(classMetadata, fieldInfo)) {
deserializeOnto(target, fieldInfo, field.getValue());
} else if (fieldInfo == null) {
logger.warn("Cannot deserialize unknown field '{}' onto '{}'", field.getKey(), classMetadata.getId());
}
public void deserializeOnto(C target, PersistedDataMap values, DeserializeFieldCheck check) {
values.entrySet().forEach(field -> goomp(target, check, field.getKey(), field.getValue()));
}

private void goomp(C target, DeserializeFieldCheck check, String fieldName, PersistedData data) {
var fieldInfo = classMetadata.getField(fieldName);
if (fieldInfo != null && check.shouldDeserialize(classMetadata, fieldInfo)) {
deserializeOnto(target, fieldInfo, data);
} else if (fieldInfo == null) {
logger.warn("Cannot deserialize unknown field '{}' onto '{}'", fieldName, classMetadata.getId());
}
}

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

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


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The Terasology Foundation
// Copyright 2022 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0

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

protected TypeHandlerLibrary(SerializationSandbox sandbox) {
this.sandbox = sandbox;
ConstructorLibrary constructorLibrary = new ConstructorLibrary(instanceCreators);
addTypeHandlerFactory(new ObjectFieldMapTypeHandlerFactory(constructorLibrary));
TypeHandlerLibrary.populateBuiltInHandlers(this);
addTypeHandlerFactory(new CollectionTypeHandlerFactory(constructorLibrary));
addTypeHandlerFactory(new CollectionTypeHandlerFactory());
}

public TypeHandlerLibrary(Reflections reflections) {
Expand Down Expand Up @@ -122,11 +122,11 @@ public TypeHandlerLibrary copy() {
* @param type The ClassMetadata for the type of interest
* @return A serializer for serializing/deserializing the type
*/
public Serializer getSerializerFor(ClassMetadata<?, ?> type) {
Serializer serializer = serializerMap.get(type);
public <C> Serializer<C> getSerializerFor(ClassMetadata<C, ? extends FieldMetadata<C, ?>> type) {
@SuppressWarnings("unchecked") Serializer<C> serializer = (Serializer<C>) serializerMap.get(type);
if (serializer == null) {
Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlerMap = getFieldHandlerMap(type);
serializer = new Serializer(type, fieldHandlerMap);
var fieldHandlerMap = getFieldHandlerMap(type);
serializer = new Serializer<>(type, fieldHandlerMap);
serializerMap.put(type, serializer);
}
return serializer;
Expand Down Expand Up @@ -209,10 +209,9 @@ public <T> void addInstanceCreator(TypeInfo<T> typeInfo, InstanceCreator<T> inst
* @param type The {@link Type} describing the type for which to retrieve the {@link TypeHandler}.
* @return The {@link TypeHandler} for the specified type, if available.
*/
@SuppressWarnings("unchecked")
public Optional<TypeHandler<?>> getTypeHandler(Type type) {
TypeInfo typeInfo = TypeInfo.of(type);
return (Optional<TypeHandler<?>>) getTypeHandler(typeInfo);
public <T> Optional<TypeHandler<T>> getTypeHandler(Type type) {
TypeInfo<T> typeInfo = TypeInfo.of(type);
return getTypeHandler(typeInfo);
}

/**
Expand Down Expand Up @@ -310,17 +309,17 @@ public <T> TypeHandler<T> getBaseTypeHandler(TypeInfo<T> typeInfo) {
return new RuntimeDelegatingTypeHandler<>(delegateHandler, typeInfo, context);
}

private Map<FieldMetadata<?, ?>, TypeHandler> getFieldHandlerMap(ClassMetadata<?, ?> type) {
Map<FieldMetadata<?, ?>, TypeHandler> handlerMap = Maps.newHashMap();
for (FieldMetadata<?, ?> field : type.getFields()) {
Optional<TypeHandler<?>> handler = getTypeHandler(field.getField().getGenericType());

if (handler.isPresent()) {
handlerMap.put(field, handler.get());
} else {
logger.error("Unsupported field: '{}.{}'", type.getId(), field.getName());
}
}
private <C> Map<FieldMetadata<C, ?>, TypeHandler<?>> getFieldHandlerMap(ClassMetadata<C, ? extends FieldMetadata<C, ?>> type) {
Map<FieldMetadata<C, ?>, TypeHandler<?>> handlerMap = new HashMap<>();
type.getFields().forEach(field ->
getFmConsumer(field).ifPresentOrElse(
handler -> handlerMap.put(field, handler),
() -> logger.error("Unsupported field: '{}.{}'", type.getId(), field.getName())
));
return handlerMap;
}

private <T> Optional<TypeHandler<T>> getFmConsumer(FieldMetadata<?, T> field) {
return getTypeHandler(field.getField().getGenericType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public PersistedData serializeNonNull(T value, PersistedDataSerializer serialize
Type runtimeType = getRuntimeTypeIfMoreSpecific(value);

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

chosenHandler =
(TypeHandler<T>)
Expand Down
Loading

0 comments on commit 518450c

Please sign in to comment.