Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 47 additions & 19 deletions src/main/java/fr/maxlego08/sarah/ConsumerConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,21 @@ public class ConsumerConstructor {
*/
public static Consumer<Schema> createConsumerFromTemplate(Class<?> template, Object data) {
Constructor<?>[] constructors = template.getDeclaredConstructors();
Constructor<?> firstConstructor = constructors[0];
firstConstructor.setAccessible(true);

Field[] allFields = template.getDeclaredFields();
// Filter out synthetic fields (added by compiler for local/anonymous classes)
// Filter out synthetic fields and those annotated with @TransientField
Field[] fields = Arrays.stream(allFields)
.filter(f -> !f.isSynthetic())
.filter(f -> !f.isAnnotationPresent(TransientField.class))
.toArray(Field[]::new);

// For local/anonymous classes, count only non-synthetic constructor parameters
long nonSyntheticParamCount = Arrays.stream(firstConstructor.getParameters())
.filter(p -> !p.isSynthetic())
.count();
boolean hasCompatibleConstructor = Arrays.stream(constructors)
.peek(constructor -> constructor.setAccessible(true))
.anyMatch(constructor -> Arrays.stream(constructor.getParameters())
.filter(p -> !p.isSynthetic())
.count() == fields.length);

if (fields.length != nonSyntheticParamCount) {
if (!hasCompatibleConstructor) {
throw new IllegalArgumentException("Fields count does not match constructor parameters count");
}

Expand Down Expand Up @@ -148,13 +148,14 @@ public static Consumer<Schema> createConsumerFromTemplate(Class<?> template, Obj
* </ul>
* <p>
* If the type is not supported, an {@link IllegalArgumentException} is thrown.
* If the object is not compatible with the type, an {@link IllegalArgumentException} is thrown.
* <p>
* @param schema the schema to add the column to
* @param type the type of the column
* @param name the name of the column
* @param object the value of the column, or null if the column should be added without a value
*/
private static void schemaFromType(Schema schema, String type, String name, Object object) {
private static void schemaFromType(Schema schema, String type, String name, Object object) throws IllegalArgumentException {
switch (type.toLowerCase()) {
case "string":
if (object == null) {
Expand All @@ -174,34 +175,61 @@ private static void schemaFromType(Schema schema, String type, String name, Obje
schema.bigInt(name);
break;
}
schema.bigInt(name, Long.parseLong(object.toString()));
break;
if (object instanceof Number) {
schema.bigInt(name, ((Number) object).longValue());
break;
}
if (object instanceof String) {
try {
schema.bigInt(name, Long.parseLong((String) object));
break;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Object is not a valid number for type " + type);
}
}
throw new IllegalArgumentException("Object is not a number for type " + type);
case "boolean":
if (object == null) {
schema.bool(name);
break;
}
schema.bool(name, (Boolean) object);
break;
if (object instanceof Boolean) {
schema.bool(name, (Boolean) object);
break;
}
throw new IllegalArgumentException("Object is not a boolean for type " + type);
case "double":
case "float":
case "bigdecimal":
if (object == null) {
schema.decimal(name);
break;
}
schema.decimal(name, (Double) object);
break;
if (object instanceof Number) {
schema.decimal(name, ((Number) object));
break;
}
throw new IllegalArgumentException("Object is not a number for type " + type);
case "uuid":
if (object == null) {
schema.uuid(name);
break;
}
schema.uuid(name, (UUID) object);
break;
if (object instanceof UUID) {
schema.uuid(name, (UUID) object);
break;
}
throw new IllegalArgumentException("Object is not a UUID for type " + type);
case "date":
schema.date(name, (Date) object).nullable();
break;
if (object == null) {
schema.date(name, null).nullable();
break;
}
if (object instanceof Date) {
schema.date(name, (Date) object).nullable();
break;
}
throw new IllegalArgumentException("Object is not a Date for type " + type);
case "timestamp":
schema.timestamp(name).nullable();
break;
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/fr/maxlego08/sarah/TransientField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package fr.maxlego08.sarah;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marks a field as transient for schema/constructor mapping.
* Fields annotated with this will be ignored by schema generation and mapping logic.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface TransientField {
}

40 changes: 40 additions & 0 deletions src/test/java/fr/maxlego08/sarah/DTOConsumerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,34 @@ public ComplexDTO(Long id, String textField, Integer intField, Double doubleFiel
public Boolean getBoolField() { return boolField; }
}

private static class OverloadedTemplateDTO {
private final String name;
private final Integer quantity;

public OverloadedTemplateDTO(String name) {
this(name, null);
}

public OverloadedTemplateDTO(String name, Integer quantity) {
this.name = name;
this.quantity = quantity;
}
}

private static class IncompatibleTemplateDTO {
private final String code;
private final Integer version;

public IncompatibleTemplateDTO() {
this(null);
}

public IncompatibleTemplateDTO(String code) {
this.code = code;
this.version = null;
}
}

@Test
public void testCreateTableFromDTOClass() throws Exception {
// Create table using DTO class template
Expand Down Expand Up @@ -304,4 +332,16 @@ public void testDTOWithDifferentTypes() throws Exception {
assertEquals(3.14, retrieved.getDoubleField(), 0.001);
assertTrue(retrieved.getBoolField());
}

@Test
public void testCreateConsumerWithOverloadedConstructors() {
assertDoesNotThrow(() -> ConsumerConstructor.createConsumerFromTemplate(OverloadedTemplateDTO.class, null));
}

@Test
public void testCreateConsumerThrowsWhenNoCompatibleConstructorExists() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> ConsumerConstructor.createConsumerFromTemplate(IncompatibleTemplateDTO.class, null));
assertEquals("Fields count does not match constructor parameters count", exception.getMessage());
}
}
Loading