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.FlexConsts;
020import com.mybatisflex.core.FlexGlobalConfig;
021import com.mybatisflex.core.exception.FlexExceptions;
022import com.mybatisflex.core.util.ClassUtil;
023import com.mybatisflex.core.util.CollectionUtil;
024import com.mybatisflex.core.util.StringUtil;
025import org.apache.ibatis.reflection.Reflector;
026import org.apache.ibatis.session.Configuration;
027import org.apache.ibatis.type.JdbcType;
028import org.apache.ibatis.type.TypeHandler;
029import org.apache.ibatis.type.TypeHandlerRegistry;
030import org.apache.ibatis.type.UnknownTypeHandler;
031import org.apache.ibatis.util.MapUtil;
032
033import java.lang.reflect.Field;
034import java.lang.reflect.Modifier;
035import java.lang.reflect.ParameterizedType;
036import java.lang.reflect.Type;
037import java.math.BigDecimal;
038import java.math.BigInteger;
039import java.sql.Time;
040import java.sql.Timestamp;
041import java.time.*;
042import java.time.chrono.JapaneseDate;
043import java.util.*;
044import java.util.concurrent.ConcurrentHashMap;
045import java.util.stream.Collectors;
046
047public class TableInfoFactory {
048
049
050    private static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet(
051            int.class, Integer.class,
052            short.class, Short.class,
053            long.class, Long.class,
054            float.class, Float.class,
055            double.class, Double.class,
056            boolean.class, Boolean.class,
057            Date.class, java.sql.Date.class, Time.class, Timestamp.class,
058            Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class,
059            Year.class, Month.class, YearMonth.class, JapaneseDate.class,
060            byte[].class, Byte[].class,
061            BigInteger.class, BigDecimal.class,
062            char.class, String.class, Character.class
063    );
064
065
066    private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>();
067    private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>();
068    private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>();
069
070
071    public static TableInfo ofMapperClass(Class<?> mapperClass) {
072        return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> {
073            Class<?> entityClass = getEntityClass(mapperClass);
074            if (entityClass == null) {
075                return null;
076            }
077            return ofEntityClass(entityClass);
078        });
079    }
080
081
082    public static TableInfo ofEntityClass(Class<?> entityClass) {
083        return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> {
084            TableInfo tableInfo = createTableInfo(entityClass);
085            tableInfoMap.put(tableInfo.getTableName(), tableInfo);
086            return tableInfo;
087        });
088    }
089
090
091    public static TableInfo ofTableName(String tableName) {
092        return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null;
093    }
094
095
096    private static Class<?> getEntityClass(Class<?> mapperClass) {
097        if (mapperClass == null || mapperClass == Object.class) {
098            return null;
099        }
100        Type[] genericInterfaces = mapperClass.getGenericInterfaces();
101        if (genericInterfaces.length == 1) {
102            Type type = genericInterfaces[0];
103            if (type instanceof ParameterizedType) {
104                Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
105                return actualTypeArgument instanceof Class ? (Class<?>) actualTypeArgument : null;
106            } else if (type instanceof Class) {
107                return getEntityClass((Class<?>) type);
108            }
109        }
110        return getEntityClass(mapperClass.getSuperclass());
111    }
112
113
114    private static TableInfo createTableInfo(Class<?> entityClass) {
115
116        TableInfo tableInfo = new TableInfo();
117        tableInfo.setEntityClass(entityClass);
118        tableInfo.setReflector(new Reflector(entityClass));
119
120
121        //初始化表名
122        Table table = entityClass.getAnnotation(Table.class);
123        if (table != null) {
124            tableInfo.setTableName(table.value());
125            tableInfo.setSchema(table.schema());
126            tableInfo.setCamelToUnderline(table.camelToUnderline());
127
128            if (table.onInsert().length > 0) {
129                List<InsertListener> insertListeners = Arrays.stream(table.onInsert())
130                        .filter(listener -> listener != NoneListener.class)
131                        .map(ClassUtil::newInstance)
132                        .collect(Collectors.toList());
133                tableInfo.setOnInsertListeners(insertListeners);
134            }
135
136            if (table.onUpdate().length > 0) {
137                List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate())
138                        .filter(listener -> listener != NoneListener.class)
139                        .map(ClassUtil::newInstance)
140                        .collect(Collectors.toList());
141                tableInfo.setOnUpdateListeners(updateListeners);
142            }
143
144            if (table.onSet().length > 0) {
145                List<SetListener> setListeners = Arrays.stream(table.onSet())
146                        .filter(listener -> listener != NoneListener.class)
147                        .map(ClassUtil::newInstance)
148                        .collect(Collectors.toList());
149                tableInfo.setOnSetListeners(setListeners);
150            }
151
152            if (StringUtil.isNotBlank(table.dataSource())) {
153                tableInfo.setDataSource(table.dataSource());
154            }
155        } else {
156            //默认为类名转驼峰下划线
157            String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
158            tableInfo.setTableName(tableName);
159        }
160
161        //初始化字段相关
162        List<ColumnInfo> columnInfoList = new ArrayList<>();
163        List<IdInfo> idInfos = new ArrayList<>();
164
165        Field idField = null;
166
167        String logicDeleteColumn = null;
168        String versionColumn = null;
169        String tenantIdColumn = null;
170
171        //数据插入时,默认插入数据字段
172        Map<String, String> onInsertColumns = new HashMap<>();
173
174        //数据更新时,默认更新内容的字段
175        Map<String, String> onUpdateColumns = new HashMap<>();
176
177        //大字段列
178        Set<String> largeColumns = new LinkedHashSet<>();
179        // 默认查询列
180        Set<String> defaultColumns = new LinkedHashSet<>();
181
182
183        List<Field> entityFields = ClassUtil.getAllFields(entityClass);
184        for (Field field : entityFields) {
185
186            Column column = field.getAnnotation(Column.class);
187            if (column != null && column.ignore()) {
188                continue; // ignore
189            }
190
191
192            if (Modifier.isStatic(field.getModifiers())) {
193                //ignore static field
194                continue;
195            }
196
197
198            //未配置 typeHandler 的情况下,只支持基本数据类型,不支持比如 list set 或者自定义的类等
199            if ((column == null || column.typeHandler() == UnknownTypeHandler.class)
200                    && !field.getType().isEnum()
201                    && !defaultSupportColumnTypes.contains(field.getType())) {
202                continue;
203            }
204
205
206            //列名
207            String columnName = column != null && StringUtil.isNotBlank(column.value())
208                    ? column.value()
209                    : (tableInfo.isCamelToUnderline() ? StringUtil.camelToUnderline(field.getName()) : field.getName());
210
211            //逻辑删除字段
212            if (column != null && column.isLogicDelete()) {
213                if (logicDeleteColumn == null) {
214                    logicDeleteColumn = columnName;
215                } else {
216                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
217                }
218            }
219
220            //乐观锁版本字段
221            if (column != null && column.version()) {
222                if (versionColumn == null) {
223                    versionColumn = columnName;
224                } else {
225                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
226                }
227            }
228
229            //租户ID 字段
230            if (column != null && column.tenantId()) {
231                if (tenantIdColumn == null) {
232                    tenantIdColumn = columnName;
233                } else {
234                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
235                }
236            }
237
238            if (column != null && StringUtil.isNotBlank(column.onInsertValue())) {
239                onInsertColumns.put(columnName, column.onInsertValue().trim());
240            }
241
242
243            if (column != null && StringUtil.isNotBlank(column.onUpdateValue())) {
244                onUpdateColumns.put(columnName, column.onUpdateValue().trim());
245            }
246
247
248            if (column != null && column.isLarge()) {
249                largeColumns.add(columnName);
250            }
251
252            if (column == null || !column.isLarge()) {
253                defaultColumns.add(columnName);
254            }
255
256            Id id = field.getAnnotation(Id.class);
257            ColumnInfo columnInfo;
258            if (id != null) {
259                columnInfo = new IdInfo(columnName, field.getName(), field.getType(), id);
260                idInfos.add((IdInfo) columnInfo);
261            } else {
262                columnInfo = new ColumnInfo();
263                columnInfoList.add(columnInfo);
264            }
265
266            columnInfo.setColumn(columnName);
267            columnInfo.setProperty(field.getName());
268            columnInfo.setPropertyType(field.getType());
269
270            if (column != null && column.typeHandler() != UnknownTypeHandler.class) {
271                Class<?> typeHandlerClass = column.typeHandler();
272                Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration();
273                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
274                TypeHandler<?> typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass);
275                columnInfo.setTypeHandler(typeHandler);
276            }
277
278            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
279            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
280                if (String.class != field.getType()) {
281                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
282                }
283                columnInfo.setMaskType(columnMask.value().trim());
284            }
285
286            if (column != null && column.jdbcType() != JdbcType.UNDEFINED) {
287                columnInfo.setJdbcType(column.jdbcType());
288            }
289
290            if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(field.getName())) {
291                idField = field;
292            }
293        }
294
295
296        if (idInfos.isEmpty() && idField != null) {
297            int index = -1;
298            for (int i = 0; i < columnInfoList.size(); i++) {
299                ColumnInfo columnInfo = columnInfoList.get(i);
300                if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(columnInfo.getProperty())) {
301                    index = i;
302                    break;
303                }
304            }
305            if (index >= 0) {
306                ColumnInfo removedColumnInfo = columnInfoList.remove(index);
307                idInfos.add(new IdInfo(removedColumnInfo));
308            }
309        }
310
311        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
312        tableInfo.setVersionColumn(versionColumn);
313        tableInfo.setTenantIdColumn(tenantIdColumn);
314
315        if (!onInsertColumns.isEmpty()) {
316            tableInfo.setOnInsertColumns(onInsertColumns);
317        }
318
319        if (!onUpdateColumns.isEmpty()) {
320            tableInfo.setOnUpdateColumns(onUpdateColumns);
321        }
322
323        if (!largeColumns.isEmpty()) {
324            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
325        }
326        if (!defaultColumns.isEmpty()) {
327            tableInfo.setDefaultColumns(defaultColumns.toArray(new String[0]));
328        }
329
330        tableInfo.setColumnInfoList(columnInfoList);
331        tableInfo.setPrimaryKeyList(idInfos);
332
333
334        return tableInfo;
335    }
336}