Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(TypeHandlerLibrary)! make Serializer more type-safe #5049

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Large diffs are not rendered by default.

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