Skip to content

Commit 478d3d2

Browse files
committed
add support for Enum CustomTypes in the CustomTypeScanner
1 parent 0d2bd94 commit 478d3d2

4 files changed

Lines changed: 156 additions & 2 deletions

File tree

src/main/java/it/aboutbits/springboot/toolbox/autoconfiguration/web/CustomTypeScanner.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package it.aboutbits.springboot.toolbox.autoconfiguration.web;
22

33
import it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
4+
import it.aboutbits.springboot.toolbox.reflection.util.CustomTypeReflectionUtil;
45
import it.aboutbits.springboot.toolbox.type.CustomType;
56
import lombok.Getter;
67
import lombok.extern.slf4j.Slf4j;
@@ -41,7 +42,7 @@ public void setAdditionalTypePackages(String[] additionalTypePackages) {
4142
this.relevantTypes = findAllCustomTypes(classScanner);
4243
}
4344

44-
@SuppressWarnings("rawtypes")
45+
@SuppressWarnings({"rawtypes", "unchecked"})
4546
public static Set<Class<? extends CustomType>> findAllCustomTypes(ClassScannerUtil.ClassScanner classScanner) {
4647
return classScanner.getSubTypesOf(CustomType.class).stream()
4748
.filter(item ->
@@ -50,6 +51,27 @@ public static Set<Class<? extends CustomType>> findAllCustomTypes(ClassScannerUt
5051
&& !Modifier.isAbstract(item.getModifiers())
5152
&& !item.isAnnotationPresent(DisableCustomTypeConfiguration.class)
5253
)
54+
.filter(item -> {
55+
if (item.isEnum()) {
56+
return true;
57+
}
58+
59+
try {
60+
var constructor = CustomTypeReflectionUtil.getCustomTypeConstructor((Class<? extends CustomType<?>>) item);
61+
var wrappedType = constructor.getParameterTypes()[0];
62+
63+
if (!CustomTypeReflectionUtil.isSupportedWrappedType(wrappedType)) {
64+
log.debug("CustomType {} has an unsupported wrapped type {} and will be ignored.", item.getName(), wrappedType.getName());
65+
return false;
66+
}
67+
68+
return true;
69+
} catch (NoSuchMethodException _) {
70+
log.debug("CustomType {} is missing the required constructor and will be ignored.", item.getName());
71+
72+
return false;
73+
}
74+
})
5375
.collect(Collectors.toSet());
5476
}
5577

src/main/java/it/aboutbits/springboot/toolbox/reflection/util/CustomTypeReflectionUtil.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package it.aboutbits.springboot.toolbox.reflection.util;
22

33
import it.aboutbits.springboot.toolbox.type.CustomType;
4+
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
45
import org.jspecify.annotations.NullMarked;
56

67
import java.lang.reflect.Constructor;
78
import java.lang.reflect.ParameterizedType;
9+
import java.math.BigDecimal;
10+
import java.math.BigInteger;
811
import java.util.Arrays;
12+
import java.util.UUID;
913

1014
@NullMarked
1115
public final class CustomTypeReflectionUtil {
@@ -17,7 +21,7 @@ public static <T extends CustomType<?>> Constructor<T> getCustomTypeConstructor(
1721
return customType.getConstructor(
1822
getWrappedType(customType)
1923
);
20-
} catch (NoSuchMethodException | SecurityException e) {
24+
} catch (NoSuchMethodException | SecurityException _) {
2125
throw new NoSuchMethodException();
2226
}
2327
}
@@ -43,4 +47,22 @@ public static Class<?> getWrappedType(Class<? extends CustomType<?>> customType)
4347

4448
throw new NoSuchMethodException();
4549
}
50+
51+
public static boolean isSupportedWrappedType(Class<?> wrappedType) {
52+
return Boolean.class.isAssignableFrom(wrappedType)
53+
|| String.class.isAssignableFrom(wrappedType)
54+
|| Character.class.isAssignableFrom(wrappedType)
55+
|| Byte.class.isAssignableFrom(wrappedType)
56+
|| Short.class.isAssignableFrom(wrappedType)
57+
|| Integer.class.isAssignableFrom(wrappedType)
58+
|| Long.class.isAssignableFrom(wrappedType)
59+
|| Float.class.isAssignableFrom(wrappedType)
60+
|| Double.class.isAssignableFrom(wrappedType)
61+
|| BigInteger.class.isAssignableFrom(wrappedType)
62+
|| BigDecimal.class.isAssignableFrom(wrappedType)
63+
|| ScaledBigDecimal.class.isAssignableFrom(wrappedType)
64+
|| UUID.class.isAssignableFrom(wrappedType)
65+
|| Enum.class.isAssignableFrom(wrappedType)
66+
|| wrappedType.isEnum();
67+
}
4668
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package it.aboutbits.springboot.toolbox.autoconfiguration.web;
2+
3+
import it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
4+
import it.aboutbits.springboot.toolbox.type.CustomType;
5+
import org.jspecify.annotations.NullMarked;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.util.Set;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.mockito.Mockito.mock;
12+
import static org.mockito.Mockito.when;
13+
14+
@NullMarked
15+
class CustomTypeScannerTest {
16+
@Test
17+
@SuppressWarnings("unchecked")
18+
void findAllCustomTypes_shouldFilterOutTypesMissingConstructor() {
19+
// given
20+
ClassScannerUtil.ClassScanner classScanner = mock(ClassScannerUtil.ClassScanner.class);
21+
when(classScanner.getSubTypesOf(CustomType.class)).thenReturn(Set.of(
22+
EnumCustomType.class,
23+
InvalidCustomType.class,
24+
UnsupportedWrappedTypeCustomType.class
25+
));
26+
27+
// when
28+
Set<Class<? extends CustomType>> result = CustomTypeScanner.findAllCustomTypes(classScanner);
29+
30+
// then
31+
assertThat(result)
32+
.containsExactly(EnumCustomType.class)
33+
.doesNotContain(InvalidCustomType.class)
34+
.doesNotContain(UnsupportedWrappedTypeCustomType.class);
35+
}
36+
37+
public enum EnumCustomType implements CustomType<EnumCustomType> {
38+
VALUE;
39+
40+
@Override
41+
public EnumCustomType value() {
42+
return this;
43+
}
44+
}
45+
46+
@SuppressWarnings("checkstyle:RedundantModifier")
47+
public static class InvalidCustomType implements CustomType<String> {
48+
@Override
49+
public String value() {
50+
return "test";
51+
}
52+
}
53+
54+
@SuppressWarnings("checkstyle:RedundantModifier")
55+
public static class UnsupportedWrappedTypeCustomType implements CustomType<Object> {
56+
private final Object value;
57+
58+
public UnsupportedWrappedTypeCustomType(Object value) {
59+
this.value = value;
60+
}
61+
62+
@Override
63+
public Object value() {
64+
return value;
65+
}
66+
}
67+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package it.aboutbits.springboot.toolbox.jackson;
2+
3+
import it.aboutbits.springboot.toolbox.autoconfiguration.web.CustomTypeScanner;
4+
import it.aboutbits.springboot.toolbox.type.CustomType;
5+
import org.jspecify.annotations.NullMarked;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.assertj.core.api.Assertions.assertThatCode;
9+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
10+
11+
@NullMarked
12+
class CustomTypeDeserializerTest {
13+
@Test
14+
void constructor_withValidCustomType_shouldNotThrow() {
15+
assertThatCode(() -> new CustomTypeDeserializer<>(EnumCustomType.class))
16+
.doesNotThrowAnyException();
17+
}
18+
19+
@Test
20+
void constructor_withInvalidCustomType_shouldThrowException() {
21+
assertThatExceptionOfType(CustomTypeDeserializer.CustomTypeDeserializerException.class)
22+
.isThrownBy(() -> new CustomTypeDeserializer<>(InvalidCustomType.class))
23+
.withMessageContaining("Unable to find constructor for type: it.aboutbits.springboot.toolbox.jackson.CustomTypeDeserializerTest$InvalidCustomType");
24+
}
25+
26+
public enum EnumCustomType implements CustomType<EnumCustomType> {
27+
VALUE;
28+
29+
@Override
30+
public EnumCustomType value() {
31+
return this;
32+
}
33+
}
34+
35+
@SuppressWarnings("checkstyle:RedundantModifier")
36+
public static class InvalidCustomType implements CustomType<String> {
37+
@Override
38+
public String value() {
39+
return "test";
40+
}
41+
// Missing constructor(String)
42+
}
43+
}

0 commit comments

Comments
 (0)