Skip to content

Commit 920281b

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

4 files changed

Lines changed: 157 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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package it.aboutbits.springboot.toolbox.jackson;
2+
3+
import it.aboutbits.springboot.toolbox.type.CustomType;
4+
import org.jspecify.annotations.NullMarked;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThatCode;
8+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
9+
10+
@NullMarked
11+
class CustomTypeDeserializerTest {
12+
@Test
13+
void constructor_withValidCustomType_shouldNotThrow() {
14+
assertThatCode(() -> new CustomTypeDeserializer<>(EnumCustomType.class))
15+
.doesNotThrowAnyException();
16+
}
17+
18+
@Test
19+
void constructor_withInvalidCustomType_shouldThrowException() {
20+
assertThatExceptionOfType(CustomTypeDeserializer.CustomTypeDeserializerException.class)
21+
.isThrownBy(() -> new CustomTypeDeserializer<>(InvalidCustomType.class))
22+
.withMessageContaining(
23+
"Unable to find constructor for type: it.aboutbits.springboot.toolbox.jackson.CustomTypeDeserializerTest$InvalidCustomType"
24+
);
25+
}
26+
27+
public enum EnumCustomType implements CustomType<EnumCustomType> {
28+
VALUE;
29+
30+
@Override
31+
public EnumCustomType value() {
32+
return this;
33+
}
34+
}
35+
36+
@SuppressWarnings("checkstyle:RedundantModifier")
37+
public static class InvalidCustomType implements CustomType<String> {
38+
@Override
39+
public String value() {
40+
return "test";
41+
}
42+
// Missing constructor(String)
43+
}
44+
}

0 commit comments

Comments
 (0)