001/*
002 *  Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.util;
017
018import org.apache.ibatis.reflection.Reflector;
019
020import java.lang.reflect.*;
021import java.util.Collection;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024
025public class FieldWrapper {
026
027    public static Map<Class<?>, Map<String, FieldWrapper>> cache = new ConcurrentHashMap<>();
028
029    private Class<?> fieldType;
030    private Class<?> mappingType;
031    private Class<?> keyType;
032    private Method getterMethod;
033    private Method setterMethod;
034
035    public static FieldWrapper of(Class<?> clazz, String fieldName) {
036        Map<String, FieldWrapper> wrapperMap = cache.get(clazz);
037        if (wrapperMap == null) {
038            synchronized (clazz) {
039                if (wrapperMap == null) {
040                    wrapperMap = new ConcurrentHashMap<>();
041                    cache.put(clazz, wrapperMap);
042                }
043            }
044        }
045
046        FieldWrapper fieldWrapper = wrapperMap.get(fieldName);
047        if (fieldWrapper == null) {
048            synchronized (clazz) {
049                fieldWrapper = wrapperMap.get(fieldName);
050                if (fieldWrapper == null) {
051                    Field findField = ClassUtil.getFirstField(clazz, field -> field.getName().equals(fieldName));
052                    if (findField == null) {
053                        throw new IllegalStateException("Can not find field \"" + fieldName + "\" in class: " + clazz);
054                    }
055
056                    String setterName = "set" + StringUtil.firstCharToUpperCase(fieldName);
057                    Method setter = ClassUtil.getFirstMethod(clazz, method ->
058                        method.getParameterCount() == 1
059                            && Modifier.isPublic(method.getModifiers())
060                            && method.getName().equals(setterName));
061
062                    if (setter == null) {
063                        throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(fieldName) + "\" in class: " + clazz);
064                    }
065
066                    fieldWrapper = new FieldWrapper();
067                    fieldWrapper.fieldType = findField.getType();
068                    initMappingTypeAndKeyType(clazz, findField, fieldWrapper);
069
070
071                    fieldWrapper.setterMethod = setter;
072
073                    String[] getterNames = new String[]{"get" + StringUtil.firstCharToUpperCase(fieldName), "is" + StringUtil.firstCharToUpperCase(fieldName)};
074                    fieldWrapper.getterMethod = ClassUtil.getFirstMethod(clazz, method -> method.getParameterCount() == 0
075                        && Modifier.isPublic(method.getModifiers())
076                        && ArrayUtil.contains(getterNames, method.getName()));
077
078                    wrapperMap.put(fieldName, fieldWrapper);
079                }
080            }
081        }
082
083        return fieldWrapper;
084    }
085
086    private static void initMappingTypeAndKeyType(Class<?> clazz, Field field, FieldWrapper fieldWrapper) {
087        Reflector reflector = Reflectors.of(clazz);
088        Class<?> fieldType = reflector.getGetterType(field.getName());
089
090        if (Collection.class.isAssignableFrom(fieldType)) {
091            Type genericType = field.getGenericType();
092            if (genericType instanceof ParameterizedType) {
093                Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
094                fieldWrapper.mappingType = (Class<?>) actualTypeArgument;
095            }
096        } else if (Map.class.isAssignableFrom(fieldType)) {
097            Type genericType = field.getGenericType();
098            if (genericType instanceof ParameterizedType) {
099                fieldWrapper.keyType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
100                fieldWrapper.mappingType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[1];
101            }
102        } else {
103            fieldWrapper.mappingType = fieldType;
104        }
105    }
106
107
108    public void set(Object value, Object to) {
109        try {
110            setterMethod.invoke(to, value);
111        } catch (Exception e) {
112            throw new RuntimeException(e);
113        }
114    }
115
116    public Object get(Object target) {
117        try {
118            return getterMethod.invoke(target);
119        } catch (Exception e) {
120            throw new RuntimeException(e);
121        }
122    }
123
124    public Class<?> getFieldType() {
125        return fieldType;
126    }
127
128    public Class<?> getMappingType() {
129        return mappingType;
130    }
131
132    public Class<?> getKeyType() {
133        return keyType;
134    }
135}