/* * Copyright 2015 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.typeHandling; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.audio.StaticSound; import org.terasology.audio.StreamingSound; import org.terasology.engine.SimpleUri; import org.terasology.entitySystem.prefab.Prefab; import org.terasology.logic.behavior.asset.BehaviorTree; import org.terasology.math.IntegerRange; import org.terasology.math.geom.Rect2f; import org.terasology.math.geom.Rect2i; import org.terasology.math.Region3i; import org.terasology.math.geom.Vector2i; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.Vector2f; import org.terasology.math.geom.Vector3f; import org.terasology.math.geom.Vector3i; import org.terasology.math.geom.Vector4f; import org.terasology.naming.Name; import org.terasology.persistence.typeHandling.coreTypes.BooleanTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.ByteTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.DoubleTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.EnumTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.FloatTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.IntTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.ListTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.LongTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.MappedContainerTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.NumberTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.SetTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.StringMapTypeHandler; import org.terasology.persistence.typeHandling.coreTypes.StringTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.AssetTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.ColorTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.NameTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.PrefabTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.TextureRegionTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.IntegerRangeHandler; import org.terasology.persistence.typeHandling.mathTypes.Quat4fTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Rect2fTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Rect2iTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Region3iTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector2fTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector2iTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector3fTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector3iTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector4fTypeHandler; import org.terasology.reflection.MappedContainer; import org.terasology.reflection.copy.CopyStrategyLibrary; import org.terasology.reflection.metadata.ClassMetadata; import org.terasology.reflection.metadata.DefaultClassMetadata; import org.terasology.reflection.metadata.FieldMetadata; import org.terasology.reflection.reflect.ReflectFactory; import org.terasology.rendering.assets.animation.MeshAnimation; import org.terasology.rendering.assets.material.Material; import org.terasology.rendering.assets.mesh.Mesh; import org.terasology.rendering.assets.skeletalmesh.SkeletalMesh; import org.terasology.rendering.assets.texture.Texture; import org.terasology.rendering.assets.texture.TextureRegion; import org.terasology.rendering.assets.texture.TextureRegionAsset; import org.terasology.rendering.nui.Color; import org.terasology.rendering.nui.asset.UIElement; import org.terasology.utilities.ReflectionUtil; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Set; /** * A library of type handlers. This is used for the construction of class metadata. * This library should be initialised by adding a number of base type handlers, describing how to serialize each supported type. * It will then produce serializers for classes (through their ClassMetadata) on request. * */ public class TypeSerializationLibrary { private static final Logger logger = LoggerFactory.getLogger(TypeSerializationLibrary.class); private Map<Class<?>, TypeHandler<?>> typeHandlers = Maps.newHashMap(); private Set<Class<?>> coreTypeHandlers = Sets.newHashSet(); private ReflectFactory reflectFactory; private CopyStrategyLibrary copyStrategies; private Map<ClassMetadata<?, ?>, Serializer> serializerMap = Maps.newHashMap(); /** * @param factory The factory providing reflect implementation. * @param copyStrategies The provider of copy strategies */ public TypeSerializationLibrary(ReflectFactory factory, CopyStrategyLibrary copyStrategies) { this.reflectFactory = factory; this.copyStrategies = copyStrategies; add(Boolean.class, new BooleanTypeHandler()); add(Boolean.TYPE, new BooleanTypeHandler()); add(Byte.class, new ByteTypeHandler()); add(Byte.TYPE, new ByteTypeHandler()); add(Double.class, new DoubleTypeHandler()); add(Double.TYPE, new DoubleTypeHandler()); add(Float.class, new FloatTypeHandler()); add(Float.TYPE, new FloatTypeHandler()); add(Integer.class, new IntTypeHandler()); add(Integer.TYPE, new IntTypeHandler()); add(Long.class, new LongTypeHandler()); add(Long.TYPE, new LongTypeHandler()); add(String.class, new StringTypeHandler()); add(Number.class, new NumberTypeHandler()); } /** * Creates a copy of an existing serialization library. This copy is initialised with all type handlers that were added to the original, but does not retain any * serializers or type handlers that were generated. This can be used to override specific types handlers from another type serializer. * * @param original The original type serialization library to copy. */ public TypeSerializationLibrary(TypeSerializationLibrary original) { this.reflectFactory = original.reflectFactory; this.copyStrategies = original.copyStrategies; for (Class<?> type : original.coreTypeHandlers) { typeHandlers.put(type, original.typeHandlers.get(type)); coreTypeHandlers.add(type); } } public static TypeSerializationLibrary createDefaultLibrary(ReflectFactory factory, CopyStrategyLibrary copyStrategies) { TypeSerializationLibrary serializationLibrary = new TypeSerializationLibrary(factory, copyStrategies); serializationLibrary.add(Color.class, new ColorTypeHandler()); serializationLibrary.add(Quat4f.class, new Quat4fTypeHandler()); serializationLibrary.add(Texture.class, new AssetTypeHandler<>(Texture.class)); serializationLibrary.add(UIElement.class, new AssetTypeHandler<>(UIElement.class)); serializationLibrary.add(Mesh.class, new AssetTypeHandler<>(Mesh.class)); serializationLibrary.add(StaticSound.class, new AssetTypeHandler<>(StaticSound.class)); serializationLibrary.add(StreamingSound.class, new AssetTypeHandler<>(StreamingSound.class)); serializationLibrary.add(Material.class, new AssetTypeHandler<>(Material.class)); serializationLibrary.add(Name.class, new NameTypeHandler()); serializationLibrary.add(SkeletalMesh.class, new AssetTypeHandler<>(SkeletalMesh.class)); serializationLibrary.add(MeshAnimation.class, new AssetTypeHandler<>(MeshAnimation.class)); serializationLibrary.add(TextureRegion.class, new TextureRegionTypeHandler()); serializationLibrary.add(TextureRegionAsset.class, new TextureRegionTypeHandler()); serializationLibrary.add(Vector4f.class, new Vector4fTypeHandler()); serializationLibrary.add(Vector3f.class, new Vector3fTypeHandler()); serializationLibrary.add(Vector2f.class, new Vector2fTypeHandler()); serializationLibrary.add(Vector3i.class, new Vector3iTypeHandler()); serializationLibrary.add(Vector2i.class, new Vector2iTypeHandler()); serializationLibrary.add(Rect2i.class, new Rect2iTypeHandler()); serializationLibrary.add(Rect2f.class, new Rect2fTypeHandler()); serializationLibrary.add(Region3i.class, new Region3iTypeHandler()); serializationLibrary.add(Prefab.class, new PrefabTypeHandler()); serializationLibrary.add(BehaviorTree.class, new AssetTypeHandler<>(BehaviorTree.class)); serializationLibrary.add(IntegerRange.class, new IntegerRangeHandler()); return serializationLibrary; } /** * Obtains a serializer for the given type * * @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); if (serializer == null) { Map<FieldMetadata<?, ?>, TypeHandler> fieldHandlerMap = getFieldHandlerMap(type); serializer = new Serializer(type, fieldHandlerMap); serializerMap.put(type, serializer); } return serializer; } /** * Adds a type handler that will be to serialize a specified type. * If a type handler was previously registered for that type, it will be replaced with the new type handler. * Existing serializers will not be updated. * * @param type The type to handle. * @param handler The TypeHandler * @param <T> The type to handle. */ public <T> void add(Class<T> type, TypeHandler<? super T> handler) { typeHandlers.put(type, handler); coreTypeHandlers.add(type); } public ImmutableSet<Class<?>> getCoreTypes() { return ImmutableSet.copyOf(coreTypeHandlers); } public void clear() { typeHandlers.clear(); coreTypeHandlers.clear(); } // TODO: Refactor @SuppressWarnings("unchecked") public TypeHandler<?> getHandlerFor(Type genericType) { Class<?> typeClass = ReflectionUtil.getClassOfType(genericType); if (typeClass == null) { logger.error("Unabled to get class from type {}", genericType); return null; } if (Enum.class.isAssignableFrom(typeClass)) { return new EnumTypeHandler(typeClass); } else if (List.class.isAssignableFrom(typeClass)) { // For lists, createEntityRef the handler for the contained type and wrap in a list type handler Type parameter = ReflectionUtil.getTypeParameter(genericType, 0); if (parameter != null) { TypeHandler<?> innerHandler = getHandlerFor(parameter); if (innerHandler != null) { return new ListTypeHandler<>(innerHandler); } } logger.error("List field is not parametrized, or holds unsupported type"); return null; } else if (Set.class.isAssignableFrom(typeClass)) { // For sets: Type parameter = ReflectionUtil.getTypeParameter(genericType, 0); if (parameter != null) { TypeHandler<?> innerHandler = getHandlerFor(parameter); if (innerHandler != null) { return new SetTypeHandler<>(innerHandler); } } logger.error("Set field is not parametrized, or holds unsupported type"); return null; } else if (Map.class.isAssignableFrom(typeClass)) { // For Maps, createEntityRef the handler for the value type (and maybe key too?) Type keyParameter = ReflectionUtil.getTypeParameter(genericType, 0); Type contentsParameter = ReflectionUtil.getTypeParameter(genericType, 1); if (keyParameter != null && contentsParameter != null && String.class == keyParameter) { TypeHandler<?> valueHandler = getHandlerFor(contentsParameter); if (valueHandler != null) { return new StringMapTypeHandler<>(valueHandler); } } logger.error("Map field is not parametrized, does not have a String key, or holds unsupported values"); } else if (typeHandlers.containsKey(typeClass)) { // For known types, just use the handler return typeHandlers.get(typeClass); } else if (typeClass.getAnnotation(MappedContainer.class) != null && !Modifier.isAbstract(typeClass.getModifiers()) && !typeClass.isLocalClass() && !(typeClass.isMemberClass() && !Modifier.isStatic(typeClass.getModifiers()))) { try { ClassMetadata<?, ?> metadata = new DefaultClassMetadata<>(new SimpleUri(), typeClass, reflectFactory, copyStrategies); MappedContainerTypeHandler<?> mappedHandler = new MappedContainerTypeHandler(typeClass, getFieldHandlerMap(metadata)); typeHandlers.put(typeClass, mappedHandler); return mappedHandler; } catch (NoSuchMethodException e) { logger.error("Unable to register field of type {}: no publicly accessible default constructor", typeClass.getSimpleName()); return null; } } else { logger.error("Unable to register field of type {}: not a supported type or MappedContainer", typeClass.getSimpleName()); } return null; } private Map<FieldMetadata<?, ?>, TypeHandler> getFieldHandlerMap(ClassMetadata<?, ?> type) { Map<FieldMetadata<?, ?>, TypeHandler> handlerMap = Maps.newHashMap(); for (FieldMetadata<?, ?> field : type.getFields()) { TypeHandler<?> handler = getHandlerFor(field.getField().getGenericType()); if (handler != null) { handlerMap.put(field, handler); } else { logger.info("Unsupported field: '{}.{}'", type.getUri(), field.getName()); } } return handlerMap; } }