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.EnumValue;
019import com.mybatisflex.core.exception.FlexExceptions;
020
021import java.lang.reflect.Field;
022import java.lang.reflect.Method;
023import java.lang.reflect.Modifier;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026
027public class EnumWrapper<E extends Enum<E>> {
028
029    private static final Map<Class, EnumWrapper> cache = new ConcurrentHashMap<>();
030
031    private boolean hasEnumValueAnnotation = false;
032
033    private final Class<?> enumClass;
034    private final E[] enums;
035    private Field property;
036    private Class<?> propertyType;
037    private Method getterMethod;
038
039    public static <R extends Enum<R>> EnumWrapper<R> of(Class<?> enumClass) {
040        return MapUtil.computeIfAbsent(cache, enumClass, EnumWrapper::new);
041    }
042
043    public EnumWrapper(Class<E> enumClass) {
044        this.enumClass = enumClass;
045        this.enums = enumClass.getEnumConstants();
046
047        Field enumValueField = ClassUtil.getFirstField(enumClass, field -> field.getAnnotation(EnumValue.class) != null);
048        if (enumValueField != null) {
049            hasEnumValueAnnotation = true;
050        }
051
052        if (hasEnumValueAnnotation) {
053            String getterMethodName = "get" + StringUtil.firstCharToUpperCase(enumValueField.getName());
054
055            Method getter = ClassUtil.getFirstMethod(enumClass, method -> {
056                String methodName = method.getName();
057                return methodName.equals(getterMethodName) && Modifier.isPublic(method.getModifiers());
058            });
059
060            propertyType = ClassUtil.getWrapType(enumValueField.getType());
061
062            if (getter == null) {
063                if (Modifier.isPublic(enumValueField.getModifiers())) {
064                    property = enumValueField;
065                } else {
066                    throw new IllegalStateException("Can not find method \"" + getterMethodName + "()\" in enum: " + enumClass.getName());
067                }
068            } else {
069                this.getterMethod = getter;
070            }
071        }
072
073        if (!hasEnumValueAnnotation) {
074            Method enumValueMethod = ClassUtil.getFirstMethodByAnnotation(enumClass, EnumValue.class);
075            if (enumValueMethod != null) {
076                String methodName = enumValueMethod.getName();
077                if (!(methodName.startsWith("get") && methodName.length() > 3)) {
078                    throw new IllegalStateException("Can not find get method \"" + methodName + "()\" in enum: " + enumClass.getName());
079                }
080
081                String enumValueFieldName;
082                if (methodName.startsWith("get")) {
083                    enumValueFieldName = StringUtil.firstCharToLowerCase(enumValueMethod.getName().substring(3));
084                } else {
085                    enumValueFieldName = enumValueMethod.getName().toLowerCase();
086                }
087                enumValueField = ClassUtil.getFirstField(enumClass, field -> enumValueFieldName.equals(field.getName()));
088                if (enumValueField != null) {
089                    propertyType = ClassUtil.getWrapType(enumValueField.getType());
090                } else {
091                    throw new IllegalStateException("Can not find field \"" + enumValueFieldName + "()\" in enum: " + enumClass.getName());
092                }
093
094                this.getterMethod = enumValueMethod;
095                this.hasEnumValueAnnotation = true;
096            }
097        }
098    }
099
100    /**
101     * 获取枚举值
102     * 顺序:
103     * 1、@EnumValue标识的get方法
104     * 2、@EnumValue标识的属性
105     * 3、没有使用@EnumValue,取枚举name
106     *
107     * @param object
108     * @return
109     */
110    public Object getEnumValue(Object object) {
111        try {
112            if (getterMethod != null) {
113                return getterMethod.invoke(object);
114            } else if (property != null) {
115                return property.get(object);
116            } else {
117                //noinspection unchecked
118                return ((E) object).name();
119            }
120        } catch (Exception e) {
121            throw FlexExceptions.wrap(e);
122        }
123    }
124
125
126    public E getEnum(Object value) {
127        if (value != null) {
128            for (E e : enums) {
129                if (value.equals(getEnumValue(e))) {
130                    return e;
131                }
132            }
133        }
134        return null;
135    }
136
137
138    public boolean hasEnumValueAnnotation() {
139        return hasEnumValueAnnotation;
140    }
141
142    public Class<?> getEnumClass() {
143        return enumClass;
144    }
145
146    public E[] getEnums() {
147        return enums;
148    }
149
150    public Field getProperty() {
151        return property;
152    }
153
154    public Class<?> getPropertyType() {
155        return propertyType;
156    }
157
158    public Method getGetterMethod() {
159        return getterMethod;
160    }
161
162}