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 java.lang.reflect.*;
019import java.util.Collection;
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022
023public class FieldWrapper {
024
025    public static Map<Class<?>, Map<String, FieldWrapper>> cache = new ConcurrentHashMap<>();
026
027    private Class<?> fieldType;
028    private Class<?> mappingType;
029    private Method setterMethod;
030
031    public static FieldWrapper of(Class<?> clazz, String fieldName) {
032        Map<String, FieldWrapper> wrapperMap = cache.get(clazz);
033        if (wrapperMap == null) {
034            synchronized (clazz) {
035                if (wrapperMap == null) {
036                    wrapperMap = new ConcurrentHashMap<>();
037                    cache.put(clazz, wrapperMap);
038                }
039            }
040        }
041
042        FieldWrapper fieldWrapper = wrapperMap.get(fieldName);
043        if (fieldWrapper == null) {
044            synchronized (clazz) {
045                fieldWrapper = wrapperMap.get(fieldName);
046                if (fieldWrapper == null) {
047                    Field findField = ClassUtil.getFirstField(clazz, field -> field.getName().equals(fieldName));
048                    if (findField == null) {
049                        throw new IllegalStateException("Can not find field \"" + fieldName + "\" in class: " + clazz);
050                    }
051
052                    Method setter = ClassUtil.getFirstMethod(clazz, method ->
053                            method.getParameterCount() == 1
054                                    && Modifier.isPublic(method.getModifiers())
055                                    && method.getName().equals("set" + StringUtil.firstCharToUpperCase(fieldName)));
056
057                    if (setter == null) {
058                        throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(fieldName) + "\" in class: " + clazz);
059                    }
060
061                    fieldWrapper = new FieldWrapper();
062                    fieldWrapper.fieldType = findField.getType();
063                    fieldWrapper.mappingType = parseMappingType(findField);
064                    fieldWrapper.setterMethod = setter;
065
066                    wrapperMap.put(fieldName, fieldWrapper);
067                }
068            }
069        }
070
071        return fieldWrapper;
072    }
073
074    private static Class<?> parseMappingType(Field field) {
075        Class<?> fieldType = field.getType();
076        if (Collection.class.isAssignableFrom(fieldType)) {
077            Type genericType = field.getGenericType();
078            if (genericType instanceof ParameterizedType) {
079                Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
080                return (Class<?>) actualTypeArgument;
081            }
082        }
083
084        if (fieldType.isArray()) {
085            return field.getType().getComponentType();
086        }
087
088        return fieldType;
089    }
090
091
092    public void set(Object value, Object to) {
093        try {
094            setterMethod.invoke(to, value);
095        } catch (Exception e) {
096            throw new RuntimeException(e);
097        }
098    }
099
100    public Class<?> getFieldType() {
101        return fieldType;
102    }
103
104    public Class<?> getMappingType() {
105        return mappingType;
106    }
107}