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 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                if (wrapperMap == null) {
043                    wrapperMap = new ConcurrentHashMap<>();
044                    cache.put(clazz, wrapperMap);
045                }
046            }
047        }
048
049        FieldWrapper fieldWrapper = wrapperMap.get(fieldName);
050        if (fieldWrapper == null) {
051            synchronized (clazz) {
052                fieldWrapper = wrapperMap.get(fieldName);
053                if (fieldWrapper == null) {
054                    Field findField = ClassUtil.getFirstField(clazz, field -> field.getName().equals(fieldName));
055                    if (findField == null) {
056                        throw new IllegalStateException("Can not find field \"" + fieldName + "\" in class: " + clazz);
057                    }
058
059                    String setterName = "set" + StringUtil.firstCharToUpperCase(fieldName);
060                    Method setter = ClassUtil.getFirstMethod(clazz, method ->
061                        method.getParameterCount() == 1
062                            && Modifier.isPublic(method.getModifiers())
063                            && method.getName().equals(setterName));
064
065                    if (setter == null) {
066                        throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(fieldName) + "\" in class: " + clazz);
067                    }
068
069                    fieldWrapper = new FieldWrapper();
070                    fieldWrapper.field = findField;
071                    fieldWrapper.fieldType = findField.getType();
072                    initMappingTypeAndKeyType(clazz, findField, fieldWrapper);
073
074                    Column column = findField.getAnnotation(Column.class);
075                    if (column != null && column.ignore()) {
076                        fieldWrapper.isIgnore = true;
077                    }
078
079                    fieldWrapper.setterMethod = setter;
080
081                    String[] getterNames = new String[]{"get" + StringUtil.firstCharToUpperCase(fieldName), "is" + StringUtil.firstCharToUpperCase(fieldName)};
082                    fieldWrapper.getterMethod = ClassUtil.getFirstMethod(clazz, method -> method.getParameterCount() == 0
083                        && Modifier.isPublic(method.getModifiers())
084                        && ArrayUtil.contains(getterNames, method.getName()));
085
086                    wrapperMap.put(fieldName, fieldWrapper);
087                }
088            }
089        }
090
091        return fieldWrapper;
092    }
093
094    private static void initMappingTypeAndKeyType(Class<?> clazz, Field field, FieldWrapper fieldWrapper) {
095        Reflector reflector = Reflectors.of(clazz);
096        Class<?> fieldType = reflector.getGetterType(field.getName());
097
098        if (Collection.class.isAssignableFrom(fieldType)) {
099            Type genericType = field.getGenericType();
100            if (genericType instanceof ParameterizedType) {
101                Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
102                fieldWrapper.mappingType = (Class<?>) actualTypeArgument;
103            }
104        } else if (Map.class.isAssignableFrom(fieldType)) {
105            Type genericType = field.getGenericType();
106            if (genericType instanceof ParameterizedType) {
107                fieldWrapper.keyType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
108                fieldWrapper.mappingType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[1];
109            }
110        } else {
111            fieldWrapper.mappingType = fieldType;
112        }
113    }
114
115
116    public void set(Object value, Object to) {
117        try {
118            setterMethod.invoke(to, value);
119        } catch (Exception e) {
120            throw new RuntimeException(e);
121        }
122    }
123
124    public Object get(Object target) {
125        try {
126            return getterMethod.invoke(target);
127        } catch (Exception e) {
128            throw new RuntimeException(e);
129        }
130    }
131
132    public Class<?> getFieldType() {
133        return fieldType;
134    }
135
136    public Class<?> getMappingType() {
137        return mappingType;
138    }
139
140    public Class<?> getKeyType() {
141        return keyType;
142    }
143
144    public Field getField() {
145        return field;
146    }
147
148    public boolean isIgnore() {
149        return isIgnore;
150    }
151}