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

com.poiji.util.ConstructorFieldMapper Maven / Gradle / Ivy

package com.poiji.util;

import com.poiji.exception.PoijiInstantiationException;

import java.beans.ConstructorProperties;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

class ConstructorFieldMapper {

    private static final Map, Object[]> fieldDefaultsMapping = new ConcurrentHashMap<>();
    private static final Map, Object[]> constructorMappings = new ConcurrentHashMap<>();

    static Object[] getConstructorFields(Constructor constructor) {
        return constructorMappings.computeIfAbsent(constructor, ignored -> getConstructorMapping(constructor));
    }

    private static Object[] getConstructorMapping(Constructor constructor) {
        final Object[] constructorMapping = new Object[constructor.getParameterCount()];
        mapFieldsWithConstructorProperties(constructor, constructorMapping);
        findNotMappedFieldsWithExamining(constructor, constructorMapping);
        fillNotMappedParametersWithDefaults(constructor, constructorMapping);
        return constructorMapping;
    }

    /**
     * ConstructorProperties is used by lombok. It allows to map any custom fields easy without examining.
     */
    private static Object[] mapFieldsWithConstructorProperties(Constructor constructor, Object[] constructorMapping) {
        final ConstructorProperties constructorProperties = constructor.getAnnotation(ConstructorProperties.class);
        if (constructorProperties != null) {
            final Field[] fields = constructor.getDeclaringClass().getDeclaredFields();
            final String[] parameterNames = constructorProperties.value();
            final Class[] parameterTypes = constructor.getParameterTypes();
            for (int i = 0; i < parameterNames.length; i++) {
                final String parameterName = parameterNames[i];
                for (Field field : fields) {
                    if (field.getName().equals(parameterName) && field.getType() == parameterTypes[i]) {
                        ReflectUtil.setAccessible(field);
                        constructorMapping[i] = field;
                        break;
                    }
                }
            }
        }
        return constructorMapping;
    }

    /**
     * The only way to find mapping between constructor parameters and instance fields is to pass special value
     * into constructor and look it up in every field in instance.
     * Knowing what parameter was passed and what field was found in we can define one mapping.
     */
    private static Object[] findNotMappedFieldsWithExamining(Constructor constructor, Object[] constructorMapping) {
        final Class entity = constructor.getDeclaringClass();
        final Class[] parameterTypes = constructor.getParameterTypes();
        final Object[] defaultConstructorParameters = fieldDefaultsMapping.computeIfAbsent(constructor, ignored -> getDefaultValues(parameterTypes));
        final Field[] fields = entity.getDeclaredFields();
        for (int i = 0; i < parameterTypes.length; i++) {
            if (constructorMapping[i] == null) {
                final Object[] instanceConstructorParameters = defaultConstructorParameters.clone();
                final Class parameterType = parameterTypes[i];
                final Object parameterToExamine = ImmutableInstanceRegistrar.getEmptyInstance(parameterType);
                instanceConstructorParameters[i] = parameterToExamine;
                try {
                    final Object instance = constructor.newInstance(instanceConstructorParameters);
                    for (Field field : fields) {
                        ReflectUtil.setAccessible(field);
                        try {
                            if (isFieldHasExampleValue(field, instance, parameterToExamine)) {
                                constructorMapping[i] = field;
                                break;
                            }
                        } catch (IllegalAccessException e) {
                            throw new PoijiInstantiationException("Can't get field " + field, e);
                        }
                    }
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                    throw new PoijiInstantiationException("Can't create instance " + entity, e);
                }
            }
        }
        return constructorMapping;
    }

    /**
     * Sometimes constructor can have parameter that not corresponds to any field.
     * We have to fill it anyway to construct instance successfully.
     */
    private static Object[] fillNotMappedParametersWithDefaults(Constructor constructor, Object[] constructorMapping) {
        final Object[] defaultConstructorParameters = fieldDefaultsMapping.computeIfAbsent(constructor, ignored -> getDefaultValues(constructor.getParameterTypes()));
        for (int i = 0; i < constructorMapping.length; i++) {
            if (constructorMapping[i] == null) {
                constructorMapping[i] = defaultConstructorParameters[i];
            }
        }
        return constructorMapping;
    }

    private static boolean isFieldHasExampleValue(Field field, Object instance, Object example) throws IllegalAccessException {
        final Object value = field.get(instance);
        return field.getType().isPrimitive() && example.equals(value) || value == example;
    }

    private static Object[] getDefaultValues(Class[] parameterTypes) {
        final Object[] defaultValues = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            defaultValues[i] = getDefaultValue(parameterTypes[i]);
        }
        return defaultValues;
    }

    private static Object getDefaultValue(Class parameterType) {
        if (parameterType.isPrimitive()) {
            return getDefaultPrimitive(parameterType);
        }
        return null;
    }

    private static Object getDefaultPrimitive(Class parameterType) {
        switch (parameterType.getName()) {
            case "boolean":
                return false;
            case "int":
                return 0;
            case "long":
                return 0L;
            case "short":
                return (short) 0;
            case "double":
                return 0.0;
            case "float":
                return 0.0f;
            case "byte":
                return (byte) 0x0;
            case "char":
                return (char) 0x0;
            default:
                return null;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy