diff --git a/engine-tests/src/main/java/org/terasology/HeadlessEnvironment.java b/engine-tests/src/main/java/org/terasology/HeadlessEnvironment.java index 79db54b429f..f83220a2118 100644 --- a/engine-tests/src/main/java/org/terasology/HeadlessEnvironment.java +++ b/engine-tests/src/main/java/org/terasology/HeadlessEnvironment.java @@ -55,7 +55,7 @@ import org.terasology.network.internal.NetworkSystemImpl; import org.terasology.persistence.StorageManager; import org.terasology.persistence.internal.ReadWriteStorageManager; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.extensionTypes.BlockFamilyTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.BlockTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.CollisionGroupTypeHandler; @@ -64,6 +64,8 @@ import org.terasology.recording.RecordAndReplayCurrentStatus; import org.terasology.recording.RecordAndReplaySerializer; import org.terasology.recording.RecordAndReplayUtils; +import org.terasology.reflection.TypeRegistry; +import org.terasology.reflection.internal.TypeRegistryImpl; import org.terasology.rendering.assets.animation.MeshAnimation; import org.terasology.rendering.assets.animation.MeshAnimationImpl; import org.terasology.rendering.assets.atlas.Atlas; @@ -164,7 +166,7 @@ protected void setupEntitySystem() { protected void setupCollisionManager() { CollisionGroupManager collisionGroupManager = new CollisionGroupManager(); context.put(CollisionGroupManager.class, collisionGroupManager); - context.get(TypeSerializationLibrary.class).add(CollisionGroup.class, new CollisionGroupTypeHandler(collisionGroupManager)); + context.get(TypeHandlerLibrary.class).addTypeHandler(CollisionGroup.class, new CollisionGroupTypeHandler(collisionGroupManager)); } @Override @@ -172,9 +174,9 @@ protected void setupBlockManager(AssetManager assetManager) { WorldAtlas worldAtlas = new NullWorldAtlas(); BlockManagerImpl blockManager = new BlockManagerImpl(worldAtlas, assetManager); context.put(BlockManager.class, blockManager); - TypeSerializationLibrary typeSerializationLibrary = context.get(TypeSerializationLibrary.class); - typeSerializationLibrary.add(BlockFamily.class, new BlockFamilyTypeHandler(blockManager)); - typeSerializationLibrary.add(Block.class, new BlockTypeHandler(blockManager)); + TypeHandlerLibrary typeHandlerLibrary = context.get(TypeHandlerLibrary.class); + typeHandlerLibrary.addTypeHandler(BlockFamily.class, new BlockFamilyTypeHandler(blockManager)); + typeHandlerLibrary.addTypeHandler(Block.class, new BlockTypeHandler(blockManager)); } @Override @@ -267,7 +269,10 @@ protected void setupConfig() { @Override protected void setupModuleManager(Set moduleNames) throws Exception { - ModuleManager moduleManager = ModuleManagerFactory.create(); + TypeRegistryImpl typeRegistry = new TypeRegistryImpl(); + context.put(TypeRegistry.class, typeRegistry); + + ModuleManager moduleManager = ModuleManagerFactory.create(typeRegistry); ModuleRegistry registry = moduleManager.getRegistry(); DependencyResolver resolver = new DependencyResolver(registry); diff --git a/engine-tests/src/main/java/org/terasology/ModuleEnvironmentTest.java b/engine-tests/src/main/java/org/terasology/ModuleEnvironmentTest.java new file mode 100644 index 00000000000..cf746d0e7cc --- /dev/null +++ b/engine-tests/src/main/java/org/terasology/ModuleEnvironmentTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 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; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.nio.file.ShrinkWrapFileSystems; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Before; +import org.mockito.Mockito; +import org.terasology.engine.module.ModuleManager; +import org.terasology.engine.paths.PathManager; +import org.terasology.module.DependencyResolver; +import org.terasology.module.ResolutionResult; +import org.terasology.reflection.TypeRegistry; +import org.terasology.reflection.internal.TypeRegistryImpl; +import org.terasology.testUtil.ModuleManagerFactory; + +import java.nio.file.FileSystem; + +import static org.junit.Assume.assumeTrue; + +public abstract class ModuleEnvironmentTest { + protected ModuleManager moduleManager; + protected TypeRegistry typeRegistry; + + @Before + public void before() throws Exception { + final JavaArchive homeArchive = ShrinkWrap.create(JavaArchive.class); + final FileSystem vfs = ShrinkWrapFileSystems.newFileSystem(homeArchive); + PathManager.getInstance().useOverrideHomePath(vfs.getPath("")); + + typeRegistry = new TypeRegistryImpl(); + moduleManager = ModuleManagerFactory.create((TypeRegistryImpl) typeRegistry); + + DependencyResolver resolver = new DependencyResolver(moduleManager.getRegistry()); + ResolutionResult result = resolver.resolve(moduleManager.getRegistry().getModuleIds()); + + assumeTrue(result.isSuccess()); + + moduleManager.loadEnvironment(result.getModules(), true); + + setup(); + } + + protected void setup() { + + } +} diff --git a/engine-tests/src/main/java/org/terasology/TerasologyTestingEnvironment.java b/engine-tests/src/main/java/org/terasology/TerasologyTestingEnvironment.java index 0a4aaaaa748..b9f57c196ba 100644 --- a/engine-tests/src/main/java/org/terasology/TerasologyTestingEnvironment.java +++ b/engine-tests/src/main/java/org/terasology/TerasologyTestingEnvironment.java @@ -45,6 +45,7 @@ import org.terasology.recording.RecordAndReplaySerializer; import org.terasology.recording.RecordAndReplayUtils; import org.terasology.recording.RecordedEventStore; +import org.terasology.reflection.TypeRegistry; import org.terasology.world.block.BlockManager; import org.terasology.world.chunks.blockdata.ExtraBlockDataManager; @@ -104,7 +105,7 @@ public void setup() throws Exception { context.put(CharacterStateEventPositionMap.class, characterStateEventPositionMap); DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList = new DirectionAndOriginPosRecorderList(); context.put(DirectionAndOriginPosRecorderList.class, directionAndOriginPosRecorderList); - RecordAndReplaySerializer recordAndReplaySerializer = new RecordAndReplaySerializer(engineEntityManager, recordedEventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, moduleManager.getEnvironment()); + RecordAndReplaySerializer recordAndReplaySerializer = new RecordAndReplaySerializer(engineEntityManager, recordedEventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, moduleManager, context.get(TypeRegistry.class)); context.put(RecordAndReplaySerializer.class, recordAndReplaySerializer); Path savePath = PathManager.getInstance().getSavePath("world1"); diff --git a/engine-tests/src/main/java/org/terasology/testUtil/ModuleManagerFactory.java b/engine-tests/src/main/java/org/terasology/testUtil/ModuleManagerFactory.java index 69f7e55fab8..055199e4a7e 100644 --- a/engine-tests/src/main/java/org/terasology/testUtil/ModuleManagerFactory.java +++ b/engine-tests/src/main/java/org/terasology/testUtil/ModuleManagerFactory.java @@ -16,6 +16,7 @@ package org.terasology.testUtil; import com.google.common.collect.Sets; +import org.mockito.Mockito; import org.terasology.engine.TerasologyConstants; import org.terasology.engine.module.ModuleManager; import org.terasology.engine.module.ModuleManagerImpl; @@ -23,9 +24,11 @@ import org.terasology.module.ModuleMetadata; import org.terasology.module.ModuleMetadataReader; import org.terasology.naming.Name; +import org.terasology.reflection.internal.TypeRegistryImpl; import java.io.InputStreamReader; import java.io.Reader; +import java.util.Set; /** */ @@ -33,13 +36,23 @@ public final class ModuleManagerFactory { private ModuleManagerFactory() { } + // Named "create" for legacy reasons, but does more than create public static ModuleManager create() throws Exception { - ModuleManager moduleManager = new ModuleManagerImpl(""); + ModuleManager manager = create(Mockito.mock(TypeRegistryImpl.class)); + manager.loadEnvironment(Sets.newHashSet(manager.getRegistry().getLatestModuleVersion(new Name("engine"))), true); + return manager; + } + + /** + * Creates a new {@link ModuleManager} instance, but does not + * {@link ModuleManager#loadEnvironment(Set, boolean) load it's environment}. + */ + public static ModuleManager create(TypeRegistryImpl typeRegistry) throws Exception { + ModuleManager moduleManager = new ModuleManagerImpl("", typeRegistry); try (Reader reader = new InputStreamReader(ModuleManagerFactory.class.getResourceAsStream("/module.txt"), TerasologyConstants.CHARSET)) { ModuleMetadata metadata = new ModuleMetadataReader().read(reader); moduleManager.getRegistry().add(ClasspathModule.create(metadata, ModuleManagerFactory.class)); } - moduleManager.loadEnvironment(Sets.newHashSet(moduleManager.getRegistry().getLatestModuleVersion(new Name("engine"))), true); return moduleManager; } } diff --git a/engine-tests/src/test/java/org/terasology/entitySystem/PojoEventSystemTests.java b/engine-tests/src/test/java/org/terasology/entitySystem/PojoEventSystemTests.java index b936356bb08..2ee9cd05857 100644 --- a/engine-tests/src/test/java/org/terasology/entitySystem/PojoEventSystemTests.java +++ b/engine-tests/src/test/java/org/terasology/entitySystem/PojoEventSystemTests.java @@ -18,6 +18,7 @@ import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; +import org.reflections.Reflections; import org.terasology.context.internal.ContextImpl; import org.terasology.engine.SimpleUri; import org.terasology.entitySystem.entity.EntityRef; @@ -36,12 +37,9 @@ import org.terasology.entitySystem.systems.BaseComponentSystem; import org.terasology.network.NetworkMode; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.recording.EventCatcher; import org.terasology.recording.RecordAndReplayCurrentStatus; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.reflect.ReflectFactory; -import org.terasology.reflection.reflect.ReflectionReflectFactory; import org.terasology.registry.CoreRegistry; import java.util.List; @@ -64,9 +62,9 @@ public class PojoEventSystemTests { public void setup() { ContextImpl context = new ContextImpl(); CoreRegistry.setContext(context); - ReflectFactory reflectFactory = new ReflectionReflectFactory(); - CopyStrategyLibrary copyStrategies = new CopyStrategyLibrary(reflectFactory); - TypeSerializationLibrary serializationLibrary = new TypeSerializationLibrary(reflectFactory, copyStrategies); + + Reflections reflections = new Reflections(getClass().getClassLoader()); + TypeHandlerLibrary serializationLibrary = new TypeHandlerLibrary(reflections); EntitySystemLibrary entitySystemLibrary = new EntitySystemLibrary(context, serializationLibrary); compLibrary = entitySystemLibrary.getComponentLibrary(); diff --git a/engine-tests/src/test/java/org/terasology/entitySystem/PojoPrefabManagerTest.java b/engine-tests/src/test/java/org/terasology/entitySystem/PojoPrefabManagerTest.java index 2201735cc2c..1cf6bd8dff6 100644 --- a/engine-tests/src/test/java/org/terasology/entitySystem/PojoPrefabManagerTest.java +++ b/engine-tests/src/test/java/org/terasology/entitySystem/PojoPrefabManagerTest.java @@ -17,6 +17,7 @@ import org.junit.Before; import org.junit.Test; +import org.reflections.Reflections; import org.terasology.assets.AssetFactory; import org.terasology.assets.ResourceUrn; import org.terasology.assets.management.AssetManager; @@ -32,12 +33,9 @@ import org.terasology.entitySystem.stubs.StringComponent; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.Vector3f; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.mathTypes.Quat4fTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector3fTypeHandler; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.reflect.ReflectFactory; -import org.terasology.reflection.reflect.ReflectionReflectFactory; import org.terasology.registry.CoreRegistry; import org.terasology.testUtil.ModuleManagerFactory; import org.terasology.utilities.Assets; @@ -60,11 +58,13 @@ public void setup() throws Exception { ContextImpl context = new ContextImpl(); CoreRegistry.setContext(context); ModuleManager moduleManager = ModuleManagerFactory.create(); - ReflectFactory reflectFactory = new ReflectionReflectFactory(); - CopyStrategyLibrary copyStrategyLibrary = new CopyStrategyLibrary(reflectFactory); - TypeSerializationLibrary lib = new TypeSerializationLibrary(reflectFactory, copyStrategyLibrary); - lib.add(Vector3f.class, new Vector3fTypeHandler()); - lib.add(Quat4f.class, new Quat4fTypeHandler()); + + Reflections reflections = new Reflections(getClass().getClassLoader()); + TypeHandlerLibrary lib = new TypeHandlerLibrary(reflections); + + lib.addTypeHandler(Vector3f.class, new Vector3fTypeHandler()); + lib.addTypeHandler(Quat4f.class, new Quat4fTypeHandler()); + entitySystemLibrary = new EntitySystemLibrary(context, lib); componentLibrary = entitySystemLibrary.getComponentLibrary(); diff --git a/engine-tests/src/test/java/org/terasology/entitySystem/PrefabTest.java b/engine-tests/src/test/java/org/terasology/entitySystem/PrefabTest.java index d4a5f343757..187cc5ac592 100644 --- a/engine-tests/src/test/java/org/terasology/entitySystem/PrefabTest.java +++ b/engine-tests/src/test/java/org/terasology/entitySystem/PrefabTest.java @@ -40,7 +40,7 @@ import org.terasology.math.Side; import org.terasology.network.NetworkMode; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.recording.RecordAndReplayCurrentStatus; import org.terasology.registry.CoreRegistry; import org.terasology.testUtil.ModuleManagerFactory; @@ -78,8 +78,8 @@ public void setup() throws Exception { assetTypeManager.registerCoreAssetType(Prefab.class, (AssetFactory) PojoPrefab::new, "prefabs"); ComponentLibrary componentLibrary = context.get(ComponentLibrary.class); - TypeSerializationLibrary typeSerializationLibrary = context.get(TypeSerializationLibrary.class); - PrefabFormat prefabFormat = new PrefabFormat(componentLibrary, typeSerializationLibrary); + TypeHandlerLibrary typeHandlerLibrary = context.get(TypeHandlerLibrary.class); + PrefabFormat prefabFormat = new PrefabFormat(componentLibrary, typeHandlerLibrary); assetTypeManager.registerCoreFormat(Prefab.class, prefabFormat); assetTypeManager.switchEnvironment(moduleManager.getEnvironment()); context.put(AssetManager.class, assetTypeManager.getAssetManager()); diff --git a/engine-tests/src/test/java/org/terasology/entitySystem/metadata/ComponentMetadataTest.java b/engine-tests/src/test/java/org/terasology/entitySystem/metadata/ComponentMetadataTest.java index 48cd84cbb00..c800dd70f4a 100644 --- a/engine-tests/src/test/java/org/terasology/entitySystem/metadata/ComponentMetadataTest.java +++ b/engine-tests/src/test/java/org/terasology/entitySystem/metadata/ComponentMetadataTest.java @@ -18,12 +18,13 @@ import org.junit.Before; import org.junit.Test; +import org.reflections.Reflections; import org.terasology.context.Context; import org.terasology.context.internal.ContextImpl; import org.terasology.engine.SimpleUri; import org.terasology.entitySystem.stubs.OwnerComponent; import org.terasology.entitySystem.stubs.StringComponent; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.reflection.copy.CopyStrategyLibrary; import org.terasology.reflection.reflect.ReflectFactory; import org.terasology.reflection.reflect.ReflectionReflectFactory; @@ -48,7 +49,8 @@ public void prepare() { @Test public void testStaticFieldsIgnored() { - EntitySystemLibrary entitySystemLibrary = new EntitySystemLibrary(context, new TypeSerializationLibrary(reflectFactory, copyStrategies)); + Reflections reflections = new Reflections(getClass().getClassLoader()); + EntitySystemLibrary entitySystemLibrary = new EntitySystemLibrary(context, new TypeHandlerLibrary(reflections)); ComponentLibrary lib = entitySystemLibrary.getComponentLibrary(); lib.register(new SimpleUri("unittest:string"), StringComponent.class); ComponentMetadata metadata = lib.getMetadata(StringComponent.class); @@ -57,7 +59,8 @@ public void testStaticFieldsIgnored() { @Test public void testOwnsReferencesPopulated() { - EntitySystemLibrary entitySystemLibrary = new EntitySystemLibrary(context, new TypeSerializationLibrary(reflectFactory, copyStrategies)); + Reflections reflections = new Reflections(getClass().getClassLoader()); + EntitySystemLibrary entitySystemLibrary = new EntitySystemLibrary(context, new TypeHandlerLibrary(reflections)); ComponentLibrary lib = entitySystemLibrary.getComponentLibrary(); lib.register(new SimpleUri("unittest:owner"), OwnerComponent.class); ComponentMetadata metadata = lib.getMetadata(OwnerComponent.class); diff --git a/engine-tests/src/test/java/org/terasology/persistence/ComponentSerializerTest.java b/engine-tests/src/test/java/org/terasology/persistence/ComponentSerializerTest.java index e929280a744..77548c62d12 100644 --- a/engine-tests/src/test/java/org/terasology/persistence/ComponentSerializerTest.java +++ b/engine-tests/src/test/java/org/terasology/persistence/ComponentSerializerTest.java @@ -19,6 +19,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.reflections.Reflections; import org.terasology.context.Context; import org.terasology.context.internal.ContextImpl; import org.terasology.engine.SimpleUri; @@ -34,20 +35,15 @@ import org.terasology.math.geom.Vector3f; import org.terasology.network.NetworkSystem; import org.terasology.persistence.serializers.ComponentSerializer; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.mathTypes.Quat4fTypeHandler; import org.terasology.persistence.typeHandling.mathTypes.Vector3fTypeHandler; import org.terasology.protobuf.EntityData; import org.terasology.recording.RecordAndReplayCurrentStatus; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.reflect.ReflectFactory; -import org.terasology.reflection.reflect.ReflectionReflectFactory; import org.terasology.registry.CoreRegistry; import org.terasology.testUtil.ModuleManagerFactory; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; /** @@ -55,8 +51,6 @@ public class ComponentSerializerTest { private static ModuleManager moduleManager; private ComponentSerializer componentSerializer; - private ReflectFactory reflectFactory = new ReflectionReflectFactory(); - private CopyStrategyLibrary copyStrategyLibrary = new CopyStrategyLibrary(reflectFactory); private Context context; @BeforeClass @@ -71,9 +65,11 @@ public void setup() { context.put(ModuleManager.class, moduleManager); CoreRegistry.setContext(context); - TypeSerializationLibrary serializationLibrary = new TypeSerializationLibrary(reflectFactory, copyStrategyLibrary); - serializationLibrary.add(Vector3f.class, new Vector3fTypeHandler()); - serializationLibrary.add(Quat4f.class, new Quat4fTypeHandler()); + Reflections reflections = new Reflections(getClass().getClassLoader()); + TypeHandlerLibrary serializationLibrary = new TypeHandlerLibrary(reflections); + + serializationLibrary.addTypeHandler(Vector3f.class, new Vector3fTypeHandler()); + serializationLibrary.addTypeHandler(Quat4f.class, new Quat4fTypeHandler()); NetworkSystem networkSystem = mock(NetworkSystem.class); context.put(NetworkSystem.class, networkSystem); diff --git a/engine-tests/src/test/java/org/terasology/persistence/internal/StorageManagerTest.java b/engine-tests/src/test/java/org/terasology/persistence/internal/StorageManagerTest.java index 850a6c62100..9405ccfbe41 100644 --- a/engine-tests/src/test/java/org/terasology/persistence/internal/StorageManagerTest.java +++ b/engine-tests/src/test/java/org/terasology/persistence/internal/StorageManagerTest.java @@ -28,6 +28,7 @@ import org.terasology.assets.ResourceUrn; import org.terasology.assets.management.AssetManager; import org.terasology.engine.bootstrap.EntitySystemSetupUtil; +import org.terasology.engine.module.ModuleManager; import org.terasology.engine.paths.PathManager; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.internal.EngineEntityManager; @@ -50,6 +51,7 @@ import org.terasology.recording.RecordAndReplaySerializer; import org.terasology.recording.RecordAndReplayUtils; import org.terasology.recording.RecordedEventStore; +import org.terasology.reflection.TypeRegistry; import org.terasology.registry.CoreRegistry; import org.terasology.world.WorldProvider; import org.terasology.world.block.Block; @@ -108,15 +110,19 @@ public void setup() throws Exception { assert !Files.isRegularFile(vfs.getPath("global.dat")); entityManager = context.get(EngineEntityManager.class); - moduleEnvironment = context.get(ModuleEnvironment.class); + moduleEnvironment = mock(ModuleEnvironment.class); blockManager = context.get(BlockManager.class); extraDataManager = context.get(ExtraBlockDataManager.class); + ModuleManager moduleManager = mock(ModuleManager.class); + + when(moduleManager.getEnvironment()).thenReturn(moduleEnvironment); + RecordedEventStore recordedEventStore = new RecordedEventStore(); recordAndReplayUtils = new RecordAndReplayUtils(); CharacterStateEventPositionMap characterStateEventPositionMap = new CharacterStateEventPositionMap(); DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList = new DirectionAndOriginPosRecorderList(); - recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, recordedEventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, moduleEnvironment); + recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, recordedEventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, moduleManager, mock(TypeRegistry.class)); recordAndReplayCurrentStatus = context.get(RecordAndReplayCurrentStatus.class); diff --git a/engine-tests/src/test/java/org/terasology/persistence/serializers/TypeSerializerTest.java b/engine-tests/src/test/java/org/terasology/persistence/serializers/TypeSerializerTest.java new file mode 100644 index 00000000000..23e3c4bf661 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/serializers/TypeSerializerTest.java @@ -0,0 +1,230 @@ +/* + * Copyright 2018 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.Lists; +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.terasology.ModuleEnvironmentTest; +import org.terasology.math.geom.Vector3f; +import org.terasology.naming.Name; +import org.terasology.persistence.ModuleContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.annotations.SerializedName; +import org.terasology.reflection.TypeInfo; +import org.terasology.rendering.nui.Color; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; + +public class TypeSerializerTest extends ModuleEnvironmentTest { + private static final SomeClass INSTANCE = new SomeClass<>(0xdeadbeef); + private static final String INSTANCE_JSON = "{\"generic-t\":-559038737,\"list\":[50,51,-52,-53],\"animals\":[{\"class\":\"org.terasology.persistence.serializers.TypeSerializerTest$Dog\",\"tailPosition\":[3.15,54.51,-0.001],\"data\":{\"class\":\"java.lang.Integer\",\"content\":1}},{\"class\":\"org.terasology.persistence.serializers.TypeSerializerTest$Cheetah\",\"spotColor\":[255,0,255,255],\"data\":{\"class\":\"java.lang.Integer\",\"content\":2}}]}"; + + static { + INSTANCE.list.addAll(Lists.newArrayList(50, 51, -52, -53)); + + INSTANCE.animals.add(new Dog<>(1, new Vector3f(3.15f, 54.51f, -0.001f))); + + INSTANCE.animals.add(new Cheetah<>(2, Color.MAGENTA)); + } + + private TypeHandlerLibrary typeHandlerLibrary; + private ProtobufSerializer protobufSerializer; + private GsonSerializer gsonSerializer; + + @Override + public void setup() { + ModuleContext.setContext(moduleManager.getEnvironment().get(new Name("unittest"))); + + typeHandlerLibrary = TypeHandlerLibrary.forModuleEnvironment(moduleManager, typeRegistry); + + protobufSerializer = new ProtobufSerializer(typeHandlerLibrary); + gsonSerializer = new GsonSerializer(typeHandlerLibrary); + } + + @Test + public void testJsonSerialize() { + String serializedJson = gsonSerializer.toJson(INSTANCE, new TypeInfo>() { + }); + assertEquals(INSTANCE_JSON, serializedJson); + } + + @Test + public void testDeserialize() { + SomeClass deserialized = + gsonSerializer.fromJson(INSTANCE_JSON, new TypeInfo>() { + }); + + assertEquals(INSTANCE, deserialized); + } + + @Test + public void testSerializeDeserialize() throws IOException { + byte[] bytes = protobufSerializer.toBytes(INSTANCE, new TypeInfo>() { + }); + + SomeClass deserializedInstance = + protobufSerializer.fromBytes(bytes, new TypeInfo>() { + }); + + assertEquals(INSTANCE, deserializedInstance); + } + + private static class SomeClass { + @SerializedName("generic-t") + private T data; + private List list = Lists.newArrayList(); + private Set> animals = Sets.newHashSet(); + + private SomeClass(T data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SomeClass someClass = (SomeClass) o; + return Objects.equals(data, someClass.data) && + Objects.equals(list, someClass.list) && + Objects.equals(animals, someClass.animals); + } + + @Override + public int hashCode() { + return Objects.hash(data, list, animals); + } + + @Override + public String toString() { + return "SomeClass{" + + "data=" + data + + ", list=" + list + + ", animals=" + animals + + '}'; + } + } + + private static class Animal { + protected final T data; + + private Animal(T data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Animal animal = (Animal) o; + return Objects.equals(data, animal.data); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + } + + private static class Dog extends Animal { + private final Vector3f tailPosition; + + private Dog(T data, Vector3f tailPosition) { + super(data); + this.tailPosition = tailPosition; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Dog dog = (Dog) o; + return Objects.equals(tailPosition, dog.tailPosition); + } + + @Override + public String toString() { + return "Dog{" + + "name='" + data + '\'' + + ", tailPosition=" + tailPosition + + '}'; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), tailPosition); + } + } + + private static class Cheetah extends Animal { + private final Color spotColor; + + private Cheetah(T data, Color spotColor) { + super(data); + this.spotColor = spotColor; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Cheetah cheetah = (Cheetah) o; + return Objects.equals(spotColor, cheetah.spotColor); + } + + @Override + public String toString() { + return "Cheetah{" + + "name='" + data + '\'' + + ", spotColor=" + spotColor + + '}'; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), spotColor); + } + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/FutureTypeHandlerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/FutureTypeHandlerTest.java new file mode 100644 index 00000000000..0385875f1d8 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/FutureTypeHandlerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019 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.Lists; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.reflections.Reflections; +import org.terasology.reflection.TypeInfo; + +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.both; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class FutureTypeHandlerTest { + private final Reflections reflections = new Reflections(getClass().getClassLoader()); + + private final TypeHandlerLibrary typeHandlerLibrary = + spy(TypeHandlerLibrary.withReflections(reflections)); + + private static class RecursiveType { + final T data; + final List> children; + + @SafeVarargs + private RecursiveType(T data, RecursiveType... children) { + this.data = data; + this.children = Lists.newArrayList(children); + } + } + + private class ResultCaptor implements Answer { + private T result = null; + public T getResult() { + return result; + } + + @Override + public T answer(InvocationOnMock invocationOnMock) throws Throwable { + result = (T) invocationOnMock.callRealMethod(); + return result; + } + } + + @Test + public void testRecursiveType() { + ResultCaptor>>> resultCaptor = new ResultCaptor<>(); + + doAnswer(resultCaptor).when(typeHandlerLibrary).getTypeHandler( + eq(new TypeInfo>() {}.getType()) + ); + + TypeHandler> typeHandler = + typeHandlerLibrary.getTypeHandler( + new TypeInfo>() {} + ).get(); + + verify(typeHandlerLibrary, times(1)).getTypeHandler( + eq(new TypeInfo>() {}.getType()) + ); + + assertThat(resultCaptor.getResult().get(), + both(notNullValue()).and(instanceOf(FutureTypeHandler.class))); + + FutureTypeHandler> future = + (FutureTypeHandler>) resultCaptor.getResult().get(); + + assertEquals(typeHandler, future.typeHandler); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/TypeHandlerLibraryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/TypeHandlerLibraryTest.java new file mode 100644 index 00000000000..06bcd243e6e --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/TypeHandlerLibraryTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 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 org.junit.Test; +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.coreTypes.CollectionTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.EnumTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.ObjectFieldMapTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.RuntimeDelegatingTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.StringMapTypeHandler; +import org.terasology.reflection.MappedContainer; +import org.terasology.reflection.TypeInfo; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TypeHandlerLibraryTest { + private final Reflections reflections = new Reflections(getClass().getClassLoader()); + private final TypeHandlerLibrary typeHandlerLibrary = new TypeHandlerLibrary(reflections); + + private enum AnEnum {} + + @MappedContainer + private static class AMappedContainer {} + + @Test + public void testEnumHandler() { + TypeHandler handler = typeHandlerLibrary.getTypeHandler(AnEnum.class).get(); + + assertTrue(handler instanceof EnumTypeHandler); + } + + @Test + public void testMappedContainerHandler() { + TypeHandler handler = typeHandlerLibrary.getTypeHandler(AMappedContainer.class).get(); + + assertTrue(handler instanceof ObjectFieldMapTypeHandler); + } + + @Test + public void testCollectionHandler() { + TypeHandler> setHandler = + typeHandlerLibrary.getTypeHandler(new TypeInfo>() {}).get(); + + assertTrue(setHandler instanceof CollectionTypeHandler); + + TypeHandler> listHandler = + typeHandlerLibrary.getTypeHandler(new TypeInfo>() {}).get(); + + assertTrue(listHandler instanceof CollectionTypeHandler); + + TypeHandler> queueHandler = + typeHandlerLibrary.getTypeHandler(new TypeInfo>() {}).get(); + + assertTrue(queueHandler instanceof CollectionTypeHandler); + } + + @Test + public void testStringMapHandler() { + TypeHandler> handler = + typeHandlerLibrary.getTypeHandler(new TypeInfo>() {}).get(); + + assertTrue(handler instanceof StringMapTypeHandler); + } + + @Test + public void testInvalidTypeHandler() { + Optional>> handler = + typeHandlerLibrary.getTypeHandler(new TypeInfo>() {}); + + assertFalse(handler.isPresent()); + } + + @Test + public void testGetBaseTypeHandler() { + TypeHandler handler = typeHandlerLibrary.getBaseTypeHandler(TypeInfo.of(Integer.class)); + + assertTrue(handler instanceof RuntimeDelegatingTypeHandler); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java new file mode 100644 index 00000000000..34133b3f439 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2018 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.coreTypes; + +import com.google.common.collect.Queues; +import com.google.gson.JsonArray; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.stubbing.Answer; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.gson.GsonPersistedDataArray; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.reflect.ObjectConstructor; + +import java.util.Collection; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ArrayTypeHandlerTest { + private static final int ARRAY_SIZE = 500; + + @Test + public void testSerialize() { + IntTypeHandler elementTypeHandler = mock(IntTypeHandler.class); + + ArrayTypeHandler typeHandler = new ArrayTypeHandler<>( + elementTypeHandler, + TypeInfo.of(Integer.class) + ); + + Integer[] array = new Integer[ARRAY_SIZE]; + final int[] c = {0}; + Collections.nCopies(array.length, -1).forEach(i -> array[c[0]++] = i); + + PersistedDataSerializer context = mock(PersistedDataSerializer.class); + + typeHandler.serialize(array, context); + + verify(elementTypeHandler, times(array.length)).serialize(any(), any()); + + verify(context).serialize(argThat((ArgumentMatcher>) argument -> + argument instanceof Collection && ((Collection) argument).size() == array.length)); + } + + @Test + public void testDeserialize() { + IntTypeHandler elementTypeHandler = mock(IntTypeHandler.class); + + ArrayTypeHandler typeHandler = new ArrayTypeHandler<>( + elementTypeHandler, + TypeInfo.of(Integer.class) + ); + + JsonArray jsonArray = new JsonArray(); + + for (Integer i : Collections.nCopies(ARRAY_SIZE, -1)) { + jsonArray.add(i); + } + + typeHandler.deserialize(new GsonPersistedDataArray(jsonArray)); + + verify(elementTypeHandler, times(jsonArray.size())).deserialize(any()); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CharacterTypeHandlerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CharacterTypeHandlerTest.java new file mode 100644 index 00000000000..9bdb40d3892 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CharacterTypeHandlerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 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.coreTypes; + +import org.junit.Test; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.inMemory.PersistedInteger; +import org.terasology.persistence.typeHandling.inMemory.PersistedString; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CharacterTypeHandlerTest { + CharacterTypeHandler typeHandler = new CharacterTypeHandler(); + + @Test + public void testSerialize() { + PersistedDataSerializer serializer = mock(PersistedDataSerializer.class); + + char linefeedChar = '\n'; + + typeHandler.serializeNonNull(linefeedChar, serializer); + + verify(serializer).serialize(eq("\n")); + } + + @Test + public void testDeserialize() { + Optional deserializedLinefeed = typeHandler.deserialize(new PersistedString("\n")); + + assertTrue(deserializedLinefeed.isPresent()); + assertEquals('\n', (char) deserializedLinefeed.get()); + + Optional deserializedInteger = typeHandler.deserialize(new PersistedInteger((int) '\n')); + + assertFalse(deserializedInteger.isPresent()); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java new file mode 100644 index 00000000000..abd63f99a01 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018 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.coreTypes; + +import com.google.common.collect.Queues; +import com.google.gson.JsonArray; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.stubbing.Answer; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.gson.GsonPersistedDataArray; +import org.terasology.reflection.reflect.ObjectConstructor; + +import java.util.Collection; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + +public class CollectionTypeHandlerTest { + @Test + public void testSerialize() { + IntTypeHandler elementTypeHandler = mock(IntTypeHandler.class); + + ObjectConstructor> constructor = Queues::newArrayDeque; + + CollectionTypeHandler typeHandler = new CollectionTypeHandler<>( + elementTypeHandler, + constructor + ); + + Collection collection = constructor.construct(); + collection.addAll(Collections.nCopies(500, -1)); + + PersistedDataSerializer context = mock(PersistedDataSerializer.class); + + typeHandler.serialize(collection, context); + + verify(elementTypeHandler, times(collection.size())).serialize(any(), any()); + + verify(context).serialize(argThat(new ArgumentMatcher>() { + @Override + public boolean matches(Iterable argument) { + return argument instanceof Collection && ((Collection) argument).size() == collection.size(); + } + })); + } + + @Test + public void testDeserialize() { + IntTypeHandler elementTypeHandler = mock(IntTypeHandler.class); + + ObjectConstructor> constructor = mock(ObjectConstructor.class); + when(constructor.construct()).then((Answer>) invocation -> Queues.newArrayDeque()); + + CollectionTypeHandler typeHandler = new CollectionTypeHandler<>( + elementTypeHandler, + constructor + ); + + JsonArray jsonArray = new JsonArray(); + + for (Integer i : Collections.nCopies(500, -1)) { + jsonArray.add(i); + } + + typeHandler.deserialize(new GsonPersistedDataArray(jsonArray)); + + verify(constructor).construct(); + + verify(elementTypeHandler, times(jsonArray.size())).deserialize(any()); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandlerSerializerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandlerSerializerTest.java index 749b122910e..b3bdc8a2f59 100644 --- a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandlerSerializerTest.java +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandlerSerializerTest.java @@ -16,11 +16,11 @@ package org.terasology.persistence.typeHandling.coreTypes; import org.junit.Test; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -37,15 +37,13 @@ public void testNullValue() throws Exception { PersistedData nullData = mock(PersistedData.class); when(nullData.isNull()).thenReturn(true); - SerializationContext serializationContext = mock(SerializationContext.class); - when(serializationContext.createNull()).thenReturn(nullData); + PersistedDataSerializer persistedDataSerializer = mock(PersistedDataSerializer.class); + when(persistedDataSerializer.serializeNull()).thenReturn(nullData); EnumTypeHandler handler = new EnumTypeHandler<>(TestEnum.class); - PersistedData serializedNull = handler.serialize(null, serializationContext); + PersistedData serializedNull = handler.serialize(null, persistedDataSerializer); assertEquals(nullData, serializedNull); - DeserializationContext deserializationContext = mock(DeserializationContext.class); - TestEnum deserializedValue = handler.deserialize(nullData, deserializationContext); - assertEquals(null, deserializedValue); + assertFalse(handler.deserialize(nullData).isPresent()); } @Test @@ -54,14 +52,13 @@ public void testNonNullValue() throws Exception { when(data.getAsString()).thenReturn(TestEnum.NON_NULL.toString()); when(data.isString()).thenReturn(true); - SerializationContext serializationContext = mock(SerializationContext.class); - when(serializationContext.create(TestEnum.NON_NULL.toString())).thenReturn(data); + PersistedDataSerializer persistedDataSerializer = mock(PersistedDataSerializer.class); + when(persistedDataSerializer.serialize(TestEnum.NON_NULL.toString())).thenReturn(data); EnumTypeHandler handler = new EnumTypeHandler<>(TestEnum.class); - PersistedData serializedNonNull = handler.serialize(TestEnum.NON_NULL, serializationContext); + PersistedData serializedNonNull = handler.serialize(TestEnum.NON_NULL, persistedDataSerializer); assertEquals(data, serializedNonNull); - DeserializationContext deserializationContext = mock(DeserializationContext.class); - TestEnum deserializedValue = handler.deserialize(data, deserializationContext); + TestEnum deserializedValue = handler.deserialize(data).get(); assertEquals(TestEnum.NON_NULL, deserializedValue); } } diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandlerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandlerTest.java new file mode 100644 index 00000000000..23993bed5c9 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandlerTest.java @@ -0,0 +1,206 @@ +/* + * Copyright 2018 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.coreTypes; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.*; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.inMemory.AbstractPersistedData; +import org.terasology.persistence.typeHandling.inMemory.PersistedMap; +import org.terasology.persistence.typeHandling.inMemory.PersistedString; +import org.terasology.persistence.typeHandling.reflection.ReflectionsSandbox; +import org.terasology.reflection.TypeInfo; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.mockito.Mockito.*; + +public class RuntimeDelegatingTypeHandlerTest { + private final TypeHandlerLibrary typeHandlerLibrary = mock(TypeHandlerLibrary.class); + + private final TypeHandlerContext context = + new TypeHandlerContext(typeHandlerLibrary, new ReflectionsSandbox(new Reflections(getClass().getClassLoader()))); + + private TypeHandler baseTypeHandler; + private TypeHandler subTypeHandler; + private Class subType; + private Type baseType; + private RuntimeDelegatingTypeHandler runtimeDelegatingTypeHandler; + + private static class Base { + int x; + } + + private static class Sub extends Base { + float y; + } + + private void setupHandlers() { + subType = Sub.class; + baseType = TypeInfo.of(Base.class).getType(); + + abstract class SubHandler extends TypeHandler {} + + baseTypeHandler = mockTypeHandler(); + subTypeHandler = mockTypeHandler(SubHandler.class); + + when(typeHandlerLibrary.getTypeHandler(eq(baseType))) + .thenReturn(Optional.of(baseTypeHandler)); + + when(typeHandlerLibrary.getTypeHandler(eq((Type) subType))) + .thenReturn(Optional.of(subTypeHandler)); + + runtimeDelegatingTypeHandler = new RuntimeDelegatingTypeHandler(baseTypeHandler, TypeInfo.of(Base.class), context); + } + + private static TypeHandler mockTypeHandler(Class subHandlerClass) { + TypeHandler mocked = mock(subHandlerClass); + + when(mocked.serialize(any(), any())).thenReturn(new AbstractPersistedData() { + @Override + public boolean isNull() { + return true; + } + }); + + return mocked; + } + + private static TypeHandler mockTypeHandler() { + return mockTypeHandler(TypeHandler.class); + } + + @Test + public void testSerializeBase() { + PersistedDataSerializer serializer = mock(PersistedDataSerializer.class); + when(serializer.serialize(any(String.class))) + .then(invocation -> new PersistedString((String) invocation.getArguments()[0])); + + setupHandlers(); + + Base base = new Base(); + runtimeDelegatingTypeHandler.serialize(base, serializer); + + verify(typeHandlerLibrary, never()).getTypeHandler(eq((Type) subType)); + + verify(baseTypeHandler).serialize(any(), any()); + verify(subTypeHandler, never()).serialize(any(), any()); + + verify(serializer, never()).serialize( + argThat((ArgumentMatcher>) argument -> { + return argument.containsKey(RuntimeDelegatingTypeHandler.TYPE_FIELD); + }) + ); + } + + @Test + public void testSerializeSub() { + PersistedDataSerializer serializer = mock(PersistedDataSerializer.class); + when(serializer.serialize(any(String.class))) + .then(invocation -> new PersistedString((String) invocation.getArguments()[0])); + + setupHandlers(); + + Base sub = new Sub(); + runtimeDelegatingTypeHandler.serialize(sub, serializer); + + verify(typeHandlerLibrary, never()).getTypeHandler(eq(baseType)); + verify(typeHandlerLibrary).getTypeHandler(eq((Type) subType)); + + verify(baseTypeHandler, never()).serialize(any(), any()); + verify(subTypeHandler).serialize(any(), any()); + + verify(serializer).serialize( + argThat((ArgumentMatcher>) argument -> { + return argument.get(RuntimeDelegatingTypeHandler.TYPE_FIELD) + .getAsString() + .equals(subType.getName()) && + argument.containsKey(RuntimeDelegatingTypeHandler.VALUE_FIELD); + }) + ); + } + + @Test + public void testDeserializeBase() { + setupHandlers(); + + PersistedData persistedBase = new PersistedMap(ImmutableMap.of()); + + runtimeDelegatingTypeHandler.deserialize(persistedBase); + + verify(typeHandlerLibrary, never()).getTypeHandler(eq((Type) subType)); + + verify(baseTypeHandler).deserialize(any()); + verify(subTypeHandler, never()).deserialize(any()); + } + + @Test + public void testDeserializeSub() { + setupHandlers(); + + PersistedData persistedSub = new PersistedMap( + ImmutableMap.of( + RuntimeDelegatingTypeHandler.TYPE_FIELD, + new PersistedString(((Class) subType).getName()), + RuntimeDelegatingTypeHandler.VALUE_FIELD, + new PersistedMap(ImmutableMap.of()) + ) + ); + + runtimeDelegatingTypeHandler.deserialize(persistedSub); + + verify(typeHandlerLibrary, never()).getTypeHandler(eq(baseType)); + verify(typeHandlerLibrary).getTypeHandler(eq((Type) subType)); + + verify(baseTypeHandler, never()).deserialize(any()); + verify(subTypeHandler).deserialize(any()); + } + + @Test + public void testDeserializeNonSub() { + setupHandlers(); + + PersistedData persistedData = new PersistedMap( + ImmutableMap.of( + RuntimeDelegatingTypeHandler.TYPE_FIELD, + new PersistedString(Integer.class.getName()), + RuntimeDelegatingTypeHandler.VALUE_FIELD, + new PersistedMap(ImmutableMap.of()) + ) + ); + + Optional deserialized = runtimeDelegatingTypeHandler.deserialize(persistedData); + + Assert.assertFalse(deserialized.isPresent()); + + verify(typeHandlerLibrary, never()).getTypeHandler(eq(baseType)); + verify(typeHandlerLibrary, never()).getTypeHandler(eq((Type) subType)); + verify(typeHandlerLibrary, never()).getTypeHandler(eq((Type) Integer.class)); + + verify(subTypeHandler, never()).deserialize(any()); + // Serializes using base type handler + verify(baseTypeHandler).deserialize(any()); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/ArrayTypeHandlerFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/ArrayTypeHandlerFactoryTest.java new file mode 100644 index 00000000000..9bfa3a4b3a1 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/ArrayTypeHandlerFactoryTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 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.coreTypes.factories; + +import org.junit.Test; +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.coreTypes.ArrayTypeHandler; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ArrayTypeHandlerFactoryTest { + private final TypeHandlerLibrary typeHandlerLibrary = mock(TypeHandlerLibrary.class); + private final ArrayTypeHandlerFactory typeHandlerFactory = new ArrayTypeHandlerFactory(); + private final TypeHandlerContext context = + new TypeHandlerContext(typeHandlerLibrary, mock(SerializationSandbox.class)); + + @Test + public void testArray() { + TypeInfo arrayTypeInfo = TypeInfo.of(int[].class); + + Optional> typeHandler = + typeHandlerFactory.create(arrayTypeInfo, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof ArrayTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(TypeInfo.of(int.class).getType())); + } + + @Test + public void testGenericArray() { + TypeInfo[]> arrayTypeInfo = new TypeInfo[]>() {}; + + Optional[]>> typeHandler = + typeHandlerFactory.create(arrayTypeInfo, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof ArrayTypeHandler); + + // Verify that the List TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(new TypeInfo>() {}.getType())); + } + + @Test + public void testNonArray() { + TypeInfo> arrayTypeInfo = new TypeInfo>() {}; + + Optional>> typeHandler = + typeHandlerFactory.create(arrayTypeInfo, context); + + assertFalse(typeHandler.isPresent()); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/CollectionTypeHandlerFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/CollectionTypeHandlerFactoryTest.java new file mode 100644 index 00000000000..6603de02c2d --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/CollectionTypeHandlerFactoryTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import com.google.common.collect.Maps; +import org.junit.Test; +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.coreTypes.CollectionTypeHandler; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.reflect.ConstructorLibrary; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CollectionTypeHandlerFactoryTest { + private final TypeHandlerLibrary typeHandlerLibrary = mock(TypeHandlerLibrary.class); + private final CollectionTypeHandlerFactory typeHandlerFactory = new CollectionTypeHandlerFactory(new ConstructorLibrary(Maps.newHashMap())); + + private final TypeHandlerContext context = + new TypeHandlerContext(typeHandlerLibrary, mock(SerializationSandbox.class)); + + @Test + public void testList() { + TypeInfo> listTypeInfo = new TypeInfo>() {}; + + Optional>> typeHandler = + typeHandlerFactory.create(listTypeInfo, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof CollectionTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(TypeInfo.of(Integer.class).getType())); + } + + @Test + public void testSet() { + TypeInfo> listTypeInfo = new TypeInfo>() {}; + + Optional>> typeHandler = + typeHandlerFactory.create(listTypeInfo, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof CollectionTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(TypeInfo.of(Integer.class).getType())); + } + + @Test + public void testQueue() { + TypeInfo> listTypeInfo = new TypeInfo>() {}; + + Optional>> typeHandler = + typeHandlerFactory.create(listTypeInfo, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof CollectionTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(TypeInfo.of(Integer.class).getType())); + } + + @Test + public void testNonGenericCollection() { + class IntList extends ArrayList {} + + Optional> typeHandler = + typeHandlerFactory.create(TypeInfo.of(IntList.class), context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof CollectionTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(TypeInfo.of(Integer.class).getType())); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/EnumTypeHandlerFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/EnumTypeHandlerFactoryTest.java new file mode 100644 index 00000000000..45b7bd5effa --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/EnumTypeHandlerFactoryTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import org.junit.Test; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.EnumTypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +import static org.junit.Assert.*; + +public class EnumTypeHandlerFactoryTest { + private enum SomeEnum { + A, B + } + + @Test + public void testEnum() { + EnumTypeHandlerFactory typeHandlerFactory = new EnumTypeHandlerFactory(); + // EnumTypeHandlerFactory does not require a TypeHandlerLibrary + Optional> typeHandler = typeHandlerFactory.create(TypeInfo.of(SomeEnum.class), null); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof EnumTypeHandler); + } + + @Test + public void testNonEnum() { + EnumTypeHandlerFactory typeHandlerFactory = new EnumTypeHandlerFactory(); + + // EnumTypeHandlerFactory does not require a TypeHandlerLibrary + Optional> typeHandler = typeHandlerFactory.create(TypeInfo.of(Integer.class), null); + + assertFalse(typeHandler.isPresent()); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/ObjectFieldMapTypeHandlerFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/ObjectFieldMapTypeHandlerFactoryTest.java new file mode 100644 index 00000000000..accdff8aac4 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/ObjectFieldMapTypeHandlerFactoryTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import com.google.common.collect.Maps; +import org.junit.Test; +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.coreTypes.ObjectFieldMapTypeHandler; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.reflect.ConstructorLibrary; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ObjectFieldMapTypeHandlerFactoryTest { + private final TypeHandlerLibrary typeHandlerLibrary = mock(TypeHandlerLibrary.class); + + private final ConstructorLibrary constructorLibrary = new ConstructorLibrary(Maps.newHashMap()); + private final ObjectFieldMapTypeHandlerFactory typeHandlerFactory = new ObjectFieldMapTypeHandlerFactory( + constructorLibrary); + + private final TypeHandlerContext context = + new TypeHandlerContext(typeHandlerLibrary, mock(SerializationSandbox.class)); + + private static class SomeClass { + private T t; + private List list; + } + + @Test + public void testObject() { + Optional>> typeHandler = + typeHandlerFactory.create(new TypeInfo>() {}, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof ObjectFieldMapTypeHandler); + + // Verify that the Integer and List TypeHandlers were loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(eq(TypeInfo.of(Integer.class).getType())); + + verify(typeHandlerLibrary).getTypeHandler(eq(new TypeInfo>() {}.getType())); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/StringMapTypeHandlerFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/StringMapTypeHandlerFactoryTest.java new file mode 100644 index 00000000000..f2c6b421b0d --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/coreTypes/factories/StringMapTypeHandlerFactoryTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.coreTypes.StringMapTypeHandler; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class StringMapTypeHandlerFactoryTest { + private final TypeHandlerLibrary typeHandlerLibrary = mock(TypeHandlerLibrary.class); + private final StringMapTypeHandlerFactory typeHandlerFactory = new StringMapTypeHandlerFactory(); + + private final TypeHandlerContext context = + new TypeHandlerContext(typeHandlerLibrary, mock(SerializationSandbox.class)); + + @Test + public void testStringMap() { + TypeInfo> listTypeInfo = new TypeInfo>() {}; + + Optional>> typeHandler = + typeHandlerFactory.create(listTypeInfo, context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof StringMapTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(ArgumentMatchers.eq(TypeInfo.of(Integer.class).getType())); + } + + @Test + public void testNonStringMap() { + TypeInfo> listTypeInfo = new TypeInfo>() {}; + + Optional>> typeHandler = + typeHandlerFactory.create(listTypeInfo, context); + + assertFalse(typeHandler.isPresent()); + } + + @Test + public void testNonGenericMap() { + class IntMap extends HashMap {} + + Optional> typeHandler = + typeHandlerFactory.create(TypeInfo.of(IntMap.class), context); + + assertTrue(typeHandler.isPresent()); + assertTrue(typeHandler.get() instanceof StringMapTypeHandler); + + // Verify that the Integer TypeHandler was loaded from the TypeHandlerLibrary + verify(typeHandlerLibrary).getTypeHandler(ArgumentMatchers.eq(TypeInfo.of(Integer.class).getType())); + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandlerTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandlerTest.java index 64452ebb7c5..bb1c7660384 100644 --- a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandlerTest.java +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandlerTest.java @@ -21,9 +21,8 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.gson.GsonPersistedDataArray; import org.terasology.persistence.typeHandling.inMemory.PersistedString; import org.terasology.rendering.nui.Color; @@ -34,19 +33,18 @@ public class ColorTypeHandlerTest { private final ColorTypeHandler handler = new ColorTypeHandler(); - private final DeserializationContext deserializationContext = Mockito.mock(DeserializationContext.class); @Test public void testSerialize() { - SerializationContext serializationContext = Mockito.mock(SerializationContext.class); - handler.serialize(new Color(0x010380FF), serializationContext); - Mockito.verify(serializationContext).create(1, 3, 128, 255); + PersistedDataSerializer persistedDataSerializer = Mockito.mock(PersistedDataSerializer.class); + handler.serialize(new Color(0x010380FF), persistedDataSerializer); + Mockito.verify(persistedDataSerializer).serialize(1, 3, 128, 255); } @Test public void testDeserializeHex() { PersistedData data = new PersistedString("DEADBEEF"); - Color color = handler.deserialize(data, deserializationContext); + Color color = handler.deserialize(data).get(); Assert.assertEquals(0xDEADBEEF, color.rgba()); } @@ -54,7 +52,7 @@ public void testDeserializeHex() { public void testDeserializeArray() { JsonArray array = new Gson().fromJson("[12, 34, 56, 78]", JsonArray.class); PersistedData data = new GsonPersistedDataArray(array); - Color color = handler.deserialize(data, deserializationContext); + Color color = handler.deserialize(data).get(); Assert.assertEquals(12, color.r()); Assert.assertEquals(34, color.g()); Assert.assertEquals(56, color.b()); diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/factories/AssetTypeHandlerFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/factories/AssetTypeHandlerFactoryTest.java new file mode 100644 index 00000000000..7df10a9526d --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/extensionTypes/factories/AssetTypeHandlerFactoryTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018 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.extensionTypes.factories; + +import com.google.common.collect.Lists; +import org.junit.Test; +import org.terasology.assets.Asset; +import org.terasology.audio.StaticSound; +import org.terasology.audio.StreamingSound; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.extensionTypes.AssetTypeHandler; +import org.terasology.reflection.TypeInfo; +import org.terasology.rendering.assets.texture.Texture; +import org.terasology.rendering.nui.asset.UIElement; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertTrue; + +public class AssetTypeHandlerFactoryTest { + @Test + public void testCreate() { + TypeHandlerFactory factory = new AssetTypeHandlerFactory(); + + List> typesToTest = Lists.newArrayList( + TypeInfo.of(Texture.class), + TypeInfo.of(UIElement.class), + TypeInfo.of(StaticSound.class), + TypeInfo.of(StreamingSound.class) + ); + + for (TypeInfo typeInfo : typesToTest) { + Optional> typeHandler = factory.create(typeInfo, null); + + assertTrue(typeHandler.isPresent()); + + assertTrue(typeHandler.get() instanceof AssetTypeHandler); + } + } +} diff --git a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactoryTest.java b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerLibraryAdapterFactoryTest.java similarity index 83% rename from engine-tests/src/test/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactoryTest.java rename to engine-tests/src/test/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerLibraryAdapterFactoryTest.java index ea97d846087..fca00044a8d 100644 --- a/engine-tests/src/test/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactoryTest.java +++ b/engine-tests/src/test/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerLibraryAdapterFactoryTest.java @@ -20,18 +20,17 @@ import com.google.gson.Gson; import org.junit.Assert; import org.junit.Test; +import org.reflections.Reflections; import org.terasology.math.geom.Rect2i; import org.terasology.math.geom.Vector4f; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.reflect.ReflectionReflectFactory; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.rendering.nui.Color; import java.util.Map; import java.util.Objects; import java.util.Set; -public class GsonTypeSerializationLibraryAdapterFactoryTest { +public class GsonTypeHandlerLibraryAdapterFactoryTest { private static final TestClass OBJECT = new TestClass( new Color(0xDEADBEEF), ImmutableSet.of(Vector4f.zero(), Vector4f.one()), @@ -46,13 +45,13 @@ public class GsonTypeSerializationLibraryAdapterFactoryTest { private static final String OBJECT_JSON = "{\"color\":[222,173,190,239],\"vector4fs\":[[0.0,0.0,0.0,0.0]," + "[1.0,1.0,1.0,1.0]],\"rect2iMap\":{\"someRect\":{\"min\":[-3,-3],\"size\":[10,10]}},\"i\":-912559}"; - private final ReflectionReflectFactory reflectFactory = new ReflectionReflectFactory(); - private final CopyStrategyLibrary copyStrategyLibrary = new CopyStrategyLibrary(reflectFactory); - private final TypeSerializationLibrary typeSerializationLibrary = - TypeSerializationLibrary.createDefaultLibrary(reflectFactory, copyStrategyLibrary); + private final Reflections reflections = new Reflections(getClass().getClassLoader()); + + private final TypeHandlerLibrary typeHandlerLibrary = + TypeHandlerLibrary.withReflections(reflections); private final Gson gson = - GsonBuilderFactory.createGsonBuilderWithTypeSerializationLibrary(typeSerializationLibrary) + GsonBuilderFactory.createGsonBuilderWithTypeSerializationLibrary(typeHandlerLibrary) .create(); @Test diff --git a/engine-tests/src/test/java/org/terasology/recording/EventSystemReplayImplTest.java b/engine-tests/src/test/java/org/terasology/recording/EventSystemReplayImplTest.java index 2074ea77404..7b86025035a 100644 --- a/engine-tests/src/test/java/org/terasology/recording/EventSystemReplayImplTest.java +++ b/engine-tests/src/test/java/org/terasology/recording/EventSystemReplayImplTest.java @@ -19,7 +19,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.reflections.Reflections; import org.terasology.context.internal.ContextImpl; +import org.terasology.engine.module.ModuleManager; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.internal.PojoEntityManager; import org.terasology.entitySystem.event.AbstractConsumableEvent; @@ -31,12 +33,11 @@ import org.terasology.entitySystem.systems.BaseComponentSystem; import org.terasology.input.binds.interaction.AttackButton; import org.terasology.input.events.InputEvent; +import org.terasology.module.ModuleEnvironment; import org.terasology.network.NetworkMode; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.reflect.ReflectFactory; -import org.terasology.reflection.reflect.ReflectionReflectFactory; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.reflection.TypeRegistry; import org.terasology.registry.CoreRegistry; import java.util.ArrayList; @@ -59,9 +60,10 @@ public class EventSystemReplayImplTest { public void setup() { ContextImpl context = new ContextImpl(); CoreRegistry.setContext(context); - ReflectFactory reflectFactory = new ReflectionReflectFactory(); - CopyStrategyLibrary copyStrategies = new CopyStrategyLibrary(reflectFactory); - TypeSerializationLibrary serializationLibrary = new TypeSerializationLibrary(reflectFactory, copyStrategies); + + Reflections reflections = new Reflections(getClass().getClassLoader()); + TypeHandlerLibrary serializationLibrary = new TypeHandlerLibrary(reflections); + EntitySystemLibrary entitySystemLibrary = new EntitySystemLibrary(context, serializationLibrary); PojoEntityManager entityManager = new PojoEntityManager(); entityManager.setComponentLibrary(entitySystemLibrary.getComponentLibrary()); @@ -73,7 +75,10 @@ public void setup() { RecordAndReplayUtils recordAndReplayUtils = new RecordAndReplayUtils(); CharacterStateEventPositionMap characterStateEventPositionMap = new CharacterStateEventPositionMap(); DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList = new DirectionAndOriginPosRecorderList(); - RecordAndReplaySerializer recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, eventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, null); + ModuleManager moduleManager = mock(ModuleManager.class); + when(moduleManager.getEnvironment()).thenReturn(mock(ModuleEnvironment.class)); + RecordAndReplaySerializer recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, eventStore, + recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, moduleManager, mock(TypeRegistry.class)); recordAndReplayCurrentStatus.setStatus(RecordAndReplayStatus.REPLAYING); entity = entityManager.create(); Long id = entity.getId(); diff --git a/engine-tests/src/test/java/org/terasology/reflection/TypeInfoTest.java b/engine-tests/src/test/java/org/terasology/reflection/TypeInfoTest.java new file mode 100644 index 00000000000..248415382b1 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/reflection/TypeInfoTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 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.reflection; + +import org.junit.Test; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TypeInfoTest { + @Test + public void testSimpleTypeInfo() { + TypeInfo typeInfo = TypeInfo.of(String.class); + + assertEquals(String.class, typeInfo.getRawType()); + assertTrue(typeInfo.getType() instanceof Class); + assertEquals(typeInfo.getRawType(), typeInfo.getType()); + } + + @Test + public void testListTypeInfo() { + TypeInfo> typeInfo = new TypeInfo>() { + }; + + assertEquals(List.class, typeInfo.getRawType()); + assertTrue(typeInfo.getType() instanceof ParameterizedType); + assertEquals(Integer.class, ((ParameterizedType) typeInfo.getType()).getActualTypeArguments()[0]); + } + + @Test + public void testArrayTypeInfo() { + TypeInfo[]> typeInfo = new TypeInfo[]>() { + }; + + assertEquals(List[].class, typeInfo.getRawType()); + assertTrue(typeInfo.getType() instanceof GenericArrayType); + assertEquals( + new TypeInfo>() { + } + .getType(), + ((GenericArrayType) typeInfo.getType()).getGenericComponentType() + ); + } + + @Test + public void testWildcardGenericTypeInfo() { + TypeInfo> typeInfo = new TypeInfo>() { + }; + + assertEquals(List.class, typeInfo.getRawType()); + assertTrue(typeInfo.getType() instanceof ParameterizedType); + Type genericType = ((ParameterizedType) typeInfo.getType()).getActualTypeArguments()[0]; + assertTrue(genericType instanceof WildcardType); + } +} diff --git a/engine-tests/src/test/java/org/terasology/reflection/internal/TypeRegistryImplTest.java b/engine-tests/src/test/java/org/terasology/reflection/internal/TypeRegistryImplTest.java new file mode 100644 index 00000000000..dd5d784b349 --- /dev/null +++ b/engine-tests/src/test/java/org/terasology/reflection/internal/TypeRegistryImplTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 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.reflection.internal; + +import org.junit.Test; +import org.reflections.Reflections; +import org.terasology.ModuleEnvironmentTest; +import org.terasology.entitySystem.Component; +import org.terasology.naming.Name; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +public class TypeRegistryImplTest extends ModuleEnvironmentTest { + static { + Reflections.log = null; + } + + @Test + public void testNonModuleTypes() { + assumeTrue(typeRegistry.getSubtypesOf(Collection.class).contains(TreeSet.class)); + + assertTrue(typeRegistry.getSubtypesOf(Map.class).contains(LinkedHashMap.class)); + } + + @Test + public void testModuleTypes() { + Set modulesDeclaringComponents = + typeRegistry.getSubtypesOf(Component.class).stream() + .map(componentClass -> moduleManager.getEnvironment().getModuleProviding(componentClass)) + .collect(Collectors.toSet()); + + assertTrue(modulesDeclaringComponents.size() >= 2); + + assertTrue(modulesDeclaringComponents.contains(new Name("engine"))); + assertTrue(modulesDeclaringComponents.contains(new Name("unittest"))); + } +} diff --git a/engine-tests/src/test/java/org/terasology/utilities/ReflectionUtilsTest.java b/engine-tests/src/test/java/org/terasology/utilities/ReflectionUtilsTest.java index 511939232ae..41c188ccc9b 100644 --- a/engine-tests/src/test/java/org/terasology/utilities/ReflectionUtilsTest.java +++ b/engine-tests/src/test/java/org/terasology/utilities/ReflectionUtilsTest.java @@ -18,13 +18,30 @@ import org.junit.Test; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.logic.location.LocationComponent; +import org.terasology.reflection.TypeInfo; import org.terasology.reflection.copy.CopyStrategy; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.sql.Struct; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** */ public class ReflectionUtilsTest { + @Test + public void testGetClassOfTypeWildcard() { + class C {} + + ParameterizedType cType = (ParameterizedType) new TypeInfo>() {}.getType(); + Type wildcardType = cType.getActualTypeArguments()[0]; + + assertEquals(Object.class, ReflectionUtil.getRawType(wildcardType)); + } @Test public void testGetParameterForField() throws Exception { @@ -33,7 +50,7 @@ public void testGetParameterForField() throws Exception { @Test public void testGetParameterForGenericInterface() throws Exception { - assertEquals(Integer.class, ReflectionUtil.getTypeParameterForSuper(ParameterisedInterfaceImplementor.class, CopyStrategy.class, 0)); + assertEquals(Integer.class, ReflectionUtil.getTypeParameterForSuper(SubInterfaceImplementor.class, CopyStrategy.class, 0)); } @Test @@ -43,7 +60,221 @@ public void testGetParameterForBuriedGenericInterface() throws Exception { @Test public void testGetParameterForUnboundGenericInterface() throws Exception { - assertEquals(null, ReflectionUtil.getTypeParameterForSuper(UnboundInterfaceImplementor.class, CopyStrategy.class, 0)); + Type parameter = ReflectionUtil.getTypeParameterForSuper(new TypeInfo>() {}.getType(), CopyStrategy.class, 0); + + assertTrue(parameter instanceof WildcardType); + } + + @Test + public void testGetTypeParameterForGenericSupertypeInGenericSubclass() { + class SubInterface implements CopyStrategy { + @Override + public T copy(T value) { + return null; + } + } + + class SuperClass {} + + class SubClass extends SuperClass {} + + Type subInterfaceType = new TypeInfo>() {}.getType(); + + assertEquals(Integer.class, ReflectionUtil.getTypeParameterForSuper(subInterfaceType, CopyStrategy.class, 0)); + + Type subClassType = new TypeInfo>() {}.getType(); + + assertEquals(Integer.class, ReflectionUtil.getTypeParameterForSuper(subClassType, SuperClass.class, 0)); + } + + @Test + public void testResolveTypeVariable() { + class SomeClass { + private T t; + } + + TypeInfo> typeInfo = new TypeInfo>() { + }; + + Type resolvedFieldType = ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[0].getGenericType() + ); + + assertEquals(Float.class, resolvedFieldType); + } + + @Test + public void testResolveParameterizedType() { + class SomeClass { + private CopyStrategy t; + } + + TypeInfo> typeInfo = new TypeInfo>() { + }; + + Type resolvedFieldType = ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[0].getGenericType() + ); + + assertEquals(new TypeInfo>() {}.getType(), resolvedFieldType); + } + + @Test + public void testResolveRawParameterizedType() { + class SomeClass { + private CopyStrategy t; + private T o; + } + + TypeInfo typeInfo = new TypeInfo() { + }; + + Type resolvedFieldType = ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[0].getGenericType() + ); + + assertEquals(new TypeInfo>() {}.getType(), resolvedFieldType); + + resolvedFieldType = ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[1].getGenericType() + ); + + assertEquals(TypeInfo.of(Object.class).getType(), resolvedFieldType); + } + + @Test + public void testResolveNothing() { + class SomeClass { + private CopyStrategy t; + private String o; + } + + TypeInfo typeInfo = new TypeInfo() { + }; + + Type resolvedFieldType = ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[0].getGenericType() + ); + + assertEquals(new TypeInfo>() {}.getType(), resolvedFieldType); + + resolvedFieldType = ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[1].getGenericType() + ); + + assertEquals(TypeInfo.of(String.class).getType(), resolvedFieldType); + } + + @Test + public void testResolveGenericArray() { + class SomeClass { + private T[] t; + } + + TypeInfo> typeInfo = new TypeInfo>() { + }; + + GenericArrayType resolvedFieldType = (GenericArrayType) ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[0].getGenericType() + ); + + assertEquals(Float[].class.getComponentType(), resolvedFieldType.getGenericComponentType()); + } + + @Test + public void testResolveWildcardType() { + class SomeClass { + private CopyStrategy t; + private CopyStrategy u; + } + + TypeInfo> typeInfo = new TypeInfo>() { + }; + + ParameterizedType resolvedFieldType = (ParameterizedType) ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[0].getGenericType() + ); + + WildcardType resolvedWildcardType = (WildcardType) resolvedFieldType.getActualTypeArguments()[0]; + + assertEquals(Float.class, resolvedWildcardType.getUpperBounds()[0]); + + resolvedFieldType = (ParameterizedType) ReflectionUtil.resolveType( + typeInfo.getType(), + typeInfo.getRawType().getDeclaredFields()[1].getGenericType() + ); + + resolvedWildcardType = (WildcardType) resolvedFieldType.getActualTypeArguments()[0]; + + assertEquals(Integer.class, resolvedWildcardType.getLowerBounds()[0]); + } + + @Test + public void testResolveThroughGenericSupertypes() { + class B implements MarkerB, MarkerA {} + + ParameterizedType resolvedTypeForB = (ParameterizedType) ReflectionUtil.resolveType( + new TypeInfo>() {}.getType(), + ReflectionUtil.parameterizeRawType(B.class) + ); + + // Could not resolve S + assertEquals(Object.class, resolvedTypeForB.getActualTypeArguments()[0]); + // Could resolve T + assertEquals(String.class, resolvedTypeForB.getActualTypeArguments()[1]); + } + + @Test + public void testResolveThroughInheritanceTree() { + class A implements MarkerA, MarkerC {} + class B extends A implements MarkerB, MarkerC {} + class C extends B {} + + final Type typeToResolve = ReflectionUtil.parameterizeRawType(C.class); + + ParameterizedType resolvedThroughMarkerA = (ParameterizedType) ReflectionUtil.resolveType( + new TypeInfo>() {}.getType(), + typeToResolve + ); + + assertEquals(String.class, resolvedThroughMarkerA.getActualTypeArguments()[0]); + + ParameterizedType resolvedThroughMarkerB = (ParameterizedType) ReflectionUtil.resolveType( + new TypeInfo>() {}.getType(), + typeToResolve + ); + + assertEquals(String.class, resolvedThroughMarkerB.getActualTypeArguments()[0]); + + + ParameterizedType resolvedThroughAWithIncorrectFirstType = + (ParameterizedType) ReflectionUtil.resolveType( + new TypeInfo>() {}.getType(), + typeToResolve + ); + + assertEquals(String.class, resolvedThroughAWithIncorrectFirstType.getActualTypeArguments()[0]); + } + + interface MarkerA {} + interface MarkerB {} + interface MarkerC {} + + interface GenericInterfaceSubInterface extends CopyStrategy {} + + class SubInterfaceImplementor implements GenericInterfaceSubInterface { + @Override + public Integer copy(Integer value) { + return null; + } } public static class ParameterisedInterfaceImplementor implements CopyStrategy { diff --git a/engine/src/main/java/org/terasology/engine/TerasologyEngine.java b/engine/src/main/java/org/terasology/engine/TerasologyEngine.java index 1e377d16fc9..8da21ae3dd6 100644 --- a/engine/src/main/java/org/terasology/engine/TerasologyEngine.java +++ b/engine/src/main/java/org/terasology/engine/TerasologyEngine.java @@ -55,12 +55,14 @@ import org.terasology.monitoring.Activity; import org.terasology.monitoring.PerformanceMonitor; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.recording.CharacterStateEventPositionMap; import org.terasology.recording.DirectionAndOriginPosRecorderList; import org.terasology.recording.RecordAndReplayCurrentStatus; import org.terasology.recording.RecordAndReplayUtils; +import org.terasology.reflection.TypeRegistry; import org.terasology.reflection.copy.CopyStrategyLibrary; +import org.terasology.reflection.internal.TypeRegistryImpl; import org.terasology.reflection.reflect.ReflectFactory; import org.terasology.reflection.reflect.ReflectionReflectFactory; import org.terasology.registry.CoreRegistry; @@ -306,7 +308,10 @@ private void verifyRequiredSystemIsRegistered(Class clazz) { private void initManagers() { changeStatus(TerasologyEngineStatus.INITIALIZING_MODULE_MANAGER); - ModuleManager moduleManager = new ModuleManagerImpl(rootContext.get(Config.class), classesOnClasspathsToAddToEngine); + TypeRegistryImpl typeRegistry = new TypeRegistryImpl(); + rootContext.put(TypeRegistry.class, typeRegistry); + + ModuleManager moduleManager = new ModuleManagerImpl(rootContext.get(Config.class), classesOnClasspathsToAddToEngine, typeRegistry); rootContext.put(ModuleManager.class, moduleManager); changeStatus(TerasologyEngineStatus.INITIALIZING_LOWLEVEL_OBJECT_MANIPULATION); @@ -315,8 +320,8 @@ private void initManagers() { CopyStrategyLibrary copyStrategyLibrary = new CopyStrategyLibrary(reflectFactory); rootContext.put(CopyStrategyLibrary.class, copyStrategyLibrary); - rootContext.put(TypeSerializationLibrary.class, new TypeSerializationLibrary(reflectFactory, - copyStrategyLibrary)); + + rootContext.put(TypeHandlerLibrary.class, TypeHandlerLibrary.forModuleEnvironment(moduleManager, typeRegistry)); changeStatus(TerasologyEngineStatus.INITIALIZING_ASSET_TYPES); assetTypeManager = new ModuleAwareAssetTypeManager(); diff --git a/engine/src/main/java/org/terasology/engine/bootstrap/EntitySystemSetupUtil.java b/engine/src/main/java/org/terasology/engine/bootstrap/EntitySystemSetupUtil.java index c53f072ea7c..f30800a5998 100644 --- a/engine/src/main/java/org/terasology/engine/bootstrap/EntitySystemSetupUtil.java +++ b/engine/src/main/java/org/terasology/engine/bootstrap/EntitySystemSetupUtil.java @@ -40,7 +40,7 @@ import org.terasology.logic.characters.CharacterMoveInputEvent; import org.terasology.module.ModuleEnvironment; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.extensionTypes.EntityRefTypeHandler; import org.terasology.recording.CharacterStateEventPositionMap; import org.terasology.recording.DirectionAndOriginPosRecorderList; @@ -51,6 +51,7 @@ import org.terasology.recording.RecordAndReplayStatus; import org.terasology.recording.RecordAndReplayUtils; import org.terasology.recording.RecordedEventStore; +import org.terasology.reflection.TypeRegistry; import org.terasology.reflection.copy.CopyStrategyLibrary; import org.terasology.reflection.reflect.ReflectFactory; import org.terasology.reflection.reflect.ReflectionReflectFactory; @@ -74,9 +75,13 @@ public static void addReflectionBasedLibraries(Context context) { context.put(ReflectFactory.class, reflectFactory); CopyStrategyLibrary copyStrategyLibrary = new CopyStrategyLibrary(reflectFactory); context.put(CopyStrategyLibrary.class, copyStrategyLibrary); - TypeSerializationLibrary typeSerializationLibrary = TypeSerializationLibrary.createDefaultLibrary(reflectFactory, copyStrategyLibrary); - context.put(TypeSerializationLibrary.class, typeSerializationLibrary); - EntitySystemLibrary library = new EntitySystemLibrary(context, typeSerializationLibrary); + + ModuleManager moduleManager = context.get(ModuleManager.class); + TypeRegistry typeRegistry = context.get(TypeRegistry.class); + TypeHandlerLibrary typeHandlerLibrary = TypeHandlerLibrary.forModuleEnvironment(moduleManager, typeRegistry); + context.put(TypeHandlerLibrary.class, typeHandlerLibrary); + + EntitySystemLibrary library = new EntitySystemLibrary(context, typeHandlerLibrary); context.put(EntitySystemLibrary.class, library); context.put(ComponentLibrary.class, library.getComponentLibrary()); context.put(EventLibrary.class, library.getEventLibrary()); @@ -90,7 +95,7 @@ public static void addReflectionBasedLibraries(Context context) { *
  • {@link NetworkSystem}
  • *
  • {@link ReflectFactory}
  • *
  • {@link CopyStrategyLibrary}
  • - *
  • {@link org.terasology.persistence.typeHandling.TypeSerializationLibrary}
  • + *
  • {@link TypeHandlerLibrary}
  • * *

    * The method will make objects for the following classes available in the context: @@ -103,7 +108,8 @@ public static void addReflectionBasedLibraries(Context context) { * */ public static void addEntityManagementRelatedClasses(Context context) { - ModuleEnvironment environment = context.get(ModuleManager.class).getEnvironment(); + ModuleManager moduleManager = context.get(ModuleManager.class); + ModuleEnvironment environment = moduleManager.getEnvironment(); NetworkSystem networkSystem = context.get(NetworkSystem.class); // Entity Manager @@ -112,9 +118,9 @@ public static void addEntityManagementRelatedClasses(Context context) { context.put(EngineEntityManager.class, entityManager); // Standard serialization library - TypeSerializationLibrary typeSerializationLibrary = context.get(TypeSerializationLibrary.class); - typeSerializationLibrary.add(EntityRef.class, new EntityRefTypeHandler(entityManager)); - entityManager.setTypeSerializerLibrary(typeSerializationLibrary); + TypeHandlerLibrary typeHandlerLibrary = context.get(TypeHandlerLibrary.class); + typeHandlerLibrary.addTypeHandler(EntityRef.class, new EntityRefTypeHandler(entityManager)); + entityManager.setTypeSerializerLibrary(typeHandlerLibrary); // Prefab Manager PrefabManager prefabManager = new PojoPrefabManager(context); @@ -130,7 +136,7 @@ public static void addEntityManagementRelatedClasses(Context context) { CharacterStateEventPositionMap characterStateEventPositionMap = context.get(CharacterStateEventPositionMap.class); DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList = context.get(DirectionAndOriginPosRecorderList.class); RecordedEventStore recordedEventStore = new RecordedEventStore(); - RecordAndReplaySerializer recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, recordedEventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, environment); + RecordAndReplaySerializer recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, recordedEventStore, recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, moduleManager, context.get(TypeRegistry.class)); context.put(RecordAndReplaySerializer.class, recordAndReplaySerializer); diff --git a/engine/src/main/java/org/terasology/engine/bootstrap/EnvironmentSwitchHandler.java b/engine/src/main/java/org/terasology/engine/bootstrap/EnvironmentSwitchHandler.java index 7ff5624666e..6dcc3400091 100644 --- a/engine/src/main/java/org/terasology/engine/bootstrap/EnvironmentSwitchHandler.java +++ b/engine/src/main/java/org/terasology/engine/bootstrap/EnvironmentSwitchHandler.java @@ -35,8 +35,10 @@ import org.terasology.entitySystem.systems.internal.DoNotAutoRegister; import org.terasology.module.ModuleEnvironment; import org.terasology.persistence.typeHandling.RegisterTypeHandler; +import org.terasology.persistence.typeHandling.RegisterTypeHandlerFactory; import org.terasology.persistence.typeHandling.TypeHandler; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.extensionTypes.CollisionGroupTypeHandler; import org.terasology.physics.CollisionGroup; import org.terasology.physics.CollisionGroupManager; @@ -70,21 +72,19 @@ public void handleSwitchToGameEnvironment(Context context) { if (copyStrategy.getAnnotation(RegisterCopyStrategy.class) == null) { continue; } - Class targetType = ReflectionUtil.getTypeParameterForSuper(copyStrategy, CopyStrategy.class, 0); - if (targetType != null) { - registerCopyStrategy(copyStrategyLibrary, targetType, copyStrategy); + Type targetType = ReflectionUtil.getTypeParameterForSuper(copyStrategy, CopyStrategy.class, 0); + if (targetType instanceof Class) { + registerCopyStrategy(copyStrategyLibrary, (Class) targetType, copyStrategy); } else { logger.error("Cannot register CopyStrategy '{}' - unable to determine target type", copyStrategy); } } - ReflectFactory reflectFactory = context.get(ReflectFactory.class); - TypeSerializationLibrary typeSerializationLibrary = TypeSerializationLibrary.createDefaultLibrary(reflectFactory, copyStrategyLibrary); - typeSerializationLibrary.add(CollisionGroup.class, new CollisionGroupTypeHandler(context.get(CollisionGroupManager.class))); - context.put(TypeSerializationLibrary.class, typeSerializationLibrary); + TypeHandlerLibrary typeHandlerLibrary = context.get(TypeHandlerLibrary.class); + typeHandlerLibrary.addTypeHandler(CollisionGroup.class, new CollisionGroupTypeHandler(context.get(CollisionGroupManager.class))); // Entity System Library - EntitySystemLibrary library = new EntitySystemLibrary(context, typeSerializationLibrary); + EntitySystemLibrary library = new EntitySystemLibrary(context, typeHandlerLibrary); context.put(EntitySystemLibrary.class, library); ComponentLibrary componentLibrary = library.getComponentLibrary(); context.put(ComponentLibrary.class, componentLibrary); @@ -92,7 +92,7 @@ public void handleSwitchToGameEnvironment(Context context) { context.put(ClassMetaLibrary.class, new ClassMetaLibraryImpl(context)); registerComponents(componentLibrary, moduleManager.getEnvironment()); - registerTypeHandlers(context, typeSerializationLibrary, moduleManager.getEnvironment()); + registerTypeHandlers(context, typeHandlerLibrary, moduleManager.getEnvironment()); ModuleAwareAssetTypeManager assetTypeManager = context.get(ModuleAwareAssetTypeManager.class); @@ -104,9 +104,9 @@ public void handleSwitchToGameEnvironment(Context context) { * existing then yet. */ unregisterPrefabFormats(assetTypeManager); - registeredPrefabFormat = new PrefabFormat(componentLibrary, typeSerializationLibrary); + registeredPrefabFormat = new PrefabFormat(componentLibrary, typeHandlerLibrary); assetTypeManager.registerCoreFormat(Prefab.class, registeredPrefabFormat); - registeredPrefabDeltaFormat = new PrefabDeltaFormat(componentLibrary, typeSerializationLibrary); + registeredPrefabDeltaFormat = new PrefabDeltaFormat(componentLibrary, typeHandlerLibrary); assetTypeManager.registerCoreDeltaFormat(Prefab.class, registeredPrefabDeltaFormat); assetTypeManager.switchEnvironment(moduleManager.getEnvironment()); @@ -178,7 +178,7 @@ private static void registerComponents(ComponentLibrary library, ModuleEnvironme } @SuppressWarnings({"rawtypes", "unchecked"}) - private static void registerTypeHandlers(Context context, TypeSerializationLibrary library, ModuleEnvironment environment) { + private static void registerTypeHandlers(Context context, TypeHandlerLibrary library, ModuleEnvironment environment) { for (Class handler : environment.getSubtypesOf(TypeHandler.class)) { RegisterTypeHandler register = handler.getAnnotation(RegisterTypeHandler.class); if (register != null) { @@ -186,9 +186,19 @@ private static void registerTypeHandlers(Context context, TypeSerializationLibra if (opt.isPresent()) { TypeHandler instance = InjectionHelper.createWithConstructorInjection(handler, context); InjectionHelper.inject(instance, context); - library.add((Class) opt.get(), instance); + library.addTypeHandler((Class) opt.get(), instance); } } } + + for (Class clazz : environment.getSubtypesOf(TypeHandlerFactory.class)) { + if (!clazz.isAnnotationPresent(RegisterTypeHandlerFactory.class)) { + continue; + } + + TypeHandlerFactory instance = InjectionHelper.createWithConstructorInjection(clazz, context); + InjectionHelper.inject(instance, context); + library.addTypeHandlerFactory(instance); + } } } diff --git a/engine/src/main/java/org/terasology/engine/modes/loadProcesses/RegisterBlocks.java b/engine/src/main/java/org/terasology/engine/modes/loadProcesses/RegisterBlocks.java index 4d1a2c045f7..5ccff9ba03a 100644 --- a/engine/src/main/java/org/terasology/engine/modes/loadProcesses/RegisterBlocks.java +++ b/engine/src/main/java/org/terasology/engine/modes/loadProcesses/RegisterBlocks.java @@ -23,7 +23,7 @@ import org.terasology.game.GameManifest; import org.terasology.module.ModuleEnvironment; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.extensionTypes.BlockFamilyTypeHandler; import org.terasology.persistence.typeHandling.extensionTypes.BlockTypeHandler; import org.terasology.world.block.Block; @@ -68,8 +68,8 @@ public boolean step() { blockManager = new BlockManagerImpl(atlas, context.get(AssetManager.class), false); } context.put(BlockManager.class, blockManager); - context.get(TypeSerializationLibrary.class).add(Block.class, new BlockTypeHandler(blockManager)); - context.get(TypeSerializationLibrary.class).add(BlockFamily.class, new BlockFamilyTypeHandler(blockManager)); + context.get(TypeHandlerLibrary.class).addTypeHandler(Block.class, new BlockTypeHandler(blockManager)); + context.get(TypeHandlerLibrary.class).addTypeHandler(BlockFamily.class, new BlockFamilyTypeHandler(blockManager)); blockManager.initialise(gameManifest.getRegisteredBlockFamilies(), gameManifest.getBlockIdMap()); diff --git a/engine/src/main/java/org/terasology/engine/module/ModuleManagerImpl.java b/engine/src/main/java/org/terasology/engine/module/ModuleManagerImpl.java index df77cde67a1..606dc6845b8 100644 --- a/engine/src/main/java/org/terasology/engine/module/ModuleManagerImpl.java +++ b/engine/src/main/java/org/terasology/engine/module/ModuleManagerImpl.java @@ -41,6 +41,7 @@ import org.terasology.module.sandbox.StandardPermissionProviderFactory; import org.terasology.module.sandbox.WarnOnlyProviderFactory; import org.terasology.naming.Name; +import org.terasology.reflection.internal.TypeRegistryImpl; import java.io.IOException; import java.io.InputStreamReader; @@ -70,11 +71,14 @@ public class ModuleManagerImpl implements ModuleManager { private ModuleMetadataJsonAdapter metadataReader; private ModuleInstallManager installManager; - public ModuleManagerImpl(String masterServerAddress) { - this(masterServerAddress, Collections.emptyList()); + private final TypeRegistryImpl typeRegistry; + + public ModuleManagerImpl(String masterServerAddress, TypeRegistryImpl typeRegistry) { + this(masterServerAddress, Collections.emptyList(), typeRegistry); } - public ModuleManagerImpl(String masterServerAddress, List> classesOnClasspathsToAddToEngine) { + public ModuleManagerImpl(String masterServerAddress, List> classesOnClasspathsToAddToEngine, TypeRegistryImpl typeRegistry) { + this.typeRegistry = typeRegistry; metadataReader = new ModuleMetadataJsonAdapter(); for (ModuleExtension ext : StandardModuleExtension.values()) { metadataReader.registerExtension(ext.getKey(), ext.getValueType()); @@ -119,12 +123,12 @@ public ModuleManagerImpl(String masterServerAddress, List> classesOnCla installManager = new ModuleInstallManager(this, masterServerAddress); } - public ModuleManagerImpl(Config config) { - this(config, Collections.emptyList()); + public ModuleManagerImpl(Config config, TypeRegistryImpl typeRegistry) { + this(config, Collections.emptyList(), typeRegistry); } - public ModuleManagerImpl(Config config, List> classesOnClasspathsToAddToEngine) { - this(config.getNetwork().getMasterServer(), classesOnClasspathsToAddToEngine); + public ModuleManagerImpl(Config config, List> classesOnClasspathsToAddToEngine, TypeRegistryImpl typeRegistry) { + this(config.getNetwork().getMasterServer(), classesOnClasspathsToAddToEngine, typeRegistry); } /** @@ -225,6 +229,7 @@ public ModuleEnvironment loadEnvironment(Set modules, boolean asPrimary) } if (asPrimary) { environment = newEnvironment; + typeRegistry.reload(environment); } return newEnvironment; } diff --git a/engine/src/main/java/org/terasology/entitySystem/entity/internal/EngineEntityManager.java b/engine/src/main/java/org/terasology/entitySystem/entity/internal/EngineEntityManager.java index c8d87987a0a..7fa5f1c80dd 100644 --- a/engine/src/main/java/org/terasology/entitySystem/entity/internal/EngineEntityManager.java +++ b/engine/src/main/java/org/terasology/entitySystem/entity/internal/EngineEntityManager.java @@ -19,7 +19,7 @@ import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.LowLevelEntityManager; import org.terasology.entitySystem.event.internal.EventSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import java.util.Optional; @@ -113,7 +113,7 @@ public interface EngineEntityManager extends LowLevelEntityManager, EngineEntity /** * @return The default serialization library to use for serializing components */ - TypeSerializationLibrary getTypeSerializerLibrary(); + TypeHandlerLibrary getTypeSerializerLibrary(); /** * Gets the entity pool associated with a given entity. diff --git a/engine/src/main/java/org/terasology/entitySystem/entity/internal/PojoEntityManager.java b/engine/src/main/java/org/terasology/entitySystem/entity/internal/PojoEntityManager.java index bfe766752b2..5911216e975 100644 --- a/engine/src/main/java/org/terasology/entitySystem/entity/internal/PojoEntityManager.java +++ b/engine/src/main/java/org/terasology/entitySystem/entity/internal/PojoEntityManager.java @@ -43,7 +43,7 @@ import org.terasology.game.GameManifest; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.Vector3f; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.world.internal.WorldInfo; import java.util.ArrayList; @@ -81,7 +81,7 @@ public class PojoEntityManager implements EngineEntityManager { private RefStrategy refStrategy = new DefaultRefStrategy(); - private TypeSerializationLibrary typeSerializerLibrary; + private TypeHandlerLibrary typeSerializerLibrary; @Override public RefStrategy getEntityRefStrategy() { @@ -405,11 +405,11 @@ public void unsubscribe(EntityChangeSubscriber subscriber) { } @Override - public TypeSerializationLibrary getTypeSerializerLibrary() { + public TypeHandlerLibrary getTypeSerializerLibrary() { return typeSerializerLibrary; } - public void setTypeSerializerLibrary(TypeSerializationLibrary serializerLibrary) { + public void setTypeSerializerLibrary(TypeHandlerLibrary serializerLibrary) { this.typeSerializerLibrary = serializerLibrary; } diff --git a/engine/src/main/java/org/terasology/entitySystem/metadata/EntitySystemLibrary.java b/engine/src/main/java/org/terasology/entitySystem/metadata/EntitySystemLibrary.java index adeebd303ee..abc34f721c1 100644 --- a/engine/src/main/java/org/terasology/entitySystem/metadata/EntitySystemLibrary.java +++ b/engine/src/main/java/org/terasology/entitySystem/metadata/EntitySystemLibrary.java @@ -17,7 +17,7 @@ package org.terasology.entitySystem.metadata; import org.terasology.context.Context; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; /** * The set of metadata libraries used by the entity system @@ -25,12 +25,12 @@ */ public class EntitySystemLibrary { - private final TypeSerializationLibrary typeSerializationLibrary; + private final TypeHandlerLibrary typeHandlerLibrary; private final ComponentLibrary componentLibrary; private final EventLibrary eventLibrary; - public EntitySystemLibrary(Context context, TypeSerializationLibrary typeSerializationLibrary) { - this.typeSerializationLibrary = typeSerializationLibrary; + public EntitySystemLibrary(Context context, TypeHandlerLibrary typeHandlerLibrary) { + this.typeHandlerLibrary = typeHandlerLibrary; this.componentLibrary = new ComponentLibrary(context); this.eventLibrary = new EventLibrary(context); @@ -46,8 +46,8 @@ public ComponentLibrary getComponentLibrary() { /** * @return The library of serializers */ - public TypeSerializationLibrary getSerializationLibrary() { - return typeSerializationLibrary; + public TypeHandlerLibrary getSerializationLibrary() { + return typeHandlerLibrary; } /** diff --git a/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabDeltaFormat.java b/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabDeltaFormat.java index 014ed9d5c54..465435e83b4 100644 --- a/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabDeltaFormat.java +++ b/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabDeltaFormat.java @@ -22,7 +22,7 @@ import org.terasology.entitySystem.prefab.PrefabData; import org.terasology.persistence.serializers.EntityDataJSONFormat; import org.terasology.persistence.serializers.PrefabSerializer; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.EntityData; import java.io.BufferedReader; @@ -34,12 +34,12 @@ public class PrefabDeltaFormat extends AbstractAssetAlterationFileFormat { private final ComponentLibrary componentLibrary; - private final TypeSerializationLibrary typeSerializationLibrary; + private final TypeHandlerLibrary typeHandlerLibrary; - public PrefabDeltaFormat(ComponentLibrary componentLibrary, TypeSerializationLibrary typeSerializationLibrary) { + public PrefabDeltaFormat(ComponentLibrary componentLibrary, TypeHandlerLibrary typeHandlerLibrary) { super("prefab"); this.componentLibrary = componentLibrary; - this.typeSerializationLibrary = typeSerializationLibrary; + this.typeHandlerLibrary = typeHandlerLibrary; } @Override @@ -47,7 +47,7 @@ public void apply(AssetDataFile assetDataFile, PrefabData assetData) throws IOEx try (BufferedReader deltaReader = new BufferedReader(new InputStreamReader(assetDataFile.openStream(), Charsets.UTF_8))) { EntityData.Prefab delta = EntityDataJSONFormat.readPrefab(deltaReader); - PrefabSerializer serializer = new PrefabSerializer(componentLibrary, typeSerializationLibrary); + PrefabSerializer serializer = new PrefabSerializer(componentLibrary, typeHandlerLibrary); serializer.deserializeDeltaOnto(delta, assetData); } } diff --git a/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabFormat.java b/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabFormat.java index 5dd7063f752..785f50843ad 100644 --- a/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabFormat.java +++ b/engine/src/main/java/org/terasology/entitySystem/prefab/internal/PrefabFormat.java @@ -25,7 +25,7 @@ import org.terasology.entitySystem.prefab.PrefabData; import org.terasology.persistence.serializers.EntityDataJSONFormat; import org.terasology.persistence.serializers.PrefabSerializer; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.EntityData; import java.io.BufferedReader; @@ -37,12 +37,12 @@ public class PrefabFormat extends AbstractAssetFileFormat { private static final Logger logger = LoggerFactory.getLogger(PrefabFormat.class); private ComponentLibrary componentLibrary; - private TypeSerializationLibrary typeSerializationLibrary; + private TypeHandlerLibrary typeHandlerLibrary; - public PrefabFormat(ComponentLibrary componentLibrary, TypeSerializationLibrary typeSerializationLibrary) { + public PrefabFormat(ComponentLibrary componentLibrary, TypeHandlerLibrary typeHandlerLibrary) { super("prefab"); this.componentLibrary = componentLibrary; - this.typeSerializationLibrary = typeSerializationLibrary; + this.typeHandlerLibrary = typeHandlerLibrary; } @Override @@ -51,7 +51,7 @@ public PrefabData load(ResourceUrn resourceUrn, List inputs) thro EntityData.Prefab prefabData = EntityDataJSONFormat.readPrefab(reader); if (prefabData != null) { logger.info("Attempting to deserialize prefab {} with inputs {}", resourceUrn, inputs); - PrefabSerializer serializer = new PrefabSerializer(componentLibrary, typeSerializationLibrary); + PrefabSerializer serializer = new PrefabSerializer(componentLibrary, typeHandlerLibrary); return serializer.deserialize(prefabData); } else { throw new IOException("Failed to read prefab for '" + resourceUrn + "'"); diff --git a/engine/src/main/java/org/terasology/logic/behavior/nui/BehaviorNodeFactory.java b/engine/src/main/java/org/terasology/logic/behavior/nui/BehaviorNodeFactory.java index 5d22b353ea2..bfe26da57e3 100644 --- a/engine/src/main/java/org/terasology/logic/behavior/nui/BehaviorNodeFactory.java +++ b/engine/src/main/java/org/terasology/logic/behavior/nui/BehaviorNodeFactory.java @@ -137,7 +137,7 @@ private List determineAnimationPoolUris() { ParameterizedType parameterizedType = (ParameterizedType) fieldType; Type[] typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 1) { - Class typeClass = ReflectionUtil.getClassOfType(typeArguments[0]); + Class typeClass = ReflectionUtil.getRawType(typeArguments[0]); if (typeClass.isAssignableFrom(MeshAnimation.class)) { animationSetUris.add(new ComponentFieldUri(uri, fieldMetadata.getName())); } diff --git a/engine/src/main/java/org/terasology/network/internal/NetworkSystemImpl.java b/engine/src/main/java/org/terasology/network/internal/NetworkSystemImpl.java index 40e57b4e6e5..3caca827a7a 100644 --- a/engine/src/main/java/org/terasology/network/internal/NetworkSystemImpl.java +++ b/engine/src/main/java/org/terasology/network/internal/NetworkSystemImpl.java @@ -76,7 +76,7 @@ import org.terasology.persistence.StorageManager; import org.terasology.persistence.serializers.EventSerializer; import org.terasology.persistence.serializers.NetworkEntitySerializer; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.NetData; import org.terasology.reflection.metadata.ClassLibrary; import org.terasology.reflection.metadata.ClassMetadata; @@ -534,12 +534,12 @@ public void connectToEntitySystem(EngineEntityManager newEntityManager, EventLib context.get(ComponentSystemManager.class).register(new NetworkEntitySystem(this), "engine:networkEntitySystem"); - TypeSerializationLibrary typeSerializationLibrary = new TypeSerializationLibrary(entityManager.getTypeSerializerLibrary()); - typeSerializationLibrary.add(EntityRef.class, new NetEntityRefTypeHandler(this, blockEntityRegistry)); + TypeHandlerLibrary typeHandlerLibrary = new TypeHandlerLibrary(entityManager.getTypeSerializerLibrary()); + typeHandlerLibrary.addTypeHandler(EntityRef.class, new NetEntityRefTypeHandler(this, blockEntityRegistry)); // TODO: Add network override types here (that use id lookup tables) - eventSerializer = new EventSerializer(eventLibrary, typeSerializationLibrary); - entitySerializer = new NetworkEntitySerializer(newEntityManager, entityManager.getComponentLibrary(), typeSerializationLibrary); + eventSerializer = new EventSerializer(eventLibrary, typeHandlerLibrary); + entitySerializer = new NetworkEntitySerializer(newEntityManager, entityManager.getComponentLibrary(), typeHandlerLibrary); entitySerializer.setComponentSerializeCheck(new NetComponentSerializeCheck()); if (mode == NetworkMode.CLIENT) { diff --git a/engine/src/main/java/org/terasology/network/serialization/NetEntityRefTypeHandler.java b/engine/src/main/java/org/terasology/network/serialization/NetEntityRefTypeHandler.java index 48efac0e620..fc211051225 100644 --- a/engine/src/main/java/org/terasology/network/serialization/NetEntityRefTypeHandler.java +++ b/engine/src/main/java/org/terasology/network/serialization/NetEntityRefTypeHandler.java @@ -16,29 +16,26 @@ package org.terasology.network.serialization; -import com.google.common.collect.Lists; import gnu.trove.list.TIntList; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.math.geom.Vector3i; import org.terasology.network.NetworkComponent; import org.terasology.network.internal.NetworkSystemImpl; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; import org.terasology.world.BlockEntityRegistry; import org.terasology.world.block.BlockComponent; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** * This type handler encodes EntityRef for network transferals. For normal entities, the Network Id of the entity is used. * For block entities the block position is used instead (this allows overriding simulated block entities). * */ -public class NetEntityRefTypeHandler implements TypeHandler { +public class NetEntityRefTypeHandler extends TypeHandler { private NetworkSystemImpl networkSystem; private BlockEntityRegistry blockEntityRegistry; @@ -48,61 +45,33 @@ public NetEntityRefTypeHandler(NetworkSystemImpl networkSystem, BlockEntityRegis } @Override - public PersistedData serialize(EntityRef value, SerializationContext context) { + public PersistedData serializeNonNull(EntityRef value, PersistedDataSerializer serializer) { BlockComponent blockComponent = value.getComponent(BlockComponent.class); if (blockComponent != null) { - return context.create(blockComponent.position.x, blockComponent.position.y, blockComponent.position.z); + Vector3i pos = blockComponent.position; + return serializer.serialize(pos.x, pos.y, pos.z); } NetworkComponent netComponent = value.getComponent(NetworkComponent.class); if (netComponent != null) { - return context.create(netComponent.getNetworkId()); + return serializer.serialize(netComponent.getNetworkId()); } - return context.createNull(); + return serializer.serializeNull(); } @Override - public EntityRef deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray array = data.getAsArray(); if (array.isNumberArray() && array.size() == 3) { TIntList items = data.getAsArray().getAsIntegerArray(); Vector3i pos = new Vector3i(items.get(0), items.get(1), items.get(2)); - return blockEntityRegistry.getBlockEntityAt(pos); + return Optional.ofNullable(blockEntityRegistry.getBlockEntityAt(pos)); } } if (data.isNumber()) { - return networkSystem.getEntity(data.getAsInteger()); + return Optional.ofNullable(networkSystem.getEntity(data.getAsInteger())); } - return EntityRef.NULL; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - List items = Lists.newArrayList(); - for (EntityRef ref : value) { - BlockComponent blockComponent = ref.getComponent(BlockComponent.class); - if (blockComponent != null) { - Vector3i blockPos = blockComponent.getPosition(); - items.add(context.create(blockPos.x, blockPos.y, blockPos.z)); - } else { - NetworkComponent netComponent = ref.getComponent(NetworkComponent.class); - if (netComponent != null) { - items.add(context.create(netComponent.getNetworkId())); - } else { - items.add(context.createNull()); - } - } - } - return context.create(items); - } - - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - List result = Lists.newArrayListWithCapacity(data.getAsArray().size()); - for (PersistedData item : data.getAsArray()) { - result.add(deserialize(item, context)); - } - return result; + return Optional.ofNullable(EntityRef.NULL); } } diff --git a/engine/src/main/java/org/terasology/persistence/internal/ReadWriteStorageManager.java b/engine/src/main/java/org/terasology/persistence/internal/ReadWriteStorageManager.java index 4805d6dc652..7295c32f7b2 100644 --- a/engine/src/main/java/org/terasology/persistence/internal/ReadWriteStorageManager.java +++ b/engine/src/main/java/org/terasology/persistence/internal/ReadWriteStorageManager.java @@ -44,7 +44,7 @@ import org.terasology.network.Client; import org.terasology.network.ClientComponent; import org.terasology.network.NetworkSystem; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.EntityData; import org.terasology.recording.RecordAndReplayCurrentStatus; import org.terasology.recording.RecordAndReplaySerializer; @@ -153,7 +153,7 @@ public ReadWriteStorageManager(Path savePath, ModuleEnvironment environment, Eng private static EngineEntityManager createPrivateEntityManager(ComponentLibrary componentLibrary) { PojoEntityManager pojoEntityManager = new PojoEntityManager(); pojoEntityManager.setComponentLibrary(componentLibrary); - pojoEntityManager.setTypeSerializerLibrary(CoreRegistry.get(TypeSerializationLibrary.class)); + pojoEntityManager.setTypeSerializerLibrary(CoreRegistry.get(TypeHandlerLibrary.class)); return pojoEntityManager; } diff --git a/engine/src/main/java/org/terasology/persistence/serializers/AbstractSerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/AbstractSerializer.java new file mode 100644 index 00000000000..256f3c3cbec --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/serializers/AbstractSerializer.java @@ -0,0 +1,78 @@ +/* + * Copyright 2019 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 org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +/** + * The abstract class that all serializers derive from. It by default provides the ability to + * serialize/deserialize an object to/from a {@link PersistedData} using the given + * {@link PersistedDataSerializer}. + *

    + * Implementors simply need to specify the type of {@link PersistedDataSerializer} to use + * and can provide convenience methods that use {@link #serialize(Object, TypeInfo)} and + * {@link #deserialize(PersistedData, TypeInfo)}. + */ +public abstract class AbstractSerializer { + protected final TypeHandlerLibrary typeHandlerLibrary; + protected final PersistedDataSerializer persistedDataSerializer; + + protected AbstractSerializer(TypeHandlerLibrary typeHandlerLibrary, PersistedDataSerializer persistedDataSerializer) { + this.typeHandlerLibrary = typeHandlerLibrary; + this.persistedDataSerializer = persistedDataSerializer; + } + + /** + * Serializes the given object to a {@link PersistedData} using the stored + * {@link #persistedDataSerializer} by loading a + * {@link org.terasology.persistence.typeHandling.TypeHandler TypeHandler} from the + * {@link #typeHandlerLibrary}. + * + * @param object The object to serialize. + * @param typeInfo A {@link TypeInfo} specifying the type of the object to serialize. + * @param The type of the object to serialize. + * @return A {@link PersistedData}, if the serialization was successful. Serialization + * usually fails only because an appropriate type handler could not be found for the + * given type. + */ + public Optional serialize(T object, TypeInfo typeInfo) { + return typeHandlerLibrary.getTypeHandler(typeInfo) + .map(typeHandler -> typeHandler.serialize(object, persistedDataSerializer)); + } + + /** + * Deserializes an object of the given type from a {@link PersistedData} using the stored + * {@link #persistedDataSerializer} by loading a + * {@link org.terasology.persistence.typeHandling.TypeHandler TypeHandler} from the + * {@link #typeHandlerLibrary}. + * + * @param data The {@link PersistedData} containing the serialized representation of the object. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}, if the deserialization was successful. + * Deserialization usually fails when an appropriate type handler could not be found for the + * type {@link T} or if the serialized object representation in {@code data} does + * not represent an object of type {@link T}. + */ + public Optional deserialize(PersistedData data, TypeInfo typeInfo) { + return typeHandlerLibrary.getTypeHandler(typeInfo).flatMap(typeHandler -> typeHandler.deserialize(data)); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/serializers/ComponentSerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/ComponentSerializer.java index e9dc391bb3b..36b1f01c4f5 100644 --- a/engine/src/main/java/org/terasology/persistence/serializers/ComponentSerializer.java +++ b/engine/src/main/java/org/terasology/persistence/serializers/ComponentSerializer.java @@ -28,13 +28,11 @@ import org.terasology.entitySystem.metadata.ComponentMetadata; import org.terasology.entitySystem.metadata.ReplicatedFieldMetadata; import org.terasology.module.Module; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.Serializer; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; -import org.terasology.persistence.typeHandling.protobuf.ProtobufDeserializationContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedData; -import org.terasology.persistence.typeHandling.protobuf.ProtobufSerializationContext; +import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedDataSerializer; import org.terasology.protobuf.EntityData; import org.terasology.reflection.metadata.FieldMetadata; @@ -57,20 +55,18 @@ public class ComponentSerializer { private ComponentLibrary componentLibrary; private BiMap, Integer> idTable = ImmutableBiMap., Integer>builder().build(); private boolean usingFieldIds; - private TypeSerializationLibrary typeSerializationLibrary; - private ProtobufSerializationContext serializationContext; - private ProtobufDeserializationContext deserializationContext; + private TypeHandlerLibrary typeHandlerLibrary; + private ProtobufPersistedDataSerializer serializationContext; /** * Creates the component serializer. * * @param componentLibrary The component library used to provide information on each component and its fields. */ - public ComponentSerializer(ComponentLibrary componentLibrary, TypeSerializationLibrary typeSerializationLibrary) { + public ComponentSerializer(ComponentLibrary componentLibrary, TypeHandlerLibrary typeHandlerLibrary) { this.componentLibrary = componentLibrary; - this.typeSerializationLibrary = typeSerializationLibrary; - this.serializationContext = new ProtobufSerializationContext(typeSerializationLibrary); - this.deserializationContext = new ProtobufDeserializationContext(typeSerializationLibrary); + this.typeHandlerLibrary = typeHandlerLibrary; + this.serializationContext = new ProtobufPersistedDataSerializer(); } public void setUsingFieldIds(boolean usingFieldIds) { @@ -183,8 +179,7 @@ public Component deserializeOnto(Component target, EntityData.Component componen private Component deserializeOnto(Component targetComponent, EntityData.Component componentData, ComponentMetadata componentMetadata, FieldSerializeCheck fieldCheck) { - Serializer serializer = typeSerializationLibrary.getSerializerFor(componentMetadata); - DeserializationContext context = new ProtobufDeserializationContext(typeSerializationLibrary); + Serializer serializer = typeHandlerLibrary.getSerializerFor(componentMetadata); Map, PersistedData> dataMap = Maps.newHashMapWithExpectedSize(componentData.getFieldCount()); for (EntityData.NameValue field : componentData.getFieldList()) { FieldMetadata fieldInfo = null; @@ -199,7 +194,7 @@ private Component deserializeOnto(Component targetComponen logger.warn("Cannot deserialize unknown field '{}' onto '{}'", field.getName(), componentMetadata.getUri()); } } - serializer.deserializeOnto(targetComponent, dataMap, context, fieldCheck); + serializer.deserializeOnto(targetComponent, dataMap, fieldCheck); return targetComponent; } @@ -230,7 +225,7 @@ public EntityData.Component serialize(Component component, FieldSerializeCheck field : componentMetadata.getFields()) { if (check.shouldSerializeField(field, component)) { PersistedData result = serializer.serialize(field, component, serializationContext); @@ -286,7 +281,7 @@ public EntityData.Component serialize(Component base, Component delta, FieldSeri EntityData.Component.Builder componentMessage = EntityData.Component.newBuilder(); serializeComponentType(componentMetadata, componentMessage); - Serializer serializer = typeSerializationLibrary.getSerializerFor(componentMetadata); + Serializer serializer = typeHandlerLibrary.getSerializerFor(componentMetadata); boolean changed = false; for (ReplicatedFieldMetadata field : componentMetadata.getFields()) { if (check.shouldSerializeField(field, delta) && serializer.getHandlerFor(field) != null) { diff --git a/engine/src/main/java/org/terasology/persistence/serializers/EntityDataJSONFormat.java b/engine/src/main/java/org/terasology/persistence/serializers/EntityDataJSONFormat.java index 68867cc049c..7321edcdae7 100644 --- a/engine/src/main/java/org/terasology/persistence/serializers/EntityDataJSONFormat.java +++ b/engine/src/main/java/org/terasology/persistence/serializers/EntityDataJSONFormat.java @@ -377,7 +377,10 @@ public EntityData.Value deserialize(JsonElement json, Type typeOfT, JsonDeserial } } } - value.setBytes(ByteString.copyFrom(byteList.toArray())); + + if (byteList.size() > 0) { + value.setBytes(ByteString.copyFrom(byteList.toArray())); + } } return value.build(); } diff --git a/engine/src/main/java/org/terasology/persistence/serializers/EntitySerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/EntitySerializer.java index 6fcacedf390..90b177af44e 100644 --- a/engine/src/main/java/org/terasology/persistence/serializers/EntitySerializer.java +++ b/engine/src/main/java/org/terasology/persistence/serializers/EntitySerializer.java @@ -27,7 +27,7 @@ import org.terasology.entitySystem.metadata.ComponentMetadata; import org.terasology.entitySystem.prefab.Prefab; import org.terasology.entitySystem.prefab.PrefabManager; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.EntityData; import java.util.Map; @@ -63,11 +63,11 @@ public EntitySerializer(EngineEntityManager entityManager) { this(entityManager, entityManager.getComponentLibrary(), entityManager.getTypeSerializerLibrary()); } - public EntitySerializer(EngineEntityManager entityManager, ComponentLibrary componentLibrary, TypeSerializationLibrary typeSerializationLibrary) { + public EntitySerializer(EngineEntityManager entityManager, ComponentLibrary componentLibrary, TypeHandlerLibrary typeHandlerLibrary) { this.entityManager = entityManager; this.prefabManager = entityManager.getPrefabManager(); this.componentLibrary = componentLibrary; - this.componentSerializer = new ComponentSerializer(componentLibrary, typeSerializationLibrary); + this.componentSerializer = new ComponentSerializer(componentLibrary, typeHandlerLibrary); } /** diff --git a/engine/src/main/java/org/terasology/persistence/serializers/EventSerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/EventSerializer.java index 0bd62154969..f9b22295966 100644 --- a/engine/src/main/java/org/terasology/persistence/serializers/EventSerializer.java +++ b/engine/src/main/java/org/terasology/persistence/serializers/EventSerializer.java @@ -26,15 +26,13 @@ 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.PersistedDataSerializer; 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.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedData; -import org.terasology.persistence.typeHandling.protobuf.ProtobufSerializationContext; +import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedDataSerializer; import org.terasology.protobuf.EntityData; import java.util.Map; @@ -45,21 +43,19 @@ public class EventSerializer { private static final Logger logger = LoggerFactory.getLogger(ComponentSerializer.class); private EventLibrary eventLibrary; - private TypeSerializationLibrary typeSerializationLibrary; + private TypeHandlerLibrary typeHandlerLibrary; private BiMap, Integer> idTable = ImmutableBiMap., Integer>builder().build(); - private SerializationContext serializationContext; - private DeserializationContext deserializationContext; + private PersistedDataSerializer persistedDataSerializer; /** * 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) { + public EventSerializer(EventLibrary eventLibrary, TypeHandlerLibrary typeHandlerLibrary) { this.eventLibrary = eventLibrary; - this.typeSerializationLibrary = typeSerializationLibrary; - this.deserializationContext = new ProtobufDeserializationContext(typeSerializationLibrary); - this.serializationContext = new ProtobufSerializationContext(typeSerializationLibrary); + this.typeHandlerLibrary = typeHandlerLibrary; + this.persistedDataSerializer = new ProtobufPersistedDataSerializer(); } /** @@ -101,7 +97,7 @@ public Event deserialize(EntityData.Event eventData) { private Event deserializeOnto(Event targetEvent, EntityData.Event eventData, EventMetadata eventMetadata) { - Serializer serializer = typeSerializationLibrary.getSerializerFor(eventMetadata); + Serializer serializer = typeHandlerLibrary.getSerializerFor(eventMetadata); for (int i = 0; i < eventData.getFieldIds().size(); ++i) { byte fieldId = eventData.getFieldIds().byteAt(i); ReplicatedFieldMetadata fieldInfo = eventMetadata.getField(fieldId); @@ -110,7 +106,7 @@ private Event deserializeOnto(Event targetEvent, EntityData.Event eventData, Eve continue; } if (fieldInfo.isReplicated()) { - serializer.deserializeOnto(targetEvent, fieldInfo, new ProtobufPersistedData(eventData.getFieldValue(i)), deserializationContext); + serializer.deserializeOnto(targetEvent, fieldInfo, new ProtobufPersistedData(eventData.getFieldValue(i))); } } return targetEvent; @@ -133,11 +129,11 @@ public EntityData.Event serialize(Event event) { EntityData.Event.Builder eventData = EntityData.Event.newBuilder(); serializeEventType(event, eventData); - Serializer eventSerializer = typeSerializationLibrary.getSerializerFor(eventMetadata); + Serializer 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, serializationContext)).getValue(); + EntityData.Value serializedValue = ((ProtobufPersistedData) eventSerializer.serialize(field, event, persistedDataSerializer)).getValue(); if (serializedValue != null) { eventData.addFieldValue(serializedValue); fieldIds.write(field.getId()); diff --git a/engine/src/main/java/org/terasology/persistence/serializers/GsonSerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/GsonSerializer.java new file mode 100644 index 00000000000..49079bb83a2 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/serializers/GsonSerializer.java @@ -0,0 +1,235 @@ +/* + * Copyright 2018 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.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.SerializationException; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.gson.GsonPersistedData; +import org.terasology.persistence.typeHandling.gson.GsonPersistedDataSerializer; +import org.terasology.reflection.TypeInfo; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Optional; + +/** + * {@link GsonSerializer} provides the ability to serialize and deserialize objects to and from JSON. + */ +public class GsonSerializer extends AbstractSerializer { + private Gson gson; + + /** + * Constructs a new {@link GsonSerializer} object with the given {@link TypeHandlerLibrary}. + */ + public GsonSerializer(TypeHandlerLibrary typeHandlerLibrary) { + super(typeHandlerLibrary, new GsonPersistedDataSerializer()); + + this.gson = new Gson(); + } + + /** + * Serializes the given object to JSON and returns the serialized JSON as a {@link String}. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param The type of the object to be serialized. + * @return The serialized JSON as a {@link String}. + * @throws SerializationException Thrown when serialization fails. + * @see #writeJson(Object, TypeInfo, Writer) + */ + public String toJson(T object, TypeInfo typeInfo) throws SerializationException { + StringWriter writer = new StringWriter(); + + writeJson(object, typeInfo, writer); + + return writer.toString(); + } + + /** + * Serializes the given object to JSON and writes the serialized JSON object to the + * {@link Writer}. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param writer The {@link Writer} to which the JSON will be written. + * @param The type of the object to be serialized. + * @throws SerializationException Thrown when serialization fails. + */ + public void writeJson(T object, TypeInfo typeInfo, Writer writer) throws SerializationException { + Optional serialized = this.serialize(object, typeInfo); + + if (!serialized.isPresent()) { + throw new SerializationException("Could not find a TypeHandler for the type " + typeInfo); + } + + GsonPersistedData persistedData = (GsonPersistedData) serialized.get(); + + try { + gson.toJson(persistedData.getElement(), writer); + } catch (JsonIOException e) { + throw new SerializationException("Could not write JSON to writer", e); + } + } + + /** + * Serializes the given object to JSON and writes the serialized JSON object to the + * {@link OutputStream}. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param stream The {@link OutputStream} to which the JSON will be written. + * @param The type of the object to be serialized. + * @throws SerializationException Thrown when serialization fails. + * @throws IOException Thrown if there is an error in writing to the + * {@link OutputStream}. + * @see #writeJson(Object, TypeInfo, Writer) + */ + public void writeJson(T object, TypeInfo typeInfo, OutputStream stream) + throws IOException, SerializationException { + try (Writer writer = new BufferedWriter(new OutputStreamWriter(stream))) { + writeJson(object, typeInfo, writer); + } + } + + /** + * Serializes the given object to JSON and writes the serialized JSON object to the + * {@link File}. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param file The {@link File} that the JSON will be written to. + * @param The type of the object to be serialized. + * @throws IOException Thrown if there is an issue writing to the file. + * @throws SerializationException Thrown when serialization fails. + * @see #writeJson(Object, TypeInfo, Writer) + */ + public void writeJson(T object, TypeInfo typeInfo, File file) + throws IOException, SerializationException { + try (Writer writer = new BufferedWriter(new FileWriter(file))) { + writeJson(object, typeInfo, writer); + } + } + + /** + * Serializes the given object to JSON and writes the serialized JSON object to the + * file with the given file name. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param fileName The name of the file that the JSON will be written to. + * @param The type of the object to be serialized. + * @throws IOException Thrown if there is an issue writing to the file. + * @throws SerializationException Thrown when serialization fails. + * @see #writeJson(Object, TypeInfo, File) + */ + public void writeJson(T object, TypeInfo typeInfo, String fileName) + throws IOException, SerializationException { + writeJson(object, typeInfo, new File(fileName)); + } + + /** + * Deserializes an object of type {@link T} from the JSON in the {@link Reader}. + * + * @param reader The {@link Reader} that contains the JSON to be deserialized. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws SerializationException Thrown if the deserialization fails. + */ + public T fromJson(Reader reader, TypeInfo typeInfo) throws SerializationException { + JsonElement jsonElement; + + try { + jsonElement = gson.fromJson(reader, JsonElement.class); + } catch (JsonIOException | JsonSyntaxException e) { + throw new SerializationException("Could not read JSON from reader", e); + } + + Optional deserialized = deserialize(new GsonPersistedData(jsonElement), typeInfo); + + if (!deserialized.isPresent()) { + throw new SerializationException("Could not deserialize object of type " + typeInfo); + } + + return deserialized.get(); + } + + /** + * Deserializes an object of type {@link T} from the JSON in the {@link InputStream}. + * + * @param stream The {@link InputStream} containing the serialized JSON. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws IOException Thrown if there is an issue reading from the {@link InputStream}. + * @throws SerializationException Thrown if the deserialization fails. + * @see #fromJson(Reader, TypeInfo) + */ + public T fromJson(InputStream stream, TypeInfo typeInfo) throws IOException, SerializationException { + try (Reader reader = new InputStreamReader(stream)) { + return fromJson(reader, typeInfo); + } + } + + /** + * Deserializes an object of type {@link T} from the JSON in the {@link File}. + * + * @param file The file containing the JSON to be deserialized. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws IOException Thrown if there is an issue reading from the {@link File}. + * @throws SerializationException Thrown if the deserialization fails. + * @see #fromJson(Reader, TypeInfo) + */ + public T fromJson(File file, TypeInfo typeInfo) throws IOException, SerializationException { + try (Reader reader = new FileReader(file)) { + return fromJson(reader, typeInfo); + } + } + + /** + * Deserializes an object of type {@link T} from the JSON in the {@link File}. + * + * @param json The JSON to be deserialized + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws SerializationException Thrown if the deserialization fails. + * @see #fromJson(Reader, TypeInfo) + */ + public T fromJson(String json, TypeInfo typeInfo) throws SerializationException { + try (StringReader reader = new StringReader(json)) { + return fromJson(reader, typeInfo); + } + } +} diff --git a/engine/src/main/java/org/terasology/persistence/serializers/NetworkEntitySerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/NetworkEntitySerializer.java index a0966a4554c..87f473661d2 100644 --- a/engine/src/main/java/org/terasology/persistence/serializers/NetworkEntitySerializer.java +++ b/engine/src/main/java/org/terasology/persistence/serializers/NetworkEntitySerializer.java @@ -36,10 +36,9 @@ import org.terasology.entitySystem.prefab.Prefab; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.Serializer; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; -import org.terasology.persistence.typeHandling.protobuf.ProtobufDeserializationContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedData; -import org.terasology.persistence.typeHandling.protobuf.ProtobufSerializationContext; +import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedDataSerializer; import org.terasology.protobuf.EntityData; import java.util.Map; @@ -53,17 +52,15 @@ public class NetworkEntitySerializer { private ComponentSerializeCheck componentSerializeCheck = ComponentSerializeCheck.NullCheck.create(); private EngineEntityManager entityManager; private ComponentLibrary componentLibrary; - private TypeSerializationLibrary typeSerializationLibrary; - private ProtobufSerializationContext serializationContext; - private ProtobufDeserializationContext deserializationContext; + private TypeHandlerLibrary typeHandlerLibrary; + private ProtobufPersistedDataSerializer serializationContext; private BiMap, Integer> idTable = ImmutableBiMap., Integer>builder().build(); - public NetworkEntitySerializer(EngineEntityManager entityManager, ComponentLibrary componentLibrary, TypeSerializationLibrary typeSerializationLibrary) { + public NetworkEntitySerializer(EngineEntityManager entityManager, ComponentLibrary componentLibrary, TypeHandlerLibrary typeHandlerLibrary) { this.entityManager = entityManager; this.componentLibrary = componentLibrary; - this.typeSerializationLibrary = typeSerializationLibrary; - this.serializationContext = new ProtobufSerializationContext(typeSerializationLibrary); - this.deserializationContext = new ProtobufDeserializationContext(typeSerializationLibrary); + this.typeHandlerLibrary = typeHandlerLibrary; + this.serializationContext = new ProtobufPersistedDataSerializer(); } public void setComponentSerializeCheck(ComponentSerializeCheck componentSerializeCheck) { @@ -151,7 +148,7 @@ private void serializeComponentDelta(Component oldComponent, Component newCompon } byte fieldCount = 0; - Serializer serializer = typeSerializationLibrary.getSerializerFor(componentMetadata); + Serializer serializer = typeHandlerLibrary.getSerializerFor(componentMetadata); for (ReplicatedFieldMetadata field : componentMetadata.getFields()) { if (fieldCheck.shouldSerializeField(field, newComponent, componentInitial)) { Object oldValue = field.getValue(oldComponent); @@ -184,7 +181,7 @@ private void serializeComponentFull(Component component, boolean ignoreIfNoField return; } - Serializer serializer = typeSerializationLibrary.getSerializerFor(componentMetadata); + Serializer serializer = typeHandlerLibrary.getSerializerFor(componentMetadata); byte fieldCount = 0; for (ReplicatedFieldMetadata field : componentMetadata.getFields()) { if (fieldCheck.shouldSerializeField(field, component, componentInitial)) { @@ -228,13 +225,13 @@ public void deserializeOnto(MutableComponentContainer entity, EntityData.PackedE createdNewComponent = true; component = metadata.newInstance(); } - Serializer serializer = typeSerializationLibrary.getSerializerFor(metadata); + Serializer serializer = typeHandlerLibrary.getSerializerFor(metadata); for (int fieldIndex = 0; fieldIndex < UnsignedBytes.toInt(entityData.getComponentFieldCounts().byteAt(componentIndex)); ++fieldIndex) { byte fieldId = entityData.getFieldIds().byteAt(fieldPos); ReplicatedFieldMetadata fieldMetadata = metadata.getField(fieldId); if (fieldMetadata != null && fieldCheck.shouldDeserialize(metadata, fieldMetadata)) { logger.trace("Deserializing field {} of component {} as value {}", fieldMetadata, metadata, entityData.getFieldValue(fieldPos)); - serializer.deserializeOnto(component, fieldMetadata, new ProtobufPersistedData(entityData.getFieldValue(fieldPos)), deserializationContext); + serializer.deserializeOnto(component, fieldMetadata, new ProtobufPersistedData(entityData.getFieldValue(fieldPos))); } fieldPos++; } diff --git a/engine/src/main/java/org/terasology/persistence/serializers/PrefabSerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/PrefabSerializer.java index 4b2dd113b73..4951f7c350c 100644 --- a/engine/src/main/java/org/terasology/persistence/serializers/PrefabSerializer.java +++ b/engine/src/main/java/org/terasology/persistence/serializers/PrefabSerializer.java @@ -26,7 +26,7 @@ import org.terasology.entitySystem.prefab.PrefabData; import org.terasology.module.Module; import org.terasology.persistence.ModuleContext; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.EntityData; import java.util.Collections; @@ -50,9 +50,9 @@ public class PrefabSerializer { private ComponentSerializer componentSerializer; private ComponentLibrary componentLibrary; - public PrefabSerializer(ComponentLibrary componentLibrary, TypeSerializationLibrary typeSerializationLibrary) { + public PrefabSerializer(ComponentLibrary componentLibrary, TypeHandlerLibrary typeHandlerLibrary) { this.componentLibrary = componentLibrary; - this.componentSerializer = new ComponentSerializer(componentLibrary, typeSerializationLibrary); + this.componentSerializer = new ComponentSerializer(componentLibrary, typeHandlerLibrary); } /** diff --git a/engine/src/main/java/org/terasology/persistence/serializers/ProtobufSerializer.java b/engine/src/main/java/org/terasology/persistence/serializers/ProtobufSerializer.java new file mode 100644 index 00000000000..5a689d62b3c --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/serializers/ProtobufSerializer.java @@ -0,0 +1,189 @@ +/* + * Copyright 2018 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 org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.SerializationException; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedData; +import org.terasology.persistence.typeHandling.protobuf.ProtobufPersistedDataSerializer; +import org.terasology.protobuf.EntityData; +import org.terasology.reflection.TypeInfo; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Optional; + +/** + * {@link ProtobufSerializer} provides the ability to serialize and deserialize objects to and + * from a binary format using Protobuf. + */ +public class ProtobufSerializer extends AbstractSerializer { + /** + * Constructs a new {@link ProtobufSerializer} using the given {@link TypeHandlerLibrary}. + */ + public ProtobufSerializer(TypeHandlerLibrary typeHandlerLibrary) { + super(typeHandlerLibrary, new ProtobufPersistedDataSerializer()); + } + + /** + * Serializes the given object to bytes and returns the serialized byte array. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param The type of the object to be serialized. + * @return The serialized byte array. + * @throws SerializationException Thrown when serialization fails. + * @throws IOException Thrown if there was an error creating the + * {@link ByteArrayOutputStream}. + * @see #writeBytes(Object, TypeInfo, OutputStream) + */ + public byte[] toBytes(T object, TypeInfo typeInfo) + throws SerializationException, IOException { + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + writeBytes(object, typeInfo, stream); + return stream.toByteArray(); + } + } + + /** + * Serializes the given object to bytes and writes the bytes to the file with the given name. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param fileName The name of the file to write the serialized bytes to. + * @param The type of the object to be serialized. + * @throws SerializationException Thrown when serialization fails. + * @throws IOException Thrown if there was an error writing the bytes to the file. + * @see #writeBytes(Object, TypeInfo, File) + */ + public void writeBytes(T object, TypeInfo typeInfo, String fileName) + throws SerializationException, IOException { + writeBytes(object, typeInfo, new File(fileName)); + } + + /** + * Serializes the given object to bytes and writes the bytes to the {@link File} + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param file The {@link File} to write the serialized bytes to. + * @param The type of the object to be serialized. + * @throws SerializationException Thrown when serialization fails. + * @throws IOException Thrown if there was an error writing the bytes to the file. + * @see #writeBytes(Object, TypeInfo, File) + */ + public void writeBytes(T object, TypeInfo typeInfo, File file) + throws SerializationException, IOException { + try (FileOutputStream stream = new FileOutputStream(file)) { + writeBytes(object, typeInfo, stream); + } + } + + /** + * Serializes the given object to bytes and writes the bytes to the {@link OutputStream}. + * + * @param object The object to be serialized. + * @param typeInfo The {@link TypeInfo} specifying the type of the object to be serialized. + * @param stream The {@link OutputStream} to write the serialized bytes to. + * @param The type of the object to be serialized. + * @throws SerializationException Thrown when serialization fails. + * @see #writeBytes(Object, TypeInfo, File) + */ + public void writeBytes(T object, TypeInfo typeInfo, OutputStream stream) + throws SerializationException { + Optional serialized = this.serialize(object, typeInfo); + + if (!serialized.isPresent()) { + throw new SerializationException("Could not find a TypeHandler for the type " + typeInfo); + } + + ProtobufPersistedData persistedData = (ProtobufPersistedData) serialized.get(); + + try { + persistedData.getValue().writeDelimitedTo(stream); + } catch (IOException e) { + throw new SerializationException("Could not write bytes to stream", e); + } + } + + /** + * Deserializes an object of type {@link T} from the bytes in the {@link InputStream}. + * + * @param stream The {@link InputStream} that contains the bytes to be deserialized. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws SerializationException Thrown if the deserialization fails. + */ + public T fromBytes(InputStream stream, TypeInfo typeInfo) throws SerializationException { + EntityData.Value value; + + try { + value = EntityData.Value.parseDelimitedFrom(stream); + } catch (IOException e) { + throw new SerializationException("Could not parse bytes from Stream", e); + } + + Optional deserialized = this.deserialize(new ProtobufPersistedData(value), typeInfo); + + if (!deserialized.isPresent()) { + throw new SerializationException("Could not deserialize object of type " + typeInfo); + } + + return deserialized.get(); + } + + /** + * Deserializes an object of type {@link T} from the bytes in the {@link File}. + * + * @param file The {@link File} that contains the bytes to be deserialized. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws IOException Thrown if there was an error reading from the file. + * @throws SerializationException Thrown if the deserialization fails. + * @see #fromBytes(InputStream, TypeInfo) + */ + public T fromBytes(File file, TypeInfo typeInfo) throws IOException, SerializationException { + try (InputStream stream = new FileInputStream(file)) { + return fromBytes(stream, typeInfo); + } + } + + /** + * Deserializes an object of type {@link T} from the given bytes. + * + * @param bytes The bytes to be deserialized. + * @param typeInfo The {@link TypeInfo} specifying the type to deserialize the object as. + * @param The type to deserialize the object as. + * @return The deserialized object of type {@link T}. + * @throws IOException Thrown if there was an error creating a {@link ByteArrayInputStream}. + * @throws SerializationException Thrown if the deserialization fails. + * @see #fromBytes(InputStream, TypeInfo) + */ + public T fromBytes(byte[] bytes, TypeInfo typeInfo) throws IOException, SerializationException { + try (InputStream reader = new ByteArrayInputStream(bytes)) { + return fromBytes(reader, typeInfo); + } + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/DeserializationContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/DeserializationContext.java deleted file mode 100644 index 054d9f8750f..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/DeserializationContext.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.typeHandling; - -import java.util.List; - -/** - */ -public interface DeserializationContext { - - /** - * Attempts to deserialize the given persisted data as the specified type. Type handlers should take care not to invoke this on the type they handle or otherwise in - * a recursive manner. - * - * @param data The persisted data to deserialize - * @param type The type of the deserialized object. - * @param The type of the deserialized object. - * @return An object of type - * @throws org.terasology.persistence.typeHandling.DeserializationException if the data cannot be deserialized as type. - */ - T deserializeAs(PersistedData data, Class type); - - /** - * Atempts to deserialize the given persisted data as a List of the specified type. Type handlers should take care not to invoke this on the type they handle - * or otherwise in a recursive manner. - * - * @param data The persisted data to deserialize - should be an array - * @param type The type of the objects in the collection to deserialize as - * @param - * @return A list of objects of type - * @throws org.terasology.persistence.typeHandling.DeserializationException if the data cannot be deserialized as type - * @throws java.lang.IllegalStateException if the data is not an array. - */ - List deserializeCollection(PersistedData data, Class type); - -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/FutureTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/FutureTypeHandler.java new file mode 100644 index 00000000000..c23dee529fa --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/FutureTypeHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 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 java.util.Optional; + +class FutureTypeHandler extends TypeHandler { + public TypeHandler typeHandler; + + private void throwIfTypeHandlerNull() { + if (typeHandler == null) { + throw new SerializationException("Future TypeHandler has not been generated yet"); + } + } + + @Override + public PersistedData serialize(T value, PersistedDataSerializer serializer) { + throwIfTypeHandlerNull(); + return typeHandler.serialize(value, serializer); + } + + @Override + protected PersistedData serializeNonNull(T value, PersistedDataSerializer serializer) { + throwIfTypeHandlerNull(); + return typeHandler.serializeNonNull(value, serializer); + } + + @Override + public Optional deserialize(PersistedData data) { + throwIfTypeHandlerNull(); + return typeHandler.deserialize(data); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/InstanceCreator.java b/engine/src/main/java/org/terasology/persistence/typeHandling/InstanceCreator.java new file mode 100644 index 00000000000..28fde996d09 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/InstanceCreator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 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 java.lang.reflect.Type; + +/** + * Creates new instances of the type {@link T} to be used during deserialization. + * @param The type of which new instances are to be created. + */ +public interface InstanceCreator { + /** + * This method is called during deserialization to create an instance of the + * specified type. The fields of the returned instance are overwritten with the deserialized data. + * Since the prior contents of the object are destroyed and overwritten, always return different instances. In particular, do not return a common instance, + * always use {@code new} to create a new instance. + * + * @param type the parameterized type {@link T} represented as a {@link Type}. + * @return a default object instance of type {@link T}. + */ + T createInstance(Type type); +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataMap.java b/engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataMap.java index 9e7206eeb30..c11077eebf5 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataMap.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataMap.java @@ -15,6 +15,8 @@ */ package org.terasology.persistence.typeHandling; +import org.terasology.persistence.typeHandling.inMemory.PersistedMap; + import java.util.Map; import java.util.Set; @@ -44,4 +46,7 @@ public interface PersistedDataMap extends PersistedData { Set> entrySet(); + static PersistedDataMap of(Map map) { + return new PersistedMap(map); + } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/SerializationContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataSerializer.java similarity index 65% rename from engine/src/main/java/org/terasology/persistence/typeHandling/SerializationContext.java rename to engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataSerializer.java index 45b04b5f96d..f09167c4c07 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/SerializationContext.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/PersistedDataSerializer.java @@ -27,184 +27,164 @@ /** * */ -public interface SerializationContext { +public interface PersistedDataSerializer { /** * Serializes a single string * @param value * @return A serialized string */ - PersistedData create(String value); + PersistedData serialize(String value); /** * Serializes an array of strings. * @param values * @return A serialized array of strings. */ - PersistedData create(String... values); + PersistedData serialize(String... values); /** * Serializes a collection of strings. Null values will be stripped. * @param value * @return A serialized array of strings */ - PersistedData createStrings(Iterable value); + PersistedData serializeStrings(Iterable value); /** * Serializes a single float * @param value * @return A serialized float */ - PersistedData create(float value); + PersistedData serialize(float value); /** * Serializes an array of floats * @param values * @return A serialized array of floats */ - PersistedData create(float... values); + PersistedData serialize(float... values); /** * Serializes a collection of floats * @param value * @return A serialized array of floats */ - PersistedData create(TFloatIterator value); + PersistedData serialize(TFloatIterator value); /** * Serializes a single integer * @param value * @return A serialized integer */ - PersistedData create(int value); + PersistedData serialize(int value); /** * Serializes an array of integers * @param values * @return A serialized array of integers */ - PersistedData create(int... values); + PersistedData serialize(int... values); /** * Serializes a collection of integers * @param value * @return A serialized array of integers */ - PersistedData create(TIntIterator value); + PersistedData serialize(TIntIterator value); /** * Serializes a single long * @param value * @return A serialized long */ - PersistedData create(long value); + PersistedData serialize(long value); /** * Serializes an array of longs * @param values * @return A serialized array of longs */ - PersistedData create(long... values); + PersistedData serialize(long... values); /** * Serializes a collection of longs * @param value * @return A serialized array of longs */ - PersistedData create(TLongIterator value); + PersistedData serialize(TLongIterator value); /** * Serializes a single boolean * @param value * @return A serialized boolean */ - PersistedData create(boolean value); + PersistedData serialize(boolean value); /** * Serializes an array of booleans * @param values * @return A serialized array of booleans */ - PersistedData create(boolean... values); + PersistedData serialize(boolean... values); /** * Serializes a single double * @param value * @return A serialized double */ - PersistedData create(double value); + PersistedData serialize(double value); /** * Serializes an array of doubles * @param values * @return A serialized array double */ - PersistedData create(double... values); + PersistedData serialize(double... values); /** * Serializes a collection of doubles * @param value * @return A serialized array of double */ - PersistedData create(TDoubleIterator value); + PersistedData serialize(TDoubleIterator value); /** * Serializes an array of bytes * @param value * @return Serialized bytes */ - PersistedData create(byte[] value); + PersistedData serialize(byte[] value); /** * Serializes a buffer of bytes * @param value * @return Serialized bytes */ - PersistedData create(ByteBuffer value); + PersistedData serialize(ByteBuffer value); /** * Serializes an array of values * @param values * @return */ - PersistedData create(PersistedData... values); + PersistedData serialize(PersistedData... values); /** * Serializes a collection of values * @param data * @return */ - PersistedData create(Iterable data); + PersistedData serialize(Iterable data); /** * Serializes a map of name-value pairs * @param data * @return */ - PersistedData create(Map data); - - /** - * Attempts to serialize the given data using registered an appropriate type handler. Type handlers should take care not to invoke this on the type they - * handle or otherwise in a recursive manner. - * @param data - * @param type - * @param - * @return The serialized data - */ - PersistedData create(T data, Class type); - - /** - * Attempts to serialize the given data using registered an appropriate type handler. Type handlers should take care not to invoke this on the type they - * handle or otherwise in a recursive manner. - * @param data - * @param type - * @param - * @return The serialized data - */ - PersistedData create(Collection data, Class type); + PersistedData serialize(Map data); /** * @return A 'null' PersistedData */ - PersistedData createNull(); + PersistedData serializeNull(); } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandler.java index ecb98678bd9..a3a7c15dfec 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandler.java @@ -25,7 +25,7 @@ /** * Marks a {@link TypeHandler} to be automatically registered at a - * {@link org.terasology.persistence.typeHandling.TypeSerializationLibrary TypeSerializationLibrary} on environment change. + * {@link TypeHandlerLibrary TypeHandlerLibrary} on environment change. * This can be used to (de)serialize custom components. *

    * By default, the TypeHandler must have an empty constructor. diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandlerFactory.java new file mode 100644 index 00000000000..9dc866e21c3 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/RegisterTypeHandlerFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 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 org.terasology.module.sandbox.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a {@link TypeHandlerFactory} to be automatically registered to the + * {@link TypeHandlerLibrary} on environment change. + * This can be used to (de)serialize custom components. + *

    + * The {@link org.terasology.registry.In} annotation can be used to access objects + * in the parent {@link org.terasology.context.Context}. + */ +@API +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RegisterTypeHandlerFactory { +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/Serializer.java b/engine/src/main/java/org/terasology/persistence/typeHandling/Serializer.java index b1b235cce51..17ffc15c526 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/Serializer.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/Serializer.java @@ -56,7 +56,7 @@ public TypeHandler getHandlerFor(FieldMetadata field) { * @return The serialized value of the field */ @SuppressWarnings("unchecked") - public PersistedData serialize(FieldMetadata field, Object container, SerializationContext context) { + public PersistedData serialize(FieldMetadata field, Object container, PersistedDataSerializer context) { Object rawValue = field.getValue(container); if (rawValue != null) { TypeHandler handler = getHandlerFor(field); @@ -64,7 +64,7 @@ public PersistedData serialize(FieldMetadata field, Object container, Seri return handler.serialize(rawValue, context); } } - return context.createNull(); + return context.serializeNull(); } /** @@ -76,7 +76,7 @@ public PersistedData serialize(FieldMetadata field, Object container, Seri * @return The serialized value */ @SuppressWarnings("unchecked") - public PersistedData serializeValue(FieldMetadata fieldMetadata, Object rawValue, SerializationContext context) { + public PersistedData serializeValue(FieldMetadata fieldMetadata, Object rawValue, PersistedDataSerializer context) { return fieldHandlers.get(fieldMetadata).serialize(rawValue, context); } @@ -86,15 +86,14 @@ public PersistedData serializeValue(FieldMetadata fieldMetadata, Object ra * @param target The object to deserialize the field onto * @param fieldMetadata The metadata of the field * @param data The serialized value of the field - * @param context The deserialization context */ - public void deserializeOnto(Object target, FieldMetadata fieldMetadata, PersistedData data, DeserializationContext context) { + public void deserializeOnto(Object target, FieldMetadata fieldMetadata, PersistedData data) { TypeHandler 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.deserialize(data, context); + Object deserializedValue = handler.deserializeOrNull(data); fieldMetadata.setValue(target, deserializedValue); } catch (DeserializationException e) { logger.error("Unable to deserialize field '{}' from '{}'", fieldMetadata.getName(), data.toString(), e); @@ -107,10 +106,9 @@ 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 - * @param context The deserialization context */ - public void deserializeOnto(Object target, PersistedDataMap values, DeserializationContext context) { - deserializeOnto(target, values, DeserializeFieldCheck.NullCheck.newInstance(), context); + public void deserializeOnto(Object target, PersistedDataMap values) { + deserializeOnto(target, values, DeserializeFieldCheck.NullCheck.newInstance()); } /** @@ -120,12 +118,12 @@ public void deserializeOnto(Object target, PersistedDataMap values, Deserializat * @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, DeserializationContext context) { + public void deserializeOnto(Object target, PersistedDataMap values, DeserializeFieldCheck check) { for (Map.Entry field : values.entrySet()) { FieldMetadata fieldInfo = classMetadata.getField(field.getKey()); if (fieldInfo != null && check.shouldDeserialize(classMetadata, fieldInfo)) { - deserializeOnto(target, fieldInfo, field.getValue(), context); + deserializeOnto(target, fieldInfo, field.getValue()); } else if (fieldInfo == null) { logger.warn("Cannot deserialize unknown field '{}' onto '{}'", field.getKey(), classMetadata.getUri()); } @@ -138,8 +136,8 @@ 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, PersistedData> values, DeserializationContext context) { - deserializeOnto(target, values, context, DeserializeFieldCheck.NullCheck.newInstance()); + public void deserializeOnto(Object target, Map, PersistedData> values) { + deserializeOnto(target, values, DeserializeFieldCheck.NullCheck.newInstance()); } /** @@ -149,10 +147,10 @@ public void deserializeOnto(Object target, Map, 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, PersistedData> values, DeserializationContext context, DeserializeFieldCheck check) { + public void deserializeOnto(Object target, Map, PersistedData> values, DeserializeFieldCheck check) { for (Map.Entry, PersistedData> field : values.entrySet()) { if (check.shouldDeserialize(classMetadata, field.getKey())) { - deserializeOnto(target, field.getKey(), field.getValue(), context); + deserializeOnto(target, field.getKey(), field.getValue()); } } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/SimpleTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/SimpleTypeHandler.java deleted file mode 100644 index c465a3fa45a..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/SimpleTypeHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.typeHandling; - -import com.google.common.collect.Lists; - -import java.util.Collection; -import java.util.List; - -/** - * Abstract class for type handlers where collections of the type are simply handled by nesting individually serialized values into another value - that is there is - * no special manner in which they are handled. - * - */ -public abstract class SimpleTypeHandler implements TypeHandler { - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - List rawList = Lists.newArrayList(); - for (T item : value) { - rawList.add(serialize(item, context)); - } - return context.create(rawList); - } - - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData value : array) { - result.add(deserialize(value, context)); - } - return result; - } - return Lists.newArrayList(); - } - -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/StringRepresentationTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/StringRepresentationTypeHandler.java index f9ef1b61f96..8f8b08409da 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/StringRepresentationTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/StringRepresentationTypeHandler.java @@ -15,61 +15,28 @@ */ package org.terasology.persistence.typeHandling; -import com.google.common.collect.Lists; - -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public abstract class StringRepresentationTypeHandler implements TypeHandler { +public abstract class StringRepresentationTypeHandler extends TypeHandler { public abstract String getAsString(T item); public abstract T getFromString(String representation); @Override - public PersistedData serialize(T value, SerializationContext context) { + public PersistedData serializeNonNull(T value, PersistedDataSerializer serializer) { String stringValue = getAsString(value); - if (stringValue == null) { - return context.createNull(); - } else { - return context.create(stringValue); - } + return serializer.serialize(stringValue); } @Override - public T deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isString()) { - return getFromString(data.getAsString()); + return Optional.ofNullable(getFromString(data.getAsString())); } - return null; + return Optional.empty(); } - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - String[] result = new String[value.size()]; - int index = 0; - for (T item : value) { - if (item != null) { - result[index++] = getAsString(item); - } else { - result[index++] = ""; - } - } - return context.create(result); - } - - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - List result = Lists.newArrayList(); - for (String item : data.getAsArray().getAsStringArray()) { - if (item == null || item.isEmpty()) { - result.add(null); - } else { - result.add(getFromString(item)); - } - } - return result; - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandler.java index 8a35aaf1b88..c53f2fa8657 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandler.java @@ -15,46 +15,99 @@ */ package org.terasology.persistence.typeHandling; -import java.util.Collection; -import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; /** + * Serializes objects of type {@link T} to and from a {@link PersistedData}. */ -public interface TypeHandler { +public abstract class TypeHandler { + /** + * Serializes a single non-null value. + * + * @param value The value to serialize - will never be null. + * @param serializer The serializer used to serialize simple values + * @return The serialized value. + */ + protected abstract PersistedData serializeNonNull(T value, PersistedDataSerializer serializer); /** * Serializes a single value. - * This method should return null if the value cannot or should not be serialized. An example would be if value itself is null. + * + * The default implementation of this method returns {@link PersistedDataSerializer#serializeNull()} + * if {@code value} is null, otherwise delegates to {@link #serializeNonNull}. * * @param value The value to serialize - may be null - * @param context The persistence context to serialize into + * @param serializer The serializer used to serialize simple values * @return The serialized value. */ - PersistedData serialize(T value, SerializationContext context); + public PersistedData serialize(T value, PersistedDataSerializer serializer) { + if (value == null) { + return serializer.serializeNull(); + } + + return serializeNonNull(value, serializer); + } /** - * Deserializes a single value. + * Deserializes a single value to the type {@link T}. * - * @param data The persisted data to deserialize from - * @return The deserialized value. - * @throws org.terasology.persistence.typeHandling.DeserializationException if there was an error deserializing the data + * @param data The persisted data to deserialize from. + * @return The deserialized value. {@link Optional#empty()} if the value could not be deserialized. */ - T deserialize(PersistedData data, DeserializationContext context); + public abstract Optional deserialize(PersistedData data); /** - * Serializes a collection of this type. This allows for efficiency for types that can be serialized more efficiently in this way, such as primitives + * Deserializes a single value to the type {@link T}. If the type was not serialized + * (i.e. {@link #deserialize(PersistedData)} returned {@link Optional#empty()}), null is returned. * - * @param value The values to serialize - * @return The serialized values. + * @param data The persisted data to deserialize from. + * @return The deserialized value. {@code null} if the value could not be deserialized. */ - PersistedData serializeCollection(Collection value, SerializationContext context); + public final T deserializeOrNull(PersistedData data) { + return deserialize(data).orElse(null); + } /** - * Deserializes a collection of this type. + * Deserializes a single value to the type {@link T}. If the type was not serialized + * (i.e. {@link #deserialize(PersistedData)} returned {@link Optional#empty()}), the value retrieved + * from the {@link Supplier} is returned. * - * @param data The persisted data to deserialize from - * @return A list of the resultant values. - * @throws org.terasology.persistence.typeHandling.DeserializationException if there was an error deserializing the data + * @param data The persisted data to deserialize from. + * @param supplier The {@link Supplier} from which to retrieve the value to be returned if + * {@code data} could not be deserialized. + * @return The deserialized value. If the value could not be deserialized, the value returned by + * {@code supplier.get()} is returned. + */ + public final T deserializeOrGet(PersistedData data, Supplier supplier) { + return deserialize(data).orElseGet(supplier); + } + + /** + * Deserializes a single value to the type {@link T}. If the type was not serialized + * (i.e. {@link #deserialize(PersistedData)} returned {@link Optional#empty()}), a + * {@link DeserializationException} is thrown. + * + * @param data The persisted data to deserialize from. + * @return The deserialized value. + * @throws DeserializationException if {@code data} could not be deserialized to a value of type {@link T}. + */ + public final T deserializeOrThrow(PersistedData data) throws DeserializationException { + return deserializeOrThrow(data, "Unable to deserialize " + data); + } + + /** + * Deserializes a single value to the type {@link T}. If the type was not serialized + * (i.e. {@link #deserialize(PersistedData)} returned {@link Optional#empty()}), a + * {@link DeserializationException} is thrown. + * + * @param data The persisted data to deserialize from. + * @param errorMessage The error message to use if the value could not be deserialized. + * @return The deserialized value. + * @throws DeserializationException if {@code data} could not be deserialized to a value of type {@link T}. */ - List deserializeCollection(PersistedData data, DeserializationContext context); + public final T deserializeOrThrow(PersistedData data, String errorMessage) throws DeserializationException { + return deserialize(data) + .orElseThrow(() -> new DeserializationException(errorMessage)); + } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerContext.java new file mode 100644 index 00000000000..7d3c1a635b0 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerContext.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 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 org.reflections.Reflections; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; + +/** + * Represents the context in which a {@link TypeHandlerFactory} creates {@link TypeHandler} instances. + *

    + * The {@link TypeHandlerContext} is used to look up {@link TypeHandler} instances and load classes + * while staying within the sandbox rules if called from a module. + */ +public class TypeHandlerContext { + private TypeHandlerLibrary typeHandlerLibrary; + private SerializationSandbox sandbox; + + public TypeHandlerContext(TypeHandlerLibrary typeHandlerLibrary, SerializationSandbox sandbox) { + this.typeHandlerLibrary = typeHandlerLibrary; + this.sandbox = sandbox; + } + + /** + * Returns the {@link TypeHandlerLibrary} that called the {@link TypeHandlerFactory#create(TypeInfo, TypeHandlerContext)} method. + */ + public TypeHandlerLibrary getTypeHandlerLibrary() { + return typeHandlerLibrary; + } + + /** + * Returns the {@link SerializationSandbox} to use to load classes in the + * {@link TypeHandlerFactory#create(TypeInfo, TypeHandlerContext)} method. + */ + public SerializationSandbox getSandbox() { + return sandbox; + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerFactory.java new file mode 100644 index 00000000000..c266f369846 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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 org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +/** + * Creates type handlers for a set of types. Type handler factories are generally used when a set of types + * are similar in serialization structure. + */ +public interface TypeHandlerFactory { + /** + * Creates a {@link TypeHandler} for the given type {@link T}. If the type is not supported by + * this {@link TypeHandlerFactory}, {@link Optional#empty()} is returned. + * + * This method is usually called only once for a type, so all expensive pre-computations and reflection + * operations can be performed here so that the generated + * {@link TypeHandler#serialize(Object, PersistedDataSerializer)} and + * {@link TypeHandler#deserialize(PersistedData)} implementations are fast. + * + * @param The type for which a {@link TypeHandler} must be generated. + * @param typeInfo The {@link TypeInfo} of the type for which a {@link TypeHandler} must be generated. + * @param context The {@link TypeHandlerLibrary} for which the {@link TypeHandler} + * is being created. + * @return An {@link Optional} wrapping the created {@link TypeHandler}, or {@link Optional#empty()} + * if the type is not supported by this {@link TypeHandlerFactory}. + */ + Optional> create(TypeInfo typeInfo, TypeHandlerContext context); +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerLibrary.java b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerLibrary.java new file mode 100644 index 00000000000..f74f65787af --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeHandlerLibrary.java @@ -0,0 +1,423 @@ +/* + * 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.Lists; +import com.google.common.collect.Maps; +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.engine.module.ModuleManager; +import org.terasology.entitySystem.prefab.Prefab; +import org.terasology.math.IntegerRange; +import org.terasology.math.geom.Quat4f; +import org.terasology.math.geom.Vector2f; +import org.terasology.math.geom.Vector2i; +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.ByteArrayTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.ByteTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.CharacterTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.DoubleTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.FloatTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.IntTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.LongTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.NumberTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.RuntimeDelegatingTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.StringTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.factories.ArrayTypeHandlerFactory; +import org.terasology.persistence.typeHandling.coreTypes.factories.CollectionTypeHandlerFactory; +import org.terasology.persistence.typeHandling.coreTypes.factories.EnumTypeHandlerFactory; +import org.terasology.persistence.typeHandling.coreTypes.factories.ObjectFieldMapTypeHandlerFactory; +import org.terasology.persistence.typeHandling.coreTypes.factories.StringMapTypeHandlerFactory; +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.extensionTypes.factories.AssetTypeHandlerFactory; +import org.terasology.persistence.typeHandling.extensionTypes.factories.TextureRegionAssetTypeHandlerFactory; +import org.terasology.persistence.typeHandling.mathTypes.IntegerRangeHandler; +import org.terasology.persistence.typeHandling.mathTypes.Quat4fTypeHandler; +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.persistence.typeHandling.mathTypes.factories.Rect2fTypeHandlerFactory; +import org.terasology.persistence.typeHandling.mathTypes.factories.Rect2iTypeHandlerFactory; +import org.terasology.persistence.typeHandling.reflection.ModuleEnvironmentSandbox; +import org.terasology.persistence.typeHandling.reflection.ReflectionsSandbox; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.TypeRegistry; +import org.terasology.reflection.metadata.ClassMetadata; +import org.terasology.reflection.metadata.FieldMetadata; +import org.terasology.reflection.reflect.ConstructorLibrary; +import org.terasology.rendering.assets.texture.TextureRegion; +import org.terasology.rendering.nui.Color; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 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 TypeHandlerLibrary { + private static final Logger logger = LoggerFactory.getLogger(TypeHandlerLibrary.class); + + private SerializationSandbox sandbox; + + private List typeHandlerFactories = Lists.newArrayList(); + + private Map, TypeHandler> typeHandlerCache = Maps.newHashMap(); + + /** + * In certain object graphs, creating a {@link TypeHandler} for a type may recursively + * require an {@link TypeHandler} for the same type. Without intervention, the recursive + * lookup would stack overflow. Thus, for type handlers in the process of being created, + * we return a delegate to the {@link TypeHandler} via {@link FutureTypeHandler} which is + * wired after the {@link TypeHandler} has been created. + */ + private final ThreadLocal, FutureTypeHandler>> futureTypeHandlers = new ThreadLocal<>(); + + private Map> instanceCreators = Maps.newHashMap(); + private ConstructorLibrary constructorLibrary; + + private Map, Serializer> serializerMap = Maps.newHashMap(); + + private TypeHandlerLibrary(SerializationSandbox sandbox) { + this.sandbox = sandbox; + + constructorLibrary = new ConstructorLibrary(instanceCreators); + + addTypeHandlerFactory(new ObjectFieldMapTypeHandlerFactory(constructorLibrary)); + + addTypeHandler(Boolean.class, new BooleanTypeHandler()); + addTypeHandler(Boolean.TYPE, new BooleanTypeHandler()); + addTypeHandler(Byte.class, new ByteTypeHandler()); + addTypeHandler(Byte.TYPE, new ByteTypeHandler()); + addTypeHandler(Character.class, new CharacterTypeHandler()); + addTypeHandler(Character.TYPE, new CharacterTypeHandler()); + addTypeHandler(Double.class, new DoubleTypeHandler()); + addTypeHandler(Double.TYPE, new DoubleTypeHandler()); + addTypeHandler(Float.class, new FloatTypeHandler()); + addTypeHandler(Float.TYPE, new FloatTypeHandler()); + addTypeHandler(Integer.class, new IntTypeHandler()); + addTypeHandler(Integer.TYPE, new IntTypeHandler()); + addTypeHandler(Long.class, new LongTypeHandler()); + addTypeHandler(Long.TYPE, new LongTypeHandler()); + addTypeHandler(String.class, new StringTypeHandler()); + addTypeHandler(Number.class, new NumberTypeHandler()); + + addTypeHandlerFactory(new ArrayTypeHandlerFactory()); + addTypeHandler(byte[].class, new ByteArrayTypeHandler()); + + addTypeHandlerFactory(new EnumTypeHandlerFactory()); + addTypeHandlerFactory(new CollectionTypeHandlerFactory(constructorLibrary)); + addTypeHandlerFactory(new StringMapTypeHandlerFactory()); + + } + + /** + * + */ + public TypeHandlerLibrary(Reflections reflections) { + this(new ReflectionsSandbox(reflections)); + } + + public TypeHandlerLibrary(ModuleManager moduleManager, TypeRegistry typeRegistry) { + this(new ModuleEnvironmentSandbox(moduleManager, typeRegistry)); + } + + /** + * 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 TypeHandlerLibrary(TypeHandlerLibrary original) { + this.typeHandlerFactories.addAll(original.typeHandlerFactories); + this.instanceCreators.putAll(original.instanceCreators); + this.sandbox = original.sandbox; + } + + public static TypeHandlerLibrary withReflections(Reflections reflections) { + TypeHandlerLibrary library = new TypeHandlerLibrary(reflections); + + populateWithDefaultHandlers(library); + + return library; + } + + public static TypeHandlerLibrary forModuleEnvironment(ModuleManager moduleManager, TypeRegistry typeRegistry) { + TypeHandlerLibrary library = new TypeHandlerLibrary(moduleManager, typeRegistry); + + populateWithDefaultHandlers(library); + + return library; + } + + private static void populateWithDefaultHandlers(TypeHandlerLibrary serializationLibrary) { + serializationLibrary.addTypeHandler(Color.class, new ColorTypeHandler()); + serializationLibrary.addTypeHandler(Quat4f.class, new Quat4fTypeHandler()); + + serializationLibrary.addTypeHandlerFactory(new AssetTypeHandlerFactory()); + + serializationLibrary.addTypeHandler(Name.class, new NameTypeHandler()); + serializationLibrary.addTypeHandler(TextureRegion.class, new TextureRegionTypeHandler()); + + serializationLibrary.addTypeHandlerFactory(new TextureRegionAssetTypeHandlerFactory()); + + serializationLibrary.addTypeHandler(Vector4f.class, new Vector4fTypeHandler()); + serializationLibrary.addTypeHandler(Vector3f.class, new Vector3fTypeHandler()); + serializationLibrary.addTypeHandler(Vector2f.class, new Vector2fTypeHandler()); + serializationLibrary.addTypeHandler(Vector3i.class, new Vector3iTypeHandler()); + serializationLibrary.addTypeHandler(Vector2i.class, new Vector2iTypeHandler()); + serializationLibrary.addTypeHandlerFactory(new Rect2iTypeHandlerFactory()); + serializationLibrary.addTypeHandlerFactory(new Rect2fTypeHandlerFactory()); + serializationLibrary.addTypeHandler(Prefab.class, new PrefabTypeHandler()); + serializationLibrary.addTypeHandler(IntegerRange.class, new IntegerRangeHandler()); + } + + /** + * 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, TypeHandler> fieldHandlerMap = getFieldHandlerMap(type); + serializer = new Serializer(type, fieldHandlerMap); + serializerMap.put(type, serializer); + } + return serializer; + } + + /** + * Adds a new {@link TypeHandlerFactory} to the {@link TypeHandlerLibrary}. Factories + * added later are given a higher priority during {@link TypeHandler} generation. + */ + public void addTypeHandlerFactory(TypeHandlerFactory typeHandlerFactory) { + typeHandlerFactories.add(typeHandlerFactory); + } + + /** + * Adds a {@link TypeHandler} for the specified type to this {@link TypeHandlerLibrary} by + * adding to the library a new {@link TypeHandlerFactory} that returns the {@link TypeHandler} + * whenever the {@link TypeHandler} for the specified type is requested. + *

    + * If the specified {@link SerializationSandbox} does not allow the addition of the given + * {@link TypeHandler} for the given type, the {@link TypeHandler} is not added to the + * library and false is returned. + * + * @param typeClass The {@link Class} of the type handled by the {@link TypeHandler}. + * @param typeHandler The {@link TypeHandler} to add to the library. + * @param The type handled by the {@link TypeHandler}. + * @return True if the {@link TypeHandler} was successfully added, false otherwise. + */ + public boolean addTypeHandler(Class typeClass, TypeHandler typeHandler) { + return addTypeHandler(TypeInfo.of(typeClass), typeHandler); + } + + /** + * Adds a {@link TypeHandler} for the specified type to this {@link TypeHandlerLibrary} by + * adding to the library a new {@link TypeHandlerFactory} that returns the {@link TypeHandler} + * whenever the {@link TypeHandler} for the specified type is requested. + *

    + * If the specified {@link SerializationSandbox} does not allow the addition of the given + * {@link TypeHandler} for the given type, the {@link TypeHandler} is not added to the + * library and false is returned. + * + * @param The type handled by the {@link TypeHandler}. + * @param type The {@link TypeInfo} of the type handled by the {@link TypeHandler}. + * @param typeHandler The {@link TypeHandler} to add to the library. + * @return True if the {@link TypeHandler} was successfully added, false otherwise. + */ + public boolean addTypeHandler(TypeInfo type, TypeHandler typeHandler) { + if (!sandbox.isValidTypeHandlerDeclaration(type, typeHandler)) { + return false; + } + + TypeHandlerFactory factory = new TypeHandlerFactory() { + @SuppressWarnings("unchecked") + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + return typeInfo.equals(type) ? Optional.of((TypeHandler) typeHandler) : Optional.empty(); + } + }; + + addTypeHandlerFactory(factory); + + return true; + } + + /** + * Adds an {@link InstanceCreator} to the {@link TypeHandlerLibrary} for the specified type. + */ + public void addInstanceCreator(Class typeClass, InstanceCreator instanceCreator) { + addInstanceCreator(TypeInfo.of(typeClass), instanceCreator); + } + + /** + * Adds an {@link InstanceCreator} to the {@link TypeHandlerLibrary} for the specified type. + */ + public void addInstanceCreator(TypeInfo typeInfo, InstanceCreator instanceCreator) { + instanceCreators.put(typeInfo.getType(), instanceCreator); + } + + /** + * Retrieves the {@link TypeHandler} for the specified type, if available. + *

    + * Each {@link TypeHandlerFactory} added to this {@link TypeHandlerLibrary} is requested + * to generate a {@link TypeHandler} for the given type. Most recently added factories are + * requested first, hence a {@link TypeHandlerFactory} can override one that was added + * before it. + * + * @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> getTypeHandler(Type type) { + TypeInfo typeInfo = TypeInfo.of(type); + return (Optional>) getTypeHandler(typeInfo); + } + + /** + * Retrieves the {@link TypeHandler} for the specified type, if available. + *

    + * Each {@link TypeHandlerFactory} added to this {@link TypeHandlerLibrary} is requested + * to generate a {@link TypeHandler} for the given type. Most recently added factories are + * requested first, hence a {@link TypeHandlerFactory} can override one that was added + * before it. + * + * @param typeClass The {@link Class} of the type for which to + * retrieve the {@link TypeHandler}. + * @param The type for which to retrieve the {@link TypeHandler}. + * @return The {@link TypeHandler} for the specified type, if available. + */ + public Optional> getTypeHandler(Class typeClass) { + return getTypeHandler(TypeInfo.of(typeClass)); + } + + /** + * Retrieves the {@link TypeHandler} for the specified type, if available. + *

    + * Each {@link TypeHandlerFactory} added to this {@link TypeHandlerLibrary} is requested + * to generate a {@link TypeHandler} for the given type. Most recently added factories are + * requested first, hence a {@link TypeHandlerFactory} can override one that was added + * before it. + * + * @param type The {@link TypeInfo} describing the type for which to + * retrieve the {@link TypeHandler}. + * @param The type for which to retrieve the {@link TypeHandler}. + * @return The {@link TypeHandler} for the specified type, if available. + */ + @SuppressWarnings("unchecked") + public Optional> getTypeHandler(TypeInfo type) { + TypeHandlerContext context = new TypeHandlerContext(this, sandbox); + + if (typeHandlerCache.containsKey(type)) { + return Optional.of((TypeHandler) typeHandlerCache.get(type)); + } + + Map, FutureTypeHandler> futures = futureTypeHandlers.get(); + boolean cleanupFutureTypeHandlers = false; + + if (futures == null) { + cleanupFutureTypeHandlers = true; + futures = new HashMap<>(); + futureTypeHandlers.set(futures); + } + + FutureTypeHandler future = (FutureTypeHandler) futures.get(type); + + if (future != null) { + return Optional.of(future); + } + + try { + future = new FutureTypeHandler<>(); + futures.put(type, future); + + // TODO: Explore reversing typeHandlerFactories itself before building object + for (int i = typeHandlerFactories.size() - 1; i >= 0; i--) { + TypeHandlerFactory typeHandlerFactory = typeHandlerFactories.get(i); + Optional> typeHandler = typeHandlerFactory.create(type, context); + + if (typeHandler.isPresent()) { + TypeHandler handler = typeHandler.get(); + + if (!sandbox.isValidTypeHandlerDeclaration(type, handler)) { + continue; + } + + typeHandlerCache.put(type, handler); + future.typeHandler = handler; + + return Optional.of(handler); + } + } + + return Optional.empty(); + } finally { + futures.remove(type); + + if (cleanupFutureTypeHandlers) { + futureTypeHandlers.remove(); + } + } + } + + /** + * Returns a {@link TypeHandler} that can handle all types deriving from {@link T}. + * + * @param typeInfo The {@link TypeInfo} describing the base type for which to return a + * {@link TypeHandler}. + * @param The base type for which to return a {@link TypeHandler}. + */ + public TypeHandler getBaseTypeHandler(TypeInfo typeInfo) { + TypeHandler delegateHandler = getTypeHandler(typeInfo).orElse(null); + + TypeHandlerContext context = new TypeHandlerContext(this, sandbox); + return new RuntimeDelegatingTypeHandler<>(delegateHandler, typeInfo, context); + } + + private Map, TypeHandler> getFieldHandlerMap(ClassMetadata type) { + Map, TypeHandler> handlerMap = Maps.newHashMap(); + for (FieldMetadata field : type.getFields()) { + Optional> handler = getTypeHandler(field.getField().getGenericType()); + + if (handler.isPresent()) { + handlerMap.put(field, handler.get()); + } else { + logger.error("Unsupported field: '{}.{}'", type.getUri(), field.getName()); + } + } + return handlerMap; + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeSerializationLibrary.java b/engine/src/main/java/org/terasology/persistence/typeHandling/TypeSerializationLibrary.java deleted file mode 100644 index 7cad2f96d5b..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/TypeSerializationLibrary.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * 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.ByteArrayTypeHandler; -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.QueueTypeHandler; -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.Queue; -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, TypeHandler> typeHandlers = Maps.newHashMap(); - private Set> coreTypeHandlers = Sets.newHashSet(); - private ReflectFactory reflectFactory; - private CopyStrategyLibrary copyStrategies; - - private Map, 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()); - add(byte[].class, new ByteArrayTypeHandler()); - } - - /** - * 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, 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 The type to handle. - */ - public void add(Class type, TypeHandler handler) { - typeHandlers.put(type, handler); - coreTypeHandlers.add(type); - } - - public ImmutableSet> 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 (Queue.class.isAssignableFrom(typeClass)) { - // For queues: - Type parameter = ReflectionUtil.getTypeParameter(genericType, 0); - if (parameter != null) { - TypeHandler innerHandler = getHandlerFor(parameter); - if (innerHandler != null) { - return new QueueTypeHandler<>(innerHandler); - } - } - logger.error("Queue 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, TypeHandler> getFieldHandlerMap(ClassMetadata type) { - Map, 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; - } - - public TypeHandler getTypeHandlerFromClass(Class c) { - return this.typeHandlers.get(c); - } -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/annotations/SerializedName.java b/engine/src/main/java/org/terasology/persistence/typeHandling/annotations/SerializedName.java new file mode 100644 index 00000000000..71775885abc --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/annotations/SerializedName.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 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.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that indicates that the decorated field should be serialized with + * the provided name value as its field name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface SerializedName { + /** + * @return the desired name of the field when it is serialized or deserialized. + */ + String value(); +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandler.java new file mode 100644 index 00000000000..e7f86be1719 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 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.coreTypes; + +import com.google.common.collect.Lists; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.lang.reflect.Array; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Serializes arrays of type {@code E[]}. + * + * {@link ArrayTypeHandler} extends {@link TypeHandler} because the type parameter {@link E} + * supports only wrapper types, and primitive array to wrapper type array (and vice versa) casts are + * unsupported. The array is accessed via the {@link Array} utility class as an {@link Object} so that + * the cast can be avoided. + * + * @param The type of an element in the array to serialize. + */ +public class ArrayTypeHandler extends TypeHandler { + private TypeHandler elementTypeHandler; + private TypeInfo elementType; + + public ArrayTypeHandler(TypeHandler elementTypeHandler, TypeInfo elementType) { + this.elementTypeHandler = elementTypeHandler; + this.elementType = elementType; + } + + @Override + protected PersistedData serializeNonNull(Object value, PersistedDataSerializer serializer) { + List items = Lists.newArrayList(); + + for (int i = 0; i < Array.getLength(value); i++) { + E element = (E) Array.get(value, i); + items.add(elementTypeHandler.serialize(element, serializer)); + } + + return serializer.serialize(items); + } + + @Override + public Optional deserialize(PersistedData data) { + if (!data.isArray()) { + return Optional.empty(); + } + + @SuppressWarnings({"unchecked"}) + List items = data.getAsArray().getAsValueArray().stream() + .map(itemData -> elementTypeHandler.deserialize(itemData)) + .filter(Optional::isPresent) + .map(Optional::get).collect(Collectors.toList()); + + Object array = Array.newInstance(elementType.getRawType(), items.size()); + + for (int i = 0; i < items.size(); i++) { + Array.set(array, i, items.get(i)); + } + + return Optional.of(array); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/BooleanTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/BooleanTypeHandler.java index d194a63d543..dd05b9311b5 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/BooleanTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/BooleanTypeHandler.java @@ -15,56 +15,27 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Booleans; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class BooleanTypeHandler implements TypeHandler { +public class BooleanTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Boolean value, SerializationContext context) { - if (value != null) { - return context.create(value); - } - return context.createNull(); + public PersistedData serializeNonNull(Boolean value, PersistedDataSerializer serializer) { + return serializer.serialize(value); } @Override - public Boolean deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isBoolean()) { - return data.getAsBoolean(); + return Optional.of(data.getAsBoolean()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Booleans.toArray(value)); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData item : array) { - if (item.isBoolean()) { - result.add(item.getAsBoolean()); - } else { - result.add(null); - } - } - return result; - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteArrayTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteArrayTypeHandler.java index 7dbb3a648d7..0c04a6f39ff 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteArrayTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteArrayTypeHandler.java @@ -15,27 +15,23 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; -public class ByteArrayTypeHandler extends SimpleTypeHandler { - @Override - public PersistedData serialize(byte[] value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value); - } - } +import java.util.Optional; - @Override - public byte[] deserialize(PersistedData data, DeserializationContext context) { - if (data.isBytes()) { - return data.getAsBytes(); - } else { - return null; - } - } +public class ByteArrayTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { + @Override + public PersistedData serializeNonNull(byte[] value, PersistedDataSerializer serializer) { + return serializer.serialize(value); + } + + @Override + public Optional deserialize(PersistedData data) { + if (data.isBytes()) { + return Optional.of(data.getAsBytes()); + } else { + return Optional.empty(); + } + } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteTypeHandler.java index c09a95e3a80..d9429609687 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ByteTypeHandler.java @@ -15,49 +15,30 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Bytes; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class ByteTypeHandler implements TypeHandler { +public class ByteTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Byte value, SerializationContext context) { - if (value != null) { - return context.create(new byte[]{value}); - } - return context.createNull(); + public PersistedData serializeNonNull(Byte value, PersistedDataSerializer serializer) { + return serializer.serialize(new byte[]{value}); } @Override - public Byte deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isBytes()) { - return data.getAsBytes()[0]; + return Optional.of(data.getAsBytes()[0]); } else if (data.isNumber()) { - return (byte) data.getAsInteger(); + return Optional.of((byte) data.getAsInteger()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Bytes.toArray(value)); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isBytes()) { - return Lists.newArrayList(Bytes.asList(data.getAsBytes())); - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/CharacterTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/CharacterTypeHandler.java new file mode 100644 index 00000000000..1805e44cc2c --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/CharacterTypeHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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.coreTypes; + +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; + +import java.util.Optional; + +public class CharacterTypeHandler extends TypeHandler { + + @Override + protected PersistedData serializeNonNull(Character value, PersistedDataSerializer serializer) { + // converts value to string since char is not a JSON primitive type + return serializer.serialize(Character.toString(value)); + } + + @Override + public Optional deserialize(PersistedData data) { + if (data.isString()) { + // returns the character that was serialized as string + return Optional.of(data.getAsString().charAt(0)); + } + return Optional.empty(); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandler.java new file mode 100644 index 00000000000..f160ab0e153 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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.coreTypes; + +import com.google.common.collect.Lists; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.reflection.reflect.ObjectConstructor; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public class CollectionTypeHandler extends TypeHandler> { + private TypeHandler elementTypeHandler; + private ObjectConstructor> constructor; + + public CollectionTypeHandler(TypeHandler elementTypeHandler, ObjectConstructor> constructor) { + this.elementTypeHandler = elementTypeHandler; + this.constructor = constructor; + } + + @Override + public PersistedData serializeNonNull(Collection value, PersistedDataSerializer serializer) { + List items = Lists.newArrayList(); + + for (E element : value) { + items.add(elementTypeHandler.serialize(element, serializer)); + } + + return serializer.serialize(items); + } + + @Override + public Optional> deserialize(PersistedData data) { + if (!data.isArray()) { + return Optional.empty(); + } + + Collection collection = constructor.construct(); + + for (PersistedData item : data.getAsArray()) { + Optional element = elementTypeHandler.deserialize(item); + element.ifPresent(collection::add); + } + + return Optional.ofNullable(collection); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/DoubleTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/DoubleTypeHandler.java index 28d89d95598..9ed0d6b77f1 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/DoubleTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/DoubleTypeHandler.java @@ -15,57 +15,27 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Doubles; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class DoubleTypeHandler implements TypeHandler { +public class DoubleTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Double value, SerializationContext context) { - if (value != null) { - return context.create(value); - } - return context.createNull(); + public PersistedData serializeNonNull(Double value, PersistedDataSerializer serializer) { + return serializer.serialize(value); } @Override - public Double deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isNumber()) { - return data.getAsDouble(); + return Optional.of(data.getAsDouble()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Doubles.toArray(value)); - + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData item : array) { - if (item.isNumber()) { - result.add(item.getAsDouble()); - } else { - result.add(null); - } - } - return result; - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandler.java index 1ba6a6cf73a..0eb8ae6d2bc 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/EnumTypeHandler.java @@ -15,26 +15,20 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; /** */ -public class EnumTypeHandler implements TypeHandler { +public class EnumTypeHandler extends TypeHandler { private static final Logger logger = LoggerFactory.getLogger(EnumTypeHandler.class); private Class enumType; @@ -48,41 +42,20 @@ public EnumTypeHandler(Class enumType) { } @Override - public PersistedData serialize(T value, SerializationContext context) { - if (value != null) { - return context.create(value.toString()); - } - return context.createNull(); + public PersistedData serializeNonNull(T value, PersistedDataSerializer serializer) { + return serializer.serialize(value.toString()); } @Override - public T deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isString()) { T result = caseInsensitiveLookup.get(data.getAsString().toLowerCase(Locale.ENGLISH)); if (result == null) { logger.warn("Unknown enum value: '{}' for enum {}", data.getAsString(), enumType.getSimpleName()); } - return result; + return Optional.ofNullable(result); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - List values = value.stream().map(T::toString).collect(Collectors.toCollection(ArrayList::new)); - return context.createStrings(values); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData item : array) { - result.add(deserialize(item, context)); - } - return result; - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/FloatTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/FloatTypeHandler.java index 9831be9d6a7..869d89aeb15 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/FloatTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/FloatTypeHandler.java @@ -15,56 +15,27 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Floats; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class FloatTypeHandler implements TypeHandler { +public class FloatTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Float value, SerializationContext context) { - if (value != null) { - return context.create(value); - } - return context.createNull(); + public PersistedData serializeNonNull(Float value, PersistedDataSerializer serializer) { + return serializer.serialize(value); } @Override - public Float deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isNumber()) { - return data.getAsFloat(); + return Optional.of(data.getAsFloat()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Floats.toArray(value)); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData item : array) { - if (item.isNumber()) { - result.add(item.getAsFloat()); - } else { - result.add(null); - } - } - return result; - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/IntTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/IntTypeHandler.java index 3aa9d259a1d..0047a84acb5 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/IntTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/IntTypeHandler.java @@ -15,56 +15,27 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Ints; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class IntTypeHandler implements TypeHandler { +public class IntTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Integer value, SerializationContext context) { - if (value != null) { - return context.create(value); - } - return context.createNull(); + public PersistedData serializeNonNull(Integer value, PersistedDataSerializer serializer) { + return serializer.serialize(value); } @Override - public Integer deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isNumber()) { - return data.getAsInteger(); + return Optional.of(data.getAsInteger()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Ints.toArray(value)); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData item : array) { - if (item.isNumber()) { - result.add(item.getAsInteger()); - } else { - result.add(null); - } - } - return result; - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ListTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ListTypeHandler.java deleted file mode 100644 index 4e43378d396..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ListTypeHandler.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.typeHandling.coreTypes; - -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; -import org.terasology.persistence.typeHandling.TypeHandler; - -import java.util.List; - -/** - */ -public class ListTypeHandler extends SimpleTypeHandler> { - private TypeHandler contentsType; - - public ListTypeHandler(TypeHandler contentsType) { - this.contentsType = contentsType; - } - - - @Override - public PersistedData serialize(List value, SerializationContext context) { - if (value.size() > 0) { - return contentsType.serializeCollection(value, context); - } - return context.createNull(); - } - - @Override - public List deserialize(PersistedData data, DeserializationContext context) { - return contentsType.deserializeCollection(data, context); - } -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/LongTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/LongTypeHandler.java index de5abbcf043..287375681da 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/LongTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/LongTypeHandler.java @@ -15,56 +15,27 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Longs; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class LongTypeHandler implements TypeHandler { +public class LongTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Long value, SerializationContext context) { - if (value != null) { - return context.create(value); - } - return context.createNull(); + public PersistedData serializeNonNull(Long value, PersistedDataSerializer serializer) { + return serializer.serialize(value); } @Override - public Long deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isNumber()) { - return data.getAsLong(); + return Optional.of(data.getAsLong()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Longs.toArray(value)); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - for (PersistedData item : array) { - if (item.isNumber()) { - result.add(item.getAsLong()); - } else { - result.add(null); - } - } - return result; - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/MappedContainerTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/MappedContainerTypeHandler.java deleted file mode 100644 index 360d3fc1ed1..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/MappedContainerTypeHandler.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.typeHandling.coreTypes; - -import com.google.common.collect.Maps; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.reflection.metadata.FieldMetadata; -import org.terasology.engine.module.UriUtil; -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; -import org.terasology.persistence.typeHandling.TypeHandler; - -import java.util.Map; - -/** - */ -public class MappedContainerTypeHandler extends SimpleTypeHandler { - - private static final Logger logger = LoggerFactory.getLogger(MappedContainerTypeHandler.class); - - private Map> fieldByName = Maps.newHashMap(); - private Map, TypeHandler> mappedFields; - private Class clazz; - - public MappedContainerTypeHandler(Class clazz, Map, TypeHandler> mappedFields) { - this.clazz = clazz; - this.mappedFields = mappedFields; - for (FieldMetadata field : mappedFields.keySet()) { - this.fieldByName.put(UriUtil.normalise(field.getName()), field); - } - } - - @Override - public PersistedData serialize(T value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } - Map mappedData = Maps.newLinkedHashMap(); - for (Map.Entry, TypeHandler> entry : mappedFields.entrySet()) { - Object val = entry.getKey().getValue(value); - if (val != null) { - TypeHandler handler = entry.getValue(); - PersistedData fieldValue = handler.serialize(val, context); - if (fieldValue != null) { - mappedData.put(entry.getKey().getName(), fieldValue); - } - } - } - return context.create(mappedData); - } - - @Override - public T deserialize(PersistedData data, DeserializationContext context) { - try { - T result = clazz.newInstance(); - for (Map.Entry entry : data.getAsValueMap().entrySet()) { - FieldMetadata fieldInfo = fieldByName.get(UriUtil.normalise(entry.getKey())); - if (fieldInfo != null) { - TypeHandler handler = mappedFields.get(fieldInfo); - Object val = handler.deserialize(entry.getValue(), context); - fieldInfo.setValue(result, val); - } - } - return result; - } catch (Exception e) { - logger.error("Unable to deserialize {}", data, e); - } - return null; - } - -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/NumberTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/NumberTypeHandler.java index 99056c76ae1..2b979fc6664 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/NumberTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/NumberTypeHandler.java @@ -16,47 +16,27 @@ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import com.google.common.primitives.Doubles; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class NumberTypeHandler implements TypeHandler { +public class NumberTypeHandler extends TypeHandler { @Override - public PersistedData serialize(Number value, SerializationContext context) { - if (value != null) { - return context.create(value.doubleValue()); - } - return context.createNull(); + public PersistedData serializeNonNull(Number value, PersistedDataSerializer serializer) { + return serializer.serialize(value.doubleValue()); } @Override - public Number deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isNumber()) { - return data.getAsDouble(); + return Optional.of(data.getAsDouble()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.create(Doubles.toArray(value)); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - List result = Lists.newArrayList(); - for (PersistedData item : data.getAsArray()) { - result.add(item.getAsDouble()); - } - return result; - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ObjectFieldMapTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ObjectFieldMapTypeHandler.java new file mode 100644 index 00000000000..2ae9261e872 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/ObjectFieldMapTypeHandler.java @@ -0,0 +1,122 @@ +/* + * 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.typeHandling.coreTypes; + +import com.google.common.base.Defaults; +import com.google.common.collect.Maps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.annotations.SerializedName; +import org.terasology.reflection.reflect.ObjectConstructor; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Serializes objects as a fieldName -> fieldValue map. It is used as the last resort while serializing an + * object through a {@link TypeHandlerLibrary}. + */ +public class ObjectFieldMapTypeHandler extends TypeHandler { + + private static final Logger logger = LoggerFactory.getLogger(ObjectFieldMapTypeHandler.class); + + private Map fieldByName = Maps.newHashMap(); + private Map> mappedFields; + private ObjectConstructor constructor; + + public ObjectFieldMapTypeHandler(ObjectConstructor constructor, Map> fieldTypeHandlers) { + this.constructor = constructor; + this.mappedFields = fieldTypeHandlers; + for (Field field : fieldTypeHandlers.keySet()) { + this.fieldByName.put(getFieldName(field), field); + } + } + + @Override + public PersistedData serializeNonNull(T value, PersistedDataSerializer serializer) { + Map mappedData = Maps.newLinkedHashMap(); + for (Map.Entry> entry : mappedFields.entrySet()) { + Field field = entry.getKey(); + + Object val; + + try { + val = field.get(value); + } catch (IllegalAccessException e) { + logger.error("Field {} is inaccessible", field); + continue; + } + + if (!Objects.equals(val, Defaults.defaultValue(field.getType()))) { + TypeHandler handler = entry.getValue(); + PersistedData fieldValue = handler.serialize(val, serializer); + if (fieldValue != null) { + mappedData.put(getFieldName(field), fieldValue); + } + } + } + return serializer.serialize(mappedData); + } + + private String getFieldName(Field field) { + SerializedName serializedName = field.getAnnotation(SerializedName.class); + + if (serializedName == null) { + return field.getName(); + } + + return serializedName.value(); + } + + @Override + public Optional deserialize(PersistedData data) { + if (!data.isValueMap()) { + return Optional.empty(); + } + + try { + T result = constructor.construct(); + for (Map.Entry entry : data.getAsValueMap().entrySet()) { + String fieldName = entry.getKey(); + Field field = fieldByName.get(fieldName); + + if (field == null) { + logger.error("Cound not find field with name {}", fieldName); + continue; + } + + TypeHandler handler = mappedFields.get(field); + Optional fieldValue = handler.deserialize(entry.getValue()); + + if (fieldValue.isPresent()) { + field.set(result, fieldValue.get()); + } else { + logger.error("Could not deserialize field {}", field.getName()); + } + } + return Optional.ofNullable(result); + } catch (Exception e) { + logger.error("Unable to deserialize {}", data, e); + } + return Optional.empty(); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/QueueTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/QueueTypeHandler.java deleted file mode 100644 index 476c8381c00..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/QueueTypeHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2018 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.coreTypes; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; -import org.terasology.persistence.typeHandling.TypeHandler; - -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -public class QueueTypeHandler extends SimpleTypeHandler> { - private TypeHandler contentsType; - - public QueueTypeHandler(TypeHandler type) { - this.contentsType = type; - } - - @Override - public PersistedData serialize(Queue value, SerializationContext context) { - if (value.size() > 0) { - return contentsType.serializeCollection(value, context); - } - return context.createNull(); - } - - @Override - public Queue deserialize(PersistedData data, DeserializationContext context) { - List list = contentsType.deserializeCollection(data, context); - return new LinkedList<>(list); - } - -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandler.java new file mode 100644 index 00000000000..a01f9bb4849 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/RuntimeDelegatingTypeHandler.java @@ -0,0 +1,242 @@ +/* + * Copyright 2018 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.coreTypes; + +import com.google.common.collect.Maps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataMap; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.inMemory.PersistedMap; +import org.terasology.persistence.typeHandling.reflection.SerializationSandbox; +import org.terasology.reflection.TypeInfo; +import org.terasology.utilities.ReflectionUtil; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Delegates serialization of a value to a handler of its runtime type if needed. It is used in + * cases where a subclass instance can be referred to as its supertype. As such, it is meant + * for internal use in another {@link TypeHandler} only, and is never directly registered + * in a {@link TypeHandlerLibrary}. + * + * @param The base type whose instances may be delegated to a subtype's {@link TypeHandler} at runtime. + */ +public class RuntimeDelegatingTypeHandler extends TypeHandler { + static final String TYPE_FIELD = "class"; + static final String VALUE_FIELD = "content"; + + private static final Logger LOGGER = LoggerFactory.getLogger(RuntimeDelegatingTypeHandler.class); + + private TypeHandler delegateHandler; + private TypeInfo typeInfo; + private TypeHandlerLibrary typeHandlerLibrary; + private SerializationSandbox sandbox; + + public RuntimeDelegatingTypeHandler(TypeHandler delegateHandler, TypeInfo typeInfo, TypeHandlerContext context) { + this.delegateHandler = delegateHandler; + this.typeInfo = typeInfo; + this.typeHandlerLibrary = context.getTypeHandlerLibrary(); + this.sandbox = context.getSandbox(); + } + + @SuppressWarnings({"unchecked"}) + @Override + public PersistedData serializeNonNull(T value, PersistedDataSerializer serializer) { + // If primitive, don't go looking for the runtime type, serialize as is + if (typeInfo.getRawType().isPrimitive() || Number.class.isAssignableFrom(typeInfo.getRawType())) { + if (delegateHandler != null) { + return delegateHandler.serialize(value, serializer); + } + + LOGGER.error("Primitive '{}' does not have a TypeHandler", typeInfo); + return serializer.serializeNull(); + } + + TypeHandler chosenHandler = delegateHandler; + Type runtimeType = getRuntimeTypeIfMoreSpecific(value); + + if (!typeInfo.getType().equals(runtimeType)) { + Optional> runtimeTypeHandler = typeHandlerLibrary.getTypeHandler(runtimeType); + + chosenHandler = (TypeHandler) runtimeTypeHandler + .map(typeHandler -> { + if (delegateHandler == null) { + return typeHandler; + } else if (!(typeHandler instanceof ObjectFieldMapTypeHandler)) { + if (typeHandler.getClass().equals(delegateHandler.getClass())) { + // Both handlers are of same type, use delegateHandler + return delegateHandler; + } + + // Custom handler for runtime type + return typeHandler; + } else if (!(delegateHandler instanceof ObjectFieldMapTypeHandler)) { + // Custom handler for specified type + return delegateHandler; + } + + return typeHandler; + }) + .orElse(delegateHandler); + } + + if (chosenHandler == null) { + LOGGER.warn("Could not find appropriate TypeHandler for runtime type '{}', " + + "serializing as base type '{}'", runtimeType, typeInfo); + return serializeViaDelegate(value, serializer); + } + + if (chosenHandler == delegateHandler) { + return serializeViaDelegate(value, serializer); + } + + Map typeValuePersistedDataMap = Maps.newLinkedHashMap(); + + Class subType = (Class) ReflectionUtil.getRawType(runtimeType); + String subTypeIdentifier = sandbox.getSubTypeIdentifier(subType, typeInfo.getRawType()); + + typeValuePersistedDataMap.put( + TYPE_FIELD, + serializer.serialize(subTypeIdentifier) + ); + + PersistedData serialized = chosenHandler.serialize(value, serializer); + + // If the serialized representation is a Map, flatten it to include the class variable + if (serialized.isValueMap()) { + for (Map.Entry entry : serialized.getAsValueMap().entrySet()) { + typeValuePersistedDataMap.put(entry.getKey(), entry.getValue()); + } + } else { + typeValuePersistedDataMap.put( + VALUE_FIELD, + serialized + ); + } + + return serializer.serialize(typeValuePersistedDataMap); + } + + private PersistedData serializeViaDelegate(T value, PersistedDataSerializer serializer) { + if (delegateHandler == null) { + LOGGER.error("Base type '{}' does not have a handler", typeInfo); + return serializer.serializeNull(); + } + + return delegateHandler.serialize(value, serializer); + } + + private Type getRuntimeTypeIfMoreSpecific(T value) { + if (value == null) { + return typeInfo.getType(); + } + + return ReflectionUtil.parameterizeandResolveRawType(typeInfo.getType(), value.getClass()); + } + + @SuppressWarnings({"unchecked"}) + @Override + public Optional deserialize(PersistedData data) { + if (!data.isValueMap()) { + return deserializeViaDelegate(data); + } + + PersistedDataMap valueMap = data.getAsValueMap(); + + if (!valueMap.has(TYPE_FIELD)) { + return deserializeViaDelegate(data); + } + + String runtimeTypeName = valueMap.getAsString(TYPE_FIELD); + + Optional typeToDeserializeAs = findSubtypeWithName(runtimeTypeName); + + if (!typeToDeserializeAs.isPresent()) { + LOGGER.warn("Cannot find subtype '{}' to deserialize as, " + + "deserializing as base type '{}'", + runtimeTypeName, + typeInfo + ); + return deserializeViaDelegate(data); + } + + TypeHandler runtimeTypeHandler = (TypeHandler) typeHandlerLibrary.getTypeHandler(typeToDeserializeAs.get()) + // To avoid compile errors in the orElseGet + .map(typeHandler -> (TypeHandler) typeHandler) + .orElseGet(() -> { + LOGGER.warn("Cannot find TypeHandler for runtime type '{}', " + + "deserializing as base type '{}'", + runtimeTypeName, typeInfo); + return delegateHandler; + }); + + PersistedData valueData; + + Set> valueEntries = valueMap.entrySet(); + + if (valueEntries.size() == 2 && valueMap.has(VALUE_FIELD)) { + // The runtime value was stored in a separate field only if the two fields stored + // are TYPE_FIELD and VALUE_FIELD + + valueData = valueMap.get(VALUE_FIELD); + } else { + // The value was flattened and stored, every field except TYPE_FIELD describes the + // serialized value + + Map valueFields = Maps.newLinkedHashMap(); + + for (Map.Entry entry : valueEntries) { + valueFields.put(entry.getKey(), entry.getValue()); + } + + valueFields.remove(TYPE_FIELD); + + valueData = PersistedDataMap.of(valueFields); + } + + return runtimeTypeHandler.deserialize(valueData); + + } + + private Optional deserializeViaDelegate(PersistedData data) { + if (delegateHandler == null) { + LOGGER.error("Base type '{}' does not have a handler and no \"{}\" field " + + "was found in the serialized form {}", + typeInfo, + TYPE_FIELD, + data); + return Optional.empty(); + } + + return delegateHandler.deserialize(data); + } + + private Optional findSubtypeWithName(String runtimeTypeName) { + return sandbox.findSubTypeOf(runtimeTypeName, typeInfo.getRawType()) + .map(runtimeClass -> + ReflectionUtil.parameterizeandResolveRawType(typeInfo.getType(), runtimeClass) + ); + } + +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/SetTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/SetTypeHandler.java deleted file mode 100644 index ca2f0a1e1df..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/SetTypeHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.typeHandling.coreTypes; - -import com.google.common.collect.Sets; -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; -import org.terasology.persistence.typeHandling.TypeHandler; - -import java.util.Set; - -/** - */ -public class SetTypeHandler extends SimpleTypeHandler> { - private TypeHandler contentsHandler; - - public SetTypeHandler(TypeHandler contentsHandler) { - this.contentsHandler = contentsHandler; - } - - public TypeHandler getContentsHandler() { - return contentsHandler; - } - - @Override - public PersistedData serialize(Set value, SerializationContext context) { - return contentsHandler.serializeCollection(value, context); - } - - @Override - public Set deserialize(PersistedData data, DeserializationContext context) { - return Sets.newLinkedHashSet(contentsHandler.deserializeCollection(data, context)); - } -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringMapTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringMapTypeHandler.java index 02e2a319048..f03c4e50c4e 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringMapTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringMapTypeHandler.java @@ -16,17 +16,16 @@ package org.terasology.persistence.typeHandling.coreTypes; import com.google.common.collect.Maps; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; import java.util.Map; +import java.util.Optional; /** */ -public class StringMapTypeHandler extends SimpleTypeHandler> { +public class StringMapTypeHandler extends TypeHandler> { private TypeHandler contentsHandler; @@ -35,25 +34,30 @@ public StringMapTypeHandler(TypeHandler contentsHandler) { } @Override - public PersistedData serialize(Map value, SerializationContext context) { + public PersistedData serializeNonNull(Map value, PersistedDataSerializer serializer) { Map map = Maps.newLinkedHashMap(); for (Map.Entry entry : value.entrySet()) { - PersistedData item = contentsHandler.serialize(entry.getValue(), context); + PersistedData item = contentsHandler.serialize(entry.getValue(), serializer); if (!item.isNull()) { map.put(entry.getKey(), item); } } - return context.create(map); + return serializer.serialize(map); } @Override - public Map deserialize(PersistedData data, DeserializationContext context) { + public Optional> deserialize(PersistedData data) { + if (!data.isValueMap()) { + return Optional.empty(); + } + Map result = Maps.newLinkedHashMap(); - if (data.isValueMap()) { - for (Map.Entry item : data.getAsValueMap().entrySet()) { - result.put(item.getKey(), contentsHandler.deserialize(item.getValue(), context)); - } + + for (Map.Entry item : data.getAsValueMap().entrySet()) { + Optional optionalValue = contentsHandler.deserialize(item.getValue()); + optionalValue.ifPresent(value -> result.put(item.getKey(), value)); } - return result; + + return Optional.of(result); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringTypeHandler.java index 89dd68c94d1..eb8b910789a 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/StringTypeHandler.java @@ -15,46 +15,27 @@ */ package org.terasology.persistence.typeHandling.coreTypes; -import com.google.common.collect.Lists; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; -import java.util.List; +import java.util.Optional; /** */ -public class StringTypeHandler implements TypeHandler { +public class StringTypeHandler extends TypeHandler { @Override - public PersistedData serialize(String value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value); - } + public PersistedData serializeNonNull(String value, PersistedDataSerializer serializer) { + return serializer.serialize(value); } @Override - public String deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isString()) { - return data.getAsString(); + return Optional.ofNullable(data.getAsString()); } - return null; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - return context.createStrings(value); + return Optional.empty(); } - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - if (data.isArray()) { - return Lists.newArrayList(data.getAsArray().getAsStringArray()); - } - return Lists.newArrayList(); - } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/ArrayTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/ArrayTypeHandlerFactory.java new file mode 100644 index 00000000000..15ccaf3a95e --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/ArrayTypeHandlerFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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.coreTypes.factories; + +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.coreTypes.ArrayTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.RuntimeDelegatingTypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; +import java.util.Optional; + + +/** + * Creates type handlers for arrays. + */ +public class ArrayTypeHandlerFactory implements TypeHandlerFactory { + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + Type type = typeInfo.getType(); + + if (!(type instanceof GenericArrayType || type instanceof Class && ((Class) type).isArray())) { + return Optional.empty(); + } + + Type elementType = type instanceof GenericArrayType + ? ((GenericArrayType) type).getGenericComponentType() + : ((Class) type).getComponentType(); + + TypeInfo elementTypeInfo = TypeInfo.of(elementType); + + Optional> declaredElementTypeHandler = + context.getTypeHandlerLibrary().getTypeHandler(elementType); + + @SuppressWarnings({"unchecked"}) + TypeHandler elementTypeHandler = new RuntimeDelegatingTypeHandler( + declaredElementTypeHandler.orElse(null), + elementTypeInfo, + context + ); + + @SuppressWarnings({"unchecked"}) + TypeHandler typeHandler = new ArrayTypeHandler(elementTypeHandler, elementTypeInfo); + + return Optional.of(typeHandler); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/CollectionTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/CollectionTypeHandlerFactory.java new file mode 100644 index 00000000000..b2da254ae54 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/CollectionTypeHandlerFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.coreTypes.CollectionTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.RuntimeDelegatingTypeHandler; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.reflect.ConstructorLibrary; +import org.terasology.reflection.reflect.ObjectConstructor; +import org.terasology.utilities.ReflectionUtil; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Optional; + +/** + * Creates type handlers for {@link Collection} types. + */ +public class CollectionTypeHandlerFactory implements TypeHandlerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(CollectionTypeHandlerFactory.class); + + private ConstructorLibrary constructorLibrary; + + public CollectionTypeHandlerFactory(ConstructorLibrary constructorLibrary) { + this.constructorLibrary = constructorLibrary; + } + + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + Class rawType = typeInfo.getRawType(); + + if (!Collection.class.isAssignableFrom(rawType)) { + return Optional.empty(); + } + + Type elementType = ReflectionUtil.getTypeParameterForSuper(typeInfo.getType(), Collection.class, 0); + + if (elementType == null) { + LOGGER.error("Collection is not parameterized and cannot be serialized"); + return Optional.empty(); + } + + TypeInfo elementTypeInfo = TypeInfo.of(elementType); + + Optional> declaredElementTypeHandler = + context.getTypeHandlerLibrary().getTypeHandler(elementType); + + @SuppressWarnings({"unchecked"}) + TypeHandler elementTypeHandler = new RuntimeDelegatingTypeHandler( + declaredElementTypeHandler.orElse(null), + elementTypeInfo, + context + ); + + ObjectConstructor collectionConstructor = constructorLibrary.get(typeInfo); + + @SuppressWarnings({"unchecked"}) + TypeHandler typeHandler = new CollectionTypeHandler(elementTypeHandler, collectionConstructor); + + return Optional.of(typeHandler); + } + +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/EnumTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/EnumTypeHandlerFactory.java new file mode 100644 index 00000000000..ffee059f06a --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/EnumTypeHandlerFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.coreTypes.EnumTypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +/** + * A {@link TypeHandlerFactory} that generates an {@link EnumTypeHandler} for enum types. + */ +public class EnumTypeHandlerFactory implements TypeHandlerFactory { + @SuppressWarnings("unchecked") + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + Class enumClass = typeInfo.getRawType(); + if (!Enum.class.isAssignableFrom(enumClass) || Enum.class.equals(enumClass)) { + return Optional.empty(); + } + + while (!enumClass.isEnum()) { + enumClass = enumClass.getSuperclass(); + } + + return Optional.of((TypeHandler) new EnumTypeHandler(enumClass)); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/ObjectFieldMapTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/ObjectFieldMapTypeHandlerFactory.java new file mode 100644 index 00000000000..e65c0328ebf --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/ObjectFieldMapTypeHandlerFactory.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import com.google.common.collect.Maps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.coreTypes.ObjectFieldMapTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.RuntimeDelegatingTypeHandler; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.reflect.ConstructorLibrary; +import org.terasology.utilities.ReflectionUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Optional; + +public class ObjectFieldMapTypeHandlerFactory implements TypeHandlerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(ObjectFieldMapTypeHandlerFactory.class); + + private ConstructorLibrary constructorLibrary; + + public ObjectFieldMapTypeHandlerFactory(ConstructorLibrary constructorLibrary) { + this.constructorLibrary = constructorLibrary; + } + + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + Class typeClass = typeInfo.getRawType(); + + if (!Modifier.isAbstract(typeClass.getModifiers()) + && !typeClass.isLocalClass() + && !(typeClass.isMemberClass() && !Modifier.isStatic(typeClass.getModifiers()))) { + Map> fieldTypeHandlerMap = Maps.newLinkedHashMap(); + + getResolvedFields(typeInfo).forEach( + (field, fieldType) -> + { + Optional> declaredFieldTypeHandler = + context.getTypeHandlerLibrary().getTypeHandler(fieldType); + + TypeInfo fieldTypeInfo = TypeInfo.of(fieldType); + + fieldTypeHandlerMap.put( + field, + new RuntimeDelegatingTypeHandler( + declaredFieldTypeHandler.orElse(null), + fieldTypeInfo, + context + ) + ); + } + ); + + ObjectFieldMapTypeHandler mappedHandler = + new ObjectFieldMapTypeHandler<>(constructorLibrary.get(typeInfo), fieldTypeHandlerMap); + + return Optional.of(mappedHandler); + } + + return Optional.empty(); + } + + private Map getResolvedFields(TypeInfo typeInfo) { + Map fields = Maps.newLinkedHashMap(); + + Type type = typeInfo.getType(); + Class rawType = typeInfo.getRawType(); + + while (!Object.class.equals(rawType)) { + for (Field field : rawType.getDeclaredFields()) { + if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) { + continue; + } + + field.setAccessible(true); + Type fieldType = ReflectionUtil.resolveType(type, field.getGenericType()); + fields.put(field, fieldType); + } + + rawType = rawType.getSuperclass(); + } + + return fields; + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/StringMapTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/StringMapTypeHandlerFactory.java new file mode 100644 index 00000000000..7b214620d96 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/coreTypes/factories/StringMapTypeHandlerFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 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.coreTypes.factories; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.coreTypes.RuntimeDelegatingTypeHandler; +import org.terasology.persistence.typeHandling.coreTypes.StringMapTypeHandler; +import org.terasology.reflection.TypeInfo; +import org.terasology.utilities.ReflectionUtil; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Optional; + +public class StringMapTypeHandlerFactory implements TypeHandlerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(StringMapTypeHandler.class); + + @SuppressWarnings("unchecked") + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + if (!Map.class.isAssignableFrom(typeInfo.getRawType())) { + return Optional.empty(); + } + + Type keyType = ReflectionUtil.getTypeParameterForSuper(typeInfo.getType(), Map.class, 0); + Type valueType = ReflectionUtil.getTypeParameterForSuper(typeInfo.getType(), Map.class, 1); + + if (!String.class.equals(keyType)) { + return Optional.empty(); + } + + if (valueType == null) { + LOGGER.error("Map is not parameterized and cannot be serialized"); + return Optional.empty(); + } + + Optional> declaredValueTypeHandler = + context.getTypeHandlerLibrary().getTypeHandler(valueType); + + TypeInfo valueTypeInfo = TypeInfo.of(valueType); + + @SuppressWarnings({"unchecked"}) + TypeHandler valueTypeHandler = new RuntimeDelegatingTypeHandler( + declaredValueTypeHandler.orElse(null), + valueTypeInfo, + context + ); + + return Optional.of((TypeHandler) new StringMapTypeHandler<>(valueTypeHandler)); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandler.java index 105fd4cbc2a..3362f9799d8 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/ColorTypeHandler.java @@ -16,42 +16,39 @@ package org.terasology.persistence.typeHandling.extensionTypes; import gnu.trove.list.TIntList; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.DeserializationException; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.rendering.nui.Color; +import java.util.Optional; + /** * Serializes {@link Color} instances to an int array [r, g, b, a]. * De-serializing also supports hexadecimal strings such as "AAAAAAFF". */ -public class ColorTypeHandler extends SimpleTypeHandler { +public class ColorTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Color value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.r(), value.g(), value.b(), value.a()); - } + public PersistedData serializeNonNull(Color value, PersistedDataSerializer serializer) { + return serializer.serialize(value.r(), value.g(), value.b(), value.a()); } @Override - public Color deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 3) { TIntList vals = dataArray.getAsIntegerArray(); - return new Color(vals.get(0), vals.get(1), vals.get(2), vals.get(3)); + return Optional.of(new Color(vals.get(0), vals.get(1), vals.get(2), vals.get(3))); } } if (data.isString()) { String value = data.getAsString(); - return new Color((int) Long.parseLong(value, 16)); + return Optional.of(new Color((int) Long.parseLong(value, 16))); } - throw new DeserializationException("Expecting integer array or hex-string, but found: " + String.valueOf(data)); + + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/EntityRefTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/EntityRefTypeHandler.java index d61bcf6469d..01a26aa49c8 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/EntityRefTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/EntityRefTypeHandler.java @@ -15,24 +15,20 @@ */ package org.terasology.persistence.typeHandling.extensionTypes; -import com.google.common.collect.Lists; import gnu.trove.iterator.TLongIterator; -import gnu.trove.list.TLongList; -import gnu.trove.list.array.TLongArrayList; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.internal.EngineEntityManager; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.TypeHandler; -import java.util.Collection; import java.util.List; +import java.util.Optional; /** */ -public class EntityRefTypeHandler implements TypeHandler { +public class EntityRefTypeHandler extends TypeHandler { private EngineEntityManager entityManager; @@ -41,44 +37,19 @@ public EntityRefTypeHandler(EngineEntityManager engineEntityManager) { } @Override - public PersistedData serialize(EntityRef value, SerializationContext context) { + public PersistedData serializeNonNull(EntityRef value, PersistedDataSerializer serializer) { if (value.exists() && value.isPersistent()) { - return context.create(value.getId()); + return serializer.serialize(value.getId()); } - return context.createNull(); + return serializer.serializeNull(); } @Override - public EntityRef deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isNumber()) { - return entityManager.getEntity(data.getAsLong()); + return Optional.ofNullable(entityManager.getEntity(data.getAsLong())); } - return EntityRef.NULL; - } - - @Override - public PersistedData serializeCollection(Collection value, SerializationContext context) { - TLongList items = new TLongArrayList(); - for (EntityRef ref : value) { - if (!ref.exists()) { - items.add(0L); - } else { - if (ref.isPersistent()) { - items.add((ref).getId()); - } else { - items.add(0L); - } - } - } - return context.create(items.iterator()); - } - - @Override - public List deserializeCollection(PersistedData data, DeserializationContext context) { - PersistedDataArray array = data.getAsArray(); - List result = Lists.newArrayListWithCapacity(array.size()); - addEntitiesFromLongArray(result, array); - return result; + return Optional.ofNullable(EntityRef.NULL); } private void addEntitiesFromLongArray(List result, PersistedDataArray array) { diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/TextureRegionAssetTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/TextureRegionAssetTypeHandler.java new file mode 100644 index 00000000000..bf16867eabf --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/TextureRegionAssetTypeHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017 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.extensionTypes; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.persistence.typeHandling.StringRepresentationTypeHandler; +import org.terasology.rendering.assets.texture.TextureRegionAsset; +import org.terasology.utilities.Assets; + +import java.util.Optional; + +public class TextureRegionAssetTypeHandler extends StringRepresentationTypeHandler { + private static final Logger logger = LoggerFactory.getLogger(TextureRegionTypeHandler.class); + + @Override + public String getAsString(TextureRegionAsset item) { + if (item != null) { + return item.getUrn().toString(); + } + return ""; + } + + @Override + public TextureRegionAsset getFromString(String representation) { + Optional region = Assets.getTextureRegion(representation); + if (region.isPresent()) { + return region.get(); + } else { + logger.error("Failed to resolve texture region '" + representation + "'"); + return Assets.getTextureRegion("engine:default").get(); + } + } + +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/factories/AssetTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/factories/AssetTypeHandlerFactory.java new file mode 100644 index 00000000000..eeedc5502c2 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/factories/AssetTypeHandlerFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.extensionTypes.factories; + +import org.terasology.assets.Asset; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.extensionTypes.AssetTypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +/** + * Generates an {@link AssetTypeHandler} for each type extending {@link Asset} so that an + * {@link AssetTypeHandler} does not have to be manually registered for each subtype of {@link Asset}. + */ +public class AssetTypeHandlerFactory implements TypeHandlerFactory { + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + Class rawType = typeInfo.getRawType(); + + if (!Asset.class.isAssignableFrom(rawType)) { + return Optional.empty(); + } + + @SuppressWarnings({"unchecked"}) + TypeHandler typeHandler = new AssetTypeHandler(rawType); + + return Optional.of(typeHandler); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/factories/TextureRegionAssetTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/factories/TextureRegionAssetTypeHandlerFactory.java new file mode 100644 index 00000000000..4149fd4acc9 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/extensionTypes/factories/TextureRegionAssetTypeHandlerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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.extensionTypes.factories; + +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.extensionTypes.TextureRegionAssetTypeHandler; +import org.terasology.reflection.TypeInfo; +import org.terasology.rendering.assets.texture.TextureRegionAsset; + +import java.util.Optional; + +public class TextureRegionAssetTypeHandlerFactory implements TypeHandlerFactory { + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + if (!TextureRegionAsset.class.equals(typeInfo.getRawType())) { + return Optional.empty(); + } + + TypeHandler handler = (TypeHandler) new TextureRegionAssetTypeHandler(); + return Optional.of(handler); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonBuilderFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonBuilderFactory.java index 864619de5fc..b6ced8e5d54 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonBuilderFactory.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonBuilderFactory.java @@ -15,10 +15,9 @@ */ package org.terasology.persistence.typeHandling.gson; -import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapterFactory; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; /** * Class containing static factory methods for generating {@link GsonBuilder} objects that follow Terasology @@ -35,14 +34,14 @@ public static GsonBuilder createDefaultGsonBuilder() { /** * Create a {@link GsonBuilder} which uses type handlers loaded from the given - * {@link TypeSerializationLibrary} and complies with Terasology JSON serialization rules. + * {@link TypeHandlerLibrary} and complies with Terasology JSON serialization rules. * - * @param typeSerializationLibrary The {@link TypeSerializationLibrary} to load type handler + * @param typeHandlerLibrary The {@link TypeHandlerLibrary} to load type handler * definitions from */ - public static GsonBuilder createGsonBuilderWithTypeSerializationLibrary(TypeSerializationLibrary typeSerializationLibrary) { + public static GsonBuilder createGsonBuilderWithTypeSerializationLibrary(TypeHandlerLibrary typeHandlerLibrary) { TypeAdapterFactory typeAdapterFactory = - new GsonTypeSerializationLibraryAdapterFactory(typeSerializationLibrary); + new GsonTypeSerializationLibraryAdapterFactory(typeHandlerLibrary); return createDefaultGsonBuilder() .registerTypeAdapterFactory(typeAdapterFactory); diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonDeserializationContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonDeserializationContext.java deleted file mode 100644 index bb3f4a90709..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonDeserializationContext.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.typeHandling.gson; - -import com.google.common.collect.Lists; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonParseException; -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.DeserializationException; -import org.terasology.persistence.typeHandling.PersistedData; - -import java.util.List; - -/** - */ -public class GsonDeserializationContext implements DeserializationContext { - - private JsonDeserializationContext context; - - public GsonDeserializationContext(JsonDeserializationContext context) { - this.context = context; - } - - @Override - public T deserializeAs(PersistedData data, Class type) { - GsonPersistedData gsonData = (GsonPersistedData) data; - try { - return context.deserialize(gsonData.getElement(), type); - } catch (JsonParseException jpe) { - throw new DeserializationException("Failed to deserialize data as " + type, jpe); - } - } - - @Override - public List deserializeCollection(PersistedData data, Class type) { - if (!data.isArray()) { - throw new IllegalStateException("Data is not an array"); - } - List result = Lists.newArrayList(); - for (PersistedData item : data.getAsArray()) { - result.add(deserializeAs(item, type)); - } - return result; - } -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonMapExclusionStrategy.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonMapExclusionStrategy.java index 6b35deedfff..b78ca8f15e8 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonMapExclusionStrategy.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonMapExclusionStrategy.java @@ -19,7 +19,6 @@ import com.google.gson.FieldAttributes; import org.terasology.utilities.ReflectionUtil; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; @@ -27,7 +26,7 @@ public class GsonMapExclusionStrategy implements ExclusionStrategy { @Override public boolean shouldSkipField(FieldAttributes f) { Type fieldType = f.getDeclaredType(); - Class fieldClass = ReflectionUtil.getClassOfType(fieldType); + Class fieldClass = ReflectionUtil.getRawType(fieldType); if (Map.class.isAssignableFrom(fieldClass)) { Type mapKeyType = ReflectionUtil.getTypeParameter(fieldType, 0); diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonSerializationContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonPersistedDataSerializer.java similarity index 68% rename from engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonSerializationContext.java rename to engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonPersistedDataSerializer.java index ab652bb3fe6..621bd05a0b0 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonSerializationContext.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonPersistedDataSerializer.java @@ -20,43 +20,35 @@ import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; import gnu.trove.iterator.TDoubleIterator; import gnu.trove.iterator.TFloatIterator; import gnu.trove.iterator.TIntIterator; import gnu.trove.iterator.TLongIterator; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.Collection; import java.util.Map; /** */ -public class GsonSerializationContext implements SerializationContext { +public class GsonPersistedDataSerializer implements PersistedDataSerializer { private static final PersistedData NULL_INSTANCE = new GsonPersistedData(JsonNull.INSTANCE); - private JsonSerializationContext context; - - public GsonSerializationContext(JsonSerializationContext context) { - this.context = context; - } - @Override - public PersistedData create(String value) { + public PersistedData serialize(String value) { return new GsonPersistedData(new JsonPrimitive(value)); } @Override - public PersistedData create(String... values) { - return createStrings(Arrays.asList(values)); + public PersistedData serialize(String... values) { + return serializeStrings(Arrays.asList(values)); } @Override - public PersistedData createStrings(Iterable value) { + public PersistedData serializeStrings(Iterable value) { JsonArray array = new JsonArray(); for (String val : value) { array.add(new JsonPrimitive(val)); @@ -65,12 +57,12 @@ public PersistedData createStrings(Iterable value) { } @Override - public PersistedData create(float value) { + public PersistedData serialize(float value) { return new GsonPersistedData(new JsonPrimitive(value)); } @Override - public PersistedData create(float... values) { + public PersistedData serialize(float... values) { JsonArray array = new JsonArray(); for (float val : values) { array.add(new JsonPrimitive(val)); @@ -79,7 +71,7 @@ public PersistedData create(float... values) { } @Override - public PersistedData create(TFloatIterator value) { + public PersistedData serialize(TFloatIterator value) { JsonArray array = new JsonArray(); while (value.hasNext()) { array.add(new JsonPrimitive(value.next())); @@ -88,12 +80,12 @@ public PersistedData create(TFloatIterator value) { } @Override - public PersistedData create(int value) { + public PersistedData serialize(int value) { return new GsonPersistedData(new JsonPrimitive(value)); } @Override - public PersistedData create(int... values) { + public PersistedData serialize(int... values) { JsonArray array = new JsonArray(); for (int val : values) { array.add(new JsonPrimitive(val)); @@ -102,7 +94,7 @@ public PersistedData create(int... values) { } @Override - public PersistedData create(TIntIterator value) { + public PersistedData serialize(TIntIterator value) { JsonArray array = new JsonArray(); while (value.hasNext()) { array.add(new JsonPrimitive(value.next())); @@ -111,12 +103,12 @@ public PersistedData create(TIntIterator value) { } @Override - public PersistedData create(long value) { + public PersistedData serialize(long value) { return new GsonPersistedData(new JsonPrimitive(value)); } @Override - public PersistedData create(long... values) { + public PersistedData serialize(long... values) { JsonArray array = new JsonArray(); for (long val : values) { array.add(new JsonPrimitive(val)); @@ -125,7 +117,7 @@ public PersistedData create(long... values) { } @Override - public PersistedData create(TLongIterator value) { + public PersistedData serialize(TLongIterator value) { JsonArray array = new JsonArray(); while (value.hasNext()) { array.add(new JsonPrimitive(value.next())); @@ -134,12 +126,12 @@ public PersistedData create(TLongIterator value) { } @Override - public PersistedData create(boolean value) { + public PersistedData serialize(boolean value) { return new GsonPersistedData(new JsonPrimitive(value)); } @Override - public PersistedData create(boolean... values) { + public PersistedData serialize(boolean... values) { JsonArray array = new JsonArray(); for (boolean val : values) { array.add(new JsonPrimitive(val)); @@ -148,12 +140,12 @@ public PersistedData create(boolean... values) { } @Override - public PersistedData create(double value) { + public PersistedData serialize(double value) { return new GsonPersistedData(new JsonPrimitive(value)); } @Override - public PersistedData create(double... values) { + public PersistedData serialize(double... values) { JsonArray array = new JsonArray(); for (double val : values) { array.add(new JsonPrimitive(val)); @@ -162,7 +154,7 @@ public PersistedData create(double... values) { } @Override - public PersistedData create(TDoubleIterator value) { + public PersistedData serialize(TDoubleIterator value) { JsonArray array = new JsonArray(); while (value.hasNext()) { array.add(new JsonPrimitive(value.next())); @@ -171,22 +163,22 @@ public PersistedData create(TDoubleIterator value) { } @Override - public PersistedData create(byte[] value) { + public PersistedData serialize(byte[] value) { return new GsonPersistedData(new JsonPrimitive(BaseEncoding.base64().encode(value))); } @Override - public PersistedData create(ByteBuffer value) { - return create(value.array()); + public PersistedData serialize(ByteBuffer value) { + return serialize(value.array()); } @Override - public PersistedData create(PersistedData... data) { - return create(Arrays.asList(data)); + public PersistedData serialize(PersistedData... data) { + return serialize(Arrays.asList(data)); } @Override - public PersistedData create(Iterable data) { + public PersistedData serialize(Iterable data) { JsonArray result = new JsonArray(); for (PersistedData val : data) { if (val != null) { @@ -199,7 +191,7 @@ public PersistedData create(Iterable data) { } @Override - public PersistedData create(Map data) { + public PersistedData serialize(Map data) { JsonObject object = new JsonObject(); for (Map.Entry entry : data.entrySet()) { object.add(entry.getKey(), ((GsonPersistedData) entry.getValue()).getElement()); @@ -208,21 +200,7 @@ public PersistedData create(Map data) { } @Override - public PersistedData create(T data, Class type) { - return new GsonPersistedData(context.serialize(data, type)); - } - - @Override - public PersistedData create(Collection data, Class type) { - JsonArray array = new JsonArray(); - for (T item : data) { - array.add(context.serialize(item, type)); - } - return new GsonPersistedData(array); - } - - @Override - public PersistedData createNull() { + public PersistedData serializeNull() { return NULL_INSTANCE; } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerAdapter.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerAdapter.java index 2ccf980cc4b..b720eb57d40 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerAdapter.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeHandlerAdapter.java @@ -58,11 +58,11 @@ public final class GsonTypeHandlerAdapter extends TypeAdapter { this.typeHandler = typeHandler; this.serializer = (src, typeOfSrc, context) -> - ((GsonPersistedData) typeHandler.serialize(src, new GsonSerializationContext(context))) + ((GsonPersistedData) typeHandler.serialize(src, new GsonPersistedDataSerializer())) .getElement(); this.deserializer = (json, typeOfT, context) -> - typeHandler.deserialize(new GsonPersistedData(json), new GsonDeserializationContext(context)); + typeHandler.deserializeOrNull(new GsonPersistedData(json)); this.gson = gson; this.typeToken = typeToken; diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactory.java index 9443b02a92c..78236af5e17 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactory.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/GsonTypeSerializationLibraryAdapterFactory.java @@ -20,20 +20,21 @@ import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import org.terasology.persistence.typeHandling.TypeHandler; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; +import org.terasology.persistence.typeHandling.coreTypes.ObjectFieldMapTypeHandler; import java.lang.reflect.Type; /** * A Gson {@link TypeAdapterFactory} that dynamically looks up the {@link TypeHandler} from a - * {@link TypeSerializationLibrary} for each type encountered, and creates a {@link GsonTypeHandlerAdapter} with + * {@link TypeHandlerLibrary} for each type encountered, and creates a {@link GsonTypeHandlerAdapter} with * the retrieved {@link TypeHandler}. */ public class GsonTypeSerializationLibraryAdapterFactory implements TypeAdapterFactory { - private final TypeSerializationLibrary typeSerializationLibrary; + private final TypeHandlerLibrary typeHandlerLibrary; - public GsonTypeSerializationLibraryAdapterFactory(TypeSerializationLibrary typeSerializationLibrary) { - this.typeSerializationLibrary = typeSerializationLibrary; + public GsonTypeSerializationLibraryAdapterFactory(TypeHandlerLibrary typeHandlerLibrary) { + this.typeHandlerLibrary = typeHandlerLibrary; } @SuppressWarnings("unchecked") @@ -41,9 +42,9 @@ public GsonTypeSerializationLibraryAdapterFactory(TypeSerializationLibrary typeS public TypeAdapter create(Gson gson, TypeToken type) { Type rawType = type.getType(); - TypeHandler typeHandler = (TypeHandler) typeSerializationLibrary.getHandlerFor(rawType); + TypeHandler typeHandler = (TypeHandler) typeHandlerLibrary.getTypeHandler(rawType).orElse(null); - if (typeHandler == null) { + if (typeHandler == null || typeHandler instanceof ObjectFieldMapTypeHandler) { return null; } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/LegacyGsonTypeHandlerAdapter.java b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/LegacyGsonTypeHandlerAdapter.java index 944e561d8cb..93b914fb285 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/gson/LegacyGsonTypeHandlerAdapter.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/gson/LegacyGsonTypeHandlerAdapter.java @@ -43,11 +43,11 @@ public LegacyGsonTypeHandlerAdapter(TypeHandler handler) { @Override public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return typeHandler.deserialize(new GsonPersistedData(json), new GsonDeserializationContext(context)); + return typeHandler.deserializeOrNull(new GsonPersistedData(json)); } @Override public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { - return ((GsonPersistedData) typeHandler.serialize(src, new GsonSerializationContext(context))).getElement(); + return ((GsonPersistedData) typeHandler.serialize(src, new GsonPersistedDataSerializer())).getElement(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/inMemory/PersistedMap.java b/engine/src/main/java/org/terasology/persistence/typeHandling/inMemory/PersistedMap.java index f9509a09008..17401f21753 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/inMemory/PersistedMap.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/inMemory/PersistedMap.java @@ -31,6 +31,16 @@ public PersistedMap(Map map) { this.map = map; } + @Override + public PersistedDataMap getAsValueMap() { + return this; + } + + @Override + public boolean isValueMap() { + return true; + } + @Override public boolean has(String name) { return map.containsKey(name); diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/BorderTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/BorderTypeHandler.java deleted file mode 100644 index 83dd337ef60..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/BorderTypeHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.typeHandling.mathTypes; - -import com.google.common.collect.Maps; -import org.terasology.math.Border; -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataMap; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; - -import java.util.Map; - -/** - */ -public class BorderTypeHandler extends SimpleTypeHandler { - private static final String LEFT_FIELD = "left"; - private static final String RIGHT_FIELD = "right"; - private static final String TOP_FIELD = "top"; - private static final String BOTTOM_FIELD = "bottom"; - - @Override - public PersistedData serialize(Border value, SerializationContext context) { - if (value != null) { - Map map = Maps.newLinkedHashMap(); - map.put(LEFT_FIELD, context.create(value.getLeft())); - map.put(RIGHT_FIELD, context.create(value.getRight())); - map.put(TOP_FIELD, context.create(value.getTop())); - map.put(BOTTOM_FIELD, context.create(value.getBottom())); - return context.create(map); - } - - return context.createNull(); - } - - @Override - public Border deserialize(PersistedData data, DeserializationContext context) { - if (!data.isNull() && data.isValueMap()) { - PersistedDataMap map = data.getAsValueMap(); - return new Border(map.getAsInteger(LEFT_FIELD), map.getAsInteger(RIGHT_FIELD), map.getAsInteger(TOP_FIELD), map.getAsInteger(BOTTOM_FIELD)); - } - return null; - } -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Quat4fTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Quat4fTypeHandler.java index b656e65ac21..ce6568d621f 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Quat4fTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Quat4fTypeHandler.java @@ -17,35 +17,30 @@ import gnu.trove.list.TFloatList; import org.terasology.math.geom.Quat4f; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; + +import java.util.Optional; /** */ -public class Quat4fTypeHandler extends SimpleTypeHandler { +public class Quat4fTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Quat4f value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.x, value.y, value.z, value.w); - } - + public PersistedData serializeNonNull(Quat4f value, PersistedDataSerializer serializer) { + return serializer.serialize(value.x, value.y, value.z, value.w); } @Override - public Quat4f deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 3) { TFloatList floats = dataArray.getAsFloatArray(); - return new Quat4f(floats.get(0), floats.get(1), floats.get(2), floats.get(3)); + return Optional.of(new Quat4f(floats.get(0), floats.get(1), floats.get(2), floats.get(3))); } } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2fTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2fTypeHandler.java index e7061570fc7..5b0cba2bd33 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2fTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2fTypeHandler.java @@ -19,42 +19,52 @@ import com.google.common.collect.Maps; import org.terasology.math.geom.Rect2f; import org.terasology.math.geom.Vector2f; -import org.terasology.persistence.typeHandling.DeserializationContext; +import org.terasology.persistence.typeHandling.DeserializationException; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataMap; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; import java.util.Map; +import java.util.Optional; /** */ -public class Rect2fTypeHandler extends SimpleTypeHandler { +public class Rect2fTypeHandler extends TypeHandler { private static final String MIN_FIELD = "min"; private static final String SIZE_FIELD = "size"; + private TypeHandler vector2fTypeHandler; + + public Rect2fTypeHandler(TypeHandler vector2fTypeHandler) { + this.vector2fTypeHandler = vector2fTypeHandler; + } + @Override - public PersistedData serialize(Rect2f value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - Map map = Maps.newLinkedHashMap(); - map.put(MIN_FIELD, context.create(value.min(), Vector2f.class)); - map.put(SIZE_FIELD, context.create(value.size(), Vector2f.class)); - return context.create(map); - } + public PersistedData serializeNonNull(Rect2f value, PersistedDataSerializer serializer) { + Map map = Maps.newLinkedHashMap(); + + map.put(MIN_FIELD, vector2fTypeHandler.serialize(value.min(), serializer)); + map.put(SIZE_FIELD, vector2fTypeHandler.serialize(value.size(), serializer)); + + return serializer.serialize(map); } @Override - public Rect2f deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (!data.isNull() && data.isValueMap()) { PersistedDataMap map = data.getAsValueMap(); - Vector2f min = context.deserializeAs(map.get(MIN_FIELD), Vector2f.class); - Vector2f size = context.deserializeAs(map.get(SIZE_FIELD), Vector2f.class); - return Rect2f.createFromMinAndSize(min, size); + + Vector2f min = vector2fTypeHandler.deserializeOrThrow(map.get(MIN_FIELD), + "Could not deserialize Rect2f." + MIN_FIELD); + + Vector2f size = vector2fTypeHandler.deserializeOrThrow(map.get(SIZE_FIELD), + "Could not deserialize Rect2f." + SIZE_FIELD); + + return Optional.ofNullable(Rect2f.createFromMinAndSize(min, size)); } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2iTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2iTypeHandler.java index 0cf8701759f..9483553e115 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2iTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Rect2iTypeHandler.java @@ -20,42 +20,52 @@ import org.terasology.math.geom.Rect2i; import org.terasology.math.geom.Vector2i; -import org.terasology.persistence.typeHandling.DeserializationContext; +import org.terasology.persistence.typeHandling.DeserializationException; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataMap; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; +import org.terasology.persistence.typeHandling.TypeHandler; import java.util.Map; +import java.util.Optional; /** */ -public class Rect2iTypeHandler extends SimpleTypeHandler { +public class Rect2iTypeHandler extends TypeHandler { private static final String MIN_FIELD = "min"; private static final String SIZE_FIELD = "size"; + private TypeHandler vector2iTypeHandler; + + public Rect2iTypeHandler(TypeHandler vector2iTypeHandler) { + this.vector2iTypeHandler = vector2iTypeHandler; + } + @Override - public PersistedData serialize(Rect2i value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - Map map = Maps.newLinkedHashMap(); - map.put(MIN_FIELD, context.create(value.min(), Vector2i.class)); - map.put(SIZE_FIELD, context.create(value.size(), Vector2i.class)); - return context.create(map); - } + public PersistedData serializeNonNull(Rect2i value, PersistedDataSerializer serializer) { + Map map = Maps.newLinkedHashMap(); + + map.put(MIN_FIELD, vector2iTypeHandler.serialize(value.min(), serializer)); + map.put(SIZE_FIELD, vector2iTypeHandler.serialize(value.size(), serializer)); + + return serializer.serialize(map); } @Override - public Rect2i deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (!data.isNull() && data.isValueMap()) { PersistedDataMap map = data.getAsValueMap(); - Vector2i min = context.deserializeAs(map.get(MIN_FIELD), Vector2i.class); - Vector2i size = context.deserializeAs(map.get(SIZE_FIELD), Vector2i.class); - return Rect2i.createFromMinAndSize(min, size); + + Vector2i min = vector2iTypeHandler.deserializeOrThrow(map.get(MIN_FIELD), + "Could not deserialize Rect2i." + MIN_FIELD); + + Vector2i size = vector2iTypeHandler.deserializeOrThrow(map.get(SIZE_FIELD), + "Could not deserialize Rect2i." + SIZE_FIELD); + + return Optional.ofNullable(Rect2i.createFromMinAndSize(min, size)); } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Region3iTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Region3iTypeHandler.java deleted file mode 100644 index 95949922fc5..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Region3iTypeHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.typeHandling.mathTypes; - -import com.google.common.collect.Maps; -import org.terasology.engine.module.UriUtil; -import org.terasology.math.Region3i; -import org.terasology.math.geom.Vector3i; -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.PersistedDataMap; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; - -import java.util.Map; - -/** - */ -public class Region3iTypeHandler extends SimpleTypeHandler { - - private static final String MIN_FIELD = UriUtil.normalise("min"); - private static final String SIZE_FIELD = UriUtil.normalise("size"); - - @Override - public PersistedData serialize(Region3i value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - Map map = Maps.newLinkedHashMap(); - map.put(MIN_FIELD, context.create(value.min(), Vector3i.class)); - map.put(SIZE_FIELD, context.create(value.size(), Vector3i.class)); - return context.create(map); - } - } - - @Override - public Region3i deserialize(PersistedData data, DeserializationContext context) { - if (!data.isNull() && data.isValueMap()) { - PersistedDataMap map = data.getAsValueMap(); - Vector3i min = context.deserializeAs(map.get(MIN_FIELD), Vector3i.class); - Vector3i size = context.deserializeAs(map.get(SIZE_FIELD), Vector3i.class); - return Region3i.createFromMinAndSize(min, size); - } - return null; - } - -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2fTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2fTypeHandler.java index de6ddfe5f78..bb1ca2abd94 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2fTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2fTypeHandler.java @@ -18,35 +18,30 @@ import gnu.trove.list.TFloatList; import org.terasology.math.geom.Vector2f; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; + +import java.util.Optional; /** */ -public class Vector2fTypeHandler extends SimpleTypeHandler { +public class Vector2fTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Vector2f value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.x, value.y); - } - + public PersistedData serializeNonNull(Vector2f value, PersistedDataSerializer serializer) { + return serializer.serialize(value.x, value.y); } @Override - public Vector2f deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 1) { TFloatList floats = dataArray.getAsFloatArray(); - return new Vector2f(floats.get(0), floats.get(1)); + return Optional.of(new Vector2f(floats.get(0), floats.get(1))); } } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2iTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2iTypeHandler.java index 28f23592042..21b8be00bce 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2iTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector2iTypeHandler.java @@ -17,34 +17,30 @@ import gnu.trove.list.TIntList; import org.terasology.math.geom.Vector2i; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; + +import java.util.Optional; /** */ -public class Vector2iTypeHandler extends SimpleTypeHandler { +public class Vector2iTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Vector2i value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.x, value.y); - } + public PersistedData serializeNonNull(Vector2i value, PersistedDataSerializer serializer) { + return serializer.serialize(value.x, value.y); } @Override - public Vector2i deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 1) { TIntList ints = dataArray.getAsIntegerArray(); - return new Vector2i(ints.get(0), ints.get(1)); + return Optional.of(new Vector2i(ints.get(0), ints.get(1))); } } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3fTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3fTypeHandler.java index d1e699f520d..0865a3cd61b 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3fTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3fTypeHandler.java @@ -17,34 +17,30 @@ import gnu.trove.list.TFloatList; import org.terasology.math.geom.Vector3f; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; + +import java.util.Optional; /** */ -public class Vector3fTypeHandler extends SimpleTypeHandler { +public class Vector3fTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Vector3f value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.x, value.y, value.z); - } + public PersistedData serializeNonNull(Vector3f value, PersistedDataSerializer serializer) { + return serializer.serialize(value.x, value.y, value.z); } @Override - public Vector3f deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 2) { TFloatList floats = dataArray.getAsFloatArray(); - return new Vector3f(floats.get(0), floats.get(1), floats.get(2)); + return Optional.of(new Vector3f(floats.get(0), floats.get(1), floats.get(2))); } } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3iTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3iTypeHandler.java index 1235fc1fbc0..5fa568ab04c 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3iTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector3iTypeHandler.java @@ -17,34 +17,30 @@ import gnu.trove.list.TIntList; import org.terasology.math.geom.Vector3i; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; + +import java.util.Optional; /** */ -public class Vector3iTypeHandler extends SimpleTypeHandler { +public class Vector3iTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Vector3i value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.x, value.y, value.z); - } + public PersistedData serializeNonNull(Vector3i value, PersistedDataSerializer serializer) { + return serializer.serialize(value.x, value.y, value.z); } @Override - public Vector3i deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 2) { TIntList ints = dataArray.getAsIntegerArray(); - return new Vector3i(ints.get(0), ints.get(1), ints.get(2)); + return Optional.of(new Vector3i(ints.get(0), ints.get(1), ints.get(2))); } } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector4fTypeHandler.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector4fTypeHandler.java index 89e5e24f8ae..c7ffaece261 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector4fTypeHandler.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/Vector4fTypeHandler.java @@ -18,34 +18,30 @@ import gnu.trove.list.TFloatList; import org.terasology.math.geom.Vector4f; -import org.terasology.persistence.typeHandling.DeserializationContext; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataArray; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.SimpleTypeHandler; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; + +import java.util.Optional; /** */ -public class Vector4fTypeHandler extends SimpleTypeHandler { +public class Vector4fTypeHandler extends org.terasology.persistence.typeHandling.TypeHandler { @Override - public PersistedData serialize(Vector4f value, SerializationContext context) { - if (value == null) { - return context.createNull(); - } else { - return context.create(value.x, value.y, value.z, value.w); - } + public PersistedData serializeNonNull(Vector4f value, PersistedDataSerializer serializer) { + return serializer.serialize(value.x, value.y, value.z, value.w); } @Override - public Vector4f deserialize(PersistedData data, DeserializationContext context) { + public Optional deserialize(PersistedData data) { if (data.isArray()) { PersistedDataArray dataArray = data.getAsArray(); if (dataArray.isNumberArray() && dataArray.size() > 3) { TFloatList floats = dataArray.getAsFloatArray(); - return new Vector4f(floats.get(0), floats.get(1), floats.get(2), floats.get(3)); + return Optional.of(new Vector4f(floats.get(0), floats.get(1), floats.get(2), floats.get(3))); } } - return null; + return Optional.empty(); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/factories/Rect2fTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/factories/Rect2fTypeHandlerFactory.java new file mode 100644 index 00000000000..84525ea2283 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/factories/Rect2fTypeHandlerFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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.mathTypes.factories; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.math.geom.Rect2f; +import org.terasology.math.geom.Vector2f; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.mathTypes.Rect2fTypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +public class Rect2fTypeHandlerFactory implements TypeHandlerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(Rect2fTypeHandlerFactory.class); + + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + if (!typeInfo.equals(TypeInfo.of(Rect2f.class))) { + return Optional.empty(); + } + + Optional> vector2fTypeHandler = + context.getTypeHandlerLibrary().getTypeHandler(Vector2f.class); + + if (!vector2fTypeHandler.isPresent()) { + LOGGER.error("No Vector2f type handler found"); + return Optional.empty(); + } + + Rect2fTypeHandler rect2fTypeHandler = + new Rect2fTypeHandler(vector2fTypeHandler.get()); + + return Optional.of((TypeHandler) rect2fTypeHandler); + + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/factories/Rect2iTypeHandlerFactory.java b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/factories/Rect2iTypeHandlerFactory.java new file mode 100644 index 00000000000..61d101e9cc3 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/mathTypes/factories/Rect2iTypeHandlerFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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.mathTypes.factories; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.math.geom.Rect2i; +import org.terasology.math.geom.Vector2i; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.persistence.typeHandling.TypeHandlerFactory; +import org.terasology.persistence.typeHandling.TypeHandlerContext; +import org.terasology.persistence.typeHandling.mathTypes.Rect2iTypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +public class Rect2iTypeHandlerFactory implements TypeHandlerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(Rect2iTypeHandlerFactory.class); + + @Override + public Optional> create(TypeInfo typeInfo, TypeHandlerContext context) { + if (!typeInfo.equals(TypeInfo.of(Rect2i.class))) { + return Optional.empty(); + } + + Optional> vector2iTypeHandler = + context.getTypeHandlerLibrary().getTypeHandler(Vector2i.class); + + if (!vector2iTypeHandler.isPresent()) { + LOGGER.error("No Vector2i type handler found"); + return Optional.empty(); + } + + Rect2iTypeHandler rect2iTypeHandler = + new Rect2iTypeHandler(vector2iTypeHandler.get()); + + return Optional.of((TypeHandler) rect2iTypeHandler); + + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufDeserializationContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufDeserializationContext.java deleted file mode 100644 index 2d461dfb741..00000000000 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufDeserializationContext.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.typeHandling.protobuf; - -import com.google.common.collect.Lists; - -import org.terasology.persistence.typeHandling.DeserializationContext; -import org.terasology.persistence.typeHandling.DeserializationException; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.TypeHandler; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; - -import java.util.List; - -/** - */ -public class ProtobufDeserializationContext implements DeserializationContext { - - private TypeSerializationLibrary typeSerializationLibrary; - - public ProtobufDeserializationContext(TypeSerializationLibrary typeSerializationLibrary) { - this.typeSerializationLibrary = typeSerializationLibrary; - } - - @Override - public T deserializeAs(PersistedData data, Class type) { - TypeHandler handler = typeSerializationLibrary.getHandlerFor(type); - if (handler == null) { - throw new DeserializationException("No handler found for " + type); - } - return type.cast(handler.deserialize(data, this)); - } - - @Override - public List deserializeCollection(PersistedData data, Class type) { - if (!data.isArray()) { - throw new IllegalStateException("Data is not an array"); - } - List result = Lists.newArrayList(); - for (PersistedData item : data.getAsArray()) { - result.add(deserializeAs(item, type)); - } - return result; - } -} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufSerializationContext.java b/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufPersistedDataSerializer.java similarity index 69% rename from engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufSerializationContext.java rename to engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufPersistedDataSerializer.java index c7fd0084d79..e58e80ac015 100644 --- a/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufSerializationContext.java +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/protobuf/ProtobufPersistedDataSerializer.java @@ -21,48 +21,39 @@ import gnu.trove.iterator.TIntIterator; import gnu.trove.iterator.TLongIterator; import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.SerializationContext; -import org.terasology.persistence.typeHandling.TypeHandler; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.protobuf.EntityData; import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.Collection; import java.util.Map; /** */ -public class ProtobufSerializationContext implements SerializationContext { - - private TypeSerializationLibrary library; - - public ProtobufSerializationContext(TypeSerializationLibrary library) { - this.library = library; - } +public class ProtobufPersistedDataSerializer implements PersistedDataSerializer { @Override - public PersistedData create(String value) { - return createStrings(Arrays.asList(value)); + public PersistedData serialize(String value) { + return serializeStrings(Arrays.asList(value)); } @Override - public PersistedData create(String... values) { - return createStrings(Arrays.asList(values)); + public PersistedData serialize(String... values) { + return serializeStrings(Arrays.asList(values)); } @Override - public PersistedData createStrings(Iterable value) { + public PersistedData serializeStrings(Iterable value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().addAllString(value).build()); } @Override - public PersistedData create(float value) { + public PersistedData serialize(float value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().addFloat(value).build()); } @Override - public PersistedData create(float... values) { + public PersistedData serialize(float... values) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (float val : values) { builder.addFloat(val); @@ -71,7 +62,7 @@ public PersistedData create(float... values) { } @Override - public PersistedData create(TFloatIterator value) { + public PersistedData serialize(TFloatIterator value) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); while (value.hasNext()) { builder.addFloat(value.next()); @@ -80,12 +71,12 @@ public PersistedData create(TFloatIterator value) { } @Override - public PersistedData create(int value) { + public PersistedData serialize(int value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().addInteger(value).build()); } @Override - public PersistedData create(int... values) { + public PersistedData serialize(int... values) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (int val : values) { builder.addInteger(val); @@ -94,7 +85,7 @@ public PersistedData create(int... values) { } @Override - public PersistedData create(TIntIterator value) { + public PersistedData serialize(TIntIterator value) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); while (value.hasNext()) { builder.addInteger(value.next()); @@ -103,12 +94,12 @@ public PersistedData create(TIntIterator value) { } @Override - public PersistedData create(long value) { + public PersistedData serialize(long value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().addLong(value).build()); } @Override - public PersistedData create(long... values) { + public PersistedData serialize(long... values) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (long val : values) { builder.addLong(val); @@ -117,7 +108,7 @@ public PersistedData create(long... values) { } @Override - public PersistedData create(TLongIterator value) { + public PersistedData serialize(TLongIterator value) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); while (value.hasNext()) { builder.addLong(value.next()); @@ -126,12 +117,12 @@ public PersistedData create(TLongIterator value) { } @Override - public PersistedData create(boolean value) { + public PersistedData serialize(boolean value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().addBoolean(value).build()); } @Override - public PersistedData create(boolean... values) { + public PersistedData serialize(boolean... values) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (boolean val : values) { builder.addBoolean(val); @@ -140,12 +131,12 @@ public PersistedData create(boolean... values) { } @Override - public PersistedData create(double value) { + public PersistedData serialize(double value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().addDouble(value).build()); } @Override - public PersistedData create(double... values) { + public PersistedData serialize(double... values) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (double val : values) { builder.addDouble(val); @@ -154,7 +145,7 @@ public PersistedData create(double... values) { } @Override - public PersistedData create(TDoubleIterator value) { + public PersistedData serialize(TDoubleIterator value) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); while (value.hasNext()) { builder.addDouble(value.next()); @@ -163,22 +154,22 @@ public PersistedData create(TDoubleIterator value) { } @Override - public PersistedData create(byte[] value) { + public PersistedData serialize(byte[] value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().setBytes(ByteString.copyFrom(value)).build()); } @Override - public PersistedData create(ByteBuffer value) { + public PersistedData serialize(ByteBuffer value) { return new ProtobufPersistedData(EntityData.Value.newBuilder().setBytes(ByteString.copyFrom(value)).build()); } @Override - public PersistedData create(PersistedData... data) { - return create(Arrays.asList(data)); + public PersistedData serialize(PersistedData... data) { + return serialize(Arrays.asList(data)); } @Override - public PersistedData create(Iterable data) { + public PersistedData serialize(Iterable data) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (PersistedData value : data) { builder.addValue(((ProtobufPersistedData) value).getValue()); @@ -187,7 +178,7 @@ public PersistedData create(Iterable data) { } @Override - public PersistedData create(Map data) { + public PersistedData serialize(Map data) { EntityData.Value.Builder builder = EntityData.Value.newBuilder(); for (Map.Entry entry : data.entrySet()) { builder.addNameValue(EntityData.NameValue.newBuilder() @@ -198,20 +189,7 @@ public PersistedData create(Map data) { } @Override - public PersistedData create(T data, Class type) { - TypeHandler handler = (TypeHandler) library.getHandlerFor(type); - return handler.serialize(data, this); - } - - @Override - public PersistedData create(Collection data, Class type) { - TypeHandler handler = (TypeHandler) library.getHandlerFor(type); - return handler.serializeCollection(data, this); - - } - - @Override - public PersistedData createNull() { + public PersistedData serializeNull() { return new ProtobufPersistedData(EntityData.Value.newBuilder().build()); } } diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/ModuleEnvironmentSandbox.java b/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/ModuleEnvironmentSandbox.java new file mode 100644 index 00000000000..a1ef521706a --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/ModuleEnvironmentSandbox.java @@ -0,0 +1,172 @@ +/* + * Copyright 2019 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.reflection; + +import com.google.common.base.Preconditions; +import org.terasology.engine.SimpleUri; +import org.terasology.engine.module.ModuleManager; +import org.terasology.module.ModuleEnvironment; +import org.terasology.naming.Name; +import org.terasology.persistence.ModuleContext; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.TypeRegistry; + +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; + +public class ModuleEnvironmentSandbox implements SerializationSandbox { + private final TypeRegistry typeRegistry; + private final ModuleManager moduleManager; + + public ModuleEnvironmentSandbox(ModuleManager moduleManager, TypeRegistry typeRegistry) { + this.moduleManager = moduleManager; + this.typeRegistry = typeRegistry; + } + + private ModuleEnvironment getEnvironment() { + return moduleManager.getEnvironment(); + } + + @Override + public Optional> findSubTypeOf(String subTypeIdentifier, Class clazz) { + if (getModuleProviding(clazz) == null) { + // Assume that subTypeIdentifier is full name + return typeRegistry.load(subTypeIdentifier) + // If loaded class is not a subtype, return empty + .filter(clazz::isAssignableFrom) + .map(sub -> (Class) sub); + } + + Iterator> possibilities = + typeRegistry + .getSubtypesOf(clazz) + .stream() + .filter(subclass -> doesSubclassMatch(subclass, subTypeIdentifier)) + .iterator(); + + if (possibilities.hasNext()) { + Class possibility = possibilities.next(); + + // Multiple possibilities + if (possibilities.hasNext()) { + return Optional.empty(); + } + + return Optional.of(possibility); + } + + // No possibility + return Optional.empty(); + } + + private boolean doesSubclassMatch(Class subclass, String subTypeIdentifier) { + if (subclass == null) { + return false; + } + + SimpleUri subTypeUri = new SimpleUri(subTypeIdentifier); + Name subTypeName = subTypeUri.isValid() ? subTypeUri.getObjectName() : new Name(subTypeIdentifier); + + // First check full name + boolean fullNameEquals = subTypeName.toString().equals((subclass.getName())); + + if (fullNameEquals) { + return true; + } + + // Now check through module and simple name + Name providingModule = getModuleProviding(subclass); + Name givenModuleName; + + if (subTypeUri.isValid()) { + givenModuleName = subTypeUri.getModuleName(); + } else { + // Assume that the requested subtype is in the context module + givenModuleName = ModuleContext.getContext() != null ? ModuleContext.getContext().getId() : null; + } + + return Objects.equals(givenModuleName, providingModule) && subTypeName.toString().equals(subclass.getSimpleName()); + } + + @Override + public String getSubTypeIdentifier(Class subType, Class baseType) { + String subTypeUri = getTypeUri(subType); + + if (getModuleProviding(baseType) == null) { + return subType.getName(); + } + + long subTypesWithSameUri = typeRegistry.getSubtypesOf(baseType).stream() + .map(this::getTypeUri) + .filter(subTypeUri::equals) + .count(); + + Preconditions.checkArgument(subTypesWithSameUri > 0, + "Subtype " + subType + " was not found in the module environment"); + + if (subTypesWithSameUri > 1) { + // More than one subType with same SimpleUri, use fully qualified name + return subType.getName(); + } + + return subTypeUri.toString(); + } + + @Override + public boolean isValidTypeHandlerDeclaration(TypeInfo type, TypeHandler typeHandler) { + Name moduleDeclaringHandler = getModuleProviding(typeHandler.getClass()); + + // If handler was declared outside of a module (engine or somewhere else), we allow it + // TODO: Possibly find better way to refer to engine module + if (moduleDeclaringHandler == null || moduleDeclaringHandler.equals(new Name("engine"))) { + return true; + } + + // Handler has been declared in a module, proceed accordingly + + if (type.getRawType().getClassLoader() == null) { + // Modules cannot specify handlers for builtin classes + return false; + } + + Name moduleDeclaringType = getModuleProviding(type.getRawType()); + + // Both the type and the handler must come from the same module + return Objects.equals(moduleDeclaringType, moduleDeclaringHandler); + } + + private String getTypeUri(Class type) { + Name moduleProvidingType = getModuleProviding(type); + + if (moduleProvidingType == null || moduleProvidingType.isEmpty()) { + return type.getName(); + } + + String typeSimpleName = type.getSimpleName(); + + return new SimpleUri(moduleProvidingType, typeSimpleName).toString(); + } + + private Name getModuleProviding(Class type) { + if (type.getClassLoader() == null) { + return null; + } + + return getEnvironment().getModuleProviding(type); + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/ReflectionsSandbox.java b/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/ReflectionsSandbox.java new file mode 100644 index 00000000000..05324baffca --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/ReflectionsSandbox.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 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.reflection; + +import org.reflections.Reflections; +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; +import java.util.Set; + +public class ReflectionsSandbox implements SerializationSandbox { + private final Reflections reflections; + + public ReflectionsSandbox(Reflections reflections) { + this.reflections = reflections; + } + + @Override + public Optional> findSubTypeOf(String subTypeIdentifier, Class clazz) { + Set> subTypes = reflections.getSubTypesOf(clazz); + + subTypes.removeIf(subType -> !subTypeIdentifier.equals(subType.getName())); + + if (subTypes.size() == 1) { + return Optional.ofNullable(subTypes.iterator().next()); + } + + // If there are multiple/no possibilities, return empty Optional + return Optional.empty(); + } + + @Override + public String getSubTypeIdentifier(Class subType, Class baseType) { + return subType.getName(); + } + + @Override + public boolean isValidTypeHandlerDeclaration(TypeInfo type, TypeHandler typeHandler) { + return true; + } +} diff --git a/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/SerializationSandbox.java b/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/SerializationSandbox.java new file mode 100644 index 00000000000..a6f2aed8a03 --- /dev/null +++ b/engine/src/main/java/org/terasology/persistence/typeHandling/reflection/SerializationSandbox.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 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.reflection; + +import org.terasology.persistence.typeHandling.TypeHandler; +import org.terasology.reflection.TypeInfo; + +import java.util.Optional; + +/** + * Provides an interface to the sandboxed environment that serialization may be performed in. + * This allows serialization to load types according to the rules specified in the sandbox it + * is being executed in. + */ +public interface SerializationSandbox { + /** + * Finds a subtype of {@link T} with the given identifier. If zero or more than one + * subtypes are identified by the given identifier, {@link Optional#empty()} is returned. + * + * @param subTypeIdentifier The identifier of the subtype to look up + * @param clazz The {@link Class} of the base type whose subtype is to be found. + * @param The base type whose subtype is to be found. + * @return An {@link Optional} containing the unique subtype of {@link T}, if found. + */ + Optional> findSubTypeOf(String subTypeIdentifier, Class clazz); + + /** + * Returns a unique identifier of the given subtype of the given base type. This method is + * guaranteed to not return the same identifier for any other subtype of the given base type. + * + * @param subType The {@link Class} specifying the subtype which is to be identified. + * @param baseType The {@link Class} specifying the base type. + * @param The base type whose subtype needs to be identified. + * @return The unique identifier for {@code subType}. + */ + String getSubTypeIdentifier(Class subType, Class baseType); + + /** + * Checks whether the given {@link TypeHandler} should be allowed to handle instances of + * the given type according to the rules in the sandbox. + * + * @param type The {@link TypeInfo} describing {@link T}. + * @param typeHandler An instance of the {@link TypeHandler} implementation that handles + * instances of type {@link T}. + * @param The type being handled by the {@link TypeHandler}. + * @return True if the sandbox allows this handler implementation, false otherwise. + */ + boolean isValidTypeHandlerDeclaration(TypeInfo type, TypeHandler typeHandler); +} diff --git a/engine/src/main/java/org/terasology/recording/RecordAndReplaySerializer.java b/engine/src/main/java/org/terasology/recording/RecordAndReplaySerializer.java index 183dec6a62d..fe88443df8a 100644 --- a/engine/src/main/java/org/terasology/recording/RecordAndReplaySerializer.java +++ b/engine/src/main/java/org/terasology/recording/RecordAndReplaySerializer.java @@ -23,10 +23,11 @@ import com.google.gson.stream.JsonWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.terasology.engine.module.ModuleManager; import org.terasology.engine.paths.PathManager; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.math.geom.Vector3f; -import org.terasology.module.ModuleEnvironment; +import org.terasology.reflection.TypeRegistry; import java.io.FileWriter; import java.io.FileReader; @@ -47,7 +48,6 @@ public final class RecordAndReplaySerializer { private static final String STATE_EVENT_POSITION = "/state_event_position" + JSON; private static final String DIRECTION_ORIGIN_LIST = "/direction_origin_list" + JSON; - private EntityManager entityManager; private RecordedEventStore recordedEventStore; private RecordAndReplayUtils recordAndReplayUtils; private CharacterStateEventPositionMap characterStateEventPositionMap; @@ -55,14 +55,15 @@ public final class RecordAndReplaySerializer { private RecordedEventSerializer recordedEventSerializer; public RecordAndReplaySerializer(EntityManager manager, RecordedEventStore store, - RecordAndReplayUtils recordAndReplayUtils, CharacterStateEventPositionMap characterStateEventPositionMap, - DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList, ModuleEnvironment moduleEnvironment) { - this.entityManager = manager; + RecordAndReplayUtils recordAndReplayUtils, + CharacterStateEventPositionMap characterStateEventPositionMap, + DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList, + ModuleManager moduleManager, TypeRegistry typeRegistry) { this.recordedEventStore = store; this.recordAndReplayUtils = recordAndReplayUtils; this.characterStateEventPositionMap = characterStateEventPositionMap; this.directionAndOriginPosRecorderList = directionAndOriginPosRecorderList; - this.recordedEventSerializer = new RecordedEventSerializer(entityManager, moduleEnvironment); + this.recordedEventSerializer = new RecordedEventSerializer(manager, moduleManager, typeRegistry); } /** @@ -73,7 +74,7 @@ public void serializeRecordAndReplayData() { serializeRecordedEvents(recordingPath); Gson gson = new GsonBuilder().create(); serializeFileAmount(gson, recordingPath); - serializeCharacterStateEventPositonMap(gson, recordingPath); + serializeCharacterStateEventPositionMap(gson, recordingPath); serializeAttackEventExtraRecorder(gson, recordingPath); } @@ -97,7 +98,7 @@ public void deserializeRecordAndReplayData() { deserializeRecordedEvents(recordingPath); Gson gson = new GsonBuilder().create(); deserializeFileAmount(gson, recordingPath); - deserializeCharacterStateEventPositonMap(gson, recordingPath); + deserializeCharacterStateEventPositionMap(gson, recordingPath); deserializeAttackEventExtraRecorder(gson, recordingPath); } @@ -135,7 +136,7 @@ private void deserializeFileAmount(Gson gson, String recordingPath) { } } - private void serializeCharacterStateEventPositonMap(Gson gson, String recordingPath) { + private void serializeCharacterStateEventPositionMap(Gson gson, String recordingPath) { try { JsonWriter writer = new JsonWriter(new FileWriter(recordingPath + STATE_EVENT_POSITION)); gson.toJson(characterStateEventPositionMap.getIdToData(), HashMap.class, writer); @@ -147,7 +148,7 @@ private void serializeCharacterStateEventPositonMap(Gson gson, String recordingP } } - private void deserializeCharacterStateEventPositonMap(Gson gson, String recordingPath) { + private void deserializeCharacterStateEventPositionMap(Gson gson, String recordingPath) { try (FileReader fileReader = new FileReader(recordingPath + STATE_EVENT_POSITION)) { JsonParser parser = new JsonParser(); JsonElement jsonElement = parser.parse(fileReader); diff --git a/engine/src/main/java/org/terasology/recording/RecordedEventSerializer.java b/engine/src/main/java/org/terasology/recording/RecordedEventSerializer.java index 7981f6dd066..92fe46da2ca 100644 --- a/engine/src/main/java/org/terasology/recording/RecordedEventSerializer.java +++ b/engine/src/main/java/org/terasology/recording/RecordedEventSerializer.java @@ -15,67 +15,23 @@ */ package org.terasology.recording; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonWriter; -import gnu.trove.list.TFloatList; -import gnu.trove.list.array.TFloatArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.terasology.audio.StaticSound; -import org.terasology.audio.events.PlaySoundEvent; -import org.terasology.engine.SimpleUri; +import org.terasology.engine.module.ModuleManager; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.entitySystem.entity.EntityRef; -import org.terasology.entitySystem.entity.LowLevelEntityManager; import org.terasology.entitySystem.entity.internal.EngineEntityManager; -import org.terasology.entitySystem.event.Event; -import org.terasology.input.cameraTarget.CameraTargetChangedEvent; -import org.terasology.logic.characters.CharacterMoveInputEvent; -import org.terasology.logic.characters.GetMaxSpeedEvent; -import org.terasology.logic.characters.MovementMode; -import org.terasology.logic.characters.events.AttackEvent; -import org.terasology.math.geom.Vector2i; -import org.terasology.math.geom.Vector3f; -import org.terasology.math.geom.Vector3i; -import org.terasology.module.ModuleEnvironment; -import org.terasology.persistence.typeHandling.PersistedData; -import org.terasology.persistence.typeHandling.TypeHandler; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; -import org.terasology.persistence.typeHandling.coreTypes.EnumTypeHandler; +import org.terasology.persistence.serializers.GsonSerializer; +import org.terasology.persistence.typeHandling.SerializationException; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.extensionTypes.EntityRefTypeHandler; -import org.terasology.persistence.typeHandling.gson.GsonDeserializationContext; -import org.terasology.persistence.typeHandling.gson.GsonPersistedData; -import org.terasology.persistence.typeHandling.gson.GsonSerializationContext; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.reflect.ReflectionReflectFactory; -import org.terasology.naming.Name; -import org.terasology.input.BindAxisEvent; -import org.terasology.input.BindButtonEvent; -import org.terasology.input.events.InputEvent; -import org.terasology.input.events.KeyUpEvent; -import org.terasology.input.events.KeyRepeatEvent; -import org.terasology.input.events.KeyDownEvent; -import org.terasology.input.events.KeyEvent; -import org.terasology.input.events.MouseAxisEvent; -import org.terasology.input.events.MouseButtonEvent; -import org.terasology.input.events.MouseWheelEvent; -import org.terasology.input.ButtonState; -import org.terasology.input.Keyboard; -import org.terasology.input.MouseInput; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.TypeRegistry; - -import java.io.FileReader; -import java.io.FileWriter; +import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * Serializes and deserializes RecordedEvents. @@ -83,441 +39,46 @@ class RecordedEventSerializer { private static final Logger logger = LoggerFactory.getLogger(RecordedEventSerializer.class); - private static final double DEFAULT_DOUBLE_VALUE = 0.0; - private TypeSerializationLibrary typeSerializationLibrary; - private EntityManager entityManager; - private ModuleEnvironment moduleEnvironment; - private Map> inputEventClassMap; - - - RecordedEventSerializer(EntityManager entityManager, ModuleEnvironment moduleEnvironment) { - ReflectionReflectFactory reflectFactory = new ReflectionReflectFactory(); - CopyStrategyLibrary copyStrategyLibrary = new CopyStrategyLibrary(reflectFactory); - this.typeSerializationLibrary = TypeSerializationLibrary.createDefaultLibrary(reflectFactory, copyStrategyLibrary); - typeSerializationLibrary.add(EntityRef.class, new EntityRefTypeHandler((EngineEntityManager) entityManager)); - typeSerializationLibrary.add(MouseAxisEvent.MouseAxis.class, new EnumTypeHandler<>(MouseAxisEvent.MouseAxis.class)); - typeSerializationLibrary.add(ButtonState.class, new EnumTypeHandler<>(ButtonState.class)); - typeSerializationLibrary.add(Keyboard.Key.class, new EnumTypeHandler<>(Keyboard.Key.class)); - typeSerializationLibrary.add(MouseInput.class, new EnumTypeHandler<>(MouseInput.class)); - typeSerializationLibrary.add(MovementMode.class, new EnumTypeHandler<>(MovementMode.class)); - this.entityManager = entityManager; - this.moduleEnvironment = moduleEnvironment; - } - void serializeRecordedEvents(List events, String filePath) { - try { - JsonWriter writer = new JsonWriter(new FileWriter(filePath)); - writer.beginObject(); - writer.name("events"); - writer.beginArray(); - for (RecordedEvent event : events) { - writer.beginObject(); - writer.name("entityRef_ID").value(event.getEntityId()); - writer.name("timestamp").value(event.getTimestamp()); - writer.name("index").value(event.getIndex()); - writer.name("event_class").value(event.getEvent().getClass().getName()); - writer.name("event_data"); - writer.beginObject(); - writeSpecificEventData(writer, event.getEvent()); - writer.endObject(); - writer.endObject(); - } - writer.endArray(); - writer.endObject(); - writer.close(); - } catch (Exception e) { - logger.error("Error while serializing events:", e); - } + private GsonSerializer gsonSerializer; + public RecordedEventSerializer(EntityManager entityManager, ModuleManager moduleManager, TypeRegistry typeRegistry) { + TypeHandlerLibrary typeHandlerLibrary = TypeHandlerLibrary.forModuleEnvironment(moduleManager, typeRegistry); + typeHandlerLibrary.addTypeHandler(EntityRef.class, new EntityRefTypeHandler((EngineEntityManager) entityManager)); + gsonSerializer = new GsonSerializer(typeHandlerLibrary); } - private void writeSpecificEventData(JsonWriter writer, Event event) { + /** + * Serializes RecordedEvent's list. + * + * @param events RecordedEvent's list. + * @param filePath path where the data should be saved. + */ + public void serializeRecordedEvents(List events, String filePath) { try { - GsonSerializationContext serializationContext = new GsonSerializationContext(null); - Gson gson = new GsonBuilder().create(); - if (event instanceof InputEvent) { - InputEvent e = (InputEvent) event; - writer.name("delta").value(e.getDelta()); - writer.name("consumed").value(e.isConsumed()); - writer.name("target").value(e.getTarget().getId()); - - writeVector3fData(writer, serializationContext, e); - writeInputEventInstanceData(writer, event, serializationContext); - - - } else if (event instanceof CameraTargetChangedEvent) { - CameraTargetChangedEvent e = (CameraTargetChangedEvent) event; - writer.name("OldTarget").value(e.getOldTarget().getId()); - writer.name("NewTarget").value(e.getNewTarget().getId()); - } else if (event instanceof PlaySoundEvent) { - PlaySoundEvent e = (PlaySoundEvent) event; - writer.name("volume").value(e.getVolume()); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(StaticSound.class); - PersistedData data = handler.serialize(e.getSound(), serializationContext); - writer.name("sound").value(data.getAsString()); - - } else if (event instanceof CharacterMoveInputEvent) { - CharacterMoveInputEvent e = (CharacterMoveInputEvent) event; - writer.name("delta").value(e.getDeltaMs()); - writer.name("pitch").value(e.getPitch()); - writer.name("yaw").value(e.getYaw()); - writer.name("running").value(e.isRunning()); - writer.name("crouching").value(e.isCrouching()); - writer.name("jumpRequested").value(e.isJumpRequested()); - writer.name("sequeceNumber").value(e.getSequenceNumber()); - writer.name("firstRun").value(e.isFirstRun()); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(Vector3f.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getMovementDirection(), serializationContext); - writer.name("movementDirection"); - writer.beginObject(); - JsonArray array = data.getElement().getAsJsonArray(); - writer.name("x").value(array.get(0).getAsFloat()); - writer.name("y").value(array.get(1).getAsFloat()); - writer.name("z").value(array.get(2).getAsFloat()); - writer.endObject(); - - } else if (event instanceof GetMaxSpeedEvent) { - GetMaxSpeedEvent e = (GetMaxSpeedEvent) event; - writer.name("baseValue").value(e.getBaseValue()); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(MovementMode.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getMovementMode(), serializationContext); - writer.name("movementMode").value(data.getAsString()); - writer.name("modifiers"); - gson.toJson(e.getModifiers(), TFloatArrayList.class, writer); - writer.name("multipliers"); - gson.toJson(e.getMultipliers(), TFloatArrayList.class, writer); - writer.name("postModifiers"); - gson.toJson(e.getPostModifiers(), TFloatArrayList.class, writer); - } else if (event instanceof AttackEvent) { - AttackEvent e = (AttackEvent) event; - writer.name("instigator").value(e.getInstigator().getId()); - writer.name("directCause").value(e.getDirectCause().getId()); - } else { - logger.error("ERROR: EVENT NOT SUPPORTED FOR SERIALIZATION"); - } - } catch (Exception e) { - logger.error("Could not serialize this event: " + event.toString(), e); - } - } - - private void writeVector3fData(JsonWriter writer, GsonSerializationContext serializationContext, InputEvent e) throws IOException { - if (e.getHitNormal() == null) { - writeDefaultVector3fData(writer); - } else { - writeRealVector3fData(writer, serializationContext, e); - } - } - - private void writeRealVector3fData(JsonWriter writer, GsonSerializationContext serializationContext, InputEvent e) throws IOException { - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(Vector3f.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getHitNormal(), serializationContext); - writer.name("hitNormal"); - writer.beginObject(); - JsonArray array = data.getElement().getAsJsonArray(); - writer.name("x").value(array.get(0).getAsFloat()); - writer.name("y").value(array.get(1).getAsFloat()); - writer.name("z").value(array.get(2).getAsFloat()); - writer.endObject(); - - data = (GsonPersistedData) handler.serialize(e.getHitPosition(), serializationContext); - writer.name("hitPosition"); - writer.beginObject(); - array = data.getElement().getAsJsonArray(); - writer.name("x").value(array.get(0).getAsFloat()); - writer.name("y").value(array.get(1).getAsFloat()); - writer.name("z").value(array.get(2).getAsFloat()); - writer.endObject(); - - handler = typeSerializationLibrary.getTypeHandlerFromClass(Vector3i.class); - data = (GsonPersistedData) handler.serialize(e.getTargetBlockPosition(), serializationContext); - writer.name("targetBlockPosition"); - writer.beginObject(); - array = data.getElement().getAsJsonArray(); - writer.name("x").value(array.get(0).getAsInt()); - writer.name("y").value(array.get(1).getAsInt()); - writer.name("z").value(array.get(2).getAsInt()); - writer.endObject(); - } - - private void writeDefaultVector3fData(JsonWriter writer) throws IOException { - writer.name("hitNormal"); - writer.beginObject(); - writer.name("x").value(DEFAULT_DOUBLE_VALUE); - writer.name("y").value(DEFAULT_DOUBLE_VALUE); - writer.name("z").value(DEFAULT_DOUBLE_VALUE); - writer.endObject(); - - writer.name("hitPosition"); - writer.beginObject(); - writer.name("x").value(DEFAULT_DOUBLE_VALUE); - writer.name("y").value(DEFAULT_DOUBLE_VALUE); - writer.name("z").value(DEFAULT_DOUBLE_VALUE); - writer.endObject(); - - writer.name("targetBlockPosition"); - writer.beginObject(); - writer.name("x").value(DEFAULT_DOUBLE_VALUE); - writer.name("y").value(DEFAULT_DOUBLE_VALUE); - writer.name("z").value(DEFAULT_DOUBLE_VALUE); - writer.endObject(); - } - - private void writeInputEventInstanceData(JsonWriter writer, Event event, GsonSerializationContext serializationContext) throws Exception { - if (event instanceof MouseWheelEvent) { - MouseWheelEvent e = (MouseWheelEvent) event; - writer.name("wheelTurns").value(e.getWheelTurns()); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(Vector2i.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getMousePosition(), serializationContext); - writer.name("mousePosition"); - writer.beginObject(); - JsonArray array = data.getElement().getAsJsonArray(); - writer.name("x").value(array.get(0).getAsInt()); - writer.name("y").value(array.get(1).getAsInt()); - writer.endObject(); - } else if (event instanceof MouseAxisEvent) { - MouseAxisEvent e = (MouseAxisEvent) event; - writer.name("value").value(e.getValue()); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(MouseAxisEvent.MouseAxis.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getMouseAxis(), serializationContext); - writer.name("mouseAxis").value(data.getAsString()); - } else if (event instanceof BindAxisEvent) { - BindAxisEvent e = (BindAxisEvent) event; - writer.name("id").value(e.getId()); - writer.name("value").value(e.getValue()); - } else if (event instanceof BindButtonEvent) { - BindButtonEvent e = (BindButtonEvent) event; - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(ButtonState.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getState(), serializationContext); - writer.name("state").value(data.getAsString()); - writer.name("id"); - writer.beginObject(); - handler = typeSerializationLibrary.getTypeHandlerFromClass(Name.class); - data = (GsonPersistedData) handler.serialize(e.getId().getModuleName(), serializationContext); - writer.name("moduleName").value(data.getAsString()); - data = (GsonPersistedData) handler.serialize(e.getId().getObjectName(), serializationContext); - writer.name("objectName").value(data.getAsString()); - writer.endObject(); - } else if (event instanceof KeyEvent) { - KeyEvent e = (KeyEvent) event; - writer.name("keychar").value(e.getKeyCharacter()); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(ButtonState.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getState(), serializationContext); - writer.name("state").value(data.getAsString()); - handler = typeSerializationLibrary.getTypeHandlerFromClass(Keyboard.Key.class); // might need to add some things to key - data = (GsonPersistedData) handler.serialize(e.getKey(), serializationContext); - writer.name("input").value(data.getAsString()); - } else if (event instanceof MouseButtonEvent) { - MouseButtonEvent e = (MouseButtonEvent) event; - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(ButtonState.class); - GsonPersistedData data = (GsonPersistedData) handler.serialize(e.getState(), serializationContext); - writer.name("state").value(data.getAsString()); - handler = typeSerializationLibrary.getTypeHandlerFromClass(MouseInput.class); - data = (GsonPersistedData) handler.serialize(e.getButton(), serializationContext); - writer.name("button").value(data.getAsString()); - handler = typeSerializationLibrary.getTypeHandlerFromClass(Vector2i.class); - data = (GsonPersistedData) handler.serialize(e.getMousePosition(), serializationContext); - writer.name("mousePosition"); - writer.beginObject(); - JsonArray array = data.getElement().getAsJsonArray(); - writer.name("x").value(array.get(0).getAsInt()); - writer.name("y").value(array.get(1).getAsInt()); - writer.endObject(); - } else { - logger.error("ERROR, EVENT NOT COMPATIBLE"); + gsonSerializer.writeJson(events, new TypeInfo>() {}, filePath); + } catch (IOException | SerializationException e) { + logger.error("Error while serializing recorded events", e); } } - List deserializeRecordedEvents(String path) { + /** + * Deserializes RecordedEvent's list. + * + * @param filePath path where the data should be saved. + */ + public List deserializeRecordedEvents(String filePath) { List events = new ArrayList<>(); - createInputEventClassMap(); - JsonObject jsonObject; - try (FileReader fileReader = new FileReader(path)) { - JsonParser parser = new JsonParser(); - JsonElement jsonElement = parser.parse(fileReader); - jsonObject = jsonElement.getAsJsonObject(); - JsonArray jsonEvents = jsonObject.getAsJsonArray("events"); - for (JsonElement element : jsonEvents) { - jsonObject = element.getAsJsonObject(); - String className = jsonObject.get("event_class").getAsString(); - long refId = jsonObject.get("entityRef_ID").getAsLong(); - long index = jsonObject.get("index").getAsLong(); - long timestamp = jsonObject.get("timestamp").getAsLong(); - Event event = deserializeSpecificEventData(jsonObject.get("event_data").getAsJsonObject(), className); - RecordedEvent re = new RecordedEvent(refId, event, timestamp, index); - events.add(re); - } - } catch (Exception e) { - logger.error("Error while deserializing event:", e); - } - - return events; - } - - private void createInputEventClassMap() { - this.inputEventClassMap = new HashMap<>(); - Iterable> classes = moduleEnvironment.getSubtypesOf(InputEvent.class); - for (Class c : classes) { - this.inputEventClassMap.put(c.getName(), c); - } - } - - private Event deserializeSpecificEventData(JsonObject jsonObject, String className) { - Event result = null; - Gson gson = new GsonBuilder().create(); - GsonDeserializationContext deserializationContext = new GsonDeserializationContext(null); - if (className.equals(CameraTargetChangedEvent.class.getName())) { - EntityRef oldTarget = new RecordedEntityRef(jsonObject.get("OldTarget").getAsLong(), (LowLevelEntityManager) this.entityManager); - EntityRef newTarget = new RecordedEntityRef(jsonObject.get("NewTarget").getAsLong(), (LowLevelEntityManager) this.entityManager); - result = new CameraTargetChangedEvent(oldTarget, newTarget); - } else if (className.equals(PlaySoundEvent.class.getName())) { - float volume = jsonObject.get("volume").getAsFloat(); - GsonPersistedData data = new GsonPersistedData(jsonObject.get("sound")); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(StaticSound.class); - StaticSound sound = (StaticSound) handler.deserialize(data, deserializationContext); - result = new PlaySoundEvent(sound, volume); - } else if (className.equals(CharacterMoveInputEvent.class.getName())) { - long delta = jsonObject.get("delta").getAsLong(); - float pitch = jsonObject.get("pitch").getAsFloat(); - float yaw = jsonObject.get("yaw").getAsFloat(); - boolean running = jsonObject.get("running").getAsBoolean(); - boolean crouching = jsonObject.get("crouching").getAsBoolean(); - boolean jumpRequested = jsonObject.get("jumpRequested").getAsBoolean(); - int sequenceNumber = jsonObject.get("sequeceNumber").getAsInt(); - boolean firstRun = jsonObject.get("firstRun").getAsBoolean(); - JsonObject objMoveDirection = jsonObject.get("movementDirection").getAsJsonObject(); - Vector3f movementDirection = new Vector3f(objMoveDirection.get("x").getAsFloat(), - objMoveDirection.get("y").getAsFloat(), - objMoveDirection.get("z").getAsFloat()); - result = new CharacterMoveInputEvent(sequenceNumber, pitch, yaw, movementDirection, running, crouching, jumpRequested, delta); - } else if (className.equals(GetMaxSpeedEvent.class.getName())) { - float baseValue = jsonObject.get("baseValue").getAsFloat(); - TypeHandler handler = typeSerializationLibrary.getTypeHandlerFromClass(MovementMode.class); - GsonPersistedData data = new GsonPersistedData(jsonObject.get("movementMode")); - MovementMode movementMode = (MovementMode) handler.deserialize(data, deserializationContext); - TFloatList modifiers = gson.fromJson(jsonObject.get("modifiers"), TFloatArrayList.class); - TFloatList multipliers = gson.fromJson(jsonObject.get("multipliers"), TFloatArrayList.class); - TFloatList postModifiers = gson.fromJson(jsonObject.get("postModifiers"), TFloatArrayList.class); - GetMaxSpeedEvent event = new GetMaxSpeedEvent(baseValue, movementMode); - event.setPostModifiers(postModifiers); - event.setMultipliers(multipliers); - event.setModifiers(modifiers); - result = event; - } else if (className.equals(AttackEvent.class.getName())) { - EntityRef instigator = new RecordedEntityRef(jsonObject.get("instigator").getAsLong(), (LowLevelEntityManager) this.entityManager); - EntityRef directCause = new RecordedEntityRef(jsonObject.get("directCause").getAsLong(), (LowLevelEntityManager) this.entityManager); - result = new AttackEvent(instigator, directCause); - } else if (getInputEventSpecificType(jsonObject, className, deserializationContext) != null) { //input events - result = getInputEventSpecificType(jsonObject, className, deserializationContext); - } - - return result; - } - private InputEvent getInputEventSpecificType(JsonObject jsonObject, String className, GsonDeserializationContext deserializationContext) { - InputEvent newEvent = null; try { - Class clazz = this.inputEventClassMap.get(className); - if (BindButtonEvent.class.isAssignableFrom(clazz) || BindAxisEvent.class.isAssignableFrom(clazz)) { - newEvent = (InputEvent) clazz.getConstructor().newInstance(); - } else if (clazz.equals(KeyDownEvent.class) || clazz.equals(KeyRepeatEvent.class) || clazz.equals(KeyUpEvent.class)) { //KeyEvent - GsonPersistedData data = new GsonPersistedData(jsonObject.get("input")); - TypeHandler typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(Keyboard.Key.class); - Keyboard.Key input = (Keyboard.Key) typeHandler.deserialize(data, deserializationContext); - data = new GsonPersistedData(jsonObject.get("state")); - typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(ButtonState.class); - ButtonState state = (ButtonState) typeHandler.deserialize(data, deserializationContext); - char keychar = jsonObject.get("keychar").getAsCharacter(); - float delta = jsonObject.get("delta").getAsFloat(); - KeyEvent aux; - if (clazz.equals(KeyDownEvent.class)) { - aux = KeyDownEvent.create(input, keychar, delta); // The instance created here is static - aux = KeyDownEvent.createCopy((KeyDownEvent) aux); // This copies the static value so each KeyEvent does not have the same value - } else if (clazz.equals(KeyRepeatEvent.class)) { - aux = KeyRepeatEvent.create(input, keychar, delta); - aux = KeyRepeatEvent.createCopy((KeyRepeatEvent) aux); - } else { - aux = KeyUpEvent.create(input, keychar, delta); - aux = KeyUpEvent.createCopy((KeyUpEvent) aux); - } - newEvent = aux; - } else if (clazz.equals(MouseButtonEvent.class)) { - GsonPersistedData data = new GsonPersistedData(jsonObject.get("button")); - TypeHandler typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(MouseInput.class); - MouseInput button = (MouseInput) typeHandler.deserialize(data, deserializationContext); - data = new GsonPersistedData(jsonObject.get("state")); - typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(ButtonState.class); - ButtonState state = (ButtonState) typeHandler.deserialize(data, deserializationContext); - JsonObject aux = jsonObject.get("mousePosition").getAsJsonObject(); - Vector2i mousePosition = new Vector2i(aux.get("x").getAsInt(), aux.get("y").getAsInt()); - float delta = jsonObject.get("delta").getAsFloat(); - MouseButtonEvent event = new MouseButtonEvent(button, state, delta); - event.setMousePosition(mousePosition); - newEvent = event; - } else if (clazz.equals(MouseAxisEvent.class)) { - GsonPersistedData data = new GsonPersistedData(jsonObject.get("mouseAxis")); - TypeHandler typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(MouseAxisEvent.MouseAxis.class); - MouseAxisEvent.MouseAxis mouseAxis = (MouseAxisEvent.MouseAxis) typeHandler.deserialize(data, deserializationContext); - float value = jsonObject.get("value").getAsFloat(); - float delta = jsonObject.get("delta").getAsFloat(); - MouseAxisEvent aux = MouseAxisEvent.create(mouseAxis, value, delta); - newEvent = MouseAxisEvent.createCopy(aux); - } else if (clazz.equals(MouseWheelEvent.class)) { - JsonObject aux = jsonObject.get("mousePosition").getAsJsonObject(); - Vector2i mousePosition = new Vector2i(aux.get("x").getAsInt(), aux.get("y").getAsInt()); - int wheelTurns = jsonObject.get("wheelTurns").getAsInt(); - float delta = jsonObject.get("delta").getAsFloat(); - newEvent = new MouseWheelEvent(mousePosition, wheelTurns, delta); - } else { - logger.error("Not an Input Event"); - return null; - } - - if (newEvent instanceof BindButtonEvent) { - bindButtonEventSetup((BindButtonEvent) newEvent, jsonObject, deserializationContext); - } else if (newEvent instanceof BindAxisEvent) { - bindAxisEvent((BindAxisEvent) newEvent, jsonObject); - } - - inputEventSetup(newEvent, jsonObject); - } catch (Exception e) { - logger.error("Error while deserializing event. Could not find class " + className, e); + List recordedEvents = + gsonSerializer.fromJson(new File(filePath), new TypeInfo>() {}); + events.addAll(recordedEvents); + } catch (SerializationException | IOException e) { + logger.error("Error while serializing recorded events", e); } - return newEvent; - } - private void bindButtonEventSetup(BindButtonEvent event, JsonObject jsonObject, GsonDeserializationContext deserializationContext) { - GsonPersistedData data = new GsonPersistedData(jsonObject.get("state")); - TypeHandler typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(ButtonState.class); - ButtonState state = (ButtonState) typeHandler.deserialize(data, deserializationContext); - float delta = jsonObject.get("delta").getAsFloat(); - typeHandler = typeSerializationLibrary.getTypeHandlerFromClass(Name.class); - JsonObject aux = jsonObject.get("id").getAsJsonObject(); - data = new GsonPersistedData(aux.get("moduleName")); - Name moduleName = (Name) typeHandler.deserialize(data, deserializationContext); - data = new GsonPersistedData(aux.get("objectName")); - Name objectName = (Name) typeHandler.deserialize(data, deserializationContext); - SimpleUri id = new SimpleUri(moduleName, objectName); - event.prepare(id, state, delta); - } - - private void bindAxisEvent(BindAxisEvent event, JsonObject jsonObject) { - String id = jsonObject.get("id").getAsString(); - float value = jsonObject.get("value").getAsFloat(); - float delta = jsonObject.get("delta").getAsFloat(); - event.prepare(id, value, delta); - } - - private void inputEventSetup(InputEvent event, JsonObject jsonObject) { - float delta = jsonObject.get("delta").getAsFloat(); - boolean consumed = jsonObject.get("consumed").getAsBoolean(); - EntityRef target = new RecordedEntityRef(jsonObject.get("target").getAsLong(), (LowLevelEntityManager) this.entityManager); - JsonObject aux = jsonObject.get("hitNormal").getAsJsonObject(); - Vector3f hitNormal = new Vector3f(aux.get("x").getAsFloat(), aux.get("y").getAsFloat(), aux.get("z").getAsFloat()); - aux = jsonObject.get("hitPosition").getAsJsonObject(); - Vector3f hitPosition = new Vector3f(aux.get("x").getAsFloat(), aux.get("y").getAsFloat(), aux.get("z").getAsFloat()); - aux = jsonObject.get("targetBlockPosition").getAsJsonObject(); - Vector3i targetBlockPosition = new Vector3i(aux.get("x").getAsInt(), aux.get("y").getAsInt(), aux.get("z").getAsInt()); - event.setTargetInfo(target, targetBlockPosition, hitPosition, hitNormal); + return events; } } diff --git a/engine/src/main/java/org/terasology/reflection/TypeInfo.java b/engine/src/main/java/org/terasology/reflection/TypeInfo.java new file mode 100644 index 00000000000..d5ed07e3bda --- /dev/null +++ b/engine/src/main/java/org/terasology/reflection/TypeInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017 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.reflection; + +import org.terasology.module.sandbox.API; +import org.terasology.utilities.ReflectionUtil; + +import java.lang.reflect.Type; + +/** + * Represents the type {@link T}. The type information generated is more comprehensive than {@link Class}, + * and {@link #type} correctly represents {@link T} regardless of whether it is generic or a wildcard type. + * + *

    + * Clients must create a subclass so that the proper {@link Type} can be retrieved at run-time: + * + *

    + * {@code TypeInfo> list = new TypeInfo>() {}; } + * + *

    + * However, if the {@link Type} for the given type is already available, {@link TypeInfo#of(Type)} + * can be used: + * + *

    + * {@code TypeInfo fieldType = TypeInfo.of(field.getGenericType()); } + * + *

    + * Alternatively, if the type is a simple class, {@link TypeInfo#of(Class)} will suffice: + * + *

    + * {@code TypeInfo string = TypeInfo.of(String.class); } + * + * @param The type for which type information is to be generated. + */ +@API +public abstract class TypeInfo { + private final Class rawType; + private final Type type; + private final int hashCode; + + /** + * Constructs a new {@link TypeInfo} where the represented type is derived from the type parameter. + */ + @SuppressWarnings("unchecked") + protected TypeInfo() { + this.type = ReflectionUtil.getTypeParameterForSuper(getClass(), TypeInfo.class, 0); + this.rawType = (Class) ReflectionUtil.getRawType(type); + this.hashCode = type.hashCode(); + } + + /** + * Constructs a new {@link TypeInfo} directly from the type. + */ + @SuppressWarnings("unchecked") + protected TypeInfo(Type type) { + this.type = type; + this.rawType = (Class) ReflectionUtil.getRawType(type); + this.hashCode = type.hashCode(); + } + + /** + * Creates a {@link TypeInfo} for the given type. + */ + public static TypeInfo of(Type type) { + return new TypeInfo(type) {}; + } + + /** + * Creates a {@link TypeInfo} for the given {@link Class}. + */ + public static TypeInfo of(Class type) { + return new TypeInfo(type) {}; + } + + public final Class getRawType() { + return rawType; + } + + public final Type getType() { + return type; + } + + @Override + public final int hashCode() { + return this.hashCode; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + return o instanceof TypeInfo + && ReflectionUtil.typeEquals(type, ((TypeInfo) o).type); + } + + @Override + public final String toString() { + return type.toString(); + } +} diff --git a/engine/src/main/java/org/terasology/reflection/TypeRegistry.java b/engine/src/main/java/org/terasology/reflection/TypeRegistry.java new file mode 100644 index 00000000000..e20e957da01 --- /dev/null +++ b/engine/src/main/java/org/terasology/reflection/TypeRegistry.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 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.reflection; + +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.Set; + +public interface TypeRegistry { + Set> getSubtypesOf(Class type); + + Set> getTypesAnnotatedWith(Class annotationType); + + Optional> load(String name); +} diff --git a/engine/src/main/java/org/terasology/reflection/copy/CopyStrategyLibrary.java b/engine/src/main/java/org/terasology/reflection/copy/CopyStrategyLibrary.java index ef550a3d6d9..0da5ab050a0 100644 --- a/engine/src/main/java/org/terasology/reflection/copy/CopyStrategyLibrary.java +++ b/engine/src/main/java/org/terasology/reflection/copy/CopyStrategyLibrary.java @@ -84,7 +84,7 @@ public void clear() { // TODO: Consider CopyStrategyFactory system for Collections and similar public CopyStrategy getStrategy(Type genericType) { - Class typeClass = ReflectionUtil.getClassOfType(genericType); + Class typeClass = ReflectionUtil.getRawType(genericType); if (typeClass == null) { logger.error("Cannot obtain class for type {}, using default strategy", genericType); return defaultStrategy; diff --git a/engine/src/main/java/org/terasology/reflection/internal/TypeRegistryImpl.java b/engine/src/main/java/org/terasology/reflection/internal/TypeRegistryImpl.java new file mode 100644 index 00000000000..5e93b530f44 --- /dev/null +++ b/engine/src/main/java/org/terasology/reflection/internal/TypeRegistryImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019 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.reflection.internal; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import org.reflections.ReflectionUtils; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.scanners.TypeAnnotationsScanner; +import org.terasology.module.ModuleEnvironment; +import org.terasology.reflection.TypeRegistry; +import org.terasology.utilities.ReflectionUtil; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class TypeRegistryImpl implements TypeRegistry { + private Reflections reflections; + private ClassLoader[] classLoaders; + + /** + * Creates an empty {@link TypeRegistryImpl}. No types are loaded when this constructor + * is called -- to populate the registry use one of the other parameterized constructors. + */ + public TypeRegistryImpl() {} + + public TypeRegistryImpl(ClassLoader classLoader) { + initializeReflections(classLoader); + } + + public void reload(ModuleEnvironment environment) { + // FIXME: Reflection -- may break with updates to gestalt-module + initializeReflections((ClassLoader) ReflectionUtil.readField(environment, "finalClassLoader")); + } + + private void initializeReflections(ClassLoader classLoader) { + List allClassLoaders = Lists.newArrayList(); + + while (classLoader != null) { + allClassLoaders.add(classLoader); + classLoader = classLoader.getParent(); + } + + // Here allClassLoaders contains child class loaders followed by their parent. The list is + // reversed so that classes are loaded using the originally declaring/loading class loader, + // not a child class loader (like a ModuleClassLoader, for example) + Collections.reverse(allClassLoaders); + + classLoaders = allClassLoaders.toArray(new ClassLoader[0]); + + reflections = new Reflections( + allClassLoaders, + new SubTypesScanner(false), + new TypeAnnotationsScanner() + ); + } + + @Override + public Set> getSubtypesOf(Class type) { + Preconditions.checkArgument(!Object.class.equals(type)); + + return reflections.getSubTypesOf(type); + } + + @Override + public Set> getTypesAnnotatedWith(Class annotationType) { + return reflections.getTypesAnnotatedWith(annotationType); + } + + @Override + public Optional> load(String name) { + return Optional.ofNullable(ReflectionUtils.forName(name, classLoaders)); + } +} diff --git a/engine/src/main/java/org/terasology/reflection/reflect/ConstructorLibrary.java b/engine/src/main/java/org/terasology/reflection/reflect/ConstructorLibrary.java new file mode 100644 index 00000000000..a21393aefca --- /dev/null +++ b/engine/src/main/java/org/terasology/reflection/reflect/ConstructorLibrary.java @@ -0,0 +1,230 @@ +/* + * Copyright 2018 MovingBlocks + * Copyright (C) 2011 Google Inc. + * + * 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. + * + * Based on Gson v2.6.2 com.google.gson.internal.ConstructorConstructor + */ +package org.terasology.reflection.reflect; + +import org.terasology.persistence.typeHandling.InstanceCreator; +import org.terasology.persistence.typeHandling.SerializationException; +import org.terasology.reflection.TypeInfo; +import org.terasology.reflection.reflect.internal.UnsafeAllocator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +public class ConstructorLibrary { + private final Map> instanceCreators; + + public ConstructorLibrary(Map> instanceCreators) { + this.instanceCreators = instanceCreators; + } + + public ObjectConstructor get(TypeInfo typeInfo) { + final Type type = typeInfo.getType(); + final Class rawType = typeInfo.getRawType(); + + // first try an instance creator + + @SuppressWarnings("unchecked") // types must agree + final InstanceCreator typeCreator = (InstanceCreator) instanceCreators.get(type); + if (typeCreator != null) { + return () -> typeCreator.createInstance(type); + } + + // Next try raw type match for instance creators + @SuppressWarnings("unchecked") // types must agree + final InstanceCreator rawTypeCreator = + (InstanceCreator) instanceCreators.get(rawType); + if (rawTypeCreator != null) { + return () -> rawTypeCreator.createInstance(type); + } + + ObjectConstructor defaultConstructor = newDefaultConstructor(rawType); + if (defaultConstructor != null) { + return defaultConstructor; + } + + ObjectConstructor defaultImplementation = newDefaultImplementationConstructor(type, rawType); + if (defaultImplementation != null) { + return defaultImplementation; + } + + return newUnsafeAllocator(typeInfo); + } + + private ObjectConstructor newUnsafeAllocator(TypeInfo typeInfo) { + return new ObjectConstructor() { + private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); + @SuppressWarnings("unchecked") + @Override public T construct() { + try { + Object newInstance = unsafeAllocator.newInstance(typeInfo.getRawType()); + return (T) newInstance; + } catch (Exception e) { + throw new RuntimeException("Unable to create an instance of " + typeInfo.getType() + + ". Registering an InstanceCreator for this type may fix this problem.", e); + } + } + }; + } + + private ObjectConstructor newDefaultConstructor(Class rawType) { + @SuppressWarnings("unchecked") // T is the same raw type as is requested + Constructor constructor = (Constructor) Arrays.stream(rawType.getDeclaredConstructors()) + .min(Comparator.comparingInt(c -> c.getParameterTypes().length)) + .orElse(null); + + if (constructor == null || constructor.getParameterTypes().length != 0) { + return null; + } + + if (!constructor.isAccessible()) { + constructor.setAccessible(true); + } + return () -> { + try { + return constructor.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException("Failed to invoke " + constructor + " with no args", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Failed to invoke " + constructor + " with no args", + e.getTargetException()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + }; + } + + /** + * Constructors for common interface types like Map and List and their + * subtypes. + */ + @SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is + private ObjectConstructor newDefaultImplementationConstructor( + final Type type, Class rawType) { + if (Collection.class.isAssignableFrom(rawType)) { + if (SortedSet.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new TreeSet(); + } + }; + } else if (EnumSet.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @SuppressWarnings("rawtypes") + @Override + public T construct() { + if (type instanceof ParameterizedType) { + Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (elementType instanceof Class) { + return (T) EnumSet.noneOf((Class) elementType); + } else { + throw new SerializationException("Invalid EnumSet type: " + type.toString()); + } + } else { + throw new SerializationException("Invalid EnumSet type: " + type.toString()); + } + } + }; + } else if (Set.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new LinkedHashSet(); + } + }; + } else if (Queue.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new LinkedList(); + } + }; + } else { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new ArrayList(); + } + }; + } + } + + if (Map.class.isAssignableFrom(rawType)) { + if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new ConcurrentSkipListMap(); + } + }; + } else if (ConcurrentMap.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new ConcurrentHashMap(); + } + }; + } else if (SortedMap.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new TreeMap(); + } + }; + } else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom( + TypeInfo.of(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) { + return new ObjectConstructor() { + @Override + public T construct() { + return (T) new LinkedHashMap(); + } + }; + } + } + + return null; + } + + @Override + public String toString() { + return instanceCreators.toString(); + } + +} diff --git a/engine/src/main/java/org/terasology/reflection/reflect/internal/UnsafeAllocator.java b/engine/src/main/java/org/terasology/reflection/reflect/internal/UnsafeAllocator.java new file mode 100644 index 00000000000..d4a58f5010e --- /dev/null +++ b/engine/src/main/java/org/terasology/reflection/reflect/internal/UnsafeAllocator.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 Google Inc. + * Copyright 2018 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. + * + * com.google.gson.internal.UnsafeAllocator, Gson v2.6.2 + */ +package org.terasology.reflection.reflect.internal; + +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Do sneaky things to allocate objects without invoking their constructors. + * + * @author Joel Leitch + * @author Jesse Wilson + */ +public abstract class UnsafeAllocator { + public abstract T newInstance(Class c) throws Exception; + + public static UnsafeAllocator create() { + // try JVM + // public class Unsafe { + // public Object allocateInstance(Class type); + // } + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field f = unsafeClass.getDeclaredField("theUnsafe"); + f.setAccessible(true); + final Object unsafe = f.get(null); + final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class); + return new UnsafeAllocator() { + @Override + @SuppressWarnings("unchecked") + public T newInstance(Class c) throws Exception { + return (T) allocateInstance.invoke(unsafe, c); + } + }; + } catch (Exception ignored) { + } + + // try dalvikvm, post-gingerbread + // public class ObjectStreamClass { + // private static native int getConstructorId(Class c); + // private static native Object newInstance(Class instantiationClass, int methodId); + // } + try { + Method getConstructorId = ObjectStreamClass.class + .getDeclaredMethod("getConstructorId", Class.class); + getConstructorId.setAccessible(true); + final int constructorId = (Integer) getConstructorId.invoke(null, Object.class); + final Method newInstance = ObjectStreamClass.class + .getDeclaredMethod("newInstance", Class.class, int.class); + newInstance.setAccessible(true); + return new UnsafeAllocator() { + @Override + @SuppressWarnings("unchecked") + public T newInstance(Class c) throws Exception { + return (T) newInstance.invoke(null, c, constructorId); + } + }; + } catch (Exception ignored) { + } + + // try dalvikvm, pre-gingerbread + // public class ObjectInputStream { + // private static native Object newInstance( + // Class instantiationClass, Class constructorClass); + // } + try { + final Method newInstance = ObjectInputStream.class + .getDeclaredMethod("newInstance", Class.class, Class.class); + newInstance.setAccessible(true); + return new UnsafeAllocator() { + @Override + @SuppressWarnings("unchecked") + public T newInstance(Class c) throws Exception { + return (T) newInstance.invoke(null, c, Object.class); + } + }; + } catch (Exception ignored) { + } + + // give up + return new UnsafeAllocator() { + @Override + public T newInstance(Class c) { + throw new UnsupportedOperationException("Cannot allocate " + c); + } + }; + } +} diff --git a/engine/src/main/java/org/terasology/rendering/nui/asset/UIFormat.java b/engine/src/main/java/org/terasology/rendering/nui/asset/UIFormat.java index 2062ac87155..e2277de8e6d 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/asset/UIFormat.java +++ b/engine/src/main/java/org/terasology/rendering/nui/asset/UIFormat.java @@ -34,12 +34,10 @@ import org.terasology.assets.format.AssetDataFile; import org.terasology.assets.module.annotations.RegisterAssetFileFormat; import org.terasology.i18n.TranslationSystem; -import org.terasology.math.Border; import org.terasology.persistence.ModuleContext; -import org.terasology.persistence.typeHandling.TypeSerializationLibrary; +import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.persistence.typeHandling.extensionTypes.AssetTypeHandler; import org.terasology.persistence.typeHandling.gson.GsonTypeSerializationLibraryAdapterFactory; -import org.terasology.persistence.typeHandling.mathTypes.BorderTypeHandler; import org.terasology.reflection.metadata.ClassMetadata; import org.terasology.reflection.metadata.FieldMetadata; import org.terasology.registry.CoreRegistry; @@ -96,9 +94,10 @@ public UIData load(JsonElement element) throws IOException { public UIData load(JsonElement element, Locale otherLocale) throws IOException { NUIManager nuiManager = CoreRegistry.get(NUIManager.class); TranslationSystem translationSystem = CoreRegistry.get(TranslationSystem.class); - TypeSerializationLibrary library = new TypeSerializationLibrary(CoreRegistry.get(TypeSerializationLibrary.class)); - library.add(UISkin.class, new AssetTypeHandler<>(UISkin.class)); - library.add(Border.class, new BorderTypeHandler()); + TypeHandlerLibrary library = new TypeHandlerLibrary(CoreRegistry.get(TypeHandlerLibrary.class)); + library.addTypeHandler(UISkin.class, new AssetTypeHandler<>(UISkin.class)); + + // TODO: Rewrite to use TypeHandlerLibrary GsonBuilder gsonBuilder = new GsonBuilder() .registerTypeAdapterFactory(new GsonTypeSerializationLibraryAdapterFactory(library)) diff --git a/engine/src/main/java/org/terasology/utilities/ReflectionUtil.java b/engine/src/main/java/org/terasology/utilities/ReflectionUtil.java index 896e42d29e6..915adeac8eb 100644 --- a/engine/src/main/java/org/terasology/utilities/ReflectionUtil.java +++ b/engine/src/main/java/org/terasology/utilities/ReflectionUtil.java @@ -21,13 +21,22 @@ import com.google.common.collect.Sets; import org.terasology.rendering.nui.UIWidget; +import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.AbstractMap; +import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; /** * @@ -36,12 +45,73 @@ public final class ReflectionUtil { private ReflectionUtil() { } + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Returns true if {@link Type} {@code a} and {@code b} are equal. + */ + public static boolean typeEquals(Type a, Type b) { + if (a == b) { + // also handles (a == null && b == null) + return true; + + } else if (a instanceof Class) { + // Class already specifies equals(). + return a.equals(b); + + } else if (a instanceof ParameterizedType) { + if (!(b instanceof ParameterizedType)) { + return false; + } + + // TODO: save a .clone() call + ParameterizedType pa = (ParameterizedType) a; + ParameterizedType pb = (ParameterizedType) b; + return equal(pa.getOwnerType(), pb.getOwnerType()) + && pa.getRawType().equals(pb.getRawType()) + && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); + + } else if (a instanceof GenericArrayType) { + if (!(b instanceof GenericArrayType)) { + return false; + } + + GenericArrayType ga = (GenericArrayType) a; + GenericArrayType gb = (GenericArrayType) b; + return typeEquals(ga.getGenericComponentType(), gb.getGenericComponentType()); + + } else if (a instanceof WildcardType) { + if (!(b instanceof WildcardType)) { + return false; + } + + WildcardType wa = (WildcardType) a; + WildcardType wb = (WildcardType) b; + return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) + && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); + + } else if (a instanceof TypeVariable) { + if (!(b instanceof TypeVariable)) { + return false; + } + TypeVariable va = (TypeVariable) a; + TypeVariable vb = (TypeVariable) b; + return va.getGenericDeclaration() == vb.getGenericDeclaration() + && va.getName().equals(vb.getName()); + + } else { + // This isn't a type we support. Could be a generic array type, wildcard type, etc. + return false; + } + } + + /** * Attempts to return the type of a parameter of a parameterised field. This uses compile-time information only - the * type should be obtained from a field with a the generic types bound. * - * @param type - * @param index * @return The type of the generic parameter at index for the given type, or null if it cannot be obtained. */ // TODO - Improve parameter lookup to go up the inheritance tree more @@ -56,13 +126,19 @@ public static Type getTypeParameter(Type type, int index) { return parameterizedType.getActualTypeArguments()[index]; } - public static Class getClassOfType(Type type) { + public static Class getRawType(Type type) { if (type instanceof Class) { return (Class) type; } else if (type instanceof ParameterizedType) { return (Class) ((ParameterizedType) type).getRawType(); + } else if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + return Array.newInstance(getRawType(genericArrayType.getGenericComponentType()), 0).getClass(); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + return getRawType(wildcardType.getUpperBounds()[0]); } - return null; + return Object.class; } public static Method findGetter(Field field) { @@ -117,8 +193,6 @@ public static Method findMethod(Class targetType, String methodName, Class * Returns an ordered list of super classes and interfaces for the given class, that have a common base class. * The set is ordered with the deepest interface first, through all the interfaces, and then all the super classes. * - * @param forClass - * @param baseClass * @return an ordered list of super classes and interfaces for the given class, that have a common base class. */ public static List> getInheritanceTree(Class forClass, Class baseClass) { @@ -153,8 +227,8 @@ private static void addInterfaceToInheritanceTree(Class interfa result.add(interfaceType); } - public static Class getTypeParameterForSuper(Type target, Class superClass, int index) { - Class targetClass = getClassOfType(target); + public static Type getTypeParameterForSuper(Type target, Class superClass, int index) { + Class targetClass = getRawType(target); Preconditions.checkArgument(superClass.isAssignableFrom(targetClass), "Target must be a child of superClass"); if (superClass.isInterface()) { @@ -164,39 +238,217 @@ public static Class getTypeParameterForSuper(Type target, Class superC } } - private static Class getTypeParameterForSuperClass(Type target, Class superClass, int index) { - Class targetClass = getClassOfType(target); - if (superClass.equals(getClassOfType(targetClass.getGenericSuperclass()))) { - Type superType = targetClass.getGenericSuperclass(); - if (superType instanceof ParameterizedType) { - if (((ParameterizedType) superType).getRawType().equals(superClass)) { - Type boundType = ((ParameterizedType) superType).getActualTypeArguments()[index]; - if (boundType instanceof Class) { - return (Class) boundType; - } else { - return null; - } - } + private static Type getTypeParameterForSuperClass(Type target, Class superClass, int index) { + for (Class targetClass = getRawType(target); + !Object.class.equals(targetClass); + target = resolveType(target, targetClass.getGenericSuperclass()), + targetClass = getRawType(target)) { + if (superClass.equals(targetClass)) { + return getTypeParameter(target, index); } } - return getTypeParameterForSuperClass(targetClass.getGenericSuperclass(), superClass, index); + + return null; } - private static Class getTypeParameterForSuperInterface(Type target, Class superClass, int index) { - Class targetClass = getClassOfType(target); - for (Type superType : targetClass.getGenericInterfaces()) { - if (superType instanceof ParameterizedType) { - if (((ParameterizedType) superType).getRawType().equals(superClass)) { - Type boundType = ((ParameterizedType) superType).getActualTypeArguments()[index]; - if (boundType instanceof Class) { - return (Class) boundType; - } else { - return null; - } - } + private static Type getTypeParameterForSuperInterface(Type target, Class superClass, int index) { + Class targetClass = getRawType(target); + + if (Object.class.equals(targetClass)) { + return null; + } + + if (targetClass.equals(superClass)) { + return getTypeParameter(target, index); + } + + Type genericSuperclass = resolveType(target, targetClass.getGenericSuperclass()); + + if (!Object.class.equals(genericSuperclass) && genericSuperclass != null) { + return getTypeParameterForSuperInterface(genericSuperclass, superClass, index); + } + + for (Type genericInterface : targetClass.getGenericInterfaces()) { + genericInterface = resolveType(target, genericInterface); + + Type typeParameter = getTypeParameterForSuperInterface(genericInterface, superClass, index); + + if (typeParameter != null) { + return typeParameter; } } - return getTypeParameterForSuperInterface(targetClass.getGenericSuperclass(), superClass, index); + + return null; + } + + /** + * Resolves all {@link TypeVariable}s in {@code type} to concrete types as per the type + * parameter definitions in {@code contextType}. All {@link TypeVariable}s in {@code type} + * should have been declared in {@code contextType} or one of its supertypes, otherwise those + * variables will be resolved to {@link Object Object.class}. + * + * @param contextType The {@link Type} which contains all type parameter definitions used in {@code type}. + * @param type The {@link Type} whose {@link TypeVariable}s are to be resolved. + * @return A copy of {@code type} with all {@link TypeVariable}s resolved. + */ + public static Type resolveType(Type contextType, Type type) { + Class contextClass = getRawType(contextType); + + // T field; + if (type instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type; + + Type resolvedTypeVariable = resolveTypeVariable(contextType, typeVariable, contextClass); + + if (resolvedTypeVariable == typeVariable) { + return typeVariable; + } + + if (resolvedTypeVariable == null) { + // TypeVariable not specified (i.e. raw type), return Object + return Object.class; + } + + return resolveType(contextType, resolvedTypeVariable); + } + + // List field; + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + + Type ownerType = parameterizedType.getOwnerType(); + Type resolvedOwnerType = resolveType(contextType, ownerType); + + boolean changed = resolvedOwnerType != ownerType; + + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + + Type[] resolvedTypeArguments = resolveTypes(contextType, typeArguments); + + changed |= resolvedTypeArguments != typeArguments; + + if (!changed) { + return parameterizedType; + } + + final Type rawType = parameterizedType.getRawType(); + + return parameterizedTypeOf(resolvedOwnerType, resolvedTypeArguments, rawType); + } + + // T[] field || List[] field; + if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + + Type componentType = arrayType.getGenericComponentType(); + Type resolvedComponentType = resolveType(contextType, componentType); + + if (resolvedComponentType == componentType) { + return type; + } else { + return new GenericArrayTypeImpl(resolvedComponentType); + } + } + + // List field; + if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type[] lowerBounds = wildcardType.getLowerBounds(); + Type[] upperBounds = wildcardType.getUpperBounds(); + + boolean changed = false; + + // Technically not required as language supports only one bound, but generalizing + Type[] resolvedLowerBounds = resolveTypes(contextType, lowerBounds); + changed |= resolvedLowerBounds != lowerBounds; + + Type[] resolvedUpperBounds = resolveTypes(contextType, upperBounds); + changed |= resolvedUpperBounds != upperBounds; + + if (!changed) { + return wildcardType; + } + + return new WildcardTypeImpl(resolvedUpperBounds, resolvedLowerBounds); + } + + return type; + } + + private static Type[] resolveTypes(Type contextType, Type[] types) { + Type[] resolvedTypes = new Type[types.length]; + + for (int i = 0; i < types.length; i++) { + resolvedTypes[i] = resolveType(contextType, types[i]); + } + + return resolvedTypes; + } + + private static Type resolveTypeVariable(Type contextType, TypeVariable typeVariable, Class contextClass) { + if (!(typeVariable.getGenericDeclaration() instanceof Class)) { + // We cannot resolve type variables declared by a method, quit + return typeVariable; + } + + return getCascadedGenericDeclaration(typeVariable) + .filter(declaration -> + declaration.getKey().isAssignableFrom(contextClass)) + .findAny() + .map(declaration -> + getTypeParameterForSuper(contextType, + declaration.getKey(), + declaration.getValue()) + ) + // If we couldn't find a declaration in the context, we will not be + // able to resolve this type variable, resort to Object.class + .orElse(Object.class); + } + + public static Stream getGenericSupertypes(Class clazz) { + return Stream.concat(Stream.of(clazz.getGenericSuperclass()), Stream.of(clazz.getGenericInterfaces())); + } + + /** + * Cascades the declaration of the type variable up the inheritance tree and returns the + * cascaded declaration classes and the corresponding index of the type variable for that + * declaration class. + */ + private static Stream, Integer>> getCascadedGenericDeclaration(TypeVariable typeVariable) { + assert typeVariable.getGenericDeclaration() instanceof Class; + + Class genericDeclaration = (Class) typeVariable.getGenericDeclaration(); + + int typeVariableIndex = Arrays.asList(genericDeclaration.getTypeParameters()) + .indexOf(typeVariable); + + return cascadeTypeVariableDeclarationToSupertypes(typeVariableIndex, genericDeclaration); + } + + private static Stream, Integer>> cascadeTypeVariableDeclarationToSupertypes( + int typeVariableIndex, Class declaration) { + TypeVariable typeVariable = declaration.getTypeParameters()[typeVariableIndex]; + + return Stream.concat( + Stream.of(new AbstractMap.SimpleEntry<>(declaration, typeVariableIndex)), + getGenericSupertypes(declaration) + .filter(supertype -> supertype instanceof ParameterizedType) + .map(supertype -> (ParameterizedType) supertype) + .flatMap(supertype -> { + int superTypeVariableIndex = + Arrays.asList(supertype.getActualTypeArguments()).indexOf(typeVariable); + + if (superTypeVariableIndex == -1) { + return Stream.empty(); + } + + return cascadeTypeVariableDeclarationToSupertypes( + superTypeVariableIndex, + getRawType(supertype) + ); + } + ) + ); } public static Object readField(Object object, String fieldName) { @@ -216,4 +468,230 @@ public static Object readField(Object object, String fieldName) { throw new IllegalArgumentException( "Cannot find field " + cls.getName() + "." + fieldName); } + + public static ParameterizedTypeImpl parameterizedTypeOf(Type ownerType, Type[] actualTypeArguments, Type rawType) { + return new ParameterizedTypeImpl(rawType, actualTypeArguments, ownerType); + } + + /** + * Returns a parameterized version of the given raw type, if it has generic type parameters. + * If it has no generic type parameters, the raw type itself is returned. + */ + public static Type parameterizeRawType(Class rawType) { + if (rawType == null) { + return null; + } + + TypeVariable[] typeParameters = rawType.getTypeParameters(); + + if (typeParameters.length == 0) { + return rawType; + } + + return parameterizedTypeOf( + parameterizeRawType(rawType.getEnclosingClass()), + typeParameters, + rawType + ); + } + + /** + * Returns a parameterized version of the given raw type that has been resolved with the given + * context type, if it has generic type parameters. If it has no generic type parameters, + * the raw type itself is returned. + */ + public static Type parameterizeandResolveRawType(Type contextType, Class rawType) { + Type parameterizedType = parameterizeRawType(rawType); + return resolveType(contextType, parameterizedType); + } + + private static class WildcardTypeImpl implements WildcardType { + private final Type[] upperBounds; + private final Type[] lowerBounds; + + public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { + this.upperBounds = upperBounds; + this.lowerBounds = lowerBounds; + } + + @Override + public Type[] getUpperBounds() { + return upperBounds; + } + + @Override + public Type[] getLowerBounds() { + return lowerBounds; + } + + public String toString() { + Type[] lowerBounds = this.getLowerBounds(); + Type[] bounds = lowerBounds; + + StringBuilder stringBuilder = new StringBuilder(); + + if (lowerBounds.length > 0) { + stringBuilder.append("? super "); + } else { + Type[] upperBounds = this.getUpperBounds(); + if (upperBounds.length <= 0 || upperBounds[0].equals(Object.class)) { + return "?"; + } + + bounds = upperBounds; + stringBuilder.append("? extends "); + } + + boolean isFirstBound = true; + + for (Type bound : bounds) { + if (!isFirstBound) { + stringBuilder.append(" & "); + } + + isFirstBound = false; + stringBuilder.append(bound.getTypeName()); + } + + return stringBuilder.toString(); + } + + public boolean equals(Object var1) { + if (!(var1 instanceof WildcardType)) { + return false; + } else { + WildcardType var2 = (WildcardType) var1; + return Arrays.equals(this.getLowerBounds(), var2.getLowerBounds()) && Arrays.equals(this.getUpperBounds(), var2.getUpperBounds()); + } + } + + public int hashCode() { + Type[] var1 = this.getLowerBounds(); + Type[] var2 = this.getUpperBounds(); + return Arrays.hashCode(var1) ^ Arrays.hashCode(var2); + } + } + + private static class GenericArrayTypeImpl implements GenericArrayType { + private final Type genericComponentType; + + private GenericArrayTypeImpl(Type genericComponentType) { + this.genericComponentType = genericComponentType; + } + + public Type getGenericComponentType() { + return this.genericComponentType; + } + + public String toString() { + Type genericComponentType = this.getGenericComponentType(); + StringBuilder stringBuilder = new StringBuilder(); + if (genericComponentType instanceof Class) { + stringBuilder.append(((Class) genericComponentType).getName()); + } else { + stringBuilder.append(genericComponentType.toString()); + } + + stringBuilder.append("[]"); + return stringBuilder.toString(); + } + + public boolean equals(Object var1) { + if (var1 instanceof GenericArrayType) { + GenericArrayType var2 = (GenericArrayType) var1; + return Objects.equals(this.genericComponentType, var2.getGenericComponentType()); + } else { + return false; + } + } + + public int hashCode() { + return Objects.hashCode(this.genericComponentType); + } + } + + private static class ParameterizedTypeImpl implements ParameterizedType { + private final Type[] actualTypeArguments; + private final Class rawType; + private final Type ownerType; + + private ParameterizedTypeImpl(Type rawType, Type[] actualTypeArguments, Type ownerType) { + this.actualTypeArguments = actualTypeArguments; + this.rawType = (Class) rawType; + this.ownerType = ownerType != null ? ownerType : this.rawType.getDeclaringClass(); + } + + public Type[] getActualTypeArguments() { + return this.actualTypeArguments.clone(); + } + + public Class getRawType() { + return this.rawType; + } + + public Type getOwnerType() { + return this.ownerType; + } + + public boolean equals(Object other) { + if (!(other instanceof ParameterizedType)) { + return false; + } + + ParameterizedType otherParameterizedType = (ParameterizedType) other; + + if (this == otherParameterizedType) { + return true; + } + + return Objects.equals(this.ownerType, otherParameterizedType.getOwnerType()) && + Objects.equals(this.rawType, otherParameterizedType.getRawType()) && + Arrays.equals(this.actualTypeArguments, otherParameterizedType.getActualTypeArguments()); + } + + public int hashCode() { + return Arrays.hashCode(this.actualTypeArguments) ^ Objects.hashCode(this.ownerType) ^ Objects.hashCode(this.rawType); + } + + public String toString() { + StringBuilder var1 = new StringBuilder(); + if (this.ownerType != null) { + if (this.ownerType instanceof Class) { + var1.append(((Class) this.ownerType).getName()); + } else { + var1.append(this.ownerType.toString()); + } + + var1.append("$"); + if (this.ownerType instanceof ParameterizedTypeImpl) { + var1.append(this.rawType.getName().replace(((ParameterizedTypeImpl) this.ownerType).rawType.getName() + "$", "")); + } else { + var1.append(this.rawType.getSimpleName()); + } + } else { + var1.append(this.rawType.getName()); + } + + if (this.actualTypeArguments != null && this.actualTypeArguments.length > 0) { + var1.append("<"); + boolean var2 = true; + Type[] var3 = this.actualTypeArguments; + int var4 = var3.length; + + for (int var5 = 0; var5 < var4; ++var5) { + Type var6 = var3[var5]; + if (!var2) { + var1.append(", "); + } + + var1.append(var6.getTypeName()); + var2 = false; + } + + var1.append(">"); + } + + return var1.toString(); + } + } }