![JAR search and dependency download from the Maven repository](/logo.png)
fr.landel.utils.assertor.helper.HelperAssertor Maven / Gradle / Ivy
/*-
* #%L
* utils-assertor
* %%
* Copyright (C) 2016 - 2018 Gilles Landel
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package fr.landel.utils.assertor.helper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import fr.landel.utils.assertor.Step;
import fr.landel.utils.assertor.StepAssertor;
import fr.landel.utils.assertor.commons.ConstantsAssertor;
import fr.landel.utils.assertor.commons.MessagesAssertor;
import fr.landel.utils.assertor.commons.ParameterAssertor;
import fr.landel.utils.assertor.commons.ResultAssertor;
import fr.landel.utils.assertor.enums.EnumOperator;
import fr.landel.utils.assertor.enums.EnumStep;
import fr.landel.utils.assertor.enums.EnumType;
import fr.landel.utils.commons.CastUtils;
import fr.landel.utils.commons.ObjectUtils;
import fr.landel.utils.commons.tuple.PairIso;
/**
* Assertor helper class, to combine steps.
*
* @since Aug 3, 2016
* @author Gilles
*
*/
public class HelperAssertor extends ConstantsAssertor {
/**
* Empty {@code String}
*/
protected static final CharSequence EMPTY_STRING = "";
/**
* Transformer used to extract objects from parameters
*/
protected static final Transformer, Object> PARAM_TRANSFORMER = new Transformer, Object>() {
@Override
public Object transform(final ParameterAssertor> input) {
return input.getObject();
}
};
/**
* When the previous check is valid followed by operator OR, we don't need
* to check the next steps, it's OK. Or, when the previous check is invalid
* followed by operator NOR, we don't need to check the next steps, it's
* also OK.
*/
private static final BiPredicate VALID = (valid, operator) -> (!valid && EnumOperator.NOR.equals(operator))
|| (valid && EnumOperator.OR.equals(operator));
/**
* When the previous check is valid followed by operator NAND, we don't need
* to check the next steps, it's KO. Or, when the previous check is invalid
* followed by operator AND, we don't need to check the next steps, it's
* also KO.
*/
private static final BiPredicate INVALID = (valid, operator) -> (!valid && EnumOperator.AND.equals(operator))
|| (valid && EnumOperator.NAND.equals(operator));
private static final String ERROR_CHECKER = "checker is missing";
/**
* Validates matcher mode and return the steps list reversed
*
* @param step
* the end step
* @param matcherMode
* if in matcher mode
* @param
* the type of current chcecked object
* @return the steps list reversed
* @throws UnsupportedOperationException
* if in matcher mode and if steps of type
* {@link EnumStep#CREATION}, {@link EnumStep#OBJECT} are found
* in the chain
*/
private static List> validatesAndReverse(final StepAssertor step, final boolean matcherMode) {
final List> steps = new ArrayList<>();
steps.add(step);
final boolean inMatcherMode = matcherMode || EnumStep.PREDICATE_OBJECT.equals(step.getStepType());
StepAssertor> first = null;
StepAssertor> currentStep = step;
StepAssertor> previousStep;
while ((previousStep = currentStep.getPreviousStep()) != null) {
if (inMatcherMode) {
final EnumStep s = previousStep.getStepType();
if (EnumStep.PREDICATE.equals(s)) {
first = previousStep;
} else if (EnumStep.CREATION.equals(s) || EnumStep.OBJECT.equals(s)) {
throw new UnsupportedOperationException("Creation step cannot be used in Predicate mode");
}
}
steps.add(previousStep);
currentStep = previousStep;
}
if (inMatcherMode && first == null) {
throw new IllegalArgumentException("StepAssertor chain must contain a matcher step");
}
Collections.reverse(steps);
return steps;
}
/**
* The combine function (the main method). This method is called by each end
* steps.
*
* @param step
* the last step
* @param loadMessage
* if the message has to loaded and formated
* @param
* the type of checked object
* @return the result
* @throws IllegalArgumentException
* if in matcher mode and object is not set
*/
public static ResultAssertor combine(final StepAssertor step, final boolean loadMessage) {
return combine(step, null, false, loadMessage);
}
/**
* The combine function (the main method). This method is called by each end
* steps.
*
*
* What is matcher mode: it's an Assertor starting with
* "{@code Assertor.matcher}...". In this mode, only one variable can be
* checked through "{@code on(object)}" method. The aim of this is to create
* the validation steps before in another method or in a static way to
* improve performance.
*
*
* @param step
* the last step
* @param object
* the object to test (only in matcher mode)
* @param marcherMode
* if it's in matcher mode (set with "on" method)
* @param loadMessage
* if the message has to beloaded and formated
* @param
* the type of checked object
* @return the result
* @throws IllegalArgumentException
* if in matcher mode and object is not set
*/
public static ResultAssertor combine(final StepAssertor step, final Object object, final boolean marcherMode,
final boolean loadMessage) {
final List> steps = validatesAndReverse(step, marcherMode);
boolean not = false;
boolean valid = true;
PairIso resultValid;
EnumOperator operator = null;
final MessagesAssertor messages = new MessagesAssertor();
Object obj;
final Object matcherObject;
boolean checked = false;
EnumType type = null;
ParameterAssertor> param = null;
final List> parameters = new ArrayList<>();
Optional> dontNeedCheck = Optional.empty();
// get the object to check
// in matcher mode, two ways are available to inject the object:
// - first: directly through the combine function
boolean inMatcherMode = marcherMode;
if (inMatcherMode) {
obj = object;
// - second:
} else if (EnumStep.PREDICATE_OBJECT.equals(step.getStepType())) {
obj = step.getObject();
inMatcherMode = true;
} else {
obj = null;
}
// create a temporary variable for sub steps (in matcher mode)
matcherObject = obj;
for (StepAssertor> s : steps) {
if (s.getStepType() == null) {
continue;
}
switch (s.getStepType()) {
// the first step of an Assertor in predicate mode (ex:
// Assertor.ofNumber...)
case PREDICATE:
type = s.getType();
if (EnumType.UNKNOWN.equals(type)) {
type = EnumType.getType(obj);
}
param = new ParameterAssertor<>(obj, type, true);
parameters.add(param);
break;
// the first step of an Assertor (ex: Assertor.that(object)...)
case CREATION:
obj = s.getObject();
type = s.getType();
checked = s.isChecked();
param = new ParameterAssertor<>(obj, type, checked);
parameters.add(param);
break;
// the validation step
case ASSERTION:
parameters.addAll(s.getParameters());
// if precondition returns false, we end all treatments
if (!HelperAssertor.preCheck(s, obj)) {
return HelperAssertor.getPreconditionMessage(s, param, parameters, loadMessage);
} else {
resultValid = HelperAssertor.validatesAndGetMessage(s, param, obj, valid, not, operator, messages, loadMessage);
valid = resultValid.getRight();
}
if (operator != null && !valid) {
dontNeedCheck = checkValidityAndOperator(resultValid.getLeft(), operator, messages, loadMessage);
}
not = false;
break;
// the combining step between two validation steps
case OPERATOR:
operator = s.getOperator();
dontNeedCheck = checkValidityAndOperator(valid, operator, messages, loadMessage);
break;
// the not step to reverse the next validation step
case NOT:
not = not ^ s.isNot();
break;
// the object provided by the mapper
case PROPERTY:
if (s.getMapper().isPresent()) {
operator = s.getOperator();
obj = s.getMapper().get().apply(obj);
type = s.getType();
checked = s.isChecked();
param = new ParameterAssertor<>(obj, type, checked);
parameters.add(param);
dontNeedCheck = checkValidityAndOperator(valid, operator, messages, loadMessage);
} else {
throw new IllegalStateException("property cannot be null");
}
break;
// the other object to validate
case OBJECT:
operator = s.getOperator();
obj = s.getObject();
type = s.getType();
checked = s.isChecked();
param = new ParameterAssertor<>(obj, type, checked);
parameters.add(param);
dontNeedCheck = checkValidityAndOperator(valid, operator, messages, loadMessage);
break;
// the sub step to emulate parenthesis in a check (ex:
// Assertor.that(2).isZero().or(Assertor.that(2).isGTE(1).and().isLTE(10)))
case SUB:
if (s.getSubStep().isPresent()) {
dontNeedCheck = checkValidityAndOperator(valid, s.getOperator(), messages, loadMessage);
if (!dontNeedCheck.isPresent()) {
final Triple output = HelperAssertor.managesSub(s, matcherObject,
inMatcherMode, parameters, valid, operator, messages, loadMessage);
if (output.getRight() != null) {
return output.getRight();
} else {
valid = output.getLeft();
operator = output.getMiddle();
}
}
}
break;
// sub assertor step to check sub properties
case SUB_ASSERTOR:
if (s.getSubAssertor().isPresent()) {
operator = s.getOperator();
dontNeedCheck = checkValidityAndOperator(valid, operator, messages, loadMessage);
if (!dontNeedCheck.isPresent()) {
final Step, ?> stepSubAssertor = Objects.requireNonNull(s.getSubAssertor().get().apply(obj),
"Sub assertor mapper cannot be null");
final ResultAssertor intermediateResult = combine(stepSubAssertor.getStep(), null, inMatcherMode, loadMessage);
valid = intermediateResult.isPrecondition() && isValid(valid, intermediateResult.isValid(), operator);
parameters.addAll(intermediateResult.getParameters());
if (!valid && loadMessage && intermediateResult.getMessages() != null) {
if (messages.isNotEmpty()) {
messages.append(operator);
}
messages.append(intermediateResult.getMessages());
}
if (intermediateResult.isPrecondition()) {
dontNeedCheck = checkValidityAndOperator(intermediateResult.isValid(), operator, messages, loadMessage);
} else {
dontNeedCheck = Optional.of(Pair.of(false, intermediateResult.getMessages()));
}
}
} else {
throw new IllegalStateException("sub assertor cannot be null");
}
break;
default: // MATCHER_OBJECT (don't need treatment)
}
if (dontNeedCheck.isPresent()) {
return new ResultAssertor(true, dontNeedCheck.get().getKey(), dontNeedCheck.get().getValue(), parameters);
}
}
return new ResultAssertor(true, valid, messages, parameters);
}
private static Optional> checkValidityAndOperator(final boolean valid, final EnumOperator operator,
final MessagesAssertor messages, final boolean loadMessage) {
Pair result = null;
if (VALID.test(valid, operator)) {
result = Pair.of(true, messages);
} else if (INVALID.test(valid, operator)) {
final MessagesAssertor formattedMessage;
if (loadMessage) {
if (messages.isNotEmpty()) {
formattedMessage = messages;
} else {
formattedMessage = new MessagesAssertor();
formattedMessage.append(MSG.INVALID_WITHOUT_MESSAGE, false,
new CharSequence[] {String.valueOf(valid), String.valueOf(operator)}, null);
}
} else {
formattedMessage = null;
}
result = Pair.of(false, formattedMessage);
}
return Optional.ofNullable(result);
}
private static ResultAssertor getPreconditionMessage(final StepAssertor step, final ParameterAssertor> param,
final List> parameters, final boolean loadMessage) {
final List> assertParameters = new ArrayList<>();
assertParameters.add(param);
assertParameters.addAll(step.getParameters());
final MessagesAssertor error;
if (loadMessage) {
error = new MessagesAssertor();
error.append(step.getMessageKey(), false, null, assertParameters);
} else {
error = null;
}
return new ResultAssertor(false, false, error, parameters);
}
private static PairIso validatesAndGetMessage(final StepAssertor step, final ParameterAssertor> param,
final Object object, final boolean valid, final boolean not, final EnumOperator operator, final MessagesAssertor messages,
final boolean loadMessage) {
final boolean currentValid = HelperAssertor.check(step, CastUtils.cast(object), not);
final boolean nextValid = HelperAssertor.isValid(valid, currentValid, operator);
if (!nextValid && loadMessage) {
if (messages.isNotEmpty() && operator != null) {
messages.append(operator);
}
final List> assertParameters = new ArrayList<>();
assertParameters.add(param);
assertParameters.addAll(step.getParameters());
messages.append(step.getMessageKey(), not ^ step.isMessageKeyNot(), null, assertParameters, step.getMessage());
}
return PairIso.of(currentValid, nextValid);
}
private static Triple managesSub(final StepAssertor> step, final Object matcherObject,
final boolean marcherMode, final List> parameters, final boolean valid, final EnumOperator operator,
final MessagesAssertor messages, final boolean loadMessage) {
final StepAssertor> subStep = step.getSubStep().get();
final EnumOperator stepOperator = step.getOperator();
EnumOperator nextOperator = operator;
boolean nextValid = valid;
final ResultAssertor subResult = HelperAssertor.combine(subStep, matcherObject, marcherMode, loadMessage);
// in matcher mode, the matcher is not required, so we remove it
final int size = subResult.getParameters().size();
if (matcherObject != null && size > 1) {
parameters.addAll(subResult.getParameters().subList(1, size));
} else {
parameters.addAll(subResult.getParameters());
}
if (!subResult.isPrecondition()) {
return Triple.of(false, null, subResult);
} else {
nextOperator = stepOperator;
nextValid = HelperAssertor.isValid(nextValid, subResult.isValid(), nextOperator);
if (!nextValid && loadMessage && subResult.getMessages() != null) {
if (messages.isNotEmpty() && nextOperator != null) {
messages.append(nextOperator);
}
messages.append(subResult.getMessages());
}
}
return Triple.of(nextValid, nextOperator, null);
}
private static boolean preCheck(final StepAssertor step, final Object object) {
if (step.getPreChecker() != null) {
return step.getPreChecker().test(CastUtils.cast(object));
}
return true;
}
private static boolean check(final StepAssertor step, final T object, final boolean not) {
Objects.requireNonNull(step.getChecker(), ERROR_CHECKER);
try {
if (step.isNotAppliedByChecker()) {
return step.getChecker().test(object, not);
} else {
return not ^ step.getChecker().test(object, not);
}
} catch (Throwable e) {
return false;
}
}
public static boolean isValid(final boolean previousOK, final boolean currentOK, final EnumOperator operator) {
return ObjectUtils.defaultIfNull(operator, EnumOperator.AND).isValid(previousOK, currentOK);
}
public static boolean isValid(final boolean all, final boolean not, final long found, final int size) {
if (all) {
if (not) { // NOT ALL
return found > 0 && found < size;
} else { // ALL
return found == size;
}
} else if (not) { // NOT ANY
return found == 0;
} else { // ANY
return found > 0;
}
}
public static boolean isValid(final Stream stream, final Predicate predicate, final boolean all, final boolean not,
final Supplier size) {
if (all && !not) { // ALL
return stream.allMatch(predicate);
} else if (!all && !not) { // ANY
return stream.anyMatch(predicate);
} else if (!all) { // NOT ANY
return stream.noneMatch(predicate);
} else { // NOT ALL
return isValid(all, not, stream.filter(predicate).count(), size.get());
}
}
/**
* Extract the last object (checked variable) from parameters
*
* @param parameters
* the parameters list
* @param
* the type of the last object
* @return a typed object
*/
public static T getLastChecked(final List> parameters) {
final int size = parameters.size();
T object = null;
for (int i = size - 1; i >= 0; --i) {
if (parameters.get(i).isChecked()) {
object = CastUtils.cast(parameters.get(i).getObject());
break;
}
}
return object;
}
/**
* Combines and gets the message from Assertor result
*
* @param result
* the Assertor result
* @return the message (can be empty)
*/
public static String getMessage(final ResultAssertor result) {
if (result.getMessages() != null) {
return result.getMessages().build();
} else {
return StringUtils.EMPTY;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy