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                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            if (genericType instanceof ParameterizedType) {
098                Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
099                fieldWrapper.mappingType = (Class<?>) actualTypeArgument;
100            }
101        } else if (Map.class.isAssignableFrom(fieldType)) {
102            Type genericType = field.getGenericType();
103            if (genericType instanceof ParameterizedType) {
104                fieldWrapper.keyType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
105                Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[1];
106                if (actualTypeArgument instanceof ParameterizedType) {
107                    fieldWrapper.mappingType = (Class<?>) ((ParameterizedType) actualTypeArgument).getRawType();
108                } else {
109                    fieldWrapper.mappingType = (Class<?>) actualTypeArgument;
110                }
111            }
112        } else {
113            fieldWrapper.mappingType = fieldType;
114        }
115    }
116
117
118    public void set(Object value, Object to) {
119        try {
120            if (setterMethod == null) {
121                throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(field.getName()) + "\" in class: " + to.getClass().getName());
122            }
123            setterMethod.invoke(to, value);
124        } catch (Exception e) {
125            throw new RuntimeException(e);
126        }
127    }
128
129    public Object get(Object target) {
130        try {
131            if (getterMethod == null) {
132                throw new IllegalStateException("Can not find method \"get" + StringUtil.firstCharToUpperCase(field.getName()) + ", is"
133                    + StringUtil.firstCharToUpperCase(field.getName()) + "\" in class: " + target.getClass().getName());
134            }
135            return getterMethod.invoke(target);
136        } catch (Exception e) {
137            throw new RuntimeException(e);
138        }
139    }
140
141    public Class<?> getFieldType() {
142        return fieldType;
143    }
144
145    public Class<?> getMappingType() {
146        return mappingType;
147    }
148
149    public Class<?> getKeyType() {
150        return keyType;
151    }
152
153    public Field getField() {
154        return field;
155    }
156
157    public boolean isIgnore() {
158        return isIgnore;
159    }
160}