public class ValidationResultsContainer {

    private final Map<String, List<ValidationStatus>> resultsMap;

    public ValidationResultsContainer() {
        resultsMap = new HashMap<>();
    }

    public Map<String, List<ValidationStatus>> getResults() {
        return resultsMap;
    }

    public boolean isValid() {
        return resultsMap.size() == 0;
    }

    public void put(final String key, final List<ValidationStatus> statuses) {
        resultsMap.put(key, statuses);
    }

    public void put(final String key, final ValidationStatus status) {
        resultsMap.put(key, Collections.singletonList(status));
    }

    public Set<String> getFailedFieldNames() {
        return getResults().keySet();
    }

    public Set<ValidationStatus> getFailedStatuses() {

        final Set<ValidationStatus> statuses = new TreeSet<>();

        for (final List<ValidationStatus> list : getResults().values()) {
            statuses.addAll(list);
        }

        return statuses;
    }

} public enum AnnotationCategory {

    UNUSED,
    GENERAL,
    BYTE,
    BOOLEAN,
    INTEGER,
    DOUBLE,
    FLOAT,
    SHORT,
    STRING,
    COLLECTION,
    ARRAY

} public class ValidationResult {

    private final ValidationStatus status;

    public ValidationResult(final ValidationStatus status) {
        this.status = status;
    }

    public ValidationResult(final boolean isValid) {
        if (isValid) {
            this.status = ValidationStatus.SUCCESS;
        } else {
            this.status = ValidationStatus.UNDEFINED_FAILURE;
        }
    }

    /**
     * Static factory
     *
     * @return successful {@link ValidationResult}
     */
    public static ValidationResult success() {
        return new ValidationResult(ValidationStatus.SUCCESS);
    }

    /**
     * Static factory
     *
     * @return failed {@link ValidationResult}
     */
    public static ValidationResult failure() {
        return new ValidationResult(ValidationStatus.UNDEFINED_FAILURE);
    }

    /**
     * Static factory
     *
     * @return null max {@link ValidationResult}
     */
    public static ValidationResult nullValue() {
        return new ValidationResult(ValidationStatus.NULL_VALUE);
    }

    public ValidationStatus getStatus() {
        return this.status;
    }

    public boolean isValid() {
        return this.status == ValidationStatus.SUCCESS;
    }

} public enum ValidationStatus {

    UNDEFINED_FAILURE,
    NULL_VALUE,
    EMPTY_COLLECTION,
    COLLECTION_CONTAINS_NULL,
    PATTERN_DID_NOT_MATCH,
    EMPTY_STRING,
    INVALID_VALUE,
    WRONG_TYPE_ARGUMENTS,
    VALUE_OUT_OF_RANGE,
    NUMBER_OUT_OF_RANGE,
    INTEGER_OUT_OF_RANGE,
    BYTE_OUT_OF_RANGE,
    DOUBLE_OUT_OF_RANGE,
    FLOAT_OUT_OF_RANGE,
    EXCEPTION,
    SUCCESS

} public final class AnnotationParser {

    /*package*/
    static Annotation getAnnotation(final Object obj, final Class annotationType) throws ReflectiveOperationException {

        final List<Field> fields = FieldParser.getAnnotatedFields(obj, SearchPolicy.DEEP);

        for (final Field f : fields) {
            if (f.getAnnotations().length != 0 && f.getAnnotation(annotationType) != null) {
                return f.getAnnotation(annotationType);
            }
        }
        return null;
    }

    public static AnnotationCategory getCategoryOfAnnotation(final Annotation annotation) {

        final Category category = annotation.annotationType().getAnnotation(Category.class);

        if (category != null) {
            return category.type();
        } else {
            return AnnotationCategory.UNUSED;
        }

    }

} public class FieldParser {

    private static final Set<Class<?>> WRAPPER_TYPES = getWrapperTypes();
    private final Set<Object> visitedFields;

    public FieldParser() {
        this.visitedFields = new HashSet<>();
    }

    public static boolean isWrapperType(final Class<?> clazz) {
        return WRAPPER_TYPES.contains(clazz);
    }

    private static Set<Class<?>> getWrapperTypes() {
        final Set<Class<?>> primitiveWrappers = new HashSet<>();
        primitiveWrappers.add(Boolean.class);
        primitiveWrappers.add(Character.class);
        primitiveWrappers.add(Byte.class);
        primitiveWrappers.add(Short.class);
        primitiveWrappers.add(Integer.class);
        primitiveWrappers.add(Long.class);
        primitiveWrappers.add(Float.class);
        primitiveWrappers.add(String.class);
        primitiveWrappers.add(Number.class);
        primitiveWrappers.add(BigInteger.class);
        primitiveWrappers.add(BigDecimal.class);
        primitiveWrappers.add(String.class);
        primitiveWrappers.add(Double.class);
        primitiveWrappers.add(Void.class);
        return primitiveWrappers;
    }

    /**
     * Gets all declared {@link Field}s in a class
     *
     * @param obj {@link Object} to be processed
     * @return {@link List} of {@link Field} found
     */
    public <T> List<Field> getDeclaredFields(final T obj, final SearchPolicy searchPolicy) {

        final List<Field> fields = new ArrayList<>();
        getDeclaredFieldsRecursively(fields, obj.getClass());

        if (searchPolicy.equals(SearchPolicy.DEEP)) {

            final List<Field> deepFields = new ArrayList<>();
            for (final Field field : fields) {
                if (!visitedFields.contains(field)) {
                    try {
                        field.setAccessible(true);
                        final Object o1 = field.get(obj);
                        deepFields.addAll(checkObjectIterable(o1));

                    } catch (final IllegalAccessException e) {
                        e.printStackTrace();
                    } finally {
                        field.setAccessible(false);
                    }
                }
            }
            fields.addAll(deepFields);

        }

        visitedFields.clear();
        return fields;
    }

    private List<Field> checkObjectIterable(final Object obj) {

        final List<Field> fields = new ArrayList<>();
        if (obj != null) {
            if (obj instanceof Map) {
                final Map map = (Map) obj;
                for (final Object value : map.values()) {
                    if (!isWrapperType(value.getClass())) {
                        fields.addAll(getDeclaredFields(value, SearchPolicy.DEEP));

                    }
                }
            } else if (obj instanceof Collection) {
                for (final Object value : (Collection) obj) {
                    if (!isWrapperType(value.getClass())) {
                        fields.addAll(getDeclaredFields(value, SearchPolicy.DEEP));
                    }
                }
            } else if (obj.getClass().isArray()) {
                for (final Object value : unpack(obj)) {
                    if (!isWrapperType(value.getClass())) {
                        fields.addAll(getDeclaredFields(value, SearchPolicy.DEEP));
                    }
                }
            } else {
                fields.addAll(Arrays.asList(obj.getClass().getFields()));
            }
        }
        visitedFields.addAll(fields);
        return fields;
    }

    public static Object[] unpack(final Object array) {
        final Object[] array2 = new Object[Array.getLength(array)];
        for (int i = 0; i < array2.length; i++) {
            array2[i] = Array.get(array, i);
        }
        return array2;
    }

    /**
     * Recursive method for getting all fields, including superclass ones
     *
     * @param fields {@link List} of {@link Field} that will be modified recursively if a superclass exists
     * @param type   {@link Class} of the object to be processed
     * @return {@link List} of {@link Field} found
     */
    public List<Field> getDeclaredFieldsRecursively(List<Field> fields,
                                                    final Class<?> type) {

        final List<Field> declaredFields = Arrays.asList(type.getDeclaredFields());

        for (final Field field : declaredFields) {
            if (visitedFields.contains(field)) {
                System.out.println("Found cyclic reference for field " + field.getName() + " in class " + field.getDeclaringClass());
            } else {
                fields.add(field);
            }
        }

        if (type.getSuperclass() != null) {
            fields = getDeclaredFieldsRecursively(fields, type.getSuperclass());
        }

        return fields;
    }

    public static List<Field> getAnnotatedFields(final Object root,
                                                 final SearchPolicy searchPolicy) throws ReflectiveOperationException {
        return getAnnotatedFields(root, searchPolicy, new HashSet<>());
    }

    private static List<Field> getAnnotatedFields(final Object root,
                                                  final SearchPolicy searchPolicy,
                                                  final Set<Object> inspected) throws ReflectiveOperationException {



        final List<Field> annotatedValues = new ArrayList<>();
        if (inspected.contains(root)) { // Prevents stack overflow.
            System.out.println("*** Breaking for already inspected");
            return Collections.emptyList();
        }
        if (isWrapperType(root.getClass())) { // Prevents stack overflow.

            System.out.println("*** Breaking for wrapper class");
            return Collections.emptyList();
        }

        for (final Field field : gatherFields(root.getClass())) {

            field.setAccessible(true);
            final Object currentValue = field.get(root);
            field.setAccessible(false);
            if (field.getAnnotations().length != 0) {
                // Found required value, search finished:
                for (final Annotation annotation : field.getAnnotations()) {
                    if (SupportedAnnotationContainer.getSupportedClasses().contains(annotation.annotationType())) {
                        annotatedValues.add(field);
                        break;
                    }
                }
                if (currentValue != null) {
                    inspected.add(currentValue);

                    if (searchPolicy == SearchPolicy.DEEP && !isWrapperType(currentValue.getClass())) {
                        // go deep if current object is iterable
                        if (currentValue instanceof Map) {
                            final Map map = (Map) currentValue;
                            for (final Object value : map.values()) {
                                if (!isWrapperType(value.getClass())) {
                                    annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                                }
                            }
                        } else if (currentValue instanceof Collection) {
                            for (final Object value : (Collection) currentValue) {
                                if (!isWrapperType(value.getClass())) {
                                    annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                                }
                            }
                        } else if (currentValue.getClass().isArray()) {
                            for (final Object value : unpack(currentValue)) {
                                if (!isWrapperType(value.getClass())) {
                                    annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                                }
                            }
                        } else {
                            annotatedValues.addAll(getAnnotatedFields(currentValue, searchPolicy, inspected));
                        }
                    }

                }

            } else if (currentValue != null) {

                // Searching for annotated fields in nested classes:
                if (searchPolicy == SearchPolicy.DEEP && !isWrapperType(currentValue.getClass())) {
                    // go deep if current object is iterable
                    if (currentValue instanceof Map) {
                        final Map map = (Map) currentValue;
                        for (final Object value : map.values()) {
                            if (!isWrapperType(value.getClass())) {
                                annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                            }
                        }
                    } else if (currentValue instanceof Collection) {
                        for (final Object value : (Collection) currentValue) {
                            if (!isWrapperType(value.getClass())) {
                                annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                            }
                        }
                    } else if (currentValue.getClass().isArray()) {
                        for (final Object value : unpack(currentValue)) {
                            if (!isWrapperType(value.getClass())) {
                                annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                            }
                        }
                    } else {
                        annotatedValues.addAll(getAnnotatedFields(currentValue, searchPolicy, inspected));
                    }
                    inspected.add(currentValue);
                }

            }
        }
        return annotatedValues;
    }

    private static Iterable<Field> gatherFields(Class<?> fromClass) {
        // Finds ALL fields, even the ones from super classes.
        final List<Field> fields = new ArrayList<>();
        while (fromClass != null) {
            fields.addAll(Arrays.asList(fromClass.getDeclaredFields()));

           /* // go deep if current object is iterable
            if (currentValue instanceof Map) {
                final Map map = (Map) currentValue;
                for (final Object value : map.values()) {
                    if (!isWrapperType(value.getClass())) {
                        annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                    }
                }
            } else if (currentValue instanceof Collection) {
                for (final Object value : (Collection) currentValue) {
                    if (!isWrapperType(value.getClass())) {
                        annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                    }
                }
            } else if (currentValue.getClass().isArray()) {
                for (final Object value : unpack(currentValue)) {
                    if (!isWrapperType(value.getClass())) {
                        annotatedValues.addAll(getAnnotatedFields(value, searchPolicy, inspected));
                    }
                }
            }*/



            fromClass = fromClass.getSuperclass();
        }
        return fields;
    }


} public interface Validator<T> {

    ValidationResult validate(T item, Annotation annotation);

} public class ArrayValidator implements Validator<Class<?>[]> {

    @Override
    public ValidationResult validate(final Class<?>[] array, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(ArrayIsNotEmpty.class)) {
            return handleIsNotEmpty(array);
        } else if (type.equals(ArrayContainsNoNulls.class)) {
            return handleContainsNoNulls(array);
        } else if (type.equals(ArrayContainsValue.class)) {
            return handleContainsValue(array, ((ArrayContainsValue) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleContainsValue(final Class<?>[] array,
                                                        final Class<?> value) {

        if (array == null || value == null) {
            return ValidationResult.nullValue();
        } else if (array.length == 0) {
            return new ValidationResult(ValidationStatus.EMPTY_COLLECTION);
        } else {
            final Class<?> genericClass = array[0].getClass();
            if (genericClass != null) {
                // This is considered a failure if the two classes do not match
                if (value.getClass().equals(genericClass)) {

                    for (final Class<?> item : array) {
                        if (value.equals(item)) {
                            return ValidationResult.success();
                        }
                    }
                    return new ValidationResult(ValidationStatus.INVALID_VALUE);
                } else {
                    return new ValidationResult(ValidationStatus.WRONG_TYPE_ARGUMENTS);
                }

            } else {
                return new ValidationResult(ValidationStatus.EXCEPTION);
            }
        }
    }

    private static ValidationResult handleContainsNoNulls(final Class<?>[] array) {

        if (array == null) {
            return ValidationResult.nullValue();
        } else {
            for (final Object item : array) {
                if (item == null) {
                    return new ValidationResult(ValidationStatus.COLLECTION_CONTAINS_NULL);
                }
            }
            return ValidationResult.success();
        }

    }

    private static ValidationResult handleIsNotEmpty(final Class<?>[] array) {

        if (array == null) {
            return ValidationResult.nullValue();
        } else if (array.length == 0) {
            return new ValidationResult(ValidationStatus.EMPTY_COLLECTION);
        } else {
            return ValidationResult.success();
        }

    }
} public class CollectionValidator implements Validator<Collection<?>> {

    @Override
    public ValidationResult validate(final Collection<?> collection, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(CollectionIsNotEmpty.class)) {
            return handleIsNotEmpty(collection);
        } else if (type.equals(CollectionContainsNoNulls.class)) {
            return handleContainsNoNulls(collection);
        } else if (type.equals(CollectionContainsValue.class)) {
            return handleContainsValue(collection, ((CollectionContainsValue) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleContainsValue(final Collection<?> collection,
                                                        final Class<?> value) {

        final ValidationResult isNotEmpty = handleIsNotEmpty(collection);

        if (isNotEmpty.isValid()) {

            if (value == null) {
                return ValidationResult.nullValue();
            }

            // get the class of the first item
            Class genericClass = null;
            final Iterator it = collection.iterator();
            if (it.hasNext()) {
                genericClass = it.next().getClass();
            }

            if (genericClass != null) {
                // This is considered a failure if the two classes do not match
                if (value.getClass().equals(genericClass)) {
                    if (collection.contains(value)) {
                        return ValidationResult.success();
                    } else {
                        return new ValidationResult(ValidationStatus.INVALID_VALUE);
                    }
                } else {
                    return new ValidationResult(ValidationStatus.WRONG_TYPE_ARGUMENTS);
                }
            } else {
                return new ValidationResult(ValidationStatus.EXCEPTION);
            }
        } else {
            return isNotEmpty;
        }
    }

    private static ValidationResult handleContainsNoNulls(final Collection<?> collection) {

        if (collection == null) {
            return ValidationResult.nullValue();
        } else {

            final Iterator<?> iterator = collection.iterator();
            //noinspection WhileLoopReplaceableByForEach
            while (iterator.hasNext()) {
                if (iterator.next() == null) {
                    return new ValidationResult(ValidationStatus.COLLECTION_CONTAINS_NULL);
                }
            }

            return ValidationResult.success();
        }

    }

    private static ValidationResult handleIsNotEmpty(final Collection<?> collection) {

        if (collection == null) {
            return ValidationResult.nullValue();
        } else if (collection.isEmpty()) {
            return new ValidationResult(ValidationStatus.EMPTY_COLLECTION);
        } else {
            return ValidationResult.success();
        }

    }

} public class BooleanValidator implements Validator<Boolean> {

    @Override
    public ValidationResult validate(final Boolean value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(BooleanEqualsValue.class)) {
            return handleEqualsValue(value, ((BooleanEqualsValue) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleEqualsValue(final Boolean bool, final boolean equalsValue) {
        if (bool == null) {
            return ValidationResult.nullValue();
        } else if (bool.equals(equalsValue)) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

} public class ObjectValidator implements Validator<Object> {

    @Override
    public ValidationResult validate(final Object item, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(NotNull.class)) {
            return handleNotNull(item);
        } else if (type.equals(IsNull.class)) {
            return handleIsNull(item);
        } else {
            return ValidationResult.failure();
        }


    }

    private static ValidationResult handleIsNull(final Object item) {
        if (item == null) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INVALID_VALUE);
        }
    }

    private static ValidationResult handleNotNull(final Object item) {
        if (item == null) {
            return ValidationResult.nullValue();
        } else {
            return ValidationResult.success();
        }
    }

} public class ByteValidator implements Validator<Byte> {

    @Override
    public ValidationResult validate(final Byte value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(ByteEqualsValue.class)) {
            return handleEqualsValue(value, ((ByteEqualsValue) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleEqualsValue(final Byte value, final byte equalsValue) {
        if (value == null) {
            return ValidationResult.nullValue();
        } else if (value.equals(equalsValue)) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.BYTE_OUT_OF_RANGE);
        }
    }


} public class DoubleValidator implements Validator<Double> {

    @Override
    public ValidationResult validate(final Double value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(DoubleMaxValue.class)) {
            final DoubleMaxValue clazz = ((DoubleMaxValue) annotation);
            return handleMaxValue(value, clazz.max(), clazz.epsilon());
        } else if (type.equals(DoubleMinValue.class)) {
            final DoubleMinValue clazz = ((DoubleMinValue) annotation);
            return handleMinValue(value, clazz.min(), clazz.epsilon());
        } else if (type.equals(DoubleEqualsValue.class)) {
            final DoubleEqualsValue clazz = ((DoubleEqualsValue) annotation);
            return handleEqualsValue(value, clazz.value(), clazz.epsilon());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleEqualsValue(final Double number,
                                                      final double equalsValue,
                                                      final double epsilon) {

        if (number == null) {
            return ValidationResult.nullValue();
        } else if (Math.abs(number - equalsValue) <= epsilon) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.DOUBLE_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMaxValue(final Double number,
                                                   final double maxValue,
                                                   final double epsilon) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (Math.abs(number - maxValue) <= epsilon || number <= maxValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.DOUBLE_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMinValue(final Double number,
                                                   final double minValue,
                                                   final double epsilon) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (Math.abs(number - minValue) <= epsilon || number >= minValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.DOUBLE_OUT_OF_RANGE);
        }
    }

} public class FloatValidator implements Validator<Float> {

    @Override
    public ValidationResult validate(final Float value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(FloatMaxValue.class)) {

            final FloatMaxValue clazz = ((FloatMaxValue) annotation);
            return handleMaxValue(value, clazz.max(), clazz.epsilon());

        } else if (type.equals(FloatMinValue.class)) {

            final FloatMinValue clazz = ((FloatMinValue) annotation);
            return handleMinValue(value, clazz.min(), clazz.epsilon());

        } else if (type.equals(FloatEqualsValue.class)) {

            final FloatEqualsValue clazz = ((FloatEqualsValue) annotation);
            return handleEqualsValue(value, clazz.value(), clazz.epsilon());

        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleEqualsValue(final Float number,
                                                      final float equalsValue,
                                                      final double epsilon) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (Math.abs(number - equalsValue) <= epsilon) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.FLOAT_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMaxValue(final Float number,
                                                   final float maxValue,
                                                   final double epsilon) {

        if (number == null) {
            return ValidationResult.nullValue();
        } else if (Math.abs(number - maxValue) <= epsilon || number <= maxValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.FLOAT_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMinValue(final Float number,
                                                   final float minValue,
                                                   final double epsilon) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (Math.abs(number - minValue) <= epsilon || number >= minValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.FLOAT_OUT_OF_RANGE);
        }
    }

} public class IntegerValidator implements Validator<Integer> {

    @Override
    public ValidationResult validate(final Integer value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(IntegerMaxValue.class)) {
            return handleMaxValue(value, ((IntegerMaxValue) annotation).value());
        } else if (type.equals(IntegerMinValue.class)) {
            return handleMinValue(value, ((IntegerMinValue) annotation).value());
        } else if (type.equals(IntegerEqualsValue.class)) {
            return handleEqualsValue(value, ((IntegerEqualsValue) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleEqualsValue(final Integer number, final int equalsValue) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (number == equalsValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMaxValue(final Integer number, final int maxValue) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (number <= maxValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMinValue(final Integer number, final int minValue) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (number >= minValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

} public class ShortValidator implements Validator<Short> {

    @Override
    public ValidationResult validate(final Short value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(ShortMaxValue.class)) {
            return handleMaxValue(value, ((ShortMaxValue) annotation).value());
        } else if (type.equals(ShortMinValue.class)) {
            return handleMinValue(value, ((ShortMinValue) annotation).value());
        } else if (type.equals(ShortEqualsValue.class)) {
            return handleEqualsValue(value, ((ShortEqualsValue) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleEqualsValue(final Short number, final short equalsValue) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (number == equalsValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMaxValue(final Short number, final short maxValue) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (number <= maxValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMinValue(final Short number, final short minValue) {
        if (number == null) {
            return ValidationResult.nullValue();
        } else if (number >= minValue) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INTEGER_OUT_OF_RANGE);
        }
    }

} public class StringValidator implements Validator<String> {

    @Override
    public ValidationResult validate(final String value, final Annotation annotation) {

        final Class<? extends Annotation> type = annotation.annotationType();

        if (type.equals(TextNotNullOrEmpty.class)) {
            return handleNotNullOrEmpty(value);
        } else if (type.equals(TextEmail.class)) {
            return handleEmail(value);
        } else if (type.equals(TextUrl.class)) {
            return handleUrl(value);
        } else if (type.equals(TextMatchesExpression.class)) {
            return handleMatchesExpression(value, ((TextMatchesExpression) annotation).expression());
        } else if (type.equals(TextExactLength.class)) {
            return handleExactLength(value, ((TextExactLength) annotation).value());
        } else if (type.equals(TextMinLength.class)) {
            return handleMinLength(value, ((TextMinLength) annotation).value());
        } else if (type.equals(TextMaxLength.class)) {
            return handleMaxLength(value, ((TextMaxLength) annotation).value());
        } else if (type.equals(TextIsNumeric.class)) {
            return handleIsNumeric(value);
        } else if (type.equals(TextDateFormat.class)) {
            return handleIsOfDateFormat(value, ((TextDateFormat) annotation).value());
        } else {
            return ValidationResult.failure();
        }

    }

    private static ValidationResult handleMatchesExpression(final String value, final String expression) {

        final ValidationResult notEmptyResult = handleNotNullOrEmpty(value);
        if (notEmptyResult.isValid()) {

            if (Pattern.compile(expression).matcher(value).matches()) {
                return ValidationResult.success();
            } else {
                return new ValidationResult(ValidationStatus.PATTERN_DID_NOT_MATCH);
            }
        } else {
            return notEmptyResult;
        }
    }

    private static ValidationResult handleUrl(final String value) {

        final ValidationResult notEmptyResult = handleNotNullOrEmpty(value);
        if (notEmptyResult.isValid()) {
            if (TextUrlValidationHelper.isUrl(value)) {
                return ValidationResult.success();
            } else {
                return new ValidationResult(ValidationStatus.PATTERN_DID_NOT_MATCH);
            }
        } else {
            return notEmptyResult;
        }
    }

    private static ValidationResult handleIsNumeric(final String value) {
        if (value == null) {
            return ValidationResult.nullValue();
        } else if (value.isEmpty()) {
            return new ValidationResult(ValidationStatus.EMPTY_STRING);
        } else if (TextNumericValidatorHelper.isNumeric(value)) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.INVALID_VALUE);
        }
    }

    private static ValidationResult handleMaxLength(final String value, final int length) {
        if (value == null) {
            return ValidationResult.nullValue();
        } else if (value.length() <= length) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.VALUE_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleMinLength(final String value, final int length) {
        if (value == null) {
            return ValidationResult.nullValue();
        } else if (value.length() >= length) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.VALUE_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleExactLength(final String value, final int length) {
        if (value == null) {
            return ValidationResult.nullValue();
        } else if (value.length() == length) {
            return ValidationResult.success();
        } else {
            return new ValidationResult(ValidationStatus.VALUE_OUT_OF_RANGE);
        }
    }

    private static ValidationResult handleEmail(final String value) {

        final ValidationResult notEmptyResult = handleNotNullOrEmpty(value);
        if (notEmptyResult.isValid()) {
            if (TextEmailValidationHelper.isEmail(value)) {
                return ValidationResult.success();
            } else {
                return new ValidationResult(ValidationStatus.PATTERN_DID_NOT_MATCH);
            }
        } else {
            return notEmptyResult;
        }

    }

    private static ValidationResult handleNotNullOrEmpty(final String value) {

        if (value == null) {
            return ValidationResult.nullValue();
        } else if (value.isEmpty()) {
            return new ValidationResult(ValidationStatus.EMPTY_STRING);
        } else {
            return ValidationResult.success();
        }

    }

    private static ValidationResult handleIsOfDateFormat(final String text, final String format) {

        if (format == null || format.length() == 0) {
            return ValidationResult.failure();
        } else if (text == null) {
            return ValidationResult.failure();
        } else {
            final Date result;
            try {
                result = new SimpleDateFormat(format).parse(text);
                if (result != null) {
                    return ValidationResult.success();
                } else {
                    return ValidationResult.failure();
                }
            } catch (final ParseException e) {
                return ValidationResult.failure();
            }


        }
    }

}
import com.michaelfotiadis.validator.annotated.ValidationResultsContainer;
import com.michaelfotiadis.validator.annotated.annotations.AnnotationCategory;
import com.michaelfotiadis.validator.annotated.model.ValidationResult;
import com.michaelfotiadis.validator.annotated.model.ValidationStatus;
import com.michaelfotiadis.validator.annotated.parser.AnnotationParser;
import com.michaelfotiadis.validator.annotated.parser.FieldParser;
import com.michaelfotiadis.validator.annotated.validators.Validator;
import com.michaelfotiadis.validator.annotated.validators.array.ArrayValidator;
import com.michaelfotiadis.validator.annotated.validators.collection.CollectionValidator;
import com.michaelfotiadis.validator.annotated.validators.conditional.BooleanValidator;
import com.michaelfotiadis.validator.annotated.validators.general.ObjectValidator;
import com.michaelfotiadis.validator.annotated.validators.numeric.ByteValidator;
import com.michaelfotiadis.validator.annotated.validators.numeric.DoubleValidator;
import com.michaelfotiadis.validator.annotated.validators.numeric.FloatValidator;
import com.michaelfotiadis.validator.annotated.validators.numeric.IntegerValidator;
import com.michaelfotiadis.validator.annotated.validators.numeric.ShortValidator;
import com.michaelfotiadis.validator.annotated.validators.text.StringValidator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
package com.michaelfotiadis.validator.annotated.processor;



/**
 *
 */
@SuppressWarnings("WeakerAccess")
public final class AnnotatedValidatorProcessor {

    private static final FailPolicy DEFAULT_FAIL_POLICY = FailPolicy.CONTINUE;
    private static final SearchPolicy DEFAULT_SEARCH_POLICY = SearchPolicy.SHALLOW;

    private final Map<AnnotationCategory, Validator<?>> validatorMap;
    private final FailPolicy failPolicy;
    private final SearchPolicy searchPolicy;

    public AnnotatedValidatorProcessor() {
        this(DEFAULT_SEARCH_POLICY, DEFAULT_FAIL_POLICY);
    }

    public AnnotatedValidatorProcessor(final SearchPolicy searchPolicy, final FailPolicy failPolicy) {
        this.searchPolicy = searchPolicy;
        this.failPolicy = failPolicy;
        this.validatorMap = new HashMap<>();
        initValidators();

    }

    private void initValidators() {
        validatorMap.put(AnnotationCategory.GENERAL, new ObjectValidator());
        validatorMap.put(AnnotationCategory.BYTE, new ByteValidator());
        validatorMap.put(AnnotationCategory.INTEGER, new IntegerValidator());