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 */ 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 tableDefClassSuffix = configuration.get(ConfigurationKey.TABLE_DEF_CLASS_SUFFIX); 129 String tableDefInstanceSuffix = configuration.get(ConfigurationKey.TABLE_DEF_INSTANCE_SUFFIX); 130 String tableDefPropertiesNameStyle = configuration.get(ConfigurationKey.TABLE_DEF_PROPERTIES_NAME_STYLE); 131 String[] tableDefIgnoreEntitySuffixes = configuration.get(ConfigurationKey.TABLE_DEF_IGNORE_ENTITY_SUFFIXES).split(","); 132 133 // 如果不指定 Tables 生成包,那么 Tables 文件就会和最后一个 entity 文件在同一个包 134 String entityClassReference = null; 135 136 // 获取需要生成的类,开始构建文件 137 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(Table.class); 138 139 for (Element entityClassElement : elementsAnnotatedWith) { 140 141 // 获取 Table 注解 142 Table table = entityClassElement.getAnnotation(Table.class); 143 144 assert table != null; 145 146 // 类属性 fix: https://gitee.com/mybatis-flex/mybatis-flex/issues/I7I08X 147 Set<ColumnInfo> columnInfos = new TreeSet<>(); 148 // 默认查询的属性,非 isLarge 字段 149 List<String> defaultColumns = new ArrayList<>(); 150 151 TypeElement classElement = (TypeElement) entityClassElement; 152 153 do { 154 // 获取类属性和默认查询字段 155 fillColumnInfoList(columnInfos, defaultColumns, (TypeElement) entityClassElement, classElement, table.camelToUnderline()); 156 classElement = (TypeElement) typeUtils.asElement(classElement.getSuperclass()); 157 } while (classElement != null); 158 159 // 获取 entity 类名 160 String entityClass = entityClassElement.toString(); 161 String entityClassName = StrUtil.getClassName(entityClass); 162 163 // 处理 entity 后缀 164 for (String entityIgnoreSuffix : tableDefIgnoreEntitySuffixes) { 165 if (entityClassName.endsWith(entityIgnoreSuffix.trim())) { 166 entityClassName = entityClassName.substring(0, entityClassName.length() - entityIgnoreSuffix.length()); 167 break; 168 } 169 } 170 171 TableInfo tableInfo = new TableInfo(); 172 tableInfo.setEntityName(entityClass); 173 tableInfo.setEntitySimpleName(entityClassName); 174 tableInfo.setSchema(table.schema()); 175 tableInfo.setTableName(table.value()); 176 tableInfo.setEntityComment(elementUtils.getDocComment(entityClassElement)); 177 178 // 生成 TableDef 文件 179 String tableDefPackage = StrUtil.buildTableDefPackage(entityClass); 180 String tableDefClassName = entityClassName.concat(tableDefClassSuffix); 181 String tableDefContent = ContentBuilder.buildTableDef(tableInfo, allInTablesEnable, tableDefPackage, tableDefClassName 182 , tableDefPropertiesNameStyle, tableDefInstanceSuffix, columnInfos, defaultColumns); 183 // 将文件所依赖的 Element 传入 Filer 中,表示此 TableDef 依赖这个类,以保证增量编译时不丢失内容。 184 processGenClass(genPath, tableDefPackage, tableDefClassName, tableDefContent, entityClassElement); 185 186 if (allInTablesEnable) { 187 // 标记 entity 类,如果没有配置 Tables 生成位置,以 entity 位置为准 188 entityClassReference = entityClass; 189 // 构建 Tables 常量属性及其导包 190 ContentBuilder.buildTablesField(importBuilder, fieldBuilder, tableInfo, tableDefClassSuffix, tableDefPropertiesNameStyle, tableDefInstanceSuffix); 191 } 192 193 // 是否生成 Mapper 文件 194 if ("true".equalsIgnoreCase(mapperGenerateEnable) && table.mapperGenerateEnable()) { 195 String realMapperPackage = StrUtil.isBlank(mapperPackage) ? StrUtil.buildMapperPackage(entityClass) : mapperPackage; 196 String mapperClassName = entityClassName.concat("Mapper"); 197 boolean mapperAnnotationEnable = "true".equalsIgnoreCase(mapperAnnotation); 198 String mapperClassContent = ContentBuilder.buildMapper(tableInfo, realMapperPackage, mapperClassName, mapperBaseClass, mapperAnnotationEnable); 199 // 生成的 Mapper 依赖于此 Element。 200 processGenClass(genPath, realMapperPackage, mapperClassName, mapperClassContent, entityClassElement); 201 } 202 } 203 // 确定了要生成 Tables 类,且拥有至少一个被 Table 注解的类时再生成 Tables 类。 204 if (allInTablesEnable && entityClassReference != null) { 205 // 生成 Tables 文件 206 String realTablesPackage = StrUtil.isBlank(allInTablesPackage) ? StrUtil.buildTableDefPackage(entityClassReference) : allInTablesPackage; 207 String realTablesClassName = StrUtil.isBlank(allInTablesClassName) ? "Tables" : allInTablesClassName; 208 String tablesContent = ContentBuilder.buildTables(importBuilder, fieldBuilder, realTablesPackage, allInTablesClassName); 209 processGenClass(genPath, realTablesPackage, realTablesClassName, tablesContent, elementsAnnotatedWith.toArray(new Element[0])); 210 } 211 } 212 return false; 213 } 214 215 @Override 216 public Set<String> getSupportedAnnotationTypes() { 217 Set<String> supportedAnnotationTypes = new HashSet<>(); 218 supportedAnnotationTypes.add(Table.class.getCanonicalName()); 219 return supportedAnnotationTypes; 220 } 221 222 @Override 223 public SourceVersion getSupportedSourceVersion() { 224 return SourceVersion.latestSupported(); 225 } 226 227 228 /** 229 * 通过 classElement 操作起所有字段,生成 ColumnInfo 并填充 columnInfos 结合 230 */ 231 private void fillColumnInfoList(Set<ColumnInfo> columnInfos, List<String> defaultColumns, TypeElement baseElement, TypeElement classElement, boolean camelToUnderline) { 232 for (Element fieldElement : classElement.getEnclosedElements()) { 233 234 // all fields 235 if (ElementKind.FIELD == fieldElement.getKind()) { 236 237 Set<Modifier> modifiers = fieldElement.getModifiers(); 238 if (modifiers.contains(Modifier.STATIC)) { 239 // ignore static fields 240 continue; 241 } 242 243 Column column = fieldElement.getAnnotation(Column.class); 244 if (column != null && column.ignore()) { 245 continue; 246 } 247 248 249 // 获取 typeHandlerClass 的名称,通过 column.typeHandler() 获取会抛出异常:MirroredTypeException: 250 // 参考 https://stackoverflow.com/questions/7687829/java-6-annotation-processing-getting-a-class-from-an-annotation 251 final String[] typeHandlerClass = {""}; 252 List<? extends AnnotationMirror> annotationMirrors = fieldElement.getAnnotationMirrors(); 253 for (AnnotationMirror annotationMirror : annotationMirrors) { 254 annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> { 255 if ("typeHandler".contentEquals(executableElement.getSimpleName())) { 256 typeHandlerClass[0] = annotationValue.toString(); 257 } 258 }); 259 } 260 261 TypeMirror typeMirror = fieldElement.asType(); 262 Element element = typeUtils.asElement(typeMirror); 263 if (element != null) { 264 typeMirror = element.asType(); 265 } 266 267 String typeString = typeMirror.toString().trim(); 268 269 TypeElement typeElement = null; 270 if (typeMirror.getKind() == TypeKind.DECLARED) { 271 typeElement = (TypeElement) ((DeclaredType) typeMirror).asElement(); 272 } 273 274 // 未配置 typeHandler 的情况下,只支持基本数据类型,不支持比如 list set 或者自定义的类等 275 if ((column == null || "org.apache.ibatis.type.UnknownTypeHandler".equals(typeHandlerClass[0])) 276 && !DEFAULT_SUPPORT_COLUMN_TYPES.contains(typeString) 277 && (typeElement != null && ElementKind.ENUM != typeElement.getKind()) 278 ) { 279 continue; 280 } 281 282 String property = fieldElement.toString(); 283 284 String columnName; 285 if (column != null && !StrUtil.isBlank(column.value())) { 286 columnName = column.value(); 287 } else { 288 if (camelToUnderline) { 289 columnName = StrUtil.camelToUnderline(property); 290 } else { 291 columnName = property; 292 } 293 } 294 295 String[] alias = getColumnAliasByGetterMethod(baseElement, property); 296 if (alias == null || alias.length == 0) { 297 ColumnAlias columnAlias = fieldElement.getAnnotation(ColumnAlias.class); 298 if (columnAlias != null) { 299 alias = columnAlias.value(); 300 } 301 } 302 303 ColumnInfo columnInfo = new ColumnInfo(); 304 columnInfo.setProperty(property); 305 columnInfo.setColumn(columnName); 306 columnInfo.setAlias(alias); 307 columnInfo.setComment(elementUtils.getDocComment(fieldElement)); 308 309 columnInfos.add(columnInfo); 310 311 if (column == null || (!column.isLarge() && !column.isLogicDelete())) { 312 defaultColumns.add(columnName); 313 } 314 } 315 } 316 } 317 318 319 private String[] getColumnAliasByGetterMethod(TypeElement baseElement, String property) { 320 if (baseElement == null) { 321 return null; 322 } 323 for (Element enclosedElement : baseElement.getEnclosedElements()) { 324 if (ElementKind.METHOD == enclosedElement.getKind()) { 325 String methodName = enclosedElement.toString(); 326 if (StrUtil.isGetterMethod(methodName, property)) { 327 ColumnAlias columnAlias = enclosedElement.getAnnotation(ColumnAlias.class); 328 if (columnAlias != null) { 329 return columnAlias.value(); 330 } else { 331 // 重写方法,忽略别名 332 return null; 333 } 334 } 335 } 336 } 337 return getColumnAliasByGetterMethod((TypeElement) typeUtils.asElement(baseElement.getSuperclass()), property); 338 } 339 340 341 private void processGenClass(String genBasePath, String genPackageName, String className, String genContent, Element... elements) { 342 Writer writer = null; 343 try { 344 JavaFileObject sourceFile = filer.createSourceFile(genPackageName + "." + className, elements); 345 if (genBasePath == null || genBasePath.trim().length() == 0) { 346 writer = new OutputStreamWriter(sourceFile.openOutputStream(), configuration.get(ConfigurationKey.CHARSET)); 347 writer.write(genContent); 348 writer.flush(); 349 return; 350 } 351 352 353 String defaultGenPath = sourceFile.toUri().getPath(); 354 355 // 真实的生成代码的目录 356 String realPath; 357 358 if (FileUtil.isAbsolutePath(genBasePath)) { 359 // 用户配置的路径为绝对路径 360 realPath = genBasePath; 361 } else { 362 // 配置的是相对路径,那么则以项目根目录为相对路径 363 String projectRootPath = FileUtil.getProjectRootPath(defaultGenPath); 364 realPath = new File(projectRootPath, genBasePath).getAbsolutePath(); 365 } 366 367 // 通过在 test/java 目录下执行编译生成的 368 boolean fromTestSource = FileUtil.isFromTestSource(defaultGenPath); 369 if (fromTestSource) { 370 realPath = new File(realPath, "src/test/java").getAbsolutePath(); 371 } else { 372 realPath = new File(realPath, "src/main/java").getAbsolutePath(); 373 } 374 375 File genJavaFile = new File(realPath, (genPackageName + "." + className).replace(".", "/") + ".java"); 376 if (!genJavaFile.getParentFile().exists() && !genJavaFile.getParentFile().mkdirs()) { 377 System.err.println(">>>>> ERROR: can not mkdirs by mybatis-flex processor for: " + genJavaFile.getParentFile()); 378 return; 379 } 380 381 writer = new PrintWriter(genJavaFile, configuration.get(ConfigurationKey.CHARSET)); 382 writer.write(genContent); 383 writer.flush(); 384 } catch (IOException e) { 385 e.printStackTrace(); 386 } finally { 387 if (writer != null) { 388 try { 389 writer.close(); 390 } catch (IOException ignored) { 391 // do nothing here. 392 } 393 } 394 } 395 } 396 397}