Thor will use his powerful Mjölnir to validate instances of objects protecting the developer from bugs.
ValidaThor is composed of 4 modules:
Contains base classes and configuration
implements validathor-base using Breadth-first search algorithm
implements validathor-base using Depth-first search algorithm
Contains some Validathors ready to use for:
- Object
- String
- List
- Map
There are three types of Validathor:
- Validathor for simple type, will extends io.github.paulmarcelinbejan.toolbox.validathor.Validathor
Example:
import io.github.paulmarcelinbejan.toolbox.validathor.Validathor;
public class StringValidathor extends Validathor<String> {
// The constructor must call the super constructor passing the class of the type we want to validate
public StringValidathor() {
super(String.class);
}
// The isValid method must be implemented choosing how we will want to validate that type of object.
@Override
public boolean isValid(String toValidate) {
return toValidate != null && !toValidate.isBlank();
}
}
- Validathor for parameterized type, will extends io.github.paulmarcelinbejan.toolbox.validathor.ValidathorParameterizedType
ValidathorParameterizedType extends Validathor
Example:
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
@SuppressWarnings("rawtypes")
public class MapValidathor extends ValidathorParameterizedType<Map> {
// The constructor must call the super constructor passing the class of the type we want to validate
// and a boolean indicating if elements must be validated
public MapValidathor(boolean toValidateParameterizedTypeElements) {
super(Map.class, toValidateParameterizedTypeElements);
}
// The isValid method must be implemented choosing how we will want to validate that type of object.
@Override
public boolean isValid(Map toValidate) {
return toValidate != null && !toValidate.isEmpty();
}
// parameterizedTypeElementsToValidate return a Function that will return a Collection of elements to be validated
// for Map we can decide for example if we want to validate only keys, only values, or both.
@Override
public Function<Map, Collection<?>> parameterizedTypeElementsToValidate() {
return (map) -> map.values();
}
}
- A default Validathor for all the objects that doesn't have a specific Validathor, will extends io.github.paulmarcelinbejan.toolbox.validathor.AbstractObjectValidathor
Example:
import io.github.paulmarcelinbejan.toolbox.validathor.AbstractObjectValidathor;
/**
* A concrete implementation of ObjectValidathor
* object to validate is valid if not null
* it will lookup into inner fields
*/
public class ObjectValidathor extends AbstractObjectValidathor {
public ObjectValidathor() {
super(true);
}
public ObjectValidathorImpl(boolean validateInnerFields) {
super(validateInnerFields);
}
@Override
public boolean isValid(Object toValidate) {
return toValidate != null;
}
}
The class ValidathorRegistry is responsible for holding all the configuration of the validation flow. The easyest way to create a ValidathorRegistry is using the builder pattern with Fluid API.
Let's see an example:
ValidathorRegistry validathorRegistry = ValidathorRegistry.builder()
.registerObjectValidathor(new ObjectValidathor())
.registerValidathor(new StringValidathor()) // register single validathor, it can be called multiple times
.registerValidathors(List.of(new StringValidathor(), new PersonValidathor())) // register multiple validathors
.registerValidathorParameterizedType(new ListValidathor()) // register single validathor parameterized type, it can be called multiple times
.registerValidathorsParameterizedType(List.of(new ListValidathor(), new MapValidathor())) // register multiple validathors of parameterized type
.useCompatibleValidathorIfSpecificNotPresent(false) // if true, an object can be validated by a compatible Validathor if the specific is not registered.
.registerSkipBeforeValidationProcessor(skipBeforeValidationProcessor)
.registerSkipAfterValidationProcessor(skipAfterValidationProcessor)
.build();
Let me provide an example of why 'useCompatibleValidathorIfSpecificNotPresent' is useful. Suppose you have a field of type 'ArrayList', and you haven't registered a 'ValidathorParameterizedType' for that type. However, you have registered a 'ValidathorParameterizedType' for a compatible class/interface, like 'Collection'. If 'useCompatibleValidathorIfSpecificNotPresent' is set to true, the 'CollectionValidathor' can be used to validate the 'ArrayList'.
It's also possible to configure two 'SkipProcessor' instances:
- SkipBeforeValidationProcessor.
- SkipAfterValidationProcessor.
Both have two sets as fields: a set of classes and a set of strings (package names). Before validating an object, if the type of this object is present in the set of classes of 'SkipBeforeValidationProcessor', or the class is in a package that starts with one of the values in the set of strings, then it will be skipped, and no validation will be executed on this object. However, if the type of object is present in the set of classes of 'SkipAfterValidationProcessor', or the class is in a package that starts with one of the values in the set of strings, then it will be validated, but it will not look up internally to validate the fields.
This is the reason why 'SkipAfterValidationProcessor' contains, by default, the "java" package.
You need to choose between BFS and DFS algorithms, then create an instance accordingly. The constructor parameters are the validathorRegistry and a boolean indicating if you want to collect all the errors, or if you want to trigger the exception as soon as one occurs.
BFS:
ValidathorBFS validathorBFS = new ValidathorBFS(validathorRegistry, false);
validathorBFS.validate(anObjectToValidate)
DFS
ValidathorDFS validathorDFS = new ValidathorDFS(validathorRegistry, false);
validathorDFS.validate(anObjectToValidate)