Skip to content

Commit baabf16

Browse files
committed
fix merge conflicts
2 parents 1498b2f + 5b77da0 commit baabf16

File tree

13 files changed

+290
-0
lines changed

13 files changed

+290
-0
lines changed

src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeDeserializer.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ private static Function<JsonParser, Object> getTypeConverter(Class<?> wrappedTyp
9696
if (UUID.class.isAssignableFrom(wrappedType)) {
9797
return getUUIDConverter();
9898
}
99+
if (Enum.class.isAssignableFrom(wrappedType) || wrappedType.isEnum()) {
100+
return getEnumConverter(wrappedType);
101+
}
99102
throw new CustomTypeDeserializerException("Value type not supported: " + wrappedType.getName());
100103
}
101104

@@ -237,6 +240,27 @@ private static Function<JsonParser, Object> getUUIDConverter() {
237240
};
238241
}
239242

243+
@SuppressWarnings("unchecked")
244+
private static Function<JsonParser, Object> getEnumConverter(Class<?> wrappedType) {
245+
var enumClass = (Class<? extends Enum<?>>) wrappedType.asSubclass(Enum.class);
246+
return jsonParser -> {
247+
try {
248+
String value = jsonParser.getValueAsString();
249+
if (value == null) {
250+
return null;
251+
}
252+
return Enum.valueOf((Class<? extends Enum>) enumClass, value);
253+
} catch (IllegalArgumentException e) {
254+
throw new CustomTypeDeserializerException(
255+
"Failed to read value as Enum: " + enumClass.getName(),
256+
e
257+
);
258+
} catch (IOException e) {
259+
throw new CustomTypeDeserializerException("Failed to read value as Enum.", e);
260+
}
261+
};
262+
}
263+
240264
public static final class CustomTypeDeserializerException extends RuntimeException {
241265
public CustomTypeDeserializerException(String message) {
242266
super(message);

src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeSerializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public void serialize(
2626
switch (value) {
2727
case String stringValue -> jsonGenerator.writeString(stringValue);
2828
case UUID uuidValue -> jsonGenerator.writeString(uuidValue.toString());
29+
case Enum<?> enumValue -> jsonGenerator.writeString(enumValue.name());
2930
case null -> jsonGenerator.writeNull();
3031
default -> jsonGenerator.writeRawValue(String.valueOf(value));
3132
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
2+
3+
import it.aboutbits.springboot.toolbox.type.CustomType;
4+
import lombok.SneakyThrows;
5+
import org.hibernate.type.descriptor.WrapperOptions;
6+
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
7+
import org.hibernate.type.descriptor.jdbc.JdbcType;
8+
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
9+
10+
import java.lang.reflect.Constructor;
11+
import java.lang.reflect.InvocationTargetException;
12+
import java.sql.Types;
13+
14+
public abstract class WrappedEnumJavaType<T extends CustomType<? extends Enum<?>>> extends AbstractClassJavaType<T> {
15+
private final transient Constructor<T> constructor;
16+
private final Class<? extends Enum<?>> enumClass;
17+
18+
@SuppressWarnings("unchecked")
19+
protected WrappedEnumJavaType(Class<T> type) {
20+
super(type);
21+
22+
try {
23+
this.enumClass = determineEnumClass(type);
24+
this.constructor = type.getConstructor(enumClass);
25+
} catch (NoSuchMethodException e) {
26+
throw new IllegalStateException("No constructor found for " + type.getName() + " with enum parameter", e);
27+
}
28+
}
29+
30+
@SuppressWarnings("unchecked")
31+
private Class<? extends Enum<?>> determineEnumClass(Class<T> type) {
32+
// Try to extract from the generic superclass (if the CustomType exposes the enum via inheritance)
33+
var genericSuperclass = type.getGenericSuperclass();
34+
if (genericSuperclass instanceof java.lang.reflect.ParameterizedType parameterizedType) {
35+
var actualTypeArguments = parameterizedType.getActualTypeArguments();
36+
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class<?> enumType) {
37+
if (enumType.isEnum()) {
38+
return (Class<? extends Enum<?>>) enumType;
39+
}
40+
}
41+
}
42+
// Fallback: inspect constructors and use the single-arg enum constructor (works for records and classes)
43+
for (var ctor : type.getDeclaredConstructors()) {
44+
var params = ctor.getParameterTypes();
45+
if (params.length == 1 && params[0].isEnum()) {
46+
return (Class<? extends Enum<?>>) params[0];
47+
}
48+
}
49+
throw new IllegalStateException("Could not determine enum type for " + type.getName());
50+
}
51+
52+
@Override
53+
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
54+
return indicators.getTypeConfiguration()
55+
.getJdbcTypeRegistry()
56+
.getDescriptor(Types.VARCHAR);
57+
}
58+
59+
@SuppressWarnings("unchecked")
60+
@Override
61+
public <X> X unwrap(T id, Class<X> aClass, WrapperOptions wrapperOptions) {
62+
var javaTypeClass = getJavaTypeClass();
63+
64+
if (id == null) {
65+
return null;
66+
}
67+
if (javaTypeClass.isAssignableFrom(aClass)) {
68+
return (X) id;
69+
}
70+
if (String.class.isAssignableFrom(aClass)) {
71+
return (X) id.value().name();
72+
}
73+
if (enumClass.isAssignableFrom(aClass)) {
74+
return (X) id.value();
75+
}
76+
77+
throw unknownUnwrap(aClass);
78+
}
79+
80+
@SuppressWarnings("unchecked")
81+
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
82+
@Override
83+
public <X> T wrap(X value, WrapperOptions wrapperOptions) {
84+
var clazz = getJavaTypeClass();
85+
86+
if (value == null) {
87+
return null;
88+
}
89+
if (clazz.isInstance(value)) {
90+
return (T) value;
91+
}
92+
if (value instanceof String stringValue) {
93+
var enumValue = Enum.valueOf((Class<? extends Enum>) enumClass, stringValue);
94+
return constructor.newInstance(enumValue);
95+
}
96+
if (enumClass.isInstance(value)) {
97+
return constructor.newInstance(value);
98+
}
99+
100+
throw unknownWrap(value.getClass());
101+
}
102+
}

src/main/java/it/aboutbits/springboot/toolbox/web/CustomTypePropertyEditor.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,25 @@ private static Function<String, Object> getTextToTypeConverter(Class<?> wrappedT
104104
if (UUID.class.isAssignableFrom(wrappedType)) {
105105
return UUID::fromString;
106106
}
107+
if (Enum.class.isAssignableFrom(wrappedType)) {
108+
return toEnumConverter(wrappedType);
109+
}
107110
throw new IllegalArgumentException("Unable to convert text to type: " + wrappedType.getName());
108111
}
112+
113+
@SuppressWarnings("unchecked")
114+
private static Function<String, Object> toEnumConverter(Class<?> wrappedType) {
115+
var enumClass = (Class<? extends Enum<?>>) wrappedType.asSubclass(Enum.class);
116+
117+
return text -> {
118+
if (text == null) {
119+
return null;
120+
}
121+
try {
122+
return Enum.valueOf((Class<? extends Enum>) enumClass, text);
123+
} catch (IllegalArgumentException e) {
124+
throw new IllegalArgumentException("Unable to convert text to enum: " + enumClass.getName(), e);
125+
}
126+
};
127+
}
109128
}

src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import it.aboutbits.springboot.toolbox._support.ApplicationTest;
44
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModel;
55
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModelRepository;
6+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.SampleEnum;
67
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimalClass;
78
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimalRecord;
89
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigIntegerClass;
@@ -15,6 +16,8 @@
1516
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapCharacterRecord;
1617
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleClass;
1718
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleRecord;
19+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass;
20+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord;
1821
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatClass;
1922
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatRecord;
2023
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapIntegerClass;
@@ -33,6 +36,7 @@
3336
import org.junit.jupiter.api.Nested;
3437
import org.junit.jupiter.api.Test;
3538
import org.junit.jupiter.params.ParameterizedTest;
39+
import org.junit.jupiter.params.provider.EnumSource;
3640
import org.junit.jupiter.params.provider.ValueSource;
3741
import org.springframework.beans.factory.annotation.Autowired;
3842

@@ -521,6 +525,40 @@ void givenStringValues_inAndOut_shouldSucceed(String uuidStringValue) {
521525
.isEqualTo(savedItem);
522526
}
523527
}
528+
529+
@Nested
530+
class WrapEnumRecordType {
531+
@Test
532+
void givenNull_inAndOut_shouldSucceed() {
533+
var item = new WrapperTypesModel();
534+
item.setEnumValue(null);
535+
536+
var savedItem = repository.save(item);
537+
538+
var retrievedItem = repository.findByEnumValue(null);
539+
540+
assertThat(retrievedItem).isPresent()
541+
.get()
542+
.usingRecursiveComparison()
543+
.isEqualTo(savedItem);
544+
}
545+
546+
@ParameterizedTest
547+
@EnumSource(SampleEnum.class)
548+
void givenValues_inAndOut_shouldSucceed(SampleEnum enumValue) {
549+
var item = new WrapperTypesModel();
550+
item.setEnumValue(new WrapEnumRecord(enumValue));
551+
552+
var savedItem = repository.save(item);
553+
554+
var retrievedItem = repository.findByEnumValue(savedItem.getEnumValue());
555+
556+
assertThat(retrievedItem).isPresent()
557+
.get()
558+
.usingRecursiveComparison()
559+
.isEqualTo(savedItem);
560+
}
561+
}
524562
}
525563

526564
@Nested
@@ -997,5 +1035,39 @@ void givenStringValues_inAndOut_shouldSucceed(String uuidStringValue) {
9971035
.isEqualTo(savedItem);
9981036
}
9991037
}
1038+
1039+
@Nested
1040+
class WrapEnumClassType {
1041+
@Test
1042+
void givenNull_inAndOut_shouldSucceed() {
1043+
var item = new WrapperTypesModel();
1044+
item.setEnumValueClass(null);
1045+
1046+
var savedItem = repository.save(item);
1047+
1048+
var retrievedItem = repository.findByEnumValueClass(null);
1049+
1050+
assertThat(retrievedItem).isPresent()
1051+
.get()
1052+
.usingRecursiveComparison()
1053+
.isEqualTo(savedItem);
1054+
}
1055+
1056+
@ParameterizedTest
1057+
@EnumSource(SampleEnum.class)
1058+
void givenValues_inAndOut_shouldSucceed(SampleEnum enumValue) {
1059+
var item = new WrapperTypesModel();
1060+
item.setEnumValueClass(new WrapEnumClass(enumValue));
1061+
1062+
var savedItem = repository.save(item);
1063+
1064+
var retrievedItem = repository.findByEnumValueClass(savedItem.getEnumValueClass());
1065+
1066+
assertThat(retrievedItem).isPresent()
1067+
.get()
1068+
.usingRecursiveComparison()
1069+
.isEqualTo(savedItem);
1070+
}
1071+
}
10001072
}
10011073
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
2+
3+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.AutoRegisteredJavaType;
4+
import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedEnumJavaType;
5+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass;
6+
7+
public final class WrapEnumClassJavaType extends WrappedEnumJavaType<WrapEnumClass> implements AutoRegisteredJavaType<WrapEnumClass> {
8+
public WrapEnumClassJavaType() {
9+
super(WrapEnumClass.class);
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
2+
3+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.AutoRegisteredJavaType;
4+
import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedEnumJavaType;
5+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord;
6+
7+
public final class WrapEnumRecordJavaType extends WrappedEnumJavaType<WrapEnumRecord> implements AutoRegisteredJavaType<WrapEnumRecord> {
8+
public WrapEnumRecordJavaType() {
9+
super(WrapEnumRecord.class);
10+
}
11+
}

src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapCharacterRecordJavaType;
1515
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapDoubleClassJavaType;
1616
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapDoubleRecordJavaType;
17+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapEnumClassJavaType;
18+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapEnumRecordJavaType;
1719
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapFloatClassJavaType;
1820
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapFloatRecordJavaType;
1921
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapIntegerClassJavaType;
@@ -40,6 +42,8 @@
4042
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapCharacterRecord;
4143
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleClass;
4244
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleRecord;
45+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass;
46+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord;
4347
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatClass;
4448
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatRecord;
4549
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapIntegerClass;
@@ -136,6 +140,10 @@ public class WrapperTypesModel implements Identified<WrapperTypesModel.ID> {
136140
@JdbcType(CharJdbcType.class)
137141
private WrapUUIDRecord uuidValueAsString;
138142

143+
@SuppressWarnings("JpaAttributeTypeInspection")
144+
@JavaType(WrapEnumRecordJavaType.class)
145+
private WrapEnumRecord enumValue;
146+
139147
@SuppressWarnings("JpaAttributeTypeInspection")
140148
@JavaType(WrapBigDecimalClassJavaType.class)
141149
private WrapBigDecimalClass bigDecimalValueClass;
@@ -194,6 +202,10 @@ public class WrapperTypesModel implements Identified<WrapperTypesModel.ID> {
194202
@JdbcType(CharJdbcType.class)
195203
private WrapUUIDClass uuidValueClassAsString;
196204

205+
@SuppressWarnings("JpaAttributeTypeInspection")
206+
@JavaType(WrapEnumClassJavaType.class)
207+
private WrapEnumClass enumValueClass;
208+
197209
public record ID(
198210
Long value
199211
) implements EntityId<Long> {

src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapCharacterRecord;
1313
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleClass;
1414
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleRecord;
15+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass;
16+
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord;
1517
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatClass;
1618
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatRecord;
1719
import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapIntegerClass;
@@ -86,4 +88,8 @@ public interface WrapperTypesModelRepository extends JpaRepository<WrapperTypesM
8688
Optional<WrapperTypesModel> findByUuidValueClass(WrapUUIDClass value);
8789

8890
Optional<WrapperTypesModel> findByUuidValueClassAsString(WrapUUIDClass value);
91+
92+
Optional<WrapperTypesModel> findByEnumValue(WrapEnumRecord value);
93+
94+
Optional<WrapperTypesModel> findByEnumValueClass(WrapEnumClass value);
8995
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
2+
3+
public enum SampleEnum {
4+
A, B, C
5+
}

0 commit comments

Comments
 (0)