Skip to content

Commit

Permalink
extract any enum type parameters correctly
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Kraschewski <daniel.kraschewski@tngtech.com>
  • Loading branch information
Daniel Kraschewski committed Nov 28, 2020
1 parent 772c171 commit 68fb6ad
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.tngtech.configbuilder.util;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.stream.Stream;

import static java.util.Arrays.stream;

public class EnumTypeExtractor {

Stream<Class<? extends Enum<?>>> getEnumTypesRelevantFor(Type type) {
if (type instanceof Class && ((Class<?>) type).isEnum()) {
return Stream.of((Class<? extends Enum<?>>) type);
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return stream(parameterizedType.getActualTypeArguments())
.flatMap(this::getEnumTypesRelevantFor)
.distinct();
}
return Stream.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class FieldValueTransformer {
private final ConfigBuilderFactory configBuilderFactory;
private final ErrorMessageSetup errorMessageSetup;
private final GenericsAndCastingHelper genericsAndCastingHelper;
private final EnumTypeExtractor enumTypeExtractor;
private Object[] additionalOptions;

//Order is important: Prefer List over Set if both apply!
Expand All @@ -38,6 +39,7 @@ public FieldValueTransformer(ConfigBuilderFactory configBuilderFactory) {
this.configBuilderFactory = configBuilderFactory;
this.errorMessageSetup = configBuilderFactory.getInstance(ErrorMessageSetup.class);
this.genericsAndCastingHelper = configBuilderFactory.getInstance(GenericsAndCastingHelper.class);
this.enumTypeExtractor = configBuilderFactory.getInstance(EnumTypeExtractor.class);
}

public Object transformFieldValue(Field field, Object sourceValue) {
Expand All @@ -51,9 +53,9 @@ private void initialize(Field field) {
for(Class<? extends TypeTransformer> transformerClass : getAllTransformers(field)) {
availableTransformers.add(configBuilderFactory.getInstance(transformerClass));
}
genericsAndCastingHelper.getEnumTypeOrParameterIfApplicable(field)
.map(StringToEnumTypeTransformer::new)
.ifPresent(availableTransformers::add);
enumTypeExtractor.getEnumTypesRelevantFor(field.getGenericType())
.map(enumClass -> new StringToEnumTypeTransformer(enumClass))
.forEach(availableTransformers::add);

additionalOptions = field.isAnnotationPresent(Separator.class)? new Object[]{field.getAnnotation(Separator.class).value()} : new Object[]{","};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import com.google.common.collect.ImmutableMap;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;

public class GenericsAndCastingHelper {

Expand Down Expand Up @@ -62,19 +60,4 @@ else if(Collection.class.isAssignableFrom((Class<?>)((ParameterizedType)targetTy
public boolean isPrimitiveOrWrapper(Class targetClass) {
return primitiveToWrapperMapping.containsKey(targetClass) || primitiveToWrapperMapping.containsValue(targetClass);
}

public <E extends Enum<E>> Optional<Class<E>> getEnumTypeOrParameterIfApplicable(Field field) {
if (field.getType().isEnum()) {
return Optional.of((Class<E>) field.getType());
}

if (!Collection.class.isAssignableFrom(field.getType())) {
return Optional.empty();
}

Type collectionParameterType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
return collectionParameterType instanceof Class && ((Class<?>) collectionParameterType).isEnum()
? Optional.of((Class<E>) collectionParameterType)
: Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.tngtech.configbuilder.util;

import org.junit.Test;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

public class EnumTypeExtractorTest {

enum TestEnum1 {}

enum TestEnum2 {}

public static class TestClass {

String simpleNonEnumField;
Map<Integer, String> nonEnumParameterizedField;

TestEnum1 simpleEnumField;
Map<TestEnum1, TestEnum2> fieldWithEnumTypeParameters;
Map<TestEnum1, List<TestEnum2>> fieldWithNestedEnumTypeParameters;
Map<TestEnum1, Map<TestEnum1, TestEnum1>> fieldWithRepeatedEnumTypeParameter;
}

private final EnumTypeExtractor enumTypeExtractor = new EnumTypeExtractor();

@Test
public void testExtractionOnSimpleNonEnumType() throws Exception {
Field simpleNonEnumField = TestClass.class.getDeclaredField("simpleNonEnumField");
assertThat(enumTypeExtractor.getEnumTypesRelevantFor(simpleNonEnumField.getGenericType()))
.isEmpty();
}

@Test
public void testExtractionOnNonEnumParameterizedType() throws Exception {
Field nonEnumParameterizedField = TestClass.class.getDeclaredField("nonEnumParameterizedField");
assertThat(enumTypeExtractor.getEnumTypesRelevantFor(nonEnumParameterizedField.getGenericType()))
.isEmpty();
}

@Test
public void testExtractionOfSimpleEnumType() throws Exception {
Field simpleEnumField = TestClass.class.getDeclaredField("simpleEnumField");
assertThat(enumTypeExtractor.getEnumTypesRelevantFor(simpleEnumField.getGenericType()))
.containsExactly(TestEnum1.class);
}

@Test
public void testExtractionOfEnumTypeParameters() throws Exception {
Field fieldWithEnumTypeParameters = TestClass.class.getDeclaredField("fieldWithEnumTypeParameters");
assertThat(enumTypeExtractor.getEnumTypesRelevantFor(fieldWithEnumTypeParameters.getGenericType()))
.containsExactlyInAnyOrder(TestEnum1.class, TestEnum2.class);
}

@Test
public void testExtractionOfNestedEnumTypeParameters() throws Exception {
Field fieldWithNestedEnumTypeParameters = TestClass.class.getDeclaredField("fieldWithNestedEnumTypeParameters");
assertThat(enumTypeExtractor.getEnumTypesRelevantFor(fieldWithNestedEnumTypeParameters.getGenericType()))
.containsExactlyInAnyOrder(TestEnum1.class, TestEnum2.class);
}

@Test
public void testExtractedEnumTypesAreDeduplicated() throws Exception {
Field fieldWithRepeatedEnumTypeParameter = TestClass.class.getDeclaredField("fieldWithRepeatedEnumTypeParameter");
assertThat(enumTypeExtractor.getEnumTypesRelevantFor(fieldWithRepeatedEnumTypeParameter.getGenericType()))
.containsExactly(TestEnum1.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private static class TestConfigClass {
public void setUp() throws Exception {
when(configBuilderFactory.getInstance(ErrorMessageSetup.class)).thenReturn(errorMessageSetup);
when(configBuilderFactory.getInstance(GenericsAndCastingHelper.class)).thenReturn(new GenericsAndCastingHelper());
when(configBuilderFactory.getInstance(EnumTypeExtractor.class)).thenReturn(new EnumTypeExtractor());

when(configBuilderFactory.getInstance(CharacterSeparatedStringToStringListTransformer.class)).thenReturn(new CharacterSeparatedStringToStringListTransformer());
when(configBuilderFactory.getInstance(CharacterSeparatedStringToStringSetTransformer.class)).thenReturn(new CharacterSeparatedStringToStringSetTransformer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class FieldValueTransformerTest {
@Mock
private GenericsAndCastingHelper genericsAndCastingHelper;
@Mock
private EnumTypeExtractor enumTypeExtractor;
@Mock
private CollectionToArrayListTransformer collectionToArrayListTransformer;
@Mock
private CharacterSeparatedStringToStringListTransformer characterSeparatedStringToStringListTransformer;
Expand All @@ -59,6 +61,7 @@ public class FieldValueTransformerTest {
public void setUp() throws Exception {
when(configBuilderFactory.getInstance(ErrorMessageSetup.class)).thenReturn(errorMessageSetup);
when(configBuilderFactory.getInstance(GenericsAndCastingHelper.class)).thenReturn(genericsAndCastingHelper);
when(configBuilderFactory.getInstance(EnumTypeExtractor.class)).thenReturn(enumTypeExtractor);

fieldValueTransformer = new FieldValueTransformer(configBuilderFactory);
field = this.getClass().getDeclaredField("testField");
Expand Down

0 comments on commit 68fb6ad

Please sign in to comment.