0

I have a java 8 functional interface that accepts a list of validators that are applied on an object and returns the validation result. The validation results are accumulated in the reduce phase. The code as follows:

public interface LogicalTableValidator extends Function<LogicalTable, ValidationResult> {

    static LogicalTableValidator addAll(LogicalTableValidator... validators) {

        //  Need to break out of this validator stream, based on the criticality of a particular validation error
        return logicalTable -> Arrays.stream(validators).map(v -> v.apply(logicalTable))
                .reduce(new ValidationResult(logicalTable.getUid()), (validationResult, currentResult) -> {
                    validationResult.addValidationMessages(currentResult.getValidationMessages());
                    return validationResult;
                });

    }

}

This validation logic gets called from here

LogicalTableValidator logicalTableValidators = LogicalTableValidator.addAll(getValidators());
List<ValidationResult> ltValidationResults = logicalTables.stream()
                                                          .parallel()
                                                          .map(logicalTableValidators)
                                                          .collect(Collectors.toList());

The problem I am facing is that, I am not able to break from the validation logic conditionally. This will be the case when I am applying the validators on the logicalObject, if the validation fails with a critical error, I dont need to run rest of the validators. Instead I need to stop the validation process right there.

A work around would be not to use lambda expression for validation and use the following code instead.

        return new LogicalTableValidator() {

            @Override
            public ValidationResult apply(LogicalTable t) {
                ValidationResult result = new ValidationResult(t.getUid());
                for (LogicalTableValidator validator : validators) {
                    ValidationResult currentResult = validator.apply(t);
                    List<ValidationMessage> messages = currentResult.getValidationMessages();
                    Boolean exit = false;
                    for (ValidationMessage message : messages) {
                        if(StringUtils.equalsIgnoreCase(message.getSeverity(),  "1")) {
                            exit = true;
                            break;
                        }
                    }
                    result.addValidationMessages(currentResult.getValidationMessages());
                    if (exit) break;
                }
                return result;
            }

        };

It seems, not using lambda expression in functional interface, defeats the purpose of using functional interface, but I couldn't figure out a way to conditionally break out of the validation loop. Is there any alternative I can use? Should this code be structured in a different way?

azro
  • 53,056
  • 7
  • 34
  • 70
Joyjit
  • 119
  • 1
  • 13
  • Why you don't use filter()? – Egor Nov 14 '19 at 15:07
  • filter() would still still iterate through all the objects checking for filter condition. I want to break out of the loop if and when a critical error is encountered. – Joyjit Nov 14 '19 at 16:02

2 Answers2

1

You can try something like below. In peek it collect ValidationMessages. In filter and findFirst it stop after first error message. It is replacement for takeWhile that was mentioned in comments, you can also check this.

public interface LogicalTableValidator extends Function<LogicalTable, ValidationResult> {

    static LogicalTableValidator addAll(LogicalTableValidator... validators) {

        logicalTable -> {
            ValidationResult result = new ValidationResult(logicalTable.getUid());
            Arrays.stream(validators).map(v -> v.apply(logicalTable))
                    .peek(currentResult -> result.addValidationMessages(currentResult.getValidationMessages()))
                    .filter(currentResult -> currentResult.getValidationMessages().stream()
                            .filter(message -> StringUtils.equalsIgnoreCase(message.getSeverity(), "1"))
                            .count() > 0)
                    .findFirst()
                    .orElse(null);
            return result;
        }
    }

}
lczapski
  • 4,026
  • 3
  • 16
  • 32
0

There are two distinct things: 1. breaking logicalTables stream and 2. breaking validators stream.

For logicalTables, your stream is parallel and even if the break was possible you would obtain potentially different results.

For validators stream, Stream.takeWhile seems to be the closest to pure stream-based solution. Unfortunately, it is in JDK since Java 9 and moreover it doesn't comprise invalid ValidationResult into resulting stream. Alternatives might exist in external libraries though the imperative code seems to me as simplest and readable enough at this moment.

Tomáš Záluský
  • 10,735
  • 2
  • 36
  • 64
  • Thanks for your comment. I am only looking breaking out of validator stream based on the criticality of the validation error. I did find java 9 Stream.takeWhile, but unfortunately we are in java 8, and not in a position to move to java 9 anytime soon. – Joyjit Nov 14 '19 at 17:36