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
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

@RequiredArgsConstructor
public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<?>> implements
public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<R>> implements
ValidationRulesData,
BetweenRule<R>,
FutureRule<R>,
Expand Down Expand Up @@ -53,6 +54,12 @@ public void addRule(@NonNull Rule rule) {
rules.add(rule);
}

public <T extends BaseRuleBuilder<T>> T withAdditionalRules(Consumer<T> registrar) {
var self = (T) this;
registrar.accept(self);
return self;
}

public void isCompliant() {
triggerValidation.run();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -217,10 +219,10 @@ private static <P> Stream<?> getValues(Rule rule, P parameterUnderTest) {
private static <T> T createCopyWithAlteredProperty(T original, String property, Object alteredValue) {
try {
// Get the class of the original object
var clazz = original.getClass();
var clazz = (Class<T>) original.getClass();

// Get all the declared fields of the class
var fields = clazz.getDeclaredFields();
var fields = getAllFields(clazz);

// Create an array to hold the values of the original object's properties
var propertyValues = new Object[fields.length];
Expand All @@ -235,10 +237,6 @@ private static <T> T createCopyWithAlteredProperty(T original, String property,
parameterTypes[i] = fields[i].getType();
}

// Get the constructor that accepts all properties as arguments
var constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);

// Create an array to hold the new property values
var newPropertyValues = new Object[propertyValues.length];

Expand All @@ -261,18 +259,46 @@ private static <T> T createCopyWithAlteredProperty(T original, String property,
}
}

// Create a copy with the altered property value
return (T) constructor.newInstance(newPropertyValues);
return createCopyWithAlteredValues(clazz, parameterTypes, newPropertyValues, fields);
} catch (NoSuchMethodException e) {
throw new RuleValidationException(
"Error creating copy with altered property. Maybe there is no all-args-constructor?",
"Error creating copy with altered property. Maybe there is no eligible-constructor?",
e
);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuleValidationException("Error creating copy with altered property: " + property, e);
}
}

private static <T> T createCopyWithAlteredValues(
Class<T> clazz,
Class<?>[] parameterTypes,
Object[] newPropertyValues,
Field[] fields
) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
T instance = null;
try {
// Get the constructor that accepts all properties as arguments
var constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);

// Create a copy with the altered property value
instance = constructor.newInstance(newPropertyValues);
} catch (NoSuchMethodException e) {
// Get the no args constructor
var constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);

// Create a copy with the altered property value
instance = constructor.newInstance();
for (var i = 0; i < newPropertyValues.length; i++) {
fields[i].setAccessible(true);
fields[i].set(instance, newPropertyValues[i]);
}
}
return instance;
}

private static <T> Set<String> getAllPropertiesOf(T object) {
var clazz = object.getClass();

Expand Down Expand Up @@ -307,12 +333,23 @@ private static boolean hasNullableAnnotation(String propertyName, Object object)
private static Field getFieldOrFail(String propertyName, Object object) {
var clazz = object.getClass();

Field field = null;
try {
field = clazz.getDeclaredField(propertyName);
} catch (NoSuchFieldException e) {
throw new RuleValidationException("Property does not exist: " + propertyName, e);
var field = Arrays.stream(getAllFields(clazz))
.filter(
f -> f.getName().equals(propertyName)
)
.findFirst();

return field.orElseThrow(() -> new RuleValidationException("Property does not exist: " + propertyName));
}

private static Field[] getAllFields(Class<?> initialClazz) {
Class<?> clazz = initialClazz;

var fields = new ArrayList<Field>();
while (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
Comment on lines +349 to 352

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one 👌🏼

return field;
return fields.toArray(new Field[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.function.Consumer;

import static it.aboutbits.springboot.testing.validation.ValidationAssertTest.TestValidationAssert.assertThatValidation;
import static org.assertj.core.api.Assertions.assertThatCode;
Expand Down Expand Up @@ -618,6 +619,49 @@ void givenNotValidatedClass_shouldAlwaysFail() {
}
}

@Test
void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() {
var item = new SomeExtendingClass();
item.notNull = "notNull";
item.notNullPositiveOrZero = ScaledBigDecimal.ONE;

assertThatValidation().of(item)
.usingBeanValidation()
.notNull("notNull")
.notNull("notNull")
.positiveOrZero("notNullPositiveOrZero")
.isCompliant();

var invalidItem = new SomeExtendingClass();

assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThatValidation().of(invalidItem)
.usingBeanValidation()
.notNull("notNull")
.notNull("notNull")
.positiveOrZero("notNullPositiveOrZero")
.isCompliant()
);
}

@Test
void usingRuleRegistrarShouldWork() {
var item = new SomeExtendingClass();
item.notNull = "notNull";
item.notNullPositiveOrZero = ScaledBigDecimal.ONE;

var registrar = (Consumer<TestValidationAssert.TestRuleBuilder>) ruleBuilder -> ruleBuilder
.notNull("notNull")
.notNull("notNull")
.positiveOrZero("notNullPositiveOrZero");

assertThatValidation().of(item)
.usingBeanValidation()
.withAdditionalRules(registrar)
.isCompliant();
}


private static SomeValidParameter getSomeValidParameter() {
return new SomeValidParameter(
// NotNull
Expand Down Expand Up @@ -791,4 +835,15 @@ public void someMethodWithoutValidParameter(Long first, String last) {
public void someMethodWithoutValidParameter(Long first, Integer second, String last) {
}
}

public abstract static class SomeBaseClass {
@NotNull
protected String notNull;
}

public static class SomeExtendingClass extends SomeBaseClass {
@NotNull
@PositiveOrZero
private ScaledBigDecimal notNullPositiveOrZero;
}
}