001/*
002 *  Copyright (c) 2022-2025, 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 com.mybatisflex.annotation.Column;
019import org.apache.ibatis.reflection.Reflector;
020
021import java.lang.reflect.*;
022import java.util.Collection;
023import java.util.Map;
024import java.util.concurrent.ConcurrentHashMap;
025
026public class FieldWrapper {
027
028    public static Map<Class<?>, Map<String, FieldWrapper>> cache = new ConcurrentHashMap<>();
029
030    private Field field;
031    private boolean isIgnore = false;
032    private Class<?> fieldType;
033    private Class<?> mappingType;
034    private Class<?> keyType;
035    private Method getterMethod;
036    private Method setterMethod;
037
038    public static FieldWrapper of(Class<?> clazz, String fieldName) {
039        Map<String, FieldWrapper> wrapperMap = cache.get(clazz);
040        if (wrapperMap == null) {
041            synchronized (clazz) {
042                wrapperMap = cache.get(clazz);
043                if (wrapperMap == null) {
044                    wrapperMap = new ConcurrentHashMap<>();
045                    cache.put(clazz, wrapperMap);
046                }
047            }
048        }
049
050        FieldWrapper fieldWrapper = wrapperMap.get(fieldName);
051        if (fieldWrapper == null) {
052            synchronized (clazz) {
053                fieldWrapper = wrapperMap.get(fieldName);
054                if (fieldWrapper == null) {
055                    Field findField = ClassUtil.getFirstField(clazz, field -> field.getName().equalsIgnoreCase(fieldName));
056                    if (findField == null) {
057                        throw new IllegalStateException("Can not find field \"" + fieldName + "\" in class: " + clazz.getName());
058                    }
059
060                    String setterName = "set" + StringUtil.firstCharToUpperCase(findField.getName());
061                    Method setter = ClassUtil.getFirstMethod(clazz, method ->
062                        method.getParameterCount() == 1
063                            && Modifier.isPublic(method.getModifiers())
064                            && method.getName().equals(setterName));
065
066                    fieldWrapper = new FieldWrapper();
067                    fieldWrapper.field = findField;
068                    fieldWrapper.fieldType = findField.getType();
069                    initMappingTypeAndKeyType(clazz, findField, fieldWrapper);
070
071                    Column column = findField.getAnnotation(Column.class);
072                    if (column != null && column.ignore()) {
073                        fieldWrapper.isIgnore = true;
074                    }
075
076                    fieldWrapper.setterMethod = setter;
077
078                    String[] getterNames = new String[]{"get" + StringUtil.firstCharToUpperCase(findField.getName()), "is" + StringUtil.firstCharToUpperCase(findField.getName())};
079                    fieldWrapper.getterMethod = ClassUtil.getFirstMethod(clazz, method -> method.getParameterCount() == 0
080                        && Modifier.isPublic(method.getModifiers())
081                        && ArrayUtil.contains(getterNames, method.getName()));
082
083                    wrapperMap.put(fieldName, fieldWrapper);
084                }
085            }
086        }
087
088        return fieldWrapper;
089    }
090
091    private static void initMappingTypeAndKeyType(Class<?> clazz, Field field, FieldWrapper fieldWrapper) {
092        Reflector reflector = Reflectors.of(clazz);
093        Class<?> fieldType = reflector.getGetterType(field.getName());
094
095        if (Collection.class.isAssignableFrom(fieldType)) {
096            Type genericType = field.getGenericType();
097            try{
098                //使用Kotlin时,若Collection泛型类型为协变类型时(如 List<out E>),并且传入的具体泛型类型为open,genericType的具体类型为WildcardType(? extends ExampleClass),该类型不可转为Class(无具体类型),使用instanceOf会抛出转换异常。
099                //该错误目前只在以上情况中发现
100                if (genericType instanceof ParameterizedType) {
101                    Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
102                    fieldWrapper.mappingType = (Class<?>) actualTypeArgument;
103                }
104            }catch (ClassCastException e){
105                throw new UnsupportedOperationException(String.format("不支持使用[%s]作为集合类型。请使用MutableList",fieldType.getName()));
106            }
107        } else if (Map.class.isAssignableFrom(fieldType)) {
108            Type genericType = field.getGenericType();
109            if (genericType instanceof ParameterizedType) {
110                fieldWrapper.keyType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
111                Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[1];
112                if (actualTypeArgument instanceof ParameterizedType) {
113                    fieldWrapper.mappingType = (Class<?>) ((ParameterizedType) actualTypeArgument).getRawType();
114                } else {
115                    fieldWrapper.mappingType = (Class<?>) actualTypeArgument;
116                }
117            }
118        } else {
119            fieldWrapper.mappingType = fieldType;
120        }
121    }
122
123
124    public void set(Object value, Object to) {
125        try {
126            if (setterMethod == null) {
127                throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(field.getName()) + "\" in class: " + to.getClass().getName());
128            }
129            setterMethod.invoke(to, value);
130        } catch (Exception e) {
131            throw new RuntimeException(e);
132        }
133    }
134
135    public Object get(Object target) {
136        try {
137            if (getterMethod == null) {
138                throw new IllegalStateException("Can not find method \"get" + StringUtil.firstCharToUpperCase(field.getName()) + ", is"
139                    + StringUtil.firstCharToUpperCase(field.getName()) + "\" in class: " + target.getClass().getName());
140            }
141            return getterMethod.invoke(target);
142        } catch (Exception e) {
143            throw new RuntimeException(e);
144        }
145    }
146
147    public Class<?> getFieldType() {
148        return fieldType;
149    }
150
151    public Class<?> getMappingType() {
152        return mappingType;
153    }
154
155    public Class<?> getKeyType() {
156        return keyType;
157    }
158
159    public Field getField() {
160        return field;
161    }
162
163    public boolean isIgnore() {
164        return isIgnore;
165    }
166}