diff --git a/engine/build.gradle b/engine/build.gradle index 8b1ea6d6a90..2e48574fd15 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -148,6 +148,7 @@ dependencies { implementation group: 'org.terasology.crashreporter', name: 'cr-terasology', version: '4.2.0' api(project(":subsystems:TypeHandlerLibrary")) + api(project(":subsystems:Context")) } protobuf { diff --git a/subsystems/Context/README.MD b/subsystems/Context/README.MD new file mode 100644 index 00000000000..92bc8143ce3 --- /dev/null +++ b/subsystems/Context/README.MD @@ -0,0 +1,21 @@ +# Context + +>It is really not subsystem. but used by them + +Context is simple DI implementation for TS + +Provide interface for working with context. + +## How to use (from scratch, not Terasology) + +1. Implement `Context` class +2. Create it +3. Provide it. +4. Use `InjectionHelper` with context to inject at `@In` fields + +## How to use (with Terasology) + +1. Create your system/manager/another injectable class type +2. Write field with `@In` - be sure that Field Type is injectable. then Field will be injected +with manager/system/ another injectable class type +3. Mark with `@Share` - be sure that your class is injectable type. then your class be provided to `Context`. \ No newline at end of file diff --git a/subsystems/Context/build.gradle.kts b/subsystems/Context/build.gradle.kts new file mode 100644 index 00000000000..950e7147242 --- /dev/null +++ b/subsystems/Context/build.gradle.kts @@ -0,0 +1,19 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +plugins { + java + `java-library` +} + +apply(from = "$rootDir/config/gradle/common.gradle") + +dependencies { + implementation("org.terasology.gestalt:gestalt-util:7.2.0-SNAPSHOT") + implementation("org.terasology.gestalt:gestalt-module:7.2.0-SNAPSHOT") + + implementation("ch.qos.logback:logback-classic:1.2.3") + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.5.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.5.2") +} \ No newline at end of file diff --git a/engine/src/main/java/org/terasology/engine/context/Context.java b/subsystems/Context/src/main/java/org/terasology/engine/context/Context.java similarity index 100% rename from engine/src/main/java/org/terasology/engine/context/Context.java rename to subsystems/Context/src/main/java/org/terasology/engine/context/Context.java diff --git a/engine/src/main/java/org/terasology/engine/registry/CoreRegistry.java b/subsystems/Context/src/main/java/org/terasology/engine/registry/CoreRegistry.java similarity index 100% rename from engine/src/main/java/org/terasology/engine/registry/CoreRegistry.java rename to subsystems/Context/src/main/java/org/terasology/engine/registry/CoreRegistry.java diff --git a/engine/src/main/java/org/terasology/engine/registry/In.java b/subsystems/Context/src/main/java/org/terasology/engine/registry/In.java similarity index 100% rename from engine/src/main/java/org/terasology/engine/registry/In.java rename to subsystems/Context/src/main/java/org/terasology/engine/registry/In.java diff --git a/engine/src/main/java/org/terasology/engine/registry/InjectionHelper.java b/subsystems/Context/src/main/java/org/terasology/engine/registry/InjectionHelper.java similarity index 86% rename from engine/src/main/java/org/terasology/engine/registry/InjectionHelper.java rename to subsystems/Context/src/main/java/org/terasology/engine/registry/InjectionHelper.java index 3900ab89636..5f4bf42af96 100644 --- a/engine/src/main/java/org/terasology/engine/registry/InjectionHelper.java +++ b/subsystems/Context/src/main/java/org/terasology/engine/registry/InjectionHelper.java @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.registry; -import org.reflections.ReflectionUtils; +import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.context.Context; @@ -16,6 +16,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.Set; public final class InjectionHelper { private static final Logger logger = LoggerFactory.getLogger(InjectionHelper.class); @@ -25,7 +26,7 @@ private InjectionHelper() { public static void inject(final Object object, Context context) { AccessController.doPrivileged((PrivilegedAction) () -> { - for (Field field : ReflectionUtils.getAllFields(object.getClass(), ReflectionUtils.withAnnotation(In.class))) { + for (Field field : getAllFields(object.getClass(), In.class)) { Object value = context.get(field.getType()); if (value != null) { try { @@ -43,7 +44,7 @@ public static void inject(final Object object, Context context) { public static void inject(final Object object) { AccessController.doPrivileged((PrivilegedAction) () -> { - for (Field field : ReflectionUtils.getAllFields(object.getClass(), ReflectionUtils.withAnnotation(In.class))) { + for (Field field : getAllFields(object.getClass(), In.class)) { Object value = CoreRegistry.get(field.getType()); if (value != null) { try { @@ -64,7 +65,7 @@ public static void inject(final Object object) { public static void inject(final Object object, final Class annotation, final Map, T> source) { AccessController.doPrivileged((PrivilegedAction) () -> { - for (Field field : ReflectionUtils.getAllFields(object.getClass(), ReflectionUtils.withAnnotation(annotation))) { + for (Field field : getAllFields(object.getClass(), annotation)) { Object value = source.get(field.getType()); if (value != null) { try { @@ -82,6 +83,20 @@ public static void inject(final Object object, final Class getAllFields(Class clazz, Class annotation) { + Set result = Sets.newLinkedHashSet(); + Class candidate = clazz; + while (candidate != null) { + for (Field field : candidate.getDeclaredFields()) { + if (field.getAnnotation(annotation) != null) { + result.add(field); + } + } + candidate = candidate.getSuperclass(); + } + return result; + } + public static void share(Object object) { Share share = object.getClass().getAnnotation(Share.class); if (share != null && share.value() != null) { diff --git a/engine/src/main/java/org/terasology/engine/registry/Share.java b/subsystems/Context/src/main/java/org/terasology/engine/registry/Share.java similarity index 100% rename from engine/src/main/java/org/terasology/engine/registry/Share.java rename to subsystems/Context/src/main/java/org/terasology/engine/registry/Share.java diff --git a/engine/src/main/java/org/terasology/engine/registry/package-info.java b/subsystems/Context/src/main/java/org/terasology/engine/registry/package-info.java similarity index 100% rename from engine/src/main/java/org/terasology/engine/registry/package-info.java rename to subsystems/Context/src/main/java/org/terasology/engine/registry/package-info.java diff --git a/subsystems/Context/src/test/java/org/terasology/engine/registry/ContextImplementation.java b/subsystems/Context/src/test/java/org/terasology/engine/registry/ContextImplementation.java new file mode 100644 index 00000000000..d4cbee4dcd3 --- /dev/null +++ b/subsystems/Context/src/test/java/org/terasology/engine/registry/ContextImplementation.java @@ -0,0 +1,27 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.registry; + +import com.google.common.collect.Maps; +import org.terasology.engine.context.Context; + +import java.util.Map; + +public class ContextImplementation implements Context { + private final Map, Object> map = Maps.newConcurrentMap(); + + @Override + public T get(Class type) { + T result = type.cast(map.get(type)); + if (result != null) { + return result; + } + return null; + } + + @Override + public void put(Class type, U object) { + map.put(type, object); + } +} diff --git a/engine-tests/src/test/java/org/terasology/engine/registry/CoreRegistryTest.java b/subsystems/Context/src/test/java/org/terasology/engine/registry/CoreRegistryTest.java similarity index 55% rename from engine-tests/src/test/java/org/terasology/engine/registry/CoreRegistryTest.java rename to subsystems/Context/src/test/java/org/terasology/engine/registry/CoreRegistryTest.java index dad43023317..d1c04a6a800 100644 --- a/engine-tests/src/test/java/org/terasology/engine/registry/CoreRegistryTest.java +++ b/subsystems/Context/src/test/java/org/terasology/engine/registry/CoreRegistryTest.java @@ -3,16 +3,11 @@ package org.terasology.engine.registry; -import com.google.common.collect.Maps; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.terasology.engine.context.Context; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - public class CoreRegistryTest { private Context context; @@ -31,7 +26,7 @@ public void setup() { @Test public void testContextChange() { CoreRegistry.setContext(new ContextImplementation()); - assertNotEquals(CoreRegistry.get(Context.class), context); + Assertions.assertNotEquals(CoreRegistry.get(Context.class), context); } /** @@ -41,8 +36,8 @@ public void testContextChange() { public void testNullReturnOnMissingContext() { CoreRegistry.setContext(null); - assertEquals(CoreRegistry.put(Integer.class, 10), null); - assertEquals(CoreRegistry.get(Integer.class), null); + Assertions.assertEquals(CoreRegistry.put(Integer.class, 10), null); + Assertions.assertEquals(CoreRegistry.get(Integer.class), null); } /** @@ -51,9 +46,9 @@ public void testNullReturnOnMissingContext() { */ @Test public void testContextGetIndependenceFromContextInterfaceImplementation() { - assertEquals(CoreRegistry.get(Context.class), context); + Assertions.assertEquals(CoreRegistry.get(Context.class), context); - assertEquals(context.get(Context.class), null); + Assertions.assertEquals(context.get(Context.class), null); } /** @@ -64,35 +59,18 @@ public void testContextMethodsCalled() { // Load value in context Integer value = 10; CoreRegistry.put(Integer.class, value); - assertEquals(value, context.get(Integer.class)); - assertEquals(context.get(Integer.class), CoreRegistry.get(Integer.class)); + Assertions.assertEquals(value, context.get(Integer.class)); + Assertions.assertEquals(context.get(Integer.class), CoreRegistry.get(Integer.class)); // Change context CoreRegistry.setContext(new ContextImplementation()); - assertNotEquals(CoreRegistry.get(Context.class), context); - assertEquals(CoreRegistry.get(Integer.class), null); + Assertions.assertNotEquals(CoreRegistry.get(Context.class), context); + Assertions.assertEquals(CoreRegistry.get(Integer.class), null); // Restore first context CoreRegistry.setContext(context); - assertEquals(CoreRegistry.get(Integer.class), value); + Assertions.assertEquals(CoreRegistry.get(Integer.class), value); } - private static class ContextImplementation implements Context { - private final Map, Object> map = Maps.newConcurrentMap(); - - @Override - public T get(Class type) { - T result = type.cast(map.get(type)); - if (result != null) { - return result; - } - return null; - } - - @Override - public void put(Class type, U object) { - map.put(type, object); - } - } } diff --git a/engine-tests/src/test/java/org/terasology/engine/registry/InjectionHelperTest.java b/subsystems/Context/src/test/java/org/terasology/engine/registry/InjectionHelperTest.java similarity index 82% rename from engine-tests/src/test/java/org/terasology/engine/registry/InjectionHelperTest.java rename to subsystems/Context/src/test/java/org/terasology/engine/registry/InjectionHelperTest.java index e2cf7df60a3..86cf832bec1 100644 --- a/engine-tests/src/test/java/org/terasology/engine/registry/InjectionHelperTest.java +++ b/subsystems/Context/src/test/java/org/terasology/engine/registry/InjectionHelperTest.java @@ -6,13 +6,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.terasology.engine.context.Context; -import org.terasology.engine.context.internal.ContextImpl; import java.util.NoSuchElementException; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - public class InjectionHelperTest { private ServiceA serviceA; @@ -24,23 +20,23 @@ public void setUp() { serviceB = new ServiceBImpl(); //injection helper uses the core registry, //make sure the shared classes are not used over multiple tests - CoreRegistry.setContext(new ContextImpl()); + CoreRegistry.setContext(new ContextImplementation()); } @Test public void testSharePopulatesCoreRegistry() { - assertNull(CoreRegistry.get(ServiceA.class)); + Assertions.assertNull(CoreRegistry.get(ServiceA.class)); InjectionHelper.share(serviceA); - assertEquals(CoreRegistry.get(ServiceA.class), serviceA); + Assertions.assertEquals(CoreRegistry.get(ServiceA.class), serviceA); } @Test public void testShareRequiresShareAnnotation() { InjectionHelper.share(new ServiceAImplNoAnnotation()); - assertNull(CoreRegistry.get(ServiceA.class)); + Assertions.assertNull(CoreRegistry.get(ServiceA.class)); } @Test @@ -52,8 +48,8 @@ public void testDefaultFieldInjection() { FieldInjectionAB fieldInjectionAB = new FieldInjectionAB(); InjectionHelper.inject(fieldInjectionAB); - assertEquals(fieldInjectionAB.getServiceA(), serviceA); - assertEquals(fieldInjectionAB.getServiceB(), serviceB); + Assertions.assertEquals(fieldInjectionAB.getServiceA(), serviceA); + Assertions.assertEquals(fieldInjectionAB.getServiceB(), serviceB); } @Test @@ -64,26 +60,26 @@ public void testInjectUnavailableObject() { FieldInjectionAB fieldInjectionAB = new FieldInjectionAB(); InjectionHelper.inject(fieldInjectionAB); - assertEquals(fieldInjectionAB.getServiceA(), serviceA); - assertNull(fieldInjectionAB.getServiceB()); + Assertions.assertEquals(fieldInjectionAB.getServiceA(), serviceA); + Assertions.assertNull(fieldInjectionAB.getServiceB()); } @Test public void testDefaultConstructorInjection() { - Context context = new ContextImpl(); + Context context = new ContextImplementation(); context.put(ServiceA.class, serviceA); context.put(ServiceB.class, serviceB); ConstructorAB constructorAB = InjectionHelper.createWithConstructorInjection(ConstructorAB.class, context); //the two-arg constructor should be used as it has the most parameters and all can be populated - assertEquals(constructorAB.getServiceA(), serviceA); - assertEquals(constructorAB.getServiceB(), serviceB); + Assertions.assertEquals(constructorAB.getServiceA(), serviceA); + Assertions.assertEquals(constructorAB.getServiceB(), serviceB); } @Test public void testConstructorInjectionNotAllParametersPopulated() { - Context context = new ContextImpl(); + Context context = new ContextImplementation(); context.put(ServiceA.class, serviceA); //context.put(ServiceB.class, serviceB); @@ -91,27 +87,27 @@ public void testConstructorInjectionNotAllParametersPopulated() { //the two-arg constructor can't be populated because serviceB is not available //there is no fallback for a constructor with only serviceA, so the default constructor is called - assertNull(constructorAB.getServiceA()); - assertNull(constructorAB.getServiceB()); + Assertions.assertNull(constructorAB.getServiceA()); + Assertions.assertNull(constructorAB.getServiceB()); } @SuppressWarnings("checkstyle:LocalVariableName") @Test public void testConstructorInjectionNotAllParametersPopulatedFallback() { - Context context = new ContextImpl(); + Context context = new ContextImplementation(); context.put(ServiceA.class, serviceA); //context.put(ServiceB.class, serviceB); ConstructorA_AB constructorA_AB = InjectionHelper.createWithConstructorInjection(ConstructorA_AB.class, context); //the one-arg constructor is used as it can be populated with serviceA which is available - assertEquals(constructorA_AB.getServiceA(), serviceA); - assertNull(constructorA_AB.getServiceB()); + Assertions.assertEquals(constructorA_AB.getServiceA(), serviceA); + Assertions.assertNull(constructorA_AB.getServiceB()); } @Test public void testConstructorInjectionNoDefaultConstructorForFallback() { - Context context = new ContextImpl(); + Context context = new ContextImplementation(); context.put(ServiceA.class, serviceA); //context.put(ServiceB.class, serviceB);