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