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.table;
017
018import com.mybatisflex.annotation.*;
019import com.mybatisflex.core.BaseMapper;
020import com.mybatisflex.core.FlexGlobalConfig;
021import com.mybatisflex.core.exception.FlexExceptions;
022import com.mybatisflex.core.query.QueryChain;
023import com.mybatisflex.core.query.QueryColumn;
024import com.mybatisflex.core.query.QueryCondition;
025import com.mybatisflex.core.query.QueryWrapper;
026import com.mybatisflex.core.util.ClassUtil;
027import com.mybatisflex.core.util.CollectionUtil;
028import com.mybatisflex.core.util.Reflectors;
029import com.mybatisflex.core.util.StringUtil;
030import org.apache.ibatis.io.ResolverUtil;
031import org.apache.ibatis.reflection.Reflector;
032import org.apache.ibatis.reflection.TypeParameterResolver;
033import org.apache.ibatis.session.Configuration;
034import org.apache.ibatis.type.*;
035import org.apache.ibatis.util.MapUtil;
036
037import java.lang.reflect.*;
038import java.math.BigDecimal;
039import java.math.BigInteger;
040import java.sql.Time;
041import java.sql.Timestamp;
042import java.time.*;
043import java.time.chrono.JapaneseDate;
044import java.util.*;
045import java.util.concurrent.ConcurrentHashMap;
046import java.util.stream.Collectors;
047
048public class TableInfoFactory {
049
050    private TableInfoFactory() {
051    }
052
053    public static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet(
054        int.class, Integer.class,
055        short.class, Short.class,
056        long.class, Long.class,
057        float.class, Float.class,
058        double.class, Double.class,
059        boolean.class, Boolean.class,
060        Date.class, java.sql.Date.class, Time.class, Timestamp.class,
061        Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class,
062        Year.class, Month.class, YearMonth.class, JapaneseDate.class,
063        byte[].class, Byte[].class, Byte.class,
064        BigInteger.class, BigDecimal.class,
065        char.class, String.class, Character.class
066    );
067
068    static final Set<Class<?>> ignoreColumnTypes = CollectionUtil.newHashSet(
069        QueryWrapper.class, QueryColumn.class, QueryCondition.class, QueryChain.class
070    );
071
072
073    private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>();
074    private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>();
075    private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>();
076    private static final Set<String> initedPackageNames = new HashSet<>();
077
078
079    public synchronized static void init(String mapperPackageName) {
080        if (!initedPackageNames.contains(mapperPackageName)) {
081            ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
082            resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName);
083            Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
084            for (Class<? extends Class<?>> mapperClass : mapperSet) {
085                ofMapperClass(mapperClass);
086            }
087            initedPackageNames.add(mapperPackageName);
088        }
089    }
090
091
092    public static TableInfo ofMapperClass(Class<?> mapperClass) {
093        return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> {
094            Class<?> entityClass = getEntityClass(mapperClass);
095            if (entityClass == null) {
096                return null;
097            }
098            return ofEntityClass(entityClass);
099        });
100    }
101
102
103    public static TableInfo ofEntityClass(Class<?> entityClass) {
104        return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> {
105            TableInfo tableInfo = createTableInfo(entityClass);
106            tableInfoMap.put(tableInfo.getTableNameWithSchema(), tableInfo);
107            return tableInfo;
108        });
109    }
110
111
112    public static TableInfo ofTableName(String tableName) {
113        return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null;
114    }
115
116
117    private static Class<?> getEntityClass(Class<?> mapperClass) {
118        if (mapperClass == null || mapperClass == Object.class) {
119            return null;
120        }
121        return getEntityClass(mapperClass, null);
122    }
123
124    private static Class<?> getEntityClass(Class<?> mapperClass, Type[] actualTypeArguments) {
125        // 检查基接口
126        Type[] genericInterfaces = mapperClass.getGenericInterfaces();
127        for (Type type : genericInterfaces) {
128            if (type instanceof ParameterizedType) {
129                // 泛型基接口
130                ParameterizedType parameterizedType = (ParameterizedType) type;
131                Type rawType = parameterizedType.getRawType();
132                Type[] typeArguments = parameterizedType.getActualTypeArguments();
133                adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments);
134                if (rawType == BaseMapper.class) {
135                    // 找到了
136                    if (typeArguments[0] instanceof Class) {
137                        return (Class<?>) typeArguments[0];
138                    }
139                } else if (rawType instanceof Class) {
140                    // 其他泛型基接口
141                    Class<?> entityClass = getEntityClass((Class<?>) rawType, typeArguments);
142                    if (entityClass != null) {
143                        return entityClass;
144                    }
145                }
146            } else if (type instanceof Class) {
147                // 其他基接口
148                Class<?> entityClass = getEntityClass((Class<?>) type);
149                if (entityClass != null) {
150                    return entityClass;
151                }
152            }
153        }
154        // 检查基类
155        Class<?> superclass = mapperClass.getSuperclass();
156        if (superclass == null || superclass == Object.class) {
157            return null;
158        }
159        Type[] typeArguments = superclass.getTypeParameters();
160        adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments);
161        return getEntityClass(superclass, typeArguments);
162    }
163
164    private static void adjustTypeArguments(Class<?> subclass, Type[] subclassTypeArguments, Type[] typeArguments) {
165        for (int i = 0; i < typeArguments.length; i++) {
166            if (typeArguments[i] instanceof TypeVariable) {
167                TypeVariable<?> typeVariable = (TypeVariable<?>) typeArguments[i];
168                TypeVariable<?>[] typeParameters = subclass.getTypeParameters();
169                for (int j = 0; j < typeParameters.length; j++) {
170                    if (Objects.equals(typeVariable.getName(), typeParameters[j].getName())) {
171                        typeArguments[i] = subclassTypeArguments[j];
172                        break;
173                    }
174                }
175            }
176        }
177    }
178
179
180    private static TableInfo createTableInfo(Class<?> entityClass) {
181
182        TableInfo tableInfo = new TableInfo();
183        tableInfo.setEntityClass(entityClass);
184        Reflector reflector = Reflectors.of(entityClass);
185        tableInfo.setReflector(reflector);
186
187        //初始化表名
188        Table table = entityClass.getAnnotation(Table.class);
189        if (table != null) {
190            tableInfo.setSchema(table.schema());
191            tableInfo.setTableName(table.value());
192            tableInfo.setCamelToUnderline(table.camelToUnderline());
193
194            if (table.onInsert().length > 0) {
195                List<InsertListener> insertListeners = Arrays.stream(table.onInsert())
196                    .filter(listener -> listener != NoneListener.class)
197                    .map(ClassUtil::newInstance)
198                    .collect(Collectors.toList());
199                tableInfo.setOnInsertListeners(insertListeners);
200            }
201
202            if (table.onUpdate().length > 0) {
203                List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate())
204                    .filter(listener -> listener != NoneListener.class)
205                    .map(ClassUtil::newInstance)
206                    .collect(Collectors.toList());
207                tableInfo.setOnUpdateListeners(updateListeners);
208            }
209
210            if (table.onSet().length > 0) {
211                List<SetListener> setListeners = Arrays.stream(table.onSet())
212                    .filter(listener -> listener != NoneListener.class)
213                    .map(ClassUtil::newInstance)
214                    .collect(Collectors.toList());
215                tableInfo.setOnSetListeners(setListeners);
216            }
217
218            if (StringUtil.isNotBlank(table.dataSource())) {
219                tableInfo.setDataSource(table.dataSource());
220            }
221        } else {
222            //默认为类名转驼峰下划线
223            String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
224            tableInfo.setTableName(tableName);
225        }
226
227        //初始化字段相关
228        List<ColumnInfo> columnInfoList = new ArrayList<>();
229        List<IdInfo> idInfos = new ArrayList<>();
230
231
232        String logicDeleteColumn = null;
233        String versionColumn = null;
234        String tenantIdColumn = null;
235
236        //数据插入时,默认插入数据字段
237        Map<String, String> onInsertColumns = new HashMap<>();
238
239        //数据更新时,默认更新内容的字段
240        Map<String, String> onUpdateColumns = new HashMap<>();
241
242        //大字段列
243        Set<String> largeColumns = new LinkedHashSet<>();
244
245        // 默认查询列
246        Set<String> defaultQueryColumns = new LinkedHashSet<>();
247
248        List<Field> entityFields = getColumnFields(entityClass);
249
250        FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig();
251
252        for (Field field : entityFields) {
253
254            Class<?> fieldType = reflector.getGetterType(field.getName());
255
256            //移除默认的忽略字段
257            boolean isIgnoreField = false;
258            for (Class<?> ignoreColumnType : ignoreColumnTypes) {
259                if (ignoreColumnType.isAssignableFrom(fieldType)) {
260                    isIgnoreField = true;
261                    break;
262                }
263            }
264
265            if (isIgnoreField) {
266                continue;
267            }
268
269            Column columnAnnotation = field.getAnnotation(Column.class);
270
271            //满足以下 3 种情况,不支持该类型
272            if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler
273                && !fieldType.isEnum()   // 类型不是枚举
274                && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型
275            ) {
276                // 忽略 集合 实体类 解析
277                if (columnAnnotation != null && columnAnnotation.ignore()) {
278                    continue;
279                }
280                // 集合嵌套
281                if (Collection.class.isAssignableFrom(fieldType)) {
282                    Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
283                    if (genericType instanceof ParameterizedType) {
284                        Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
285                        if (actualTypeArgument instanceof Class) {
286                            tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument);
287                        }
288                    }
289                }
290                // 实体类嵌套
291                else if (!Map.class.isAssignableFrom(fieldType)
292                    && !fieldType.isArray()) {
293                    tableInfo.addAssociationType(field.getName(), fieldType);
294                }
295                // 不支持的类型直接跳过
296                continue;
297            }
298
299            //列名
300            String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation);
301
302            //逻辑删除字段
303            if ((columnAnnotation != null && columnAnnotation.isLogicDelete())
304                || columnName.equals(config.getLogicDeleteColumn())) {
305                if (logicDeleteColumn == null) {
306                    logicDeleteColumn = columnName;
307                } else {
308                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
309                }
310            }
311
312            //乐观锁版本字段
313            if ((columnAnnotation != null && columnAnnotation.version())
314                || columnName.equals(config.getVersionColumn())) {
315                if (versionColumn == null) {
316                    versionColumn = columnName;
317                } else {
318                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
319                }
320            }
321
322            //租户ID 字段
323            if ((columnAnnotation != null && columnAnnotation.tenantId())
324                || columnName.equals(config.getTenantColumn())) {
325                if (tenantIdColumn == null) {
326                    tenantIdColumn = columnName;
327                } else {
328                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
329                }
330            }
331
332
333            if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) {
334                onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim());
335            }
336
337
338            if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) {
339                onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim());
340            }
341
342
343            if (columnAnnotation != null && columnAnnotation.isLarge()) {
344                largeColumns.add(columnName);
345            }
346
347            //主键配置
348            Id id = field.getAnnotation(Id.class);
349            ColumnInfo columnInfo;
350            if (id != null) {
351                columnInfo = new IdInfo(id);
352                idInfos.add((IdInfo) columnInfo);
353            } else {
354                columnInfo = new ColumnInfo();
355                columnInfoList.add(columnInfo);
356            }
357
358            ColumnAlias columnAlias = null;
359
360            // 属性上没有别名,查找 getter 方法上有没有别名
361            Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName()));
362            if (getterMethod != null) {
363                columnAlias = getterMethod.getAnnotation(ColumnAlias.class);
364            }
365
366            if (columnAlias == null) {
367                columnAlias = field.getAnnotation(ColumnAlias.class);
368            }
369
370            if (columnAlias != null) {
371                columnInfo.setAlias(columnAlias.value());
372            }
373
374            columnInfo.setColumn(columnName);
375            columnInfo.setProperty(field.getName());
376            columnInfo.setPropertyType(fieldType);
377            columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore());
378
379
380            // 默认查询列 没有忽略且不是大字段
381            if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) {
382                defaultQueryColumns.add(columnName);
383            }
384
385
386            //typeHandler 配置
387            if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) {
388                TypeHandler<?> typeHandler;
389
390                //集合类型,支持泛型
391                //fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE
392                if (Collection.class.isAssignableFrom(fieldType)) {
393                    typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType);
394                }
395
396                //非集合类型
397                else {
398                    Class<?> typeHandlerClass = columnAnnotation.typeHandler();
399                    Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration();
400                    TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
401                    typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass);
402                }
403
404                columnInfo.setTypeHandler(typeHandler);
405            }
406
407            // 数据脱敏配置
408            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
409            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
410                if (String.class != fieldType) {
411                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
412                }
413                columnInfo.setMaskType(columnMask.value().trim());
414            }
415
416            // jdbcType 配置
417            if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) {
418                columnInfo.setJdbcType(columnAnnotation.jdbcType());
419            }
420
421        }
422
423
424        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
425        tableInfo.setVersionColumn(versionColumn);
426        tableInfo.setTenantIdColumn(tenantIdColumn);
427
428        if (!onInsertColumns.isEmpty()) {
429            tableInfo.setOnInsertColumns(onInsertColumns);
430        }
431
432        if (!onUpdateColumns.isEmpty()) {
433            tableInfo.setOnUpdateColumns(onUpdateColumns);
434        }
435
436        if (!largeColumns.isEmpty()) {
437            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
438        }
439
440        if (!defaultQueryColumns.isEmpty()) {
441            tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0]));
442        }
443
444        // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column,
445        // 否则会影响 SQL 的字段构建顺序
446        tableInfo.setPrimaryKeyList(idInfos);
447        tableInfo.setColumnInfoList(columnInfoList);
448
449
450        return tableInfo;
451    }
452
453    /**
454     * 创建 typeHandler
455     * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)}
456     *
457     * @param entityClass
458     * @param field
459     * @param typeHandlerClass
460     * @param fieldType
461     */
462    private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) {
463        Class<?> genericClass = null;
464        Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
465        if (genericType instanceof ParameterizedType) {
466            Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
467            if (actualTypeArgument instanceof Class) {
468                genericClass = (Class<?>) actualTypeArgument;
469            }
470        }
471
472        try {
473            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class);
474            return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass);
475        } catch (NoSuchMethodException ignored) {
476        } catch (Exception e) {
477            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
478        }
479        try {
480            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class);
481            return (TypeHandler<?>) constructor.newInstance(fieldType);
482        } catch (NoSuchMethodException ignored) {
483        } catch (Exception e) {
484            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
485        }
486        try {
487            Constructor<?> c = typeHandlerClass.getConstructor();
488            return (TypeHandler<?>) c.newInstance();
489        } catch (Exception e) {
490            throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
491        }
492    }
493
494
495    static String getColumnName(boolean isCamelToUnderline, Field field, Column column) {
496        if (column != null && StringUtil.isNotBlank(column.value())) {
497            return column.value();
498        }
499        if (isCamelToUnderline) {
500            return StringUtil.camelToUnderline(field.getName());
501        }
502        return field.getName();
503    }
504
505
506    public static List<Field> getColumnFields(Class<?> entityClass) {
507        List<Field> fields = new ArrayList<>();
508        doGetFields(entityClass, fields);
509        return fields;
510    }
511
512
513    private static void doGetFields(Class<?> entityClass, List<Field> fields) {
514        if (entityClass == null || entityClass == Object.class) {
515            return;
516        }
517
518        Field[] declaredFields = entityClass.getDeclaredFields();
519        for (Field declaredField : declaredFields) {
520            if (Modifier.isStatic(declaredField.getModifiers())
521                || existName(fields, declaredField)) {
522                continue;
523            }
524            fields.add(declaredField);
525        }
526
527        doGetFields(entityClass.getSuperclass(), fields);
528    }
529
530
531    private static boolean existName(List<Field> fields, Field field) {
532        for (Field f : fields) {
533            if (f.getName().equalsIgnoreCase(field.getName())) {
534                return true;
535            }
536        }
537        return false;
538    }
539
540}