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.FlexConsts;
021import com.mybatisflex.core.FlexGlobalConfig;
022import com.mybatisflex.core.exception.FlexExceptions;
023import com.mybatisflex.core.util.ClassUtil;
024import com.mybatisflex.core.util.CollectionUtil;
025import com.mybatisflex.core.util.Reflectors;
026import com.mybatisflex.core.util.StringUtil;
027import org.apache.ibatis.io.ResolverUtil;
028import org.apache.ibatis.reflection.Reflector;
029import org.apache.ibatis.reflection.TypeParameterResolver;
030import org.apache.ibatis.session.Configuration;
031import org.apache.ibatis.type.JdbcType;
032import org.apache.ibatis.type.TypeHandler;
033import org.apache.ibatis.type.TypeHandlerRegistry;
034import org.apache.ibatis.type.UnknownTypeHandler;
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
069    private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>();
070    private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>();
071    private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>();
072    private static final Set<String> initedPackageNames = new HashSet<>();
073
074
075    public synchronized static void init(String mapperPackageName) {
076        if (!initedPackageNames.contains(mapperPackageName)) {
077            ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
078            resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName);
079            Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
080            for (Class<? extends Class<?>> mapperClass : mapperSet) {
081                ofMapperClass(mapperClass);
082            }
083            initedPackageNames.add(mapperPackageName);
084        }
085    }
086
087
088    public static TableInfo ofMapperClass(Class<?> mapperClass) {
089        return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> {
090            Class<?> entityClass = getEntityClass(mapperClass);
091            if (entityClass == null) {
092                return null;
093            }
094            return ofEntityClass(entityClass);
095        });
096    }
097
098
099    public static TableInfo ofEntityClass(Class<?> entityClass) {
100        return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> {
101            TableInfo tableInfo = createTableInfo(entityClass);
102            tableInfoMap.put(tableInfo.getTableNameWithSchema(), tableInfo);
103            return tableInfo;
104        });
105    }
106
107
108    public static TableInfo ofTableName(String tableName) {
109        return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null;
110    }
111
112
113    private static Class<?> getEntityClass(Class<?> mapperClass) {
114        if (mapperClass == null || mapperClass == Object.class) {
115            return null;
116        }
117        Type[] genericInterfaces = mapperClass.getGenericInterfaces();
118        if (genericInterfaces.length == 1) {
119            Type type = genericInterfaces[0];
120            if (type instanceof ParameterizedType) {
121                Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
122                return actualTypeArgument instanceof Class ? (Class<?>) actualTypeArgument : null;
123            } else if (type instanceof Class) {
124                return getEntityClass((Class<?>) type);
125            }
126        }
127        return getEntityClass(mapperClass.getSuperclass());
128    }
129
130
131    private static TableInfo createTableInfo(Class<?> entityClass) {
132
133        TableInfo tableInfo = new TableInfo();
134        tableInfo.setEntityClass(entityClass);
135        Reflector reflector = Reflectors.of(entityClass);
136        tableInfo.setReflector(reflector);
137
138        //初始化表名
139        Table table = entityClass.getAnnotation(Table.class);
140        if (table != null) {
141            tableInfo.setTableName(table.value());
142            tableInfo.setSchema(table.schema());
143            tableInfo.setCamelToUnderline(table.camelToUnderline());
144
145            if (table.onInsert().length > 0) {
146                List<InsertListener> insertListeners = Arrays.stream(table.onInsert())
147                    .filter(listener -> listener != NoneListener.class)
148                    .map(ClassUtil::newInstance)
149                    .collect(Collectors.toList());
150                tableInfo.setOnInsertListeners(insertListeners);
151            }
152
153            if (table.onUpdate().length > 0) {
154                List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate())
155                    .filter(listener -> listener != NoneListener.class)
156                    .map(ClassUtil::newInstance)
157                    .collect(Collectors.toList());
158                tableInfo.setOnUpdateListeners(updateListeners);
159            }
160
161            if (table.onSet().length > 0) {
162                List<SetListener> setListeners = Arrays.stream(table.onSet())
163                    .filter(listener -> listener != NoneListener.class)
164                    .map(ClassUtil::newInstance)
165                    .collect(Collectors.toList());
166                tableInfo.setOnSetListeners(setListeners);
167            }
168
169            if (StringUtil.isNotBlank(table.dataSource())) {
170                tableInfo.setDataSource(table.dataSource());
171            }
172        } else {
173            //默认为类名转驼峰下划线
174            String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
175            tableInfo.setTableName(tableName);
176        }
177
178        //初始化字段相关
179        List<ColumnInfo> columnInfoList = new ArrayList<>();
180        List<IdInfo> idInfos = new ArrayList<>();
181
182        Field idField = null;
183
184        String logicDeleteColumn = null;
185        String versionColumn = null;
186        String tenantIdColumn = null;
187
188        //数据插入时,默认插入数据字段
189        Map<String, String> onInsertColumns = new HashMap<>();
190
191        //数据更新时,默认更新内容的字段
192        Map<String, String> onUpdateColumns = new HashMap<>();
193
194        //大字段列
195        Set<String> largeColumns = new LinkedHashSet<>();
196
197        // 默认查询列
198        Set<String> defaultQueryColumns = new LinkedHashSet<>();
199
200
201        List<Field> entityFields = getColumnFields(entityClass);
202
203        for (Field field : entityFields) {
204
205            Column column = field.getAnnotation(Column.class);
206            if (column != null && column.ignore()) {
207                continue; // ignore
208            }
209
210            Class<?> fieldType = reflector.getGetterType(field.getName());
211
212            //满足以下 3 种情况,不支持该类型
213            if ((column == null || column.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler
214                && !fieldType.isEnum()   // 类型不是枚举
215                && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型
216            ) {
217                // 集合嵌套
218                if (Collection.class.isAssignableFrom(fieldType)) {
219                    Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
220                    if (genericType instanceof ParameterizedType) {
221                        Class<?> actualTypeArgument = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
222                        tableInfo.addCollectionType(field, actualTypeArgument);
223                    }
224                }
225                // 实体类嵌套
226                else if (!Map.class.isAssignableFrom(fieldType)
227                    && !fieldType.isArray()) {
228                    tableInfo.addAssociationType(field.getName(), fieldType);
229                }
230                // 不支持的类型直接跳过
231                continue;
232            }
233
234            //列名
235            String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, column);
236
237            //逻辑删除字段
238            if (column != null && column.isLogicDelete()) {
239                if (logicDeleteColumn == null) {
240                    logicDeleteColumn = columnName;
241                } else {
242                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
243                }
244            }
245
246            //乐观锁版本字段
247            if (column != null && column.version()) {
248                if (versionColumn == null) {
249                    versionColumn = columnName;
250                } else {
251                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
252                }
253            }
254
255            //租户ID 字段
256            if (column != null && column.tenantId()) {
257                if (tenantIdColumn == null) {
258                    tenantIdColumn = columnName;
259                } else {
260                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
261                }
262            }
263
264            if (column != null && StringUtil.isNotBlank(column.onInsertValue())) {
265                onInsertColumns.put(columnName, column.onInsertValue().trim());
266            }
267
268
269            if (column != null && StringUtil.isNotBlank(column.onUpdateValue())) {
270                onUpdateColumns.put(columnName, column.onUpdateValue().trim());
271            }
272
273
274            if (column != null && column.isLarge()) {
275                largeColumns.add(columnName);
276            }
277
278            if (column == null || !column.isLarge()) {
279                defaultQueryColumns.add(columnName);
280            }
281
282            Id id = field.getAnnotation(Id.class);
283            ColumnInfo columnInfo;
284            if (id != null) {
285                columnInfo = new IdInfo(id);
286                idInfos.add((IdInfo) columnInfo);
287            } else {
288                columnInfo = new ColumnInfo();
289                columnInfoList.add(columnInfo);
290            }
291
292            ColumnAlias columnAlias = null;
293            // 属性上没有别名,查找 getter 方法上有没有别名
294            Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName()));
295            if (getterMethod != null) {
296                columnAlias = getterMethod.getAnnotation(ColumnAlias.class);
297            }
298
299            if (columnAlias == null) {
300                columnAlias = field.getAnnotation(ColumnAlias.class);
301            }
302
303            if (columnAlias != null) {
304                columnInfo.setAlias(columnAlias.value());
305            }
306
307            columnInfo.setColumn(columnName);
308            columnInfo.setProperty(field.getName());
309            columnInfo.setPropertyType(fieldType);
310
311            if (column != null && column.typeHandler() != UnknownTypeHandler.class) {
312                Class<?> typeHandlerClass = column.typeHandler();
313                Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration();
314                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
315                TypeHandler<?> typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass);
316                columnInfo.setTypeHandler(typeHandler);
317            }
318
319            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
320            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
321                if (String.class != fieldType) {
322                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
323                }
324                columnInfo.setMaskType(columnMask.value().trim());
325            }
326
327            if (column != null && column.jdbcType() != JdbcType.UNDEFINED) {
328                columnInfo.setJdbcType(column.jdbcType());
329            }
330
331            if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(field.getName())) {
332                idField = field;
333            }
334        }
335
336
337        if (idInfos.isEmpty() && idField != null) {
338            int index = -1;
339            for (int i = 0; i < columnInfoList.size(); i++) {
340                ColumnInfo columnInfo = columnInfoList.get(i);
341                if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(columnInfo.getProperty())) {
342                    index = i;
343                    break;
344                }
345            }
346            if (index >= 0) {
347                ColumnInfo removedColumnInfo = columnInfoList.remove(index);
348                idInfos.add(new IdInfo(removedColumnInfo));
349            }
350        }
351
352        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
353        tableInfo.setVersionColumn(versionColumn);
354        tableInfo.setTenantIdColumn(tenantIdColumn);
355
356        if (!onInsertColumns.isEmpty()) {
357            tableInfo.setOnInsertColumns(onInsertColumns);
358        }
359
360        if (!onUpdateColumns.isEmpty()) {
361            tableInfo.setOnUpdateColumns(onUpdateColumns);
362        }
363
364        if (!largeColumns.isEmpty()) {
365            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
366        }
367
368        if (!defaultQueryColumns.isEmpty()) {
369            tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0]));
370        }
371
372        tableInfo.setPrimaryKeyList(idInfos);
373        tableInfo.setColumnInfoList(columnInfoList);
374
375
376        return tableInfo;
377    }
378
379    static String getColumnName(boolean isCamelToUnderline, Field field, Column column) {
380        if (column != null && StringUtil.isNotBlank(column.value())) {
381            return column.value();
382        }
383        if (isCamelToUnderline) {
384            return StringUtil.camelToUnderline(field.getName());
385        }
386        return field.getName();
387    }
388
389
390    public static List<Field> getColumnFields(Class<?> entityClass) {
391        List<Field> fields = new ArrayList<>();
392        doGetFields(entityClass, fields);
393        return fields;
394    }
395
396
397    private static void doGetFields(Class<?> entityClass, List<Field> fields) {
398        if (entityClass == null || entityClass == Object.class) {
399            return;
400        }
401
402        Field[] declaredFields = entityClass.getDeclaredFields();
403        for (Field declaredField : declaredFields) {
404            if (Modifier.isStatic(declaredField.getModifiers())
405                || existName(fields, declaredField)) {
406                continue;
407            }
408            fields.add(declaredField);
409        }
410
411        doGetFields(entityClass.getSuperclass(), fields);
412    }
413
414
415    private static boolean existName(List<Field> fields, Field field) {
416        for (Field f : fields) {
417            if (f.getName().equalsIgnoreCase(field.getName())) {
418                return true;
419            }
420        }
421        return false;
422    }
423
424}