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