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.core.exception.FlexExceptions;
019import com.mybatisflex.core.query.QueryColumn;
020import com.mybatisflex.core.table.TableInfo;
021import com.mybatisflex.core.table.TableInfoFactory;
022
023import java.io.Serializable;
024import java.lang.invoke.SerializedLambda;
025import java.lang.reflect.Method;
026import java.util.Map;
027import java.util.concurrent.ConcurrentHashMap;
028
029public class LambdaUtil {
030
031    private LambdaUtil() {
032    }
033
034    private static final Map<Class<?>, String> fieldNameMap = new ConcurrentHashMap<>();
035    private static final Map<Class<?>, Class<?>> implClassMap = new ConcurrentHashMap<>();
036    private static final Map<Class<?>, QueryColumn> queryColumnMap = new ConcurrentHashMap<>();
037
038    public static <T> String getFieldName(LambdaGetter<T> getter) {
039        return MapUtil.computeIfAbsent(fieldNameMap, getter.getClass(), aClass -> {
040            SerializedLambda lambda = getSerializedLambda(getter);
041            // 兼容 Kotlin KProperty 的 Lambda 解析
042            if (lambda.getCapturedArgCount() == 1) {
043                Object capturedArg = lambda.getCapturedArg(0);
044                try {
045                    return (String) capturedArg.getClass()
046                        .getMethod("getName")
047                        .invoke(capturedArg);
048                } catch (Exception e) {
049                    // 忽略这个异常,使用其他方式获取方法名
050                }
051            }
052            String methodName = lambda.getImplMethodName();
053            return StringUtil.methodToProperty(methodName);
054        });
055    }
056
057
058    public static <T> Class<?> getImplClass(LambdaGetter<T> getter) {
059        return MapUtil.computeIfAbsent(implClassMap, getter.getClass(), aClass -> {
060            SerializedLambda lambda = getSerializedLambda(getter);
061            return getImplClass(lambda, getter.getClass().getClassLoader());
062        });
063    }
064
065
066    public static <T> String getAliasName(LambdaGetter<T> getter, boolean withPrefix) {
067        QueryColumn queryColumn = getQueryColumn(getter);
068        if (queryColumn != null) {
069            String alias = StringUtil.isNotBlank(queryColumn.getAlias()) ? queryColumn.getAlias() : queryColumn.getName();
070            return withPrefix ? queryColumn.getTable().getName() + "$" + alias : alias;
071        }
072        return getFieldName(getter);
073    }
074
075
076    public static <T> QueryColumn getQueryColumn(LambdaGetter<T> getter) {
077        return MapUtil.computeIfAbsent(queryColumnMap, getter.getClass(), aClass -> {
078            ClassLoader classLoader = getter.getClass().getClassLoader();
079            SerializedLambda lambda = getSerializedLambda(getter);
080            Class<?> entityClass = getImplClass(lambda, classLoader);
081            TableInfo tableInfo = TableInfoFactory.ofEntityClass(entityClass);
082            String propertyName = getFieldName(getter);
083            return tableInfo.getQueryColumnByProperty(propertyName);
084        });
085    }
086
087
088    private static SerializedLambda getSerializedLambda(Serializable getter) {
089        try {
090            Method method = getter.getClass().getDeclaredMethod("writeReplace");
091            method.setAccessible(Boolean.TRUE);
092            return (SerializedLambda) method.invoke(getter);
093        } catch (Exception e) {
094            throw new RuntimeException(e);
095        }
096    }
097
098
099    private static Class<?> getImplClass(SerializedLambda lambda, ClassLoader classLoader) {
100        String implClass = getImplClassName(lambda);
101        try {
102            return Class.forName(implClass.replace("/", "."), true, classLoader);
103        } catch (ClassNotFoundException e) {
104            throw FlexExceptions.wrap(e);
105        }
106    }
107
108    private static String getImplClassName(SerializedLambda lambda) {
109        String type = lambda.getInstantiatedMethodType();
110        return type.substring(2, type.indexOf(";"));
111    }
112
113}