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}