All Downloads are FREE. Search and download functionalities are using the official Maven repository.

site.sorghum.anno._common.util.MetaClassUtil Maven / Gradle / Ivy

The newest version!
package site.sorghum.anno._common.util;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.yaml.YamlUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import org.noear.wood.annotation.Table;
import site.sorghum.anno._metadata.AnField;
import site.sorghum.anno._metadata.AnMeta;
import site.sorghum.anno._metadata.AnnoJavaCmd;
import site.sorghum.anno._metadata.AnnoMtm;
import site.sorghum.anno.anno.annotation.clazz.*;
import site.sorghum.anno.anno.annotation.common.AnnoTplImpl;
import site.sorghum.anno.anno.annotation.field.AnnoButton;
import site.sorghum.anno.anno.annotation.field.AnnoButtonImpl;
import site.sorghum.anno.anno.annotation.field.AnnoChartFieldImpl;
import site.sorghum.anno.anno.annotation.field.type.AnnoOptionTypeImpl;
import site.sorghum.anno.anno.annotation.field.type.AnnoTreeTypeImpl;
import site.sorghum.anno.anno.entity.common.FieldAnnoField;
import site.sorghum.anno.anno.tpl.BaseTplRender;
import site.sorghum.anno.anno.util.AnnoUtil;
import site.sorghum.anno.anno.util.QuerySqlCache;
import site.sorghum.anno.method.resource.ResourceFinder;

import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class MetaClassUtil {
    private static final Set IGNORE_METHOD_NAME = new HashSet<>();

    static {
        IGNORE_METHOD_NAME.add("getClass");
        IGNORE_METHOD_NAME.add("toString");
        IGNORE_METHOD_NAME.add("hashCode");
        IGNORE_METHOD_NAME.add("annotationType");
        IGNORE_METHOD_NAME.add("pkField");
    }

    private static final String THIS_CLASS_KEY = "thisClass";

    private static final Map> DY_TYPE_MAP = new HashMap<>();

    @SneakyThrows
    public static Map> loadSystemClass() {
        // 遍历根目录下所有资源,并过滤保留符合条件的资源
        MultiResource multiResource = ResourceFinder.of().find("dynamic/*.yml");
        Map> map = new HashMap<>();
        final DynamicType.Unloaded[] dynamicType = {null};
        multiResource.iterator().forEachRemaining(resource -> {
            try {
                log.info("加载:{}", resource);
                if (resource.getName().endsWith(".yml")) {
                    String content = resource.getReader(Charset.defaultCharset()).lines().collect(Collectors.joining());
                    map.putAll(loadClass(content, false));
                    ;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        });
        return map;
    }

    @SneakyThrows
    public static Map> loadClass(String ymlContent) {
        return loadClass(ymlContent, false);
    }

    @SneakyThrows
    public static Map> loadClass(String ymlContent, boolean toJar) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        final DynamicType.Unloaded[] dynamicType = {null};
        try {
            Dict dict = YamlUtil.load(new StringReader(ymlContent));
            AnMeta anMeta = JSONUtil.toBean(dict, AnMeta.class);
            DynamicType.Builder builder;
            ByteBuddy byteBuddy = new ByteBuddy();
            if (Objects.nonNull(anMeta.getExtend())) {
                classLoader.loadClass(anMeta.getExtend().getName());
                builder = byteBuddy.subclass(anMeta.getExtend());
            } else {
                builder = byteBuddy.subclass(Object.class);
            }
            builder = builder.name("site.sorghum.anno.meta.proxy.%s".formatted(anMeta.getEntityName()));
            // 定义annoMain
            builder = builder.annotateType(anMeta);
            // 定义annoField
            List anMetaColumns = Optional.ofNullable(anMeta.getColumns()).orElse(Collections.emptyList());
            for (AnField column : anMetaColumns) {
                builder = builder.defineField(
                    column.getJavaName(), column.getJavaType(), Modifier.PUBLIC
                ).annotateField(column);
            }
            List columnButtons = Optional.ofNullable(anMeta.getColumnButtons()).orElse(Collections.emptyList());
            for (AnnoButtonImpl columnBtn : columnButtons) {
                builder = builder.defineField(
                    columnBtn.getName(), Object.class, Modifier.PUBLIC
                ).annotateField(columnBtn);
            }
            if (Objects.isNull(dynamicType[0])) {
                dynamicType[0] = builder.make();
            } else {
                dynamicType[0].include(builder.make());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        if (toJar) {
            dynamicType[0].
                toJar(
                    new File(FileUtil.file("dynamic.jar").
                        getAbsolutePath())
                );
        }
        Map> allLoaded = dynamicType[0].
            load(classLoader).
            getAllLoaded();
        allLoaded.forEach((typeDescription, aClass) -> putDyTypeMap(aClass));
        return allLoaded;
    }

    @SneakyThrows
    public static String class2Yml(Class clazz) {
        Dict dict = class2Dict(clazz, false);
        StringWriter writer = new StringWriter();
        YamlUtil.dump(dict, writer);
        return writer.toString();
    }

    @SneakyThrows
    public static String class2Json(Class clazz) {
        return class2Json(clazz, false);
    }

    @SneakyThrows
    public static String class2Json(Class clazz, boolean deepSuper) {
        return JSONUtil.toJsonString(class2Dict(clazz, deepSuper));
    }

    @SneakyThrows
    public static AnMeta class2AnMeta(Class clazz) {
        Dict classed2Dict = class2Dict(clazz, true);
        // 删除原有thisClass
        String _thisClass = MapUtil.getStr(classed2Dict, THIS_CLASS_KEY);
        classed2Dict.remove(THIS_CLASS_KEY);
        // 获取新的thisClass
        Class thisClass;
        if ((thisClass = DY_TYPE_MAP.get(_thisClass)) == null) {
            thisClass = Class.forName(_thisClass);
        }
        AnMeta anMeta = JSONUtil.toBean(classed2Dict, AnMeta.class);
        // 重新设置 thisClass
        anMeta.setThisClass(thisClass);
        // 重新设置 javaField
        List columns = anMeta.getColumns();
        if (CollUtil.isNotEmpty(columns)) {
            for (AnField column : columns) {
                Field field = ReflectUtil.getField(clazz, column.getJavaName());
                column.setJavaField(field);
                if (column.pkField()) {
                    anMeta.setPkColumn(column);
                }
                // 重新设置tableFieldName
                if (StrUtil.isBlank(column.getTableFieldName())) {
                    column.setTableFieldName(StrUtil.toUnderlineCase(column.getJavaName()));
                    ;
                }
                // 重新设置SqlKey
                AnnoOptionTypeImpl optionType = column.getOptionType();
                if (Objects.nonNull(optionType) && StrUtil.isNotBlank(optionType.sql())) {
                    String sqlKey = QuerySqlCache.generateKey(anMeta.getEntityName(), column.getJavaName(), optionType.sql());
                    optionType.setSqlKey(sqlKey);
                    QuerySqlCache.put(sqlKey, optionType.sql());
                }
                // 重新设置SqlKey
                AnnoTreeTypeImpl treeType = column.getTreeType();
                if (Objects.nonNull(treeType) && StrUtil.isNotBlank(treeType.sql())) {
                    String sqlKey = QuerySqlCache.generateKey(anMeta.getEntityName(), column.getJavaName(), treeType.sql());
                    treeType.setSqlKey(sqlKey);
                    QuerySqlCache.put(sqlKey, treeType.sql());
                }
                // 如果optionEnum是支持的枚举,则重新设置optionData
                Class optionEnum = column.getOptionType().optionEnum();
                if (optionEnum != null && optionEnum != Enum.class) {
                    List optionData = AnnoUtil.enum2OptionData(optionEnum);
                    column.getOptionType().setValue(optionData.toArray(new AnnoOptionTypeImpl.OptionDataImpl[0]));
                }

            }
        }
        // 重新设置 chart的ID
        AnnoChartFieldImpl[] annoChartFields = anMeta.getAnnoChart().getChartFields();
        for (AnnoChartFieldImpl annoChartField : annoChartFields) {
            annoChartField.setId(anMeta.getEntityName() + ":" + MD5Util.digestHex(annoChartField.getName() + annoChartField.getRunSupplier().getName()));
        }

        // 重新设置m2m的ID
        List columnButtons = anMeta.getColumnButtons();
        if (CollUtil.isNotEmpty(columnButtons)) {
            for (AnnoButtonImpl columnBtn : columnButtons) {
                AnnoButtonImpl.M2MJoinButtonImpl m2mJoinButton = columnBtn.getM2mJoinButton();
                if (m2mJoinButton != null && m2mJoinButton.isEnable()) {
                    String id = anMeta.getEntityName() + ":" + MD5Util.digestHex(columnBtn.getName() + columnBtn.m2mJoinButton().mediumTableClazz());
                    m2mJoinButton.setId(id);
                    AnnoMtm.annoMtmMap.put(id, m2mJoinButton);
                }

                AnnoButtonImpl.JavaCmdImpl javaCmd = columnBtn.getJavaCmd();
                if (javaCmd != null && javaCmd.isEnable()) {
                    String id = anMeta.getEntityName() + ":" + MD5Util.digestHex(columnBtn.getName() + columnBtn.getJavaCmd().runSupplier().getName());
                    javaCmd.setId(id);
                    AnnoJavaCmd.annoJavCmdMap.put(id, javaCmd);
                    AnnoJavaCmd.annoJavaCmd2ButtonMap.put(id, columnBtn);
                }

                AnnoTplImpl annoTpl = columnBtn.getAnnoTpl();
                if (annoTpl != null && annoTpl.isEnable()) {
                    String id = BaseTplRender.toId(annoTpl.getTplClazz());
                    annoTpl.setId(id);
                }
            }
        }

        // 重新设置tableButton
        AnnoTableButtonImpl[] annoTableButtons = anMeta.getAnnoTableButton();
        if (annoTableButtons != null) {
            List tableButtons = anMeta.getTableButtons();
            for (AnnoTableButtonImpl annoTableButton : annoTableButtons) {
                AnnoButtonImpl.JavaCmdImpl javaCmd = annoTableButton.getJavaCmd();
                if (javaCmd != null && javaCmd.isEnable()) {
                    String id = anMeta.getEntityName() + ":" + MD5Util.digestHex(annoTableButton.getName() + annoTableButton.getJavaCmd().runSupplier().getName());
                    javaCmd.setId(id);
                    AnnoJavaCmd.annoJavCmdMap.put(id, javaCmd);
                    AnnoJavaCmd.annoJavaCmd2TableButtonMap.put(id, annoTableButton);
                }
                tableButtons.add(annoTableButton);
            }
        }
        return anMeta;
    }


    @SneakyThrows
    public static Dict class2Dict(Class clazz, boolean deepSuper) {
        AnnoMain annoMain = AnnoUtil.getAnnoMain(clazz);
        if (annoMain == null) {
            AnnoForm annoForm = AnnoUtil.getAnnoForm(clazz);
            annoMain = AnnoMainImpl.builder().name(annoForm.name()).virtualTable(true).build();
        }
        StringBuilder yml = new StringBuilder();
        yml.append("thisClass=").append(clazz.getName()).append("\n");
        yml.append("entityName=").append(clazz.getSimpleName()).append("\n");
        yml.append("extend=").append(clazz.getSuperclass().getName()).append("\n");
        printAnnotation(null, annoMain, yml);
        putDyTypeMap(clazz);
        // 正则判断 一行只有 tableName=
        if (yml.toString().contains("tableName=\n")) {
            // 重新设置 tableName
            // @Table 获取
            Table table = AnnotationUtil.getAnnotation(clazz, Table.class);
            if (Objects.nonNull(table)) {
                yml.append("tableName=").append(table.value()).append("\n");
            } else {
                yml.append("tableName=").append(StrUtil.toUnderlineCase(clazz.getSimpleName())).append("\n");
            }
        }
        List annoFields = AnnoUtil.getAnnoFields(clazz, deepSuper);
        int i = 0;
        for (FieldAnnoField annoField : annoFields) {
            int nowValue = i++;
            yml.append("columns[%d].javaName=%s\n".formatted(nowValue, annoField.getField().getName()));
            yml.append("columns[%d].javaType=%s\n".formatted(nowValue, annoField.getField().getType().getName()));
            // 手动设置主键
            if (annoField.getPrimaryKey() != null || annoField.getAnnoField().pkField()) {
                yml.append("columns[%d].pkField=%s\n".formatted(nowValue, true));
            }
            printAnnotation("columns[%d]".formatted(nowValue), annoField.getAnnoField(), yml);
        }
        List buttonFields = AnnoUtil.getAnnoButtonFields(clazz, false);
        i = 0;
        for (Field buttonField : buttonFields) {
            int nowValue = i++;
            yml.append("columnButtons[%d].javaName=%s\n".formatted(nowValue, buttonField.getName()));
            yml.append("columnButtons[%d].javaType=%s\n".formatted(nowValue, buttonField.getType().getName()));
            printAnnotation("columnButtons[%d]".formatted(nowValue), AnnotationUtil.getAnnotation(buttonField, AnnoButton.class), yml);
        }
        // 设置annoRemove信息
        AnnoRemove annoRemove = AnnoUtil.getAnnoRemove(clazz);
        printAnnotation("annoRemove", annoRemove, yml);
        Properties properties = new Properties();
        properties.load(new StringReader(yml.toString()));
        return JSONUtil.toBean(properties, Dict.class);
    }

    @SneakyThrows
    private static void printAnnotation(String superName, Annotation annotation, StringBuilder yml) {
        for (Method publicMethod : ReflectUtil.getPublicMethods(annotation.getClass())) {
            String name = StrUtil.isNotBlank(superName) ? superName + "." + publicMethod.getName() : publicMethod.getName();
            if (IGNORE_METHOD_NAME.contains(publicMethod.getName()) || publicMethod.getParameterCount() > 0) {
                continue;
            }
            Object invoke;
            try {
                invoke = publicMethod.invoke(annotation);
            } catch (Exception e) {
                continue;
            }
            processOne(invoke, name, yml);

        }
    }

    private static void processOne(Object invoke, String name, StringBuilder yml) {
        if (invoke == null) {
            return;
        }
        if (invoke instanceof Annotation _annotation) {
            printAnnotation(name, _annotation, yml);
        } else if (invoke instanceof Object[] objectArray) {
            int i = 0;
            for (Object item : objectArray) {
                processOne(item, name + "[%d]".formatted(i++), yml);
            }
        } else if (invoke.getClass().isArray()) {
            int i = 0;
            while (true) {
                try {
                    Object item = Array.get(invoke, i);
                    processOne(item, name + "[%d]".formatted(i++), yml);
                } catch (Exception e) {
                    break;
                }
            }
        } else if (invoke instanceof Class _clazz) {
            yml.append(name).append("=").append(_clazz.getName()).append("\n");
        } else {
            yml.append(name).append("=").append(invoke).append("\n");
        }
    }


    public static void putDyTypeMap(Class clazz) {
        DY_TYPE_MAP.put(clazz.getName(), clazz);
    }

    public static void main(String[] args) {

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy