cz.jalasoft.lifeconfig.validation.ConfigInterfaceValidator Maven / Gradle / Ivy
package cz.jalasoft.lifeconfig.validation;
import cz.jalasoft.lifeconfig.annotation.Converter;
import cz.jalasoft.lifeconfig.annotation.IgnoreProperty;
import cz.jalasoft.lifeconfig.annotation.Key;
import cz.jalasoft.lifeconfig.annotation.KeyPrefix;
import cz.jalasoft.lifeconfig.converter.ConverterRepository;
import cz.jalasoft.lifeconfig.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
/**
* A validator of a configuration interface and all other interfaces
* as return types of methods.
*
*
* - Provided configuration type must be an interface
* - If an annotation {@link KeyPrefix} is present, then its value must not be null or empty
* - each method defined on the interface must have no parameter
* - each method that has the annotation {@link Key} must have not null or empty value
* - each method that has an annotation {@link Converter} the its value - class - must have constructor with no arguments
*
*
* @author Honza Lastovicka ([email protected])
* @since 2016-07-26.
*/
public final class ConfigInterfaceValidator {
private final ConverterRepository converterRegistry;
public ConfigInterfaceValidator(ConverterRepository converterRegistry) {
this.converterRegistry = converterRegistry;
}
public void validate(Class> type) {
validateTypeIsInterface(type);
validateKeyPrefixIsNotEmptyIfPresent(type);
validateDeclaredMethods(type);
returnTypeInterfacesForRecursiveValidation(type).forEach(this::validate);
}
private void validateTypeIsInterface(Class> type) {
if (!type.isInterface()) {
throw new ConfigInterfaceValidationException("Type %s is not an interface.", type);
}
}
private void validateKeyPrefixIsNotEmptyIfPresent(Class> type) {
if (!type.isAnnotationPresent(KeyPrefix.class)) {
return;
}
KeyPrefix prefixAnnotation = type.getAnnotation(KeyPrefix.class);
String prefix = prefixAnnotation.value();
if (prefix.isEmpty()) {
throw new ConfigInterfaceValidationException("Key prefix on type %s must not be empty.", type);
}
}
private void validateDeclaredMethods(Class> type) {
Method[] declaredMethods = type.getDeclaredMethods();
stream(declaredMethods)
.filter(m -> !m.isAnnotationPresent(IgnoreProperty.class))
.forEach(this::validateDeclaredMethod);
}
private void validateDeclaredMethod(Method method) {
validateZeroArguments(method);
validateKeyAnnotationNotEmptyIfPresent(method);
validateConverterHasParameterlessConstructorIfPresent(method);
}
private void validateZeroArguments(Method method) {
int paramsCount = method.getParameterCount();
if (paramsCount == 0) {
return;
}
if (paramsCount > 0) {
throw new ConfigInterfaceValidationException("Number of arguments on %s is %d. Must be zero.", method, paramsCount);
}
}
private void validateKeyAnnotationNotEmptyIfPresent(Method method) {
if (!method.isAnnotationPresent(Key.class)) {
return;
}
Key annotation = method.getAnnotation(Key.class);
String key = annotation.value();
if (key.isEmpty()) {
throw new ConfigInterfaceValidationException("Method %s has annotation @Key whose value is an empty string. Provide non empty resolveKey.", method);
}
}
private void validateConverterHasParameterlessConstructorIfPresent(Method method) {
if (!method.isAnnotationPresent(Converter.class)) {
return;
}
Class> converterType = method.getAnnotation(Converter.class).value();
Constructor>[] constructors = converterType.getConstructors();
for (Constructor> constructor : constructors) {
if (constructor.getParameterCount() == 0) {
return;
}
}
throw new ConfigInterfaceValidationException("Converter %s must have parameterless constructor.", converterType);
}
private Collection> returnTypeInterfacesForRecursiveValidation(Class> type) {
Collection> suspiciousReturnTypes = stream(type.getDeclaredMethods())
.map(this::returnTypeForRecursiveValidation)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
return suspiciousReturnTypes;
}
private Optional> returnTypeForRecursiveValidation(Method method) {
Class> returnType = method.getReturnType();
if (ReflectionUtils.isPrimitiveOrWrapper(returnType)) {
return Optional.empty();
}
if (Collection.class.isAssignableFrom(returnType)) {
return Optional.empty();
}
if (method.isAnnotationPresent(Converter.class)) {
return Optional.empty();
}
if (returnType.isInterface()) {
return Optional.of(returnType);
}
return Optional.empty();
}
}