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

org.dbflute.helper.beans.impl.DfBeanDescImpl Maven / Gradle / Ivy

There is a newer version: 1.2.8
Show newest version
/*
 * Copyright 2014-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.dbflute.helper.beans.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.dbflute.helper.StringKeyMap;
import org.dbflute.helper.beans.DfBeanDesc;
import org.dbflute.helper.beans.DfCoupleProperties;
import org.dbflute.helper.beans.DfPropertyDesc;
import org.dbflute.helper.beans.exception.DfBeanFieldNotFoundException;
import org.dbflute.helper.beans.exception.DfBeanMethodNotFoundException;
import org.dbflute.helper.beans.exception.DfBeanPropertyNotFoundException;
import org.dbflute.util.DfReflectionUtil;
import org.dbflute.util.DfTypeUtil;
import org.dbflute.util.Srl;

/**
 * @author modified by jflute (originated in S2Dao)
 */
public class DfBeanDescImpl implements DfBeanDesc {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    protected static final Object[] EMPTY_ARGS = new Object[] {};
    protected static final Class[] EMPTY_PARAM_TYPES = new Class[] {};

    protected static final String PREFIX_GETTER = "get";
    protected static final String PREFIX_CLOSET_GETTER = "xdfget";
    protected static final String PREFIX_DETERMINER = "is";
    protected static final String PREFIX_SETTER = "set";

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    protected final Class _beanClass;

    // #retire needs to validate cost-benefit performance (balance with safety)
    // and lazy load is not enough to get performance because Java reflection is not flexible 
    //
    // e.g. if false -> true (property, method, field)
    // |       |  MemberCB   |  MemberCQ    |
    // | false | 33, 132, 24 | 104, 293, 88 |
    // | true  | 33,  77, 24 |  66, 176, 88 |
    //
    //protected final boolean _readOnly; // cannot block public field but OK, basically for saving memory
    //protected final DfBeanDescSetupFilter _setupFilter; // might be null

    protected final StringKeyMap _propertyDescMap = StringKeyMap.createAsCaseInsensitive();
    protected final Map _methodsMap = new HashMap();
    protected final Map _fieldMap = new HashMap();

    protected final transient Set _invalidPropertyNames = new HashSet();

    // ===================================================================================
    //                                                                         Constructor
    //                                                                         ===========
    public DfBeanDescImpl(Class beanClass) {
        if (beanClass == null) {
            String msg = "The argument 'beanClass' should not be null!";
            throw new IllegalArgumentException(msg);
        }
        _beanClass = beanClass;

        setupPropertyDesc();
        setupMethod();
        setupField();
    }

    // ===================================================================================
    //                                                                                Bean
    //                                                                                ====
    public Class getBeanClass() {
        return _beanClass;
    }

    // ===================================================================================
    //                                                                   Property Handling
    //                                                                   =================
    public boolean hasPropertyDesc(String propertyName) {
        return getPropertyDescInternally(propertyName) != null;
    }

    public DfPropertyDesc getPropertyDesc(String propertyName) throws DfBeanPropertyNotFoundException {
        DfPropertyDesc pd = getPropertyDescInternally(propertyName);
        if (pd == null) {
            throw new DfBeanPropertyNotFoundException(_beanClass, propertyName);
        }
        return pd;
    }

    private DfPropertyDesc getPropertyDescInternally(String propertyName) {
        return _propertyDescMap.get(propertyName);
    }

    public int getPropertyDescSize() {
        return _propertyDescMap.size();
    }

    public List getProppertyNameList() {
        return new ArrayList(_propertyDescMap.keySet());
    }

    // ===================================================================================
    //                                                                      Field Handling
    //                                                                      ==============
    public boolean hasField(String fieldName) {
        return _fieldMap.get(fieldName) != null;
    }

    public Field getField(String fieldName) {
        final Field field = (Field) _fieldMap.get(fieldName);
        if (field == null) {
            throw new DfBeanFieldNotFoundException(_beanClass, fieldName);
        }
        return field;
    }

    public int getFieldSize() {
        return _fieldMap.size();
    }

    // ===================================================================================
    //                                                                     Method Handling
    //                                                                     ===============
    public boolean hasMethod(String methodName) {
        return _methodsMap.get(methodName) != null;
    }

    public Method getMethod(String methodName) throws DfBeanMethodNotFoundException {
        return getMethod(methodName, EMPTY_PARAM_TYPES);
    }

    public Method getMethod(String methodName, Class[] paramTypes) throws DfBeanMethodNotFoundException {
        final Method method = getMethodNoException(methodName, paramTypes);
        if (method == null) {
            throw new DfBeanMethodNotFoundException(_beanClass, methodName, paramTypes);
        }
        return method;
    }

    public Method getMethodNoException(String methodName) {
        return getMethodNoException(methodName, EMPTY_PARAM_TYPES);
    }

    public Method getMethodNoException(String methodName, Class[] paramTypes) {
        final Method[] methods = findMethods(methodName);
        if (methods == null) {
            return null;
        }
        // you can also specify null parameter types (null means empty types)
        final Class[] specifiedTypes = paramTypes != null ? paramTypes : EMPTY_PARAM_TYPES;
        for (Method method : methods) {
            if (Arrays.equals(specifiedTypes, method.getParameterTypes())) {
                return method;
            }
            if (isAssignableParameterTypes(specifiedTypes, method)) {
                return method;
            }
        }
        return null;
    }

    protected boolean isAssignableParameterTypes(Class[] specifiedTypes, Method method) {
        final Class[] definedTypes = method.getParameterTypes();
        if (specifiedTypes.length != definedTypes.length) {
            return false;
        }
        for (int i = 0; i < specifiedTypes.length; i++) {
            if (!definedTypes[i].isAssignableFrom(specifiedTypes[i])) {
                return false;
            }
        }
        return true; // all types are assignable
    }

    public Method[] getMethods(String methodName) throws DfBeanMethodNotFoundException {
        final Method[] methods = findMethods(methodName);
        if (methods == null) {
            throw new DfBeanMethodNotFoundException(_beanClass, methodName, null);
        }
        return methods;
    }

    protected Method[] findMethods(String methodName) {
        return (Method[]) _methodsMap.get(methodName);
    }

    // ===================================================================================
    //                                                                     Set up Property
    //                                                                     ===============
    protected void setupPropertyDesc() {
        final Method[] methods = _beanClass.getMethods();
        final Map coupleMethodMap = hasCoupleProperties() ? new HashMap() : null;
        for (int i = 0; i < methods.length; i++) {
            final Method method = methods[i];
            if (DfReflectionUtil.isBridgeMethod(method) || DfReflectionUtil.isSyntheticMethod(method)) {
                continue;
            }
            final String methodName = method.getName();
            if (methodName.startsWith(PREFIX_GETTER)) {
                if (method.getParameterTypes().length != 0 || methodName.equals("getClass") || method.getReturnType() == void.class) {
                    continue;
                }
                final String propertyName = initBeansProp(methodName.substring(PREFIX_GETTER.length()));
                setupReadMethod(method, propertyName);
            } else if (methodName.startsWith(PREFIX_CLOSET_GETTER)) { // closet property
                if (method.getParameterTypes().length != 0 || method.getReturnType() == void.class) {
                    continue;
                }
                final String propertyName = initBeansProp(methodName.substring(PREFIX_CLOSET_GETTER.length()));
                setupReadMethod(method, propertyName);
            } else if (methodName.startsWith(PREFIX_DETERMINER)) {
                if (method.getParameterTypes().length != 0
                        || !method.getReturnType().equals(Boolean.TYPE) && !method.getReturnType().equals(Boolean.class)) {
                    continue;
                }
                final String propertyName = initBeansProp(methodName.substring(PREFIX_DETERMINER.length()));
                setupReadMethod(method, propertyName);
            } else if (methodName.startsWith(PREFIX_SETTER)) {
                if (method.getParameterTypes().length != 1 || methodName.equals("setClass") || method.getReturnType() != void.class) {
                    continue;
                }
                final String propertyName = initBeansProp(methodName.substring(PREFIX_SETTER.length()));
                setupWriteMethod(method, propertyName);
            } else {
                if (coupleMethodMap != null) {
                    doSetupCouplePropertyDesc(coupleMethodMap, method, methodName);
                }
            }
        }
        for (Iterator ite = _invalidPropertyNames.iterator(); ite.hasNext();) {
            _propertyDescMap.remove(ite.next());
        }
        _invalidPropertyNames.clear();
    }

    protected boolean hasCoupleProperties() {
        return DfCoupleProperties.class.isAssignableFrom(_beanClass);
    }

    protected static String initBeansProp(String name) {
        return Srl.initBeansProp(name);
    }

    protected void doSetupCouplePropertyDesc(Map coupleMethodMap, Method method, String methodName) {
        final Method coupleMethod = coupleMethodMap.get(methodName);
        if (coupleMethod != null) {
            final Class[] coupleParamTypes = coupleMethod.getParameterTypes();
            final int coupleParamLen = coupleParamTypes.length;
            if (coupleParamLen == 0) { // couple might be read method
                final Class[] paramTypes = method.getParameterTypes();
                if (paramTypes.length == 1 && paramTypes[0] == coupleMethod.getReturnType()) {
                    setupReadMethod(coupleMethod, methodName);
                    setupWriteMethod(method, methodName);
                }
            } else if (coupleParamLen == 1) { // couple might be write method
                final Class[] paramTypes = method.getParameterTypes();
                if (paramTypes.length == 0 && method.getReturnType() == coupleParamTypes[0]) {
                    setupReadMethod(method, methodName);
                    setupWriteMethod(coupleMethod, methodName);
                }
            }
        } else {
            final int paramLen = method.getParameterTypes().length;
            final Class returnType = method.getReturnType();
            if (paramLen == 0 && returnType != void.class) { // e.g. String memberName()
                coupleMethodMap.put(methodName, method);
            } else if (paramLen == 1 || returnType == void.class) { // e.g. memberName(String)
                coupleMethodMap.put(methodName, method);
            }
        }
    }

    protected void addPropertyDesc(DfPropertyDesc propertyDesc) {
        if (propertyDesc == null) {
            String msg = "The argument 'propertyDesc' should not be null!";
            throw new IllegalArgumentException(msg);
        }
        _propertyDescMap.put(propertyDesc.getPropertyName(), propertyDesc);
    }

    protected void setupReadMethod(Method readMethod, String propertyName) {
        final Class propertyType = readMethod.getReturnType();
        final DfPropertyDesc propDesc = getPropertyDescInternally(propertyName);
        if (propDesc != null) {
            if (!propDesc.getPropertyType().equals(propertyType)) {
                _invalidPropertyNames.add(propertyName);
            } else {
                propDesc.setReadMethod(readMethod);
            }
        } else {
            addPropertyDesc(createPropertyDesc(propertyName, propertyType, readMethod, null, null));
        }
    }

    protected void setupWriteMethod(Method writeMethod, String propertyName) {
        final Class propertyType = writeMethod.getParameterTypes()[0];
        final DfPropertyDesc propDesc = getPropertyDescInternally(propertyName);
        if (propDesc != null) {
            if (!propDesc.getPropertyType().equals(propertyType)) {
                _invalidPropertyNames.add(propertyName);
            } else {
                propDesc.setWriteMethod(writeMethod);
            }
        } else {
            addPropertyDesc(createPropertyDesc(propertyName, propertyType, null, writeMethod, null));
        }
    }

    // ===================================================================================
    //                                                                       Set up Method
    //                                                                       =============
    protected void setupMethod() {
        final Map> methodListMap = new LinkedHashMap>();
        final Method[] methods = _beanClass.getMethods();
        for (Method method : methods) {
            if (DfReflectionUtil.isBridgeMethod(method) || DfReflectionUtil.isSyntheticMethod(method)) {
                continue;
            }
            if (Object.class.equals(method.getDeclaringClass())) {
                continue;
            }
            final String methodName = method.getName();
            List list = (List) methodListMap.get(methodName);
            if (list == null) {
                list = new ArrayList();
                methodListMap.put(methodName, list);
            }
            list.add(method);
        }
        for (Entry> entry : methodListMap.entrySet()) {
            final String key = entry.getKey();
            final List methodList = entry.getValue();
            _methodsMap.put(key, methodList.toArray(new Method[methodList.size()]));
        }
    }

    // ===================================================================================
    //                                                                        Set up Field
    //                                                                        ============
    protected void setupField() {
        setupFields(_beanClass);
    }

    protected void setupFields(Class targetClass) {
        if (targetClass.isInterface()) {
            setupFieldsByInterface(targetClass);
        } else {
            setupFieldsByClass(targetClass);
        }
    }

    protected void setupFieldsByInterface(Class interfaceClass) {
        addFields(interfaceClass);
        final Class[] interfaces = interfaceClass.getInterfaces();
        for (int i = 0; i < interfaces.length; ++i) {
            setupFieldsByInterface(interfaces[i]);
        }
    }

    protected void addFields(Class clazz) {
        final Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            final String fieldName = field.getName();
            if (_fieldMap.containsKey(fieldName)) { // target class's fields have priority  
                continue;
            }
            if (isFieldPrivateAccessible()) {
                field.setAccessible(true);
            }
            _fieldMap.put(fieldName, field);
            if (DfReflectionUtil.isInstanceVariableField(field)) {
                if (hasPropertyDesc(fieldName)) {
                    final DfPropertyDesc pd = getPropertyDesc(fieldName);
                    pd.setField(field);
                } else if (DfReflectionUtil.isPublicField(field)) {
                    final DfPropertyDesc pd = createPropertyDesc(fieldName, field.getType(), null, null, field);
                    _propertyDescMap.put(fieldName, pd);
                }
            }
        }
    }

    protected boolean isFieldPrivateAccessible() {
        // GAE does not support private access, and DBFlute does not use private access
        // so it does not use private access as default
        return false;
    }

    protected void setupFieldsByClass(Class targetClass) {
        addFields(targetClass); // should be set up at first
        final Class[] interfaces = targetClass.getInterfaces();
        for (int i = 0; i < interfaces.length; ++i) {
            setupFieldsByInterface(interfaces[i]);
        }
        final Class superClass = targetClass.getSuperclass();
        if (superClass != Object.class && superClass != null) {
            setupFieldsByClass(superClass);
        }
    }

    // ===================================================================================
    //                                                                       Assist Helper
    //                                                                       =============
    protected DfPropertyDesc createPropertyDesc(String propertyName, Class propertyType, Method readMethod, Method writeMethod,
            Field field) {
        return new DfPropertyDescImpl(this, propertyName, propertyType, readMethod, writeMethod, field);
    }

    protected boolean adjustNumber(Class[] paramTypes, Object[] args, int index) {
        if (paramTypes[index].isPrimitive()) {
            if (paramTypes[index] == int.class) {
                args[index] = DfTypeUtil.toInteger(args[index]);
                return true;
            } else if (paramTypes[index] == double.class) {
                args[index] = DfTypeUtil.toDouble(args[index]);
                return true;
            } else if (paramTypes[index] == long.class) {
                args[index] = DfTypeUtil.toLong(args[index]);
                return true;
            } else if (paramTypes[index] == short.class) {
                args[index] = DfTypeUtil.toShort(args[index]);
                return true;
            } else if (paramTypes[index] == float.class) {
                args[index] = DfTypeUtil.toFloat(args[index]);
                return true;
            }
        } else {
            if (paramTypes[index] == Integer.class) {
                args[index] = DfTypeUtil.toInteger(args[index]);
                return true;
            } else if (paramTypes[index] == Double.class) {
                args[index] = DfTypeUtil.toDouble(args[index]);
                return true;
            } else if (paramTypes[index] == Long.class) {
                args[index] = DfTypeUtil.toLong(args[index]);
                return true;
            } else if (paramTypes[index] == Short.class) {
                args[index] = DfTypeUtil.toShort(args[index]);
                return true;
            } else if (paramTypes[index] == Float.class) {
                args[index] = DfTypeUtil.toFloat(args[index]);
                return true;
            }
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy