From 3706b3ff4b42a1334da0c48d2b67f11ca9d0f1d5 Mon Sep 17 00:00:00 2001 From: Henrik Date: Sun, 14 Mar 2021 23:16:28 +0100 Subject: [PATCH] add support for generic component classes --- .../e/ComponentMethodProxyStrategy.java | 21 ++++---- .../strategy/e/DefaultFieldProxyStrategy.java | 5 +- .../util/ExtendedTypeReflection.java | 51 +++++++++++++++++++ .../e/ComponentFieldAccessorStrategyTest.java | 16 ++++++ .../e/ComponentMethodProxyStrategyTest.java | 11 ++++ .../strategy/e/GenericComponents.java | 27 ++++++++++ 6 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/GenericComponents.java diff --git a/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategy.java b/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategy.java index 43d09eb5a..af8298a9c 100644 --- a/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategy.java +++ b/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategy.java @@ -1,5 +1,7 @@ package com.artemis.generator.strategy.e; +import static com.artemis.generator.util.ExtendedTypeReflection.resolveGenericType; +import static com.artemis.generator.util.ExtendedTypeReflection.resolveGenericReturnType; import com.artemis.annotations.Fluid; import com.artemis.annotations.FluidMethod; import com.artemis.generator.common.IterativeModelStrategy; @@ -10,6 +12,7 @@ import com.artemis.generator.util.MethodBuilder; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.Set; /** @@ -64,7 +67,7 @@ private boolean isSetter(Method method) { private MethodDescriptor methodProxyReturnFluidMethod(ComponentDescriptor component, Method method) { MethodBuilder builder = new MethodBuilder(FluidTypes.E_TYPE, component.getCompositeName(method.getName())); - String arguments = appendParameters(method, builder); + String arguments = appendParameters(component, method, builder); return builder .mapper(component, ".create(entityId)." + method.getName() + "(" + arguments + ")") @@ -73,22 +76,22 @@ private MethodDescriptor methodProxyReturnFluidMethod(ComponentDescriptor compon .build(); } - private String appendParameters(Method method, MethodBuilder builder) { - int count = 0; + private String appendParameters(ComponentDescriptor component, Method method, MethodBuilder builder) { String arguments = ""; - for (Class parameterType : method.getParameterTypes()) { - builder.parameter(parameterType, "p" + count); - arguments = arguments + (arguments.isEmpty() ? "" : ", ") + "p" + count; - count++; + final Type[] parameterTypes = method.getGenericParameterTypes(); + final int paramCount = parameterTypes.length; + for(int i = 0; i < paramCount; i++) { + builder.parameter(resolveGenericType(component, method, parameterTypes[i]), "p" + i); + arguments = arguments + (arguments.isEmpty() ? "" : ", ") + "p" + i; } return arguments; } private MethodDescriptor methodProxyMethod(ComponentDescriptor component, Method method) { - MethodBuilder builder = new MethodBuilder(method.getGenericReturnType(), component.getCompositeName(method.getName())); + MethodBuilder builder = new MethodBuilder(resolveGenericReturnType(component, method), component.getCompositeName(method.getName())); - String arguments = appendParameters(method, builder); + String arguments = appendParameters(component, method, builder); return builder .mapper("return ", component, ".create(entityId)." + method.getName() + "(" + arguments + ")") diff --git a/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/DefaultFieldProxyStrategy.java b/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/DefaultFieldProxyStrategy.java index f3b87c18f..794e89c90 100644 --- a/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/DefaultFieldProxyStrategy.java +++ b/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/strategy/e/DefaultFieldProxyStrategy.java @@ -1,5 +1,6 @@ package com.artemis.generator.strategy.e; +import static com.artemis.generator.util.ExtendedTypeReflection.resolveGenericType; import com.artemis.generator.model.FluidTypes; import com.artemis.generator.model.artemis.ComponentDescriptor; import com.artemis.generator.model.type.MethodDescriptor; @@ -46,7 +47,7 @@ public void execute(ComponentDescriptor component, Field field, TypeModel model) * int E::posX() -> obtain field directly via interface. */ private MethodDescriptor fieldGetterMethod(ComponentDescriptor component, Field field) { - return new MethodBuilder(field.getGenericType(), component.getCompositeName(field.getName())) + return new MethodBuilder(resolveGenericType(component, field), component.getCompositeName(field.getName())) .mapper("return ", component, ".create(entityId)." + field.getName()) .debugNotes(field.toGenericString()) .build(); @@ -58,7 +59,7 @@ private MethodDescriptor fieldGetterMethod(ComponentDescriptor component, Field private MethodDescriptor fieldSetterMethod(ComponentDescriptor component, Field field) { final String parameterName = field.getName(); return new MethodBuilder(FluidTypes.E_TYPE, component.getCompositeName(parameterName)) - .parameter(field.getGenericType(), parameterName) + .parameter(resolveGenericType(component, field), parameterName) .mapper(component, ".create(this.entityId)." + parameterName + "=" + parameterName) .debugNotes(field.toGenericString()) .returnFluid() diff --git a/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/util/ExtendedTypeReflection.java b/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/util/ExtendedTypeReflection.java index fde1c0bf8..ca0b14e90 100644 --- a/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/util/ExtendedTypeReflection.java +++ b/artemis-fluid/artemis-fluid-core/src/main/java/com/artemis/generator/util/ExtendedTypeReflection.java @@ -1,7 +1,9 @@ package com.artemis.generator.util; +import com.artemis.generator.model.artemis.ComponentDescriptor; import com.google.common.base.Predicate; import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; import org.reflections.ReflectionUtils; import java.lang.annotation.Annotation; @@ -103,4 +105,53 @@ public boolean apply(T input) { }; } + /** + * Resolve the actual type of the provided field. + * For non-generic fields, this will just return the field type. + * For generic fields, try to determine the provided generic type arguments and deduce the real type from it. + */ + public static Type resolveGenericType(final ComponentDescriptor component, final Field field) { + return resolveGenericType(component.getComponentType(), field.getDeclaringClass(), field.getGenericType()); + } + + /** + * Resolve the actual type of the methods parameter. + * For non-generic methods, this will just return the parameters type. + * For generic methods, try to determine the provided generic type arguments and deduce the real type from it. + */ + public static Type resolveGenericType(final ComponentDescriptor component, final Method method, final Type type) { + return resolveGenericType(component.getComponentType(), method.getDeclaringClass(), type); + } + + /** + * Resolve the actual return type of the method. + * For non-generic methods, this will just return the parameters type. + * For generic methods, try to determine the provided generic type arguments and deduce the real return type from it. + */ + public static Type resolveGenericReturnType(final ComponentDescriptor component, final Method method) { + return resolveGenericType(component.getComponentType(), method.getDeclaringClass(), method.getGenericReturnType()); + } + + /** + * Tries to deduce the actual runtime type for the generic type parameter. + * @param componentType the class of a component + * @param declaringType a base class of the given component class + * @param typeParam the generic type parameter that needs to be resolved + * @return the actual type + */ + @SuppressWarnings({"UnstableApiUsage", "rawtypes", "unchecked"}) + private static Type resolveGenericType(final Class componentType, final Class declaringType, final Type typeParam) { + if(declaringType.isAssignableFrom(componentType) && typeParam instanceof TypeVariable) { + final TypeToken actual = TypeToken.of(componentType); + final TypeToken declared = actual.getSupertype(declaringType); + final ParameterizedType parameterizedType = (ParameterizedType) declared.getType(); + final TypeVariable[] declaredTypes = ((Class) parameterizedType.getRawType()).getTypeParameters(); + final Type[] actualTypes = parameterizedType.getActualTypeArguments(); + final int typeIndex = Arrays.asList(declaredTypes).indexOf(typeParam); + if (typeIndex >= 0) { + return actualTypes[typeIndex]; + } + } + return typeParam; + } } diff --git a/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentFieldAccessorStrategyTest.java b/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentFieldAccessorStrategyTest.java index ad59fdda6..928f03cf9 100644 --- a/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentFieldAccessorStrategyTest.java +++ b/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentFieldAccessorStrategyTest.java @@ -63,4 +63,20 @@ public void When_public_field_with_parameterized_Type_Should_expose_as_parameter TypeModel model = applyStrategy(ComponentFieldAccessorStrategy.class, Proof.class); assertHasMethod(model, "com.artemis.E proofGen(java.util.List gen)"); } + + @Test + public void When_public_field_is_defined_in_generic_base_class() { + TypeModel model = applyStrategy(ComponentFieldAccessorStrategy.class, StringComponent.class); + assertHasMethod(model, "com.artemis.E stringComponentValue(java.lang.String value)"); + assertHasMethod(model, "java.lang.String stringComponentValue()"); + } + + @Test + public void When_public_field_is_defined_in_generic_base_class_with_intermediate_class() { + TypeModel model = applyStrategy(ComponentFieldAccessorStrategy.class, SimpleComponent.class); + assertHasMethod(model, "com.artemis.E simpleComponentValue1(java.lang.Integer value1)"); + assertHasMethod(model, "com.artemis.E simpleComponentValue2(java.lang.String value2)"); + assertHasMethod(model, "java.lang.Integer simpleComponentValue1()"); + assertHasMethod(model, "java.lang.String simpleComponentValue2()"); + } } \ No newline at end of file diff --git a/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategyTest.java b/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategyTest.java index 13bcb868c..840644003 100644 --- a/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategyTest.java +++ b/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/ComponentMethodProxyStrategyTest.java @@ -51,4 +51,15 @@ public void When_public_void_parameterized_method_with_own_component_type_as_ret assertHasMethod(model,"com.artemis.generator.strategy.e.Proof proofFluid(com.artemis.generator.strategy.e.Proof p0)"); } + @Test + public void When_public_void_parameterized_method_is_defined_in_generic_base_class() { + TypeModel model = applyStrategy(ComponentMethodProxyStrategy.class, SimpleComponent.class); + assertHasMethod(model,"com.artemis.E simpleComponent(java.lang.Integer p0,java.lang.String p1,int p2)"); + } + + @Test + public void When_public_return_value_parameterized_method_is_defined_in_generic_base_class() { + TypeModel model = applyStrategy(ComponentMethodProxyStrategy.class, SimpleComponent.class); + assertHasMethod(model,"java.lang.Integer simpleComponentDummy(java.lang.Integer p0,java.lang.String p1,int p2)"); + } } \ No newline at end of file diff --git a/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/GenericComponents.java b/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/GenericComponents.java new file mode 100644 index 000000000..0b589ee80 --- /dev/null +++ b/artemis-fluid/artemis-fluid-core/src/test/java/com/artemis/generator/strategy/e/GenericComponents.java @@ -0,0 +1,27 @@ +package com.artemis.generator.strategy.e; + +import com.artemis.Component; + +abstract class SingleValueComponent extends Component { + public T value; +} + +final class StringComponent extends SingleValueComponent { } + +abstract class BaseComponent extends Component { + public T1 value1; + public T2 value2; + + public void set(T1 a, T2 b, int c) { + value1 = a; + value2 = b; + } + + public T1 dummy(T1 a, T2 b, int c) { + return value1; + } +} + +abstract class SubComponent extends BaseComponent { } + +final class SimpleComponent extends SubComponent { }