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    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        tableInfo.setPrimaryKeyList(idInfos);
400        tableInfo.setColumnInfoList(columnInfoList);
401
402
403        return tableInfo;
404    }
405
406    /**
407     * 创建 typeHandler
408     * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)}
409     *
410     * @param entityClass
411     * @param field
412     * @param typeHandlerClass
413     * @param fieldType
414     */
415    private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) {
416        Class<?> genericClass = null;
417        Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
418        if (genericType instanceof ParameterizedType) {
419            Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
420            if (actualTypeArgument instanceof Class) {
421                genericClass = (Class<?>) actualTypeArgument;
422            }
423        }
424
425        try {
426            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class);
427            return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass);
428        } catch (NoSuchMethodException ignored) {
429        } catch (Exception e) {
430            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
431        }
432        try {
433            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class);
434            return (TypeHandler<?>) constructor.newInstance(fieldType);
435        } catch (NoSuchMethodException ignored) {
436        } catch (Exception e) {
437            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
438        }
439        try {
440            Constructor<?> c = typeHandlerClass.getConstructor();
441            return (TypeHandler<?>) c.newInstance();
442        } catch (Exception e) {
443            throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
444        }
445    }
446
447
448    static String getColumnName(boolean isCamelToUnderline, Field field, Column column) {
449        if (column != null && StringUtil.isNotBlank(column.value())) {
450            return column.value();
451        }
452        if (isCamelToUnderline) {
453            return StringUtil.camelToUnderline(field.getName());
454        }
455        return field.getName();
456    }
457
458
459    public static List<Field> getColumnFields(Class<?> entityClass) {
460        List<Field> fields = new ArrayList<>();
461        doGetFields(entityClass, fields);
462        return fields;
463    }
464
465
466    private static void doGetFields(Class<?> entityClass, List<Field> fields) {
467        if (entityClass == null || entityClass == Object.class) {
468            return;
469        }
470
471        Field[] declaredFields = entityClass.getDeclaredFields();
472        for (Field declaredField : declaredFields) {
473            if (Modifier.isStatic(declaredField.getModifiers())
474                || existName(fields, declaredField)) {
475                continue;
476            }
477            fields.add(declaredField);
478        }
479
480        doGetFields(entityClass.getSuperclass(), fields);
481    }
482
483
484    private static boolean existName(List<Field> fields, Field field) {
485        for (Field f : fields) {
486            if (f.getName().equalsIgnoreCase(field.getName())) {
487                return true;
488            }
489        }
490        return false;
491    }
492
493}