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.EnumValue;
019import com.mybatisflex.core.exception.FlexExceptions;
020import org.apache.ibatis.util.MapUtil;
021
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028public class EnumWrapper<E extends Enum<E>> {
029
030    private static final Map<Class, EnumWrapper> cache = new ConcurrentHashMap<>();
031
032    private boolean hasEnumValueAnnotation = false;
033
034    private Class<?> enumClass;
035    private E[] enums;
036    private Field property;
037    private Class<?> propertyType;
038    private Method getterMethod;
039
040    public static <R extends Enum<R>> EnumWrapper<R> of(Class<?> enumClass) {
041        return MapUtil.computeIfAbsent(cache, enumClass, EnumWrapper::new);
042    }
043
044    public EnumWrapper(Class<E> enumClass) {
045        this.enumClass = enumClass;
046        this.enums = enumClass.getEnumConstants();
047
048        Field enumValueField = ClassUtil.getFirstField(enumClass, field -> field.getAnnotation(EnumValue.class) != null);
049        if (enumValueField != null) {
050            hasEnumValueAnnotation = true;
051        }
052
053        if (hasEnumValueAnnotation) {
054            String getterMethodName = "get" + StringUtil.firstCharToUpperCase(enumValueField.getName());
055
056            Method getter = ClassUtil.getFirstMethod(enumClass, method -> {
057                String methodName = method.getName();
058                return methodName.equals(getterMethodName) && Modifier.isPublic(method.getModifiers());
059            });
060
061            propertyType = ClassUtil.getWrapType(enumValueField.getType());
062
063            if (getter == null) {
064                if (Modifier.isPublic(enumValueField.getModifiers())) {
065                    property = enumValueField;
066                } else {
067                    throw new IllegalStateException("Can not find method \"" + getterMethodName + "()\" in enum: " + enumClass.getName());
068                }
069            } else {
070                this.getterMethod = getter;
071            }
072        }
073
074        if (!hasEnumValueAnnotation) {
075            Method enumValueMethod = ClassUtil.getFirstMethod(enumClass, method -> method.getAnnotation(EnumValue.class) != null);
076            if (enumValueMethod != null) {
077                String methodName = enumValueMethod.getName();
078                if (!(methodName.startsWith("get") && methodName.length() > 3)) {
079                    throw new IllegalStateException("Can not find get method \"" + methodName + "()\" in enum: " + enumClass.getName());
080                }
081                this.getterMethod = enumValueMethod;
082                this.hasEnumValueAnnotation = true;
083                Class<?> returnType = enumValueMethod.getReturnType();
084                if (returnType.isPrimitive()) {
085                    returnType = ConvertUtil.primitiveToBoxed(returnType);
086                }
087                this.propertyType = returnType;
088            }
089        }
090    }
091
092
093    public Object getEnumValue(E object) {
094        try {
095            return getterMethod != null ? getterMethod.invoke(object) : property.get(object);
096        } catch (Exception e) {
097            throw FlexExceptions.wrap(e);
098        }
099    }
100
101
102    public E getEnum(Object value) {
103        if (value != null) {
104            for (E e : enums) {
105                if (value.equals(getEnumValue(e))) {
106                    return e;
107                }
108            }
109        }
110        return null;
111    }
112
113
114    public boolean hasEnumValueAnnotation() {
115        return hasEnumValueAnnotation;
116    }
117
118    public Class<?> getEnumClass() {
119        return enumClass;
120    }
121
122    public E[] getEnums() {
123        return enums;
124    }
125
126    public Field getProperty() {
127        return property;
128    }
129
130    public Class<?> getPropertyType() {
131        return propertyType;
132    }
133
134    public Method getGetterMethod() {
135        return getterMethod;
136    }
137
138}