/* * Copyright 2013 MovingBlocks * * 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 org.terasology.persistence.serializers; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.entitySystem.event.Event; import org.terasology.entitySystem.metadata.EventLibrary; import org.terasology.entitySystem.metadata.EventMetadata; import org.terasology.entitySystem.metadata.ReplicatedFieldMetadata; import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.DeserializationException; import org.terasology.persistence.typeHandling.SerializationContext; import org.terasology.persistence.typeHandling.SerializationException; import org.terasology.persistence.typeHandling.Serializer; import org.terasology.persistence.typeHandling.TypeSerializationLibrary; import org.terasology.persistence.typeHandling.protobuf.ProtobufDeserializationContext; import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedData; import org.terasology.persistence.typeHandling.protobuf.ProtobufSerializationContext; import org.terasology.protobuf.EntityData; import java.util.Map; /** */ public class EventSerializer { private static final Logger logger = LoggerFactory.getLogger(ComponentSerializer.class); private EventLibrary eventLibrary; private TypeSerializationLibrary typeSerializationLibrary; private BiMap<Class<? extends Event>, Integer> idTable = ImmutableBiMap.<Class<? extends Event>, Integer>builder().build(); private SerializationContext serializationContext; private DeserializationContext deserializationContext; /** * Creates the event serializer. * * @param eventLibrary The event library used to provide information on each event and its fields. */ public EventSerializer(EventLibrary eventLibrary, TypeSerializationLibrary typeSerializationLibrary) { this.eventLibrary = eventLibrary; this.typeSerializationLibrary = typeSerializationLibrary; this.deserializationContext = new ProtobufDeserializationContext(typeSerializationLibrary); this.serializationContext = new ProtobufSerializationContext(typeSerializationLibrary); } /** * Sets the mapping between event classes and the ids that are used for serialization * * @param table */ public void setIdMapping(Map<Class<? extends Event>, Integer> table) { idTable = ImmutableBiMap.copyOf(table); } /** * Clears the mapping between event classes and ids. This causes event to be serialized with their event * name instead. */ public void removeIdMapping() { idTable = ImmutableBiMap.<Class<? extends Event>, Integer>builder().build(); } /** * @param eventData * @return The event described by the eventData * @throws org.terasology.persistence.typeHandling.DeserializationException if an error occurs when deserializing */ public Event deserialize(EntityData.Event eventData) { Class<? extends Event> eventClass = getEventClass(eventData); if (eventClass != null) { EventMetadata<?> 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); } } 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 = typeSerializationLibrary.getSerializerFor(eventMetadata); for (int i = 0; i < eventData.getFieldIds().size(); ++i) { byte fieldId = eventData.getFieldIds().byteAt(i); ReplicatedFieldMetadata<?, ?> fieldInfo = eventMetadata.getField(fieldId); if (fieldInfo == null) { logger.error("Unable to serialize field {}, out of bounds", fieldId); continue; } if (fieldInfo.isReplicated()) { serializer.deserializeOnto(targetEvent, fieldInfo, new ProtobufPersistedData(eventData.getFieldValue(i)), deserializationContext); } } return targetEvent; } /** * Serializes an event. * * @param event * @return The serialized event * @throws org.terasology.persistence.typeHandling.SerializationException if an error occurs during serialization */ public EntityData.Event serialize(Event event) { EventMetadata<?> eventMetadata = eventLibrary.getMetadata(event.getClass()); if (eventMetadata == null) { throw new SerializationException("Unregistered event type: " + event.getClass()); } else if (!eventMetadata.isConstructable()) { throw new SerializationException("Cannot serialize event '" + eventMetadata + "' - lacks default constructor so cannot be deserialized"); } EntityData.Event.Builder eventData = EntityData.Event.newBuilder(); serializeEventType(event, eventData); Serializer eventSerializer = typeSerializationLibrary.getSerializerFor(eventMetadata); ByteString.Output fieldIds = ByteString.newOutput(); for (ReplicatedFieldMetadata field : eventMetadata.getFields()) { if (field.isReplicated()) { EntityData.Value serializedValue = ((ProtobufPersistedData) eventSerializer.serialize(field, event, serializationContext)).getValue(); if (serializedValue != null) { eventData.addFieldValue(serializedValue); fieldIds.write(field.getId()); } } } eventData.setFieldIds(fieldIds.toByteString()); return eventData.build(); } private void serializeEventType(Event event, EntityData.Event.Builder eventData) { Integer compId = idTable.get(event.getClass()); eventData.setType(compId); } /** * 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) { if (eventData.hasType()) { EventMetadata<? extends Event> metadata = null; if (!idTable.isEmpty()) { Class<? extends Event> eventClass = idTable.inverse().get(eventData.getType()); if (eventClass != null) { metadata = eventLibrary.getMetadata(eventClass); } } if (metadata == null) { logger.warn("Unable to deserialize unknown event with id: {}", eventData.getType()); return null; } return metadata.getType(); } logger.warn("Unable to deserialize event, no type provided."); return null; } public Map<Class<? extends Event>, Integer> getIdMapping() { return ImmutableMap.copyOf(idTable); } }