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

org.sql2o.reflection.PojoMetadata Maven / Gradle / Ivy

There is a newer version: 0.2.6
Show newest version
package org.sql2o.reflection;

import io.github.biezhi.anima.annotation.Column;
import org.sql2o.Sql2oException;
import org.sql2o.tools.AbstractCache;
import org.sql2o.tools.UnderscoreToCamelCase;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Stores metadata for a POJO.
 */
public class PojoMetadata {

    private static final Cache caseSensitiveFalse = new Cache();
    private static final Cache caseSensitiveTrue  = new Cache();

    private final PropertyAndFieldInfo propertyInfo;
    private final Map  columnMappings;
    private final FactoryFacade factoryFacade          = FactoryFacade.getInstance();

    private      boolean caseSensitive;
    private      boolean autoDeriveColumnNames;
    public final boolean throwOnMappingFailure;
    private      Class   clazz;

    public boolean isCaseSensitive() {
        return caseSensitive;
    }

    public boolean isAutoDeriveColumnNames() {
        return autoDeriveColumnNames;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        PojoMetadata that = (PojoMetadata) o;

        return autoDeriveColumnNames == that.autoDeriveColumnNames
                && caseSensitive == that.caseSensitive
                && clazz.equals(that.clazz)
                && columnMappings.equals(that.columnMappings)
                && propertyInfo.equals(that.propertyInfo);

    }

    @Override
    public int hashCode() {
        int result = (caseSensitive ? 1 : 0);
        result = 31 * result + clazz.hashCode();
        return result;
    }

    public PojoMetadata(Class clazz, boolean caseSensitive, boolean autoDeriveColumnNames, Map columnMappings, boolean throwOnMappingError) {
        this.caseSensitive = caseSensitive;
        this.autoDeriveColumnNames = autoDeriveColumnNames;
        this.clazz = clazz;
        this.columnMappings = columnMappings == null ? Collections.emptyMap() : columnMappings;

        this.propertyInfo = getPropertyInfoThroughCache();
        this.throwOnMappingFailure = throwOnMappingError;

    }

    public ObjectConstructor getObjectConstructor() {
        return propertyInfo.objectConstructor;
    }

    private PropertyAndFieldInfo getPropertyInfoThroughCache() {
        return (caseSensitive
                ? caseSensitiveTrue
                : caseSensitiveFalse)
                .get(clazz, this);
    }

    private PropertyAndFieldInfo initializePropertyInfo() {

        HashMap propertyGetters = new HashMap<>();
        HashMap propertySetters = new HashMap<>();
        HashMap  fields          = new HashMap<>();

        Class             theClass          = clazz;
        ObjectConstructor objectConstructor = factoryFacade.newConstructor(theClass);
        do {
            boolean isJpaColumnInClasspath = true;
            for (Field f : theClass.getDeclaredFields()) {
                if (Modifier.isStatic(f.getModifiers())) {
                    continue;
                }
                String propertyName = readAnnotatedColumnName(f, true);
                if (propertyName == null) {
                    propertyName = f.getName();
                }
                propertyName = caseSensitive ? propertyName : propertyName.toLowerCase();

                propertyGetters.put(propertyName, factoryFacade.newGetter(f));
                propertySetters.put(propertyName, factoryFacade.newSetter(f));
                fields.put(propertyName, f);
            }

            // prepare methods. Methods will override fields, if both exists.
            for (Method m : theClass.getDeclaredMethods()) {

                if (m.getName().startsWith("get")) {
                    String propertyName = m.getName().substring(3);
                    if (caseSensitive) {
                        propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                    } else {
                        propertyName = propertyName.toLowerCase();
                    }

                    propertyGetters.put(propertyName, factoryFacade.newGetter(m));
                }

                if (m.getName().startsWith("set") && m.getParameterTypes().length == 1) {
                    String propertyName = readAnnotatedColumnName(m, isJpaColumnInClasspath);
                    if (propertyName == null) {
                        propertyName = m.getName().substring(3);
                    }
                    if (caseSensitive) {
                        propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                    } else {
                        propertyName = propertyName.toLowerCase();
                    }

                    propertySetters.put(propertyName, factoryFacade.newSetter(m));
                }
            }
            theClass = theClass.getSuperclass();
        } while (!theClass.equals(Object.class));

        return new PropertyAndFieldInfo(propertyGetters, propertySetters, objectConstructor);

    }

    public Map getColumnMappings() {
        return columnMappings;
    }

    public Getter getPropertyGetter(String propertyName) {
        Getter getter = getPropertyGetterIfExists(propertyName);
        if (getter != null) {
            return getter;
        } else {
            String errorMsg = "Property with name '" + propertyName + "' not found on class " + this.clazz.toString();
            if (this.caseSensitive) {
                errorMsg += " (You have turned on case sensitive property search. Is this intentional?)";
            }
            throw new Sql2oException(errorMsg);
        }
    }

    public Getter getPropertyGetterIfExists(String propertyName) {

        String name = this.caseSensitive ? propertyName : propertyName.toLowerCase();

        if (this.columnMappings.containsKey(name)) {
            name = this.columnMappings.get(name);
        }

        if (autoDeriveColumnNames) {
            name = UnderscoreToCamelCase.convert(name);
            if (!this.caseSensitive) name = name.toLowerCase();
        }

        return propertyInfo.propertyGetters.get(name);
    }

    public Setter getPropertySetter(String propertyName) {
        Setter setter = getPropertySetterIfExists(propertyName);
        if (setter != null) {
            return setter;
        } else {
            String errorMsg = "Property with name '" + propertyName + "' not found on class " + this.clazz.toString();
            if (this.caseSensitive) {
                errorMsg += " (You have turned on case sensitive property search. Is this intentional?)";
            }
            throw new Sql2oException(errorMsg);
        }
    }

    public Setter getPropertySetterIfExists(String propertyName) {

        String name = this.caseSensitive ? propertyName : propertyName.toLowerCase();

        if (this.columnMappings.containsKey(name)) {
            name = this.columnMappings.get(name);
        }

        if (autoDeriveColumnNames) {
            name = UnderscoreToCamelCase.convert(name);
            if (!this.caseSensitive) name = name.toLowerCase();
        }

        return propertyInfo.propertySetters.get(name);
    }

    public Class getType() {
        return this.clazz;
    }

    public Object getValueOfProperty(String propertyName, Object object) {
        Getter getter = getPropertyGetter(propertyName);

        return getter.getProperty(object);
    }

    /**
     * Try to read the {@link io.github.biezhi.anima.annotation.Column} annotation and return the name of the column.
     * Returns null if no {@link io.github.biezhi.anima.annotation.Column} annotation is present or if the name of the column is empty
     */
    private String readAnnotatedColumnName(AnnotatedElement classMember, boolean isJpaColumnInClasspath) {
        if (isJpaColumnInClasspath) {
            Column columnInformation = classMember.getAnnotation(Column.class);
            if (columnInformation != null && !columnInformation.name().isEmpty()) {
                return columnInformation.name();
            }
        }
        return null;
    }

    private static class Cache extends AbstractCache, PropertyAndFieldInfo, PojoMetadata> {
        @Override
        protected PropertyAndFieldInfo evaluate(Class key, PojoMetadata param) {
            return param.initializePropertyInfo();
        }
    }

    private static class PropertyAndFieldInfo {
        // since this class is private we can just use field access
        // to make HotSpot a little less work for inlining
        final Map propertyGetters;
        final Map propertySetters;
        final ObjectConstructor   objectConstructor;

        private PropertyAndFieldInfo(
                Map propertyGetters, Map propertySetters,
                ObjectConstructor objectConstructor) {

            this.propertyGetters = propertyGetters;
            this.propertySetters = propertySetters;
            this.objectConstructor = objectConstructor;
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy