001/*
002 *  Copyright (c) 2022-2025, 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 */
016
017package com.mybatisflex.processor;
018
019import com.mybatisflex.annotation.Column;
020import com.mybatisflex.annotation.ColumnAlias;
021import com.mybatisflex.annotation.Table;
022import com.mybatisflex.processor.builder.ContentBuilder;
023import com.mybatisflex.processor.config.ConfigurationKey;
024import com.mybatisflex.processor.config.MybatisFlexConfig;
025import com.mybatisflex.processor.entity.ColumnInfo;
026import com.mybatisflex.processor.entity.TableInfo;
027import com.mybatisflex.processor.util.FileUtil;
028import com.mybatisflex.processor.util.StrUtil;
029
030import javax.annotation.processing.AbstractProcessor;
031import javax.annotation.processing.Filer;
032import javax.annotation.processing.ProcessingEnvironment;
033import javax.annotation.processing.RoundEnvironment;
034import javax.lang.model.SourceVersion;
035import javax.lang.model.element.*;
036import javax.lang.model.type.DeclaredType;
037import javax.lang.model.type.TypeKind;
038import javax.lang.model.type.TypeMirror;
039import javax.lang.model.util.Elements;
040import javax.lang.model.util.Types;
041import javax.tools.JavaFileObject;
042import java.io.*;
043import java.math.BigDecimal;
044import java.math.BigInteger;
045import java.sql.Time;
046import java.sql.Timestamp;
047import java.time.*;
048import java.time.chrono.JapaneseDate;
049import java.util.*;
050
051/**
052 * MyBatis Flex Processor.
053 *
054 * @author 王帅, CloudPlayer
055 * @since 2023-06-22
056 */
057public class MybatisFlexProcessor extends AbstractProcessor {
058
059    private static final List<String> DEFAULT_SUPPORT_COLUMN_TYPES = Arrays.asList(
060        int.class.getName(), Integer.class.getName(),
061        short.class.getName(), Short.class.getName(),
062        long.class.getName(), Long.class.getName(),
063        float.class.getName(), Float.class.getName(),
064        double.class.getName(), Double.class.getName(),
065        boolean.class.getName(), Boolean.class.getName(),
066        Date.class.getName(), java.sql.Date.class.getName(), Time.class.getName(), Timestamp.class.getName(),
067        Instant.class.getName(), LocalDate.class.getName(), LocalDateTime.class.getName(), LocalTime.class.getName(),
068        OffsetDateTime.class.getName(), OffsetTime.class.getName(), ZonedDateTime.class.getName(),
069        Year.class.getName(), Month.class.getName(), YearMonth.class.getName(), JapaneseDate.class.getName(),
070        byte[].class.getName(), Byte[].class.getName(), Byte.class.getName(),
071        BigInteger.class.getName(), BigDecimal.class.getName(),
072        char.class.getName(), String.class.getName(), Character.class.getName()
073    );
074
075    private Filer filer;
076    private Types typeUtils;
077    private Elements elementUtils;
078    private MybatisFlexConfig configuration;
079
080    @Override
081    public synchronized void init(ProcessingEnvironment processingEnvironment) {
082        super.init(processingEnvironment);
083        this.filer = processingEnvironment.getFiler();
084        this.elementUtils = processingEnvironment.getElementUtils();
085        this.typeUtils = processingEnvironment.getTypeUtils();
086        this.configuration = new MybatisFlexConfig(filer);
087    }
088
089    @Override
090    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
091        if (!roundEnv.processingOver()) {
092
093            // 不启用 APT 功能
094            if ("false".equalsIgnoreCase(configuration.get(ConfigurationKey.ENABLE))) {
095                return true;
096            }
097
098            System.out.println("mybatis flex processor run start...");
099
100            // 是否所有的类常量都生成在 Tables 类里
101            boolean allInTablesEnable = "true".equalsIgnoreCase(configuration.get(ConfigurationKey.ALL_IN_TABLES_ENABLE));
102
103            StringBuilder importBuilder;
104            StringBuilder fieldBuilder;
105
106            if (allInTablesEnable) {
107                importBuilder = new StringBuilder();
108                fieldBuilder = new StringBuilder();
109            } else {
110                fieldBuilder = null;
111                importBuilder = null;
112            }
113
114            // 其他配置选项
115            String genPath = configuration.get(ConfigurationKey.GEN_PATH);
116
117            // all in Tables 配置
118            String allInTablesPackage = configuration.get(ConfigurationKey.ALL_IN_TABLES_PACKAGE);
119            String allInTablesClassName = configuration.get(ConfigurationKey.ALL_IN_TABLES_CLASS_NAME);
120
121            // mapper 配置
122            String mapperGenerateEnable = configuration.get(ConfigurationKey.MAPPER_GENERATE_ENABLE);
123            String mapperAnnotation = configuration.get(ConfigurationKey.MAPPER_ANNOTATION);
124            String mapperPackage = configuration.get(ConfigurationKey.MAPPER_PACKAGE);
125            String mapperBaseClass = configuration.get(ConfigurationKey.MAPPER_BASE_CLASS);
126
127            // tableDef 配置
128            String tableDefPackage = configuration.get(ConfigurationKey.TABLE_DEF_PACKAGE);
129            String tableDefClassSuffix = configuration.get(ConfigurationKey.TABLE_DEF_CLASS_SUFFIX);
130            String tableDefInstanceSuffix = configuration.get(ConfigurationKey.TABLE_DEF_INSTANCE_SUFFIX);
131            String tableDefPropertiesNameStyle = configuration.get(ConfigurationKey.TABLE_DEF_PROPERTIES_NAME_STYLE);
132            String[] tableDefIgnoreEntitySuffixes = configuration.get(ConfigurationKey.TABLE_DEF_IGNORE_ENTITY_SUFFIXES).split(",");
133
134            // 如果不指定 Tables 生成包,那么 Tables 文件就会和最后一个 entity 文件在同一个包
135            String entityClassReference = null;
136
137            // 获取需要生成的类,开始构建文件
138            Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(Table.class);
139
140            for (Element entityClassElement : elementsAnnotatedWith) {
141
142                // 获取 Table 注解
143                Table table = entityClassElement.getAnnotation(Table.class);
144
145                assert table != null;
146
147                // 类属性 fix: https://gitee.com/mybatis-flex/mybatis-flex/issues/I7I08X
148                Set<ColumnInfo> columnInfos = new TreeSet<>();
149                // 默认查询的属性,非 isLarge 字段
150                List<String> defaultColumns = new ArrayList<>();
151
152                TypeElement classElement = (TypeElement) entityClassElement;
153
154                do {
155                    // 获取类属性和默认查询字段
156                    fillColumnInfoList(columnInfos, defaultColumns, (TypeElement) entityClassElement, classElement, table.camelToUnderline());
157                    classElement = (TypeElement) typeUtils.asElement(classElement.getSuperclass());
158                } while (classElement != null);
159
160                // 获取 entity 类名
161                String entityClass = entityClassElement.toString();
162                String entityClassName = StrUtil.getClassName(entityClass);
163
164                // 处理 entity 后缀
165                for (String entityIgnoreSuffix : tableDefIgnoreEntitySuffixes) {
166                    entityIgnoreSuffix = entityIgnoreSuffix.trim();
167                    if (entityIgnoreSuffix.isEmpty()) {
168                        continue;
169                    }
170                    if (entityClassName.endsWith(entityIgnoreSuffix)) {
171                        entityClassName = entityClassName.substring(0, entityClassName.length() - entityIgnoreSuffix.length());
172                        break;
173                    }
174                }
175
176                TableInfo tableInfo = new TableInfo();
177                tableInfo.setEntityName(entityClass);
178                tableInfo.setEntitySimpleName(entityClassName);
179                tableInfo.setSchema(table.schema());
180                tableInfo.setTableName(table.value());
181                tableInfo.setEntityComment(elementUtils.getDocComment(entityClassElement));
182
183                // 生成 TableDef 文件
184                String realTableDefPackage = StrUtil.isBlank(tableDefPackage) ? StrUtil.buildTableDefPackage(entityClass) : StrUtil.processPackageExpression(entityClass, tableDefPackage);
185                String tableDefClassName = entityClassName.concat(tableDefClassSuffix);
186                String tableDefContent = ContentBuilder.buildTableDef(tableInfo, allInTablesEnable, realTableDefPackage, tableDefClassName
187                    , tableDefPropertiesNameStyle, tableDefInstanceSuffix, columnInfos, defaultColumns);
188                // 将文件所依赖的 Element 传入 Filer 中,表示此 TableDef 依赖这个类,以保证增量编译时不丢失内容。
189                processGenClass(genPath, realTableDefPackage, tableDefClassName, tableDefContent, entityClassElement);
190
191                if (allInTablesEnable) {
192                    // 标记 entity 类,如果没有配置 Tables 生成位置,以 entity 位置为准
193                    entityClassReference = entityClass;
194                    // 构建 Tables 常量属性及其导包
195                    ContentBuilder.buildTablesField(importBuilder, fieldBuilder, tableInfo, tableDefClassSuffix, tableDefPropertiesNameStyle, tableDefInstanceSuffix, realTableDefPackage);
196                }
197
198                // 是否生成 Mapper 文件
199                if ("true".equalsIgnoreCase(mapperGenerateEnable) && table.mapperGenerateEnable()) {
200                    String realMapperPackage = StrUtil.isBlank(mapperPackage) ? StrUtil.buildMapperPackage(entityClass) : StrUtil.processPackageExpression(entityClass, mapperPackage);
201                    String mapperClassName = entityClassName.concat("Mapper");
202                    boolean mapperAnnotationEnable = "true".equalsIgnoreCase(mapperAnnotation);
203                    String mapperClassContent = ContentBuilder.buildMapper(tableInfo, realMapperPackage, mapperClassName, mapperBaseClass, mapperAnnotationEnable);
204                    // 生成的 Mapper 依赖于此 Element。
205                    processGenClass(genPath, realMapperPackage, mapperClassName, mapperClassContent, entityClassElement);
206                }
207            }
208            // 确定了要生成 Tables 类,且拥有至少一个被 Table 注解的类时再生成 Tables 类。
209            if (allInTablesEnable && entityClassReference != null) {
210                // 生成 Tables 文件
211                String realTablesPackage = StrUtil.isBlank(allInTablesPackage) ? StrUtil.buildTableDefPackage(entityClassReference) : StrUtil.processPackageExpression(entityClassReference, allInTablesPackage);
212                String realTablesClassName = StrUtil.isBlank(allInTablesClassName) ? "Tables" : allInTablesClassName;
213                String tablesContent = ContentBuilder.buildTables(importBuilder, fieldBuilder, realTablesPackage, allInTablesClassName);
214                processGenClass(genPath, realTablesPackage, realTablesClassName, tablesContent, elementsAnnotatedWith.toArray(new Element[0]));
215            }
216        }
217        return false;
218    }
219
220    @Override
221    public Set<String> getSupportedAnnotationTypes() {
222        Set<String> supportedAnnotationTypes = new HashSet<>();
223        supportedAnnotationTypes.add(Table.class.getCanonicalName());
224        return supportedAnnotationTypes;
225    }
226
227    @Override
228    public SourceVersion getSupportedSourceVersion() {
229        return SourceVersion.latestSupported();
230    }
231
232
233    /**
234     * 通过 classElement 操作起所有字段,生成 ColumnInfo 并填充 columnInfos 结合
235     */
236    private void fillColumnInfoList(Set<ColumnInfo> columnInfos, List<String> defaultColumns, TypeElement baseElement, TypeElement classElement, boolean camelToUnderline) {
237        for (Element fieldElement : classElement.getEnclosedElements()) {
238
239            // all fields
240            if (ElementKind.FIELD == fieldElement.getKind()) {
241
242                Set<Modifier> modifiers = fieldElement.getModifiers();
243                if (modifiers.contains(Modifier.STATIC)) {
244                    // ignore static fields
245                    continue;
246                }
247
248                Column column = fieldElement.getAnnotation(Column.class);
249                if (column != null && column.ignore()) {
250                    continue;
251                }
252
253
254                // 获取 typeHandlerClass 的名称,通过 column.typeHandler() 获取会抛出异常:MirroredTypeException:
255                // 参考 https://stackoverflow.com/questions/7687829/java-6-annotation-processing-getting-a-class-from-an-annotation
256                final String[] typeHandlerClass = {""};
257                List<? extends AnnotationMirror> annotationMirrors = fieldElement.getAnnotationMirrors();
258                for (AnnotationMirror annotationMirror : annotationMirrors) {
259                    annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> {
260                        if ("typeHandler".contentEquals(executableElement.getSimpleName())) {
261                            typeHandlerClass[0] = annotationValue.toString();
262                        }
263                    });
264                }
265
266                TypeMirror typeMirror = fieldElement.asType();
267                Element element = typeUtils.asElement(typeMirror);
268                if (element != null) {
269                    typeMirror = element.asType();
270                }
271
272                String typeString = typeMirror.toString().trim();
273
274                TypeElement typeElement = null;
275                if (typeMirror.getKind() == TypeKind.DECLARED) {
276                    typeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
277                }
278
279                // 未配置 typeHandler 的情况下,只支持基本数据类型,不支持比如 list set 或者自定义的类等
280                if ((column == null || "org.apache.ibatis.type.UnknownTypeHandler".equals(typeHandlerClass[0]))
281                    && !DEFAULT_SUPPORT_COLUMN_TYPES.contains(typeString)
282                    && (typeElement != null && ElementKind.ENUM != typeElement.getKind())
283                ) {
284                    continue;
285                }
286
287                String property = fieldElement.toString();
288
289                String columnName;
290                if (column != null && !StrUtil.isBlank(column.value())) {
291                    columnName = column.value();
292                } else {
293                    if (camelToUnderline) {
294                        columnName = StrUtil.camelToUnderline(property);
295                    } else {
296                        columnName = property;
297                    }
298                }
299
300                String[] alias = getColumnAliasByGetterMethod(baseElement, property);
301                if (alias == null || alias.length == 0) {
302                    ColumnAlias columnAlias = fieldElement.getAnnotation(ColumnAlias.class);
303                    if (columnAlias != null) {
304                        alias = columnAlias.value();
305                    }
306                }
307
308                ColumnInfo columnInfo = new ColumnInfo();
309                columnInfo.setProperty(property);
310                columnInfo.setColumn(columnName);
311                columnInfo.setAlias(alias);
312                columnInfo.setComment(elementUtils.getDocComment(fieldElement));
313
314                columnInfos.add(columnInfo);
315
316                if (column == null || (!column.isLarge() && !column.isLogicDelete())) {
317                    defaultColumns.add(columnName);
318                }
319            }
320        }
321    }
322
323
324    private String[] getColumnAliasByGetterMethod(TypeElement baseElement, String property) {
325        if (baseElement == null) {
326            return null;
327        }
328        for (Element enclosedElement : baseElement.getEnclosedElements()) {
329            if (ElementKind.METHOD == enclosedElement.getKind()) {
330                String methodName = enclosedElement.toString();
331                if (StrUtil.isGetterMethod(methodName, property)) {
332                    ColumnAlias columnAlias = enclosedElement.getAnnotation(ColumnAlias.class);
333                    if (columnAlias != null) {
334                        return columnAlias.value();
335                    } else {
336                        // 重写方法,忽略别名
337                        return null;
338                    }
339                }
340            }
341        }
342        return getColumnAliasByGetterMethod((TypeElement) typeUtils.asElement(baseElement.getSuperclass()), property);
343    }
344
345
346    private void processGenClass(String genBasePath, String genPackageName, String className, String genContent, Element... elements) {
347        Writer writer = null;
348        try {
349            JavaFileObject sourceFile = filer.createSourceFile(genPackageName + "." + className, elements);
350            if (genBasePath == null || genBasePath.trim().length() == 0) {
351                writer = new OutputStreamWriter(sourceFile.openOutputStream(), configuration.get(ConfigurationKey.CHARSET));
352                writer.write(genContent);
353                writer.flush();
354                return;
355            }
356
357
358            String defaultGenPath = sourceFile.toUri().getPath();
359
360            // 真实的生成代码的目录
361            String realPath;
362
363            if (FileUtil.isAbsolutePath(genBasePath)) {
364                // 用户配置的路径为绝对路径
365                realPath = genBasePath;
366            } else {
367                // 配置的是相对路径,那么则以项目根目录为相对路径
368                String projectRootPath = FileUtil.getProjectRootPath(defaultGenPath);
369                realPath = new File(projectRootPath, genBasePath).getAbsolutePath();
370            }
371
372            // 通过在 test/java 目录下执行编译生成的
373            boolean fromTestSource = FileUtil.isFromTestSource(defaultGenPath);
374            if (fromTestSource) {
375                realPath = new File(realPath, "src/test/java").getAbsolutePath();
376            } else {
377                realPath = new File(realPath, "src/main/java").getAbsolutePath();
378            }
379
380            File genJavaFile = new File(realPath, (genPackageName + "." + className).replace(".", "/") + ".java");
381            if (!genJavaFile.getParentFile().exists() && !genJavaFile.getParentFile().mkdirs()) {
382                System.err.println(">>>>> ERROR: can not mkdirs by mybatis-flex processor for: " + genJavaFile.getParentFile());
383                return;
384            }
385
386            writer = new PrintWriter(genJavaFile, configuration.get(ConfigurationKey.CHARSET));
387            writer.write(genContent);
388            writer.flush();
389        } catch (IOException e) {
390            e.printStackTrace();
391        } finally {
392            if (writer != null) {
393                try {
394                    writer.close();
395                } catch (IOException ignored) {
396                    // do nothing here.
397                }
398            }
399        }
400    }
401
402}