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        Type[] genericInterfaces = mapperClass.getGenericInterfaces();
122        if (genericInterfaces.length == 1) {
123            Type type = genericInterfaces[0];
124            if (type instanceof ParameterizedType) {
125                Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
126                return actualTypeArgument instanceof Class ? (Class<?>) actualTypeArgument : null;
127            } else if (type instanceof Class) {
128                return getEntityClass((Class<?>) type);
129            }
130        }
131        return getEntityClass(mapperClass.getSuperclass());
132    }
133
134
135    private static TableInfo createTableInfo(Class<?> entityClass) {
136
137        TableInfo tableInfo = new TableInfo();
138        tableInfo.setEntityClass(entityClass);
139        Reflector reflector = Reflectors.of(entityClass);
140        tableInfo.setReflector(reflector);
141
142        //初始化表名
143        Table table = entityClass.getAnnotation(Table.class);
144        if (table != null) {
145            tableInfo.setSchema(table.schema());
146            tableInfo.setTableName(table.value());
147            tableInfo.setCamelToUnderline(table.camelToUnderline());
148
149            if (table.onInsert().length > 0) {
150                List<InsertListener> insertListeners = Arrays.stream(table.onInsert())
151                    .filter(listener -> listener != NoneListener.class)
152                    .map(ClassUtil::newInstance)
153                    .collect(Collectors.toList());
154                tableInfo.setOnInsertListeners(insertListeners);
155            }
156
157            if (table.onUpdate().length > 0) {
158                List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate())
159                    .filter(listener -> listener != NoneListener.class)
160                    .map(ClassUtil::newInstance)
161                    .collect(Collectors.toList());
162                tableInfo.setOnUpdateListeners(updateListeners);
163            }
164
165            if (table.onSet().length > 0) {
166                List<SetListener> setListeners = Arrays.stream(table.onSet())
167                    .filter(listener -> listener != NoneListener.class)
168                    .map(ClassUtil::newInstance)
169                    .collect(Collectors.toList());
170                tableInfo.setOnSetListeners(setListeners);
171            }
172
173            if (StringUtil.isNotBlank(table.dataSource())) {
174                tableInfo.setDataSource(table.dataSource());
175            }
176        } else {
177            //默认为类名转驼峰下划线
178            String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
179            tableInfo.setTableName(tableName);
180        }
181
182        //初始化字段相关
183        List<ColumnInfo> columnInfoList = new ArrayList<>();
184        List<IdInfo> idInfos = new ArrayList<>();
185
186
187        String logicDeleteColumn = null;
188        String versionColumn = null;
189        String tenantIdColumn = null;
190
191        //数据插入时,默认插入数据字段
192        Map<String, String> onInsertColumns = new HashMap<>();
193
194        //数据更新时,默认更新内容的字段
195        Map<String, String> onUpdateColumns = new HashMap<>();
196
197        //大字段列
198        Set<String> largeColumns = new LinkedHashSet<>();
199
200        // 默认查询列
201        Set<String> defaultQueryColumns = new LinkedHashSet<>();
202
203        List<Field> entityFields = getColumnFields(entityClass);
204
205        FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig();
206
207        for (Field field : entityFields) {
208
209            Class<?> fieldType = reflector.getGetterType(field.getName());
210
211            //移除默认的忽略字段
212            boolean isIgnoreField = false;
213            for (Class<?> ignoreColumnType : ignoreColumnTypes) {
214                if (ignoreColumnType.isAssignableFrom(fieldType)) {
215                    isIgnoreField = true;
216                    break;
217                }
218            }
219
220            if (isIgnoreField) {
221                continue;
222            }
223
224            Column columnAnnotation = field.getAnnotation(Column.class);
225
226            //满足以下 3 种情况,不支持该类型
227            if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler
228                && !fieldType.isEnum()   // 类型不是枚举
229                && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型
230            ) {
231                // 忽略 集合 实体类 解析
232                if (columnAnnotation != null && columnAnnotation.ignore()) {
233                    continue;
234                }
235                // 集合嵌套
236                if (Collection.class.isAssignableFrom(fieldType)) {
237                    Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
238                    if (genericType instanceof ParameterizedType) {
239                        Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
240                        if (actualTypeArgument instanceof Class) {
241                            tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument);
242                        }
243                    }
244                }
245                // 实体类嵌套
246                else if (!Map.class.isAssignableFrom(fieldType)
247                    && !fieldType.isArray()) {
248                    tableInfo.addAssociationType(field.getName(), fieldType);
249                }
250                // 不支持的类型直接跳过
251                continue;
252            }
253
254            //列名
255            String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation);
256
257            //逻辑删除字段
258            if ((columnAnnotation != null && columnAnnotation.isLogicDelete())
259                || columnName.equals(config.getLogicDeleteColumn())) {
260                if (logicDeleteColumn == null) {
261                    logicDeleteColumn = columnName;
262                } else {
263                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
264                }
265            }
266
267            //乐观锁版本字段
268            if ((columnAnnotation != null && columnAnnotation.version())
269                || columnName.equals(config.getVersionColumn())) {
270                if (versionColumn == null) {
271                    versionColumn = columnName;
272                } else {
273                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
274                }
275            }
276
277            //租户ID 字段
278            if ((columnAnnotation != null && columnAnnotation.tenantId())
279                || columnName.equals(config.getTenantColumn())) {
280                if (tenantIdColumn == null) {
281                    tenantIdColumn = columnName;
282                } else {
283                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
284                }
285            }
286
287
288            if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) {
289                onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim());
290            }
291
292
293            if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) {
294                onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim());
295            }
296
297
298            if (columnAnnotation != null && columnAnnotation.isLarge()) {
299                largeColumns.add(columnName);
300            }
301
302            //主键配置
303            Id id = field.getAnnotation(Id.class);
304            ColumnInfo columnInfo;
305            if (id != null) {
306                columnInfo = new IdInfo(id);
307                idInfos.add((IdInfo) columnInfo);
308            } else {
309                columnInfo = new ColumnInfo();
310                columnInfoList.add(columnInfo);
311            }
312
313            ColumnAlias columnAlias = null;
314
315            // 属性上没有别名,查找 getter 方法上有没有别名
316            Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName()));
317            if (getterMethod != null) {
318                columnAlias = getterMethod.getAnnotation(ColumnAlias.class);
319            }
320
321            if (columnAlias == null) {
322                columnAlias = field.getAnnotation(ColumnAlias.class);
323            }
324
325            if (columnAlias != null) {
326                columnInfo.setAlias(columnAlias.value());
327            }
328
329            columnInfo.setColumn(columnName);
330            columnInfo.setProperty(field.getName());
331            columnInfo.setPropertyType(fieldType);
332            columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore());
333
334
335            // 默认查询列 没有忽略且不是大字段
336            if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) {
337                defaultQueryColumns.add(columnName);
338            }
339
340
341            //typeHandler 配置
342            if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) {
343                TypeHandler<?> typeHandler;
344
345                //集合类型,支持泛型
346                //fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE
347                if (Collection.class.isAssignableFrom(fieldType)) {
348                    typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType);
349                }
350
351                //非集合类型
352                else {
353                    Class<?> typeHandlerClass = columnAnnotation.typeHandler();
354                    Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration();
355                    TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
356                    typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass);
357                }
358
359                columnInfo.setTypeHandler(typeHandler);
360            }
361
362            // 数据脱敏配置
363            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
364            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
365                if (String.class != fieldType) {
366                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
367                }
368                columnInfo.setMaskType(columnMask.value().trim());
369            }
370
371            // jdbcType 配置
372            if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) {
373                columnInfo.setJdbcType(columnAnnotation.jdbcType());
374            }
375
376        }
377
378
379        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
380        tableInfo.setVersionColumn(versionColumn);
381        tableInfo.setTenantIdColumn(tenantIdColumn);
382
383        if (!onInsertColumns.isEmpty()) {
384            tableInfo.setOnInsertColumns(onInsertColumns);
385        }
386
387        if (!onUpdateColumns.isEmpty()) {
388            tableInfo.setOnUpdateColumns(onUpdateColumns);
389        }
390
391        if (!largeColumns.isEmpty()) {
392            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
393        }
394
395        if (!defaultQueryColumns.isEmpty()) {
396            tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0]));
397        }
398
399        // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column,
400        // 否则会影响 SQL 的字段构建顺序
401        tableInfo.setPrimaryKeyList(idInfos);
402        tableInfo.setColumnInfoList(columnInfoList);
403
404
405        return tableInfo;
406    }
407
408    /**
409     * 创建 typeHandler
410     * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)}
411     *
412     * @param entityClass
413     * @param field
414     * @param typeHandlerClass
415     * @param fieldType
416     */
417    private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) {
418        Class<?> genericClass = null;
419        Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
420        if (genericType instanceof ParameterizedType) {
421            Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
422            if (actualTypeArgument instanceof Class) {
423                genericClass = (Class<?>) actualTypeArgument;
424            }
425        }
426
427        try {
428            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class);
429            return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass);
430        } catch (NoSuchMethodException ignored) {
431        } catch (Exception e) {
432            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
433        }
434        try {
435            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class);
436            return (TypeHandler<?>) constructor.newInstance(fieldType);
437        } catch (NoSuchMethodException ignored) {
438        } catch (Exception e) {
439            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
440        }
441        try {
442            Constructor<?> c = typeHandlerClass.getConstructor();
443            return (TypeHandler<?>) c.newInstance();
444        } catch (Exception e) {
445            throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
446        }
447    }
448
449
450    static String getColumnName(boolean isCamelToUnderline, Field field, Column column) {
451        if (column != null && StringUtil.isNotBlank(column.value())) {
452            return column.value();
453        }
454        if (isCamelToUnderline) {
455            return StringUtil.camelToUnderline(field.getName());
456        }
457        return field.getName();
458    }
459
460
461    public static List<Field> getColumnFields(Class<?> entityClass) {
462        List<Field> fields = new ArrayList<>();
463        doGetFields(entityClass, fields);
464        return fields;
465    }
466
467
468    private static void doGetFields(Class<?> entityClass, List<Field> fields) {
469        if (entityClass == null || entityClass == Object.class) {
470            return;
471        }
472
473        Field[] declaredFields = entityClass.getDeclaredFields();
474        for (Field declaredField : declaredFields) {
475            if (Modifier.isStatic(declaredField.getModifiers())
476                || existName(fields, declaredField)) {
477                continue;
478            }
479            fields.add(declaredField);
480        }
481
482        doGetFields(entityClass.getSuperclass(), fields);
483    }
484
485
486    private static boolean existName(List<Field> fields, Field field) {
487        for (Field f : fields) {
488            if (f.getName().equalsIgnoreCase(field.getName())) {
489                return true;
490            }
491        }
492        return false;
493    }
494
495}