All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.leeonky.dal.util.SchemaVerifier Maven / Gradle / Ivy

There is a newer version: 0.7.4
Show newest version
package com.github.leeonky.dal.util;

import com.github.leeonky.dal.RuntimeContext;
import com.github.leeonky.dal.format.Formatter;
import com.github.leeonky.dal.format.Type;
import com.github.leeonky.dal.type.AllowNull;
import com.github.leeonky.dal.type.SubType;
import com.github.leeonky.util.BeanClass;
import com.github.leeonky.util.GenericType;
import com.github.leeonky.util.PropertyReader;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.github.leeonky.util.BeanClass.getClassName;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.stream.IntStream.range;
import static java.util.stream.StreamSupport.stream;

public class SchemaVerifier {
    private final WrappedObject object;
    private final RuntimeContext runtimeContext;

    public SchemaVerifier(RuntimeContext runtimeContext, WrappedObject object) {
        this.runtimeContext = runtimeContext;
        this.object = object;
    }

    @SuppressWarnings("unchecked")
    private  BeanClass getPolymorphicSchemaType(Class superSchemaType) {
        Class type = superSchemaType;
        SubType subType = superSchemaType.getAnnotation(SubType.class);
        if (subType != null) {
            Object value = object.getPropertyValue(subType.property());
            type = Stream.of(subType.types())
                    .filter(t -> t.value().equals(value))
                    .map(SubType.Type::type)
                    .findFirst().orElseThrow(() -> new IllegalStateException(format("Cannot guess sub type through property type value[%s]", value)));
        }
        return (BeanClass) BeanClass.create(type);
    }

    public boolean verify(Class clazz, Object schemaInstance, String subPrefix) {
        Set propertyReaderNames = object.getPropertyReaderNames();
        BeanClass schema = getPolymorphicSchemaType(clazz);
        return noMoreUnexpectedField(schema, schema.getPropertyReaders().keySet(), propertyReaderNames)
                && allMandatoryPropertyShouldBeExist(schema, propertyReaderNames)
                && allPropertyValueShouldBeValid(subPrefix, schema, schemaInstance == null ? schema.newInstance() : schemaInstance);
    }

    private boolean noMoreUnexpectedField(BeanClass polymorphicBeanClass, Set expectedFields, Set actualFields) {
        return actualFields.stream()
                .allMatch(f -> shouldNotContainsUnexpectedField(polymorphicBeanClass, expectedFields, f));
    }

    private  boolean allMandatoryPropertyShouldBeExist(BeanClass polymorphicBeanClass, Set actualFields) {
        return polymorphicBeanClass.getPropertyReaders().values().stream()
                .filter(propertyReader -> propertyReader.getAnnotation(AllowNull.class) == null)
                .allMatch(propertyReader -> shouldContainsField(actualFields, polymorphicBeanClass, propertyReader));
    }

    private  boolean allPropertyValueShouldBeValid(String subPrefix, BeanClass polymorphicBeanClass, T schemaInstance) {
        return polymorphicBeanClass.getPropertyReaders().values().stream()
                .allMatch(propertyReader -> {
                    WrappedObject wrappedPropertyValue = object.getWrappedPropertyValue(propertyReader.getName());
                    return allowNullAndIsNull(propertyReader, wrappedPropertyValue)
                            || wrappedPropertyValue.createSchemaVerifier().verifySchemaInGenericType(subPrefix + "." + propertyReader.getName(),
                            propertyReader.getGenericType(), propertyReader.getValue(schemaInstance));
                });
    }

    private  boolean allowNullAndIsNull(PropertyReader propertyReader, WrappedObject propertyValueWrapper) {
        return propertyReader.getAnnotation(AllowNull.class) != null && propertyValueWrapper.isNull();
    }

    private boolean shouldNotContainsUnexpectedField(BeanClass polymorphicBeanClass, Set expectedFields, String f) {
        return expectedFields.contains(f)
                || errorLog("Unexpected field `%s` for type %s[%s]\n", f, polymorphicBeanClass.getSimpleName(), polymorphicBeanClass.getName());
    }

    private  boolean shouldContainsField(Set actualFields, BeanClass polymorphicBeanClass, PropertyReader propertyReader) {
        return actualFields.contains(propertyReader.getName())
                || errorLog("Expected field `%s` for type %s[%s], but does not exist\n", propertyReader.getName(),
                polymorphicBeanClass.getSimpleName(), polymorphicBeanClass.getName());
    }

    private boolean errorLog(String format, Object... params) {
        System.err.printf(format, params);
        return false;
    }

    @SuppressWarnings("unchecked")
    private  boolean verifySchemaInGenericType(String subPrefix, GenericType genericType, Object schemaProperty) {
        Class fieldType = genericType.getRawType();
        if (Formatter.class.isAssignableFrom(fieldType)) {
            return verifyFormatterValue(subPrefix, getOrCreateFormatter(schemaProperty, genericType));
        } else if (runtimeContext.isRegistered(fieldType))
            return object.createSchemaVerifier().verify(fieldType, schemaProperty, subPrefix);
        else if (Iterable.class.isAssignableFrom(fieldType))
            return verifyList(subPrefix, genericType, (Iterable) schemaProperty);
        else if (Map.class.isAssignableFrom(fieldType))
            return verifyMap(subPrefix, genericType, (Map) schemaProperty);
        else if (fieldType.isArray())
            return verifyArray(subPrefix, fieldType.getComponentType(), (Object[]) schemaProperty);
        else if (Type.class.isAssignableFrom(fieldType))
            return verifyWrappedType(subPrefix, (Type) schemaProperty, genericType);
        else
            return verifyType(subPrefix, schemaProperty, fieldType);
    }

    private boolean verifyWrappedType(String subPrefix, Type schemaProperty, GenericType genericType) {
        if (schemaProperty != null)
            return schemaProperty.verify(object.getInstance())
                    || errorLog("Field `%s` is invalid\n", subPrefix);
        Class rawType = genericType.getGenericTypeParameter(0)
                .orElseThrow(() -> new IllegalStateException(format("%s should specify generic type", subPrefix))).getRawType();
        return rawType.isInstance(object.getInstance())
                || errorLog("Expected field `%s` for type [%s], but was [%s]\n", subPrefix,
                rawType.getName(), getClassName(object.getInstance()));
    }

    private boolean verifyType(String subPrefix, Object schemaProperty, Class fieldType) {
        if (schemaProperty != null)
            return Objects.equals(schemaProperty, object.getInstance())
                    || errorLog("Expected field `%s` equal to %s[%s], but was %s[%s]\n", subPrefix,
                    getClassName(schemaProperty), schemaProperty, getClassName(object.getInstance()), object.getInstance());
        return fieldType.isInstance(object.getInstance())
                || errorLog("Expected field `%s` for type [%s], but was [%s]\n", subPrefix,
                fieldType.getName(), getClassName(object.getInstance()));
    }

    private boolean verifyArray(String subPrefix, Class elementType, Object[] schemaProperty) {
        List wrappedObjectList = stream(object.getWrappedList().spliterator(), false)
                .collect(Collectors.toList());
        if (schemaProperty == null)
            return range(0, wrappedObjectList.size())
                    .allMatch(i -> wrappedObjectList.get(i).createSchemaVerifier().verifySchemaInGenericType(
                            format("%s[%d]", subPrefix, i), GenericType.createGenericType(elementType), null));
        else {
            List schemaPropertyList = asList(schemaProperty);
            return shouldBeSameSize(subPrefix, wrappedObjectList, schemaPropertyList)
                    && range(0, wrappedObjectList.size())
                    .allMatch(i -> wrappedObjectList.get(i).createSchemaVerifier().verifySchemaInGenericType(
                            format("%s[%d]", subPrefix, i), GenericType.createGenericType(elementType), schemaPropertyList.get(i)));
        }
    }

    @SuppressWarnings("unchecked")
    private Formatter getOrCreateFormatter(Object schemaProperty, GenericType genericType) {
        if (schemaProperty != null)
            return (Formatter) schemaProperty;
        Class fieldType = (Class) genericType.getRawType();
        return (Formatter) genericType.getGenericTypeParameter(0)
                .map(t -> BeanClass.newInstance(fieldType, t.getRawType()))
                .orElseGet(() -> BeanClass.newInstance(fieldType));
    }

    private boolean verifyList(String subPrefix, GenericType genericType, Iterable schemaProperties) {
        GenericType subGenericType = genericType.getGenericTypeParameter(0).orElseThrow(() ->
                new IllegalArgumentException(subPrefix + " should be generic type"));
        List wrappedObjectList = stream(object.getWrappedList().spliterator(), false)
                .collect(Collectors.toList());

        if (schemaProperties == null)
            return range(0, wrappedObjectList.size())
                    .allMatch(i -> wrappedObjectList.get(i).createSchemaVerifier().verifySchemaInGenericType(format("%s[%d]", subPrefix, i), subGenericType, null));
        else {
            List schemaPropertyList = stream(schemaProperties.spliterator(), false)
                    .collect(Collectors.toList());
            return shouldBeSameSize(subPrefix, wrappedObjectList, schemaPropertyList)
                    && range(0, wrappedObjectList.size())
                    .allMatch(i -> wrappedObjectList.get(i).createSchemaVerifier().verifySchemaInGenericType(format("%s[%d]", subPrefix, i), subGenericType, schemaPropertyList.get(i)));
        }
    }

    private boolean verifyMap(String subPrefix, GenericType genericType, Map schemaProperty) {
        GenericType subGenericType = genericType.getGenericTypeParameter(1).orElseThrow(() ->
                new IllegalArgumentException(format("`%s` should be generic type", subPrefix)));
        if (schemaProperty == null)
            return object.getPropertyReaderNames().stream()
                    .allMatch(key -> object.getWrappedPropertyValue(key).createSchemaVerifier().verifySchemaInGenericType(subPrefix + "." + key, subGenericType, null));
        return shouldBeSameSize(subPrefix, object.getPropertyReaderNames(), schemaProperty.values())
                && object.getPropertyReaderNames().stream()
                .allMatch(key -> object.getWrappedPropertyValue(key).createSchemaVerifier().verifySchemaInGenericType(subPrefix + "." + key, subGenericType, schemaProperty.get(key)));
    }

    private boolean shouldBeSameSize(String subPrefix, Collection wrappedObjectList, Collection schemaPropertyList) {
        return wrappedObjectList.size() == schemaPropertyList.size()
                || errorLog("Expected field `%s` should be size [%d], but was size [%d]\n", subPrefix, schemaPropertyList.size(), wrappedObjectList.size());
    }

    private boolean verifyFormatterValue(String subPrefix, Formatter formatter) {
        return formatter.isValid(object.getInstance())
                || errorLog("Expected field `%s` should be in `%s`, but was [%s]\n", subPrefix, formatter.getFormatterName(), object.getInstance());
    }
}