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 */ 016package com.mybatisflex.core.table; 017 018import com.mybatisflex.annotation.Column; 019import com.mybatisflex.annotation.ColumnAlias; 020import com.mybatisflex.annotation.ColumnMask; 021import com.mybatisflex.annotation.Id; 022import com.mybatisflex.annotation.InsertListener; 023import com.mybatisflex.annotation.NoneListener; 024import com.mybatisflex.annotation.SetListener; 025import com.mybatisflex.annotation.Table; 026import com.mybatisflex.annotation.TableRef; 027import com.mybatisflex.annotation.UpdateListener; 028import com.mybatisflex.core.BaseMapper; 029import com.mybatisflex.core.FlexGlobalConfig; 030import com.mybatisflex.core.exception.FlexExceptions; 031import com.mybatisflex.core.query.QueryChain; 032import com.mybatisflex.core.query.QueryColumn; 033import com.mybatisflex.core.query.QueryCondition; 034import com.mybatisflex.core.query.QueryWrapper; 035import com.mybatisflex.core.util.ClassUtil; 036import com.mybatisflex.core.util.CollectionUtil; 037import com.mybatisflex.core.util.MapUtil; 038import com.mybatisflex.core.util.Reflectors; 039import com.mybatisflex.core.util.StringUtil; 040import org.apache.ibatis.io.ResolverUtil; 041import org.apache.ibatis.reflection.Reflector; 042import org.apache.ibatis.reflection.TypeParameterResolver; 043import org.apache.ibatis.type.JdbcType; 044import org.apache.ibatis.type.TypeException; 045import org.apache.ibatis.type.TypeHandler; 046import org.apache.ibatis.type.TypeHandlerRegistry; 047import org.apache.ibatis.type.UnknownTypeHandler; 048 049import java.lang.reflect.Constructor; 050import java.lang.reflect.Field; 051import java.lang.reflect.Method; 052import java.lang.reflect.Modifier; 053import java.lang.reflect.ParameterizedType; 054import java.lang.reflect.Type; 055import java.lang.reflect.TypeVariable; 056import java.math.BigDecimal; 057import java.math.BigInteger; 058import java.sql.Time; 059import java.sql.Timestamp; 060import java.time.Instant; 061import java.time.LocalDate; 062import java.time.LocalDateTime; 063import java.time.LocalTime; 064import java.time.Month; 065import java.time.OffsetDateTime; 066import java.time.OffsetTime; 067import java.time.Year; 068import java.time.YearMonth; 069import java.time.ZonedDateTime; 070import java.time.chrono.JapaneseDate; 071import java.util.ArrayList; 072import java.util.Arrays; 073import java.util.Collection; 074import java.util.Date; 075import java.util.HashMap; 076import java.util.HashSet; 077import java.util.LinkedHashSet; 078import java.util.List; 079import java.util.Map; 080import java.util.Objects; 081import java.util.Set; 082import java.util.concurrent.ConcurrentHashMap; 083import java.util.stream.Collectors; 084 085public class TableInfoFactory { 086 087 private TableInfoFactory() { 088 } 089 090 public static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet( 091 int.class, Integer.class, 092 short.class, Short.class, 093 long.class, Long.class, 094 float.class, Float.class, 095 double.class, Double.class, 096 boolean.class, Boolean.class, 097 Date.class, java.sql.Date.class, Time.class, Timestamp.class, 098 Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class, 099 Year.class, Month.class, YearMonth.class, JapaneseDate.class, 100 byte[].class, Byte[].class, Byte.class, 101 BigInteger.class, BigDecimal.class, 102 char.class, String.class, Character.class 103 ); 104 105 static final Set<Class<?>> ignoreColumnTypes = CollectionUtil.newHashSet( 106 QueryWrapper.class, QueryColumn.class, QueryCondition.class, QueryChain.class 107 ); 108 109 110 private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>(); 111 private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>(); 112 private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>(); 113 private static final Set<String> initializedPackageNames = new HashSet<>(); 114 115 116 /** 117 * 用于解决 https://github.com/mybatis-flex/mybatis-flex/pull/376 的问题 118 * 119 * @param mapperPackageName mapper 的包名 120 */ 121 public synchronized static void init(String mapperPackageName) { 122 if (!initializedPackageNames.contains(mapperPackageName)) { 123 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); 124 resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName); 125 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 126 for (Class<? extends Class<?>> mapperClass : mapperSet) { 127 ofMapperClass(mapperClass); 128 } 129 initializedPackageNames.add(mapperPackageName); 130 } 131 } 132 133 134 public static TableInfo ofMapperClass(Class<?> mapperClass) { 135 return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> { 136 Class<?> entityClass = getEntityClass(mapperClass); 137 if (entityClass == null) { 138 return null; 139 } 140 return ofEntityClass(entityClass); 141 }); 142 } 143 144 145 public static TableInfo ofEntityClass(Class<?> entityClass) { 146 return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> { 147 TableInfo tableInfo = createTableInfo(entityClass); 148 // Entity 和 VO 有相同的表名,以第一次放入的 Entity 解析的 TableInfo 为主 149 tableInfoMap.putIfAbsent(tableInfo.getTableNameWithSchema(), tableInfo); 150 return tableInfo; 151 }); 152 } 153 154 155 public static TableInfo ofTableName(String tableName) { 156 return StringUtil.hasText(tableName) ? tableInfoMap.get(tableName) : null; 157 } 158 159 160 private static Class<?> getEntityClass(Class<?> mapperClass) { 161 if (mapperClass == null || mapperClass == Object.class) { 162 return null; 163 } 164 return getEntityClass(mapperClass, null); 165 } 166 167 private static Class<?> getEntityClass(Class<?> mapperClass, Type[] actualTypeArguments) { 168 // 检查基接口 169 Type[] genericInterfaces = mapperClass.getGenericInterfaces(); 170 for (Type type : genericInterfaces) { 171 if (type instanceof ParameterizedType) { 172 // 泛型基接口 173 ParameterizedType parameterizedType = (ParameterizedType) type; 174 Type rawType = parameterizedType.getRawType(); 175 Type[] typeArguments = parameterizedType.getActualTypeArguments(); 176 if (actualTypeArguments != null && actualTypeArguments.length > 0) { 177 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 178 } 179 if (rawType == BaseMapper.class) { 180 // 找到了 181 if (typeArguments[0] instanceof Class) { 182 return (Class<?>) typeArguments[0]; 183 } 184 } else if (rawType instanceof Class) { 185 // 其他泛型基接口 186 Class<?> entityClass = getEntityClass((Class<?>) rawType, typeArguments); 187 if (entityClass != null) { 188 return entityClass; 189 } 190 } 191 } else if (type instanceof Class) { 192 // 其他基接口 193 Class<?> entityClass = getEntityClass((Class<?>) type); 194 if (entityClass != null) { 195 return entityClass; 196 } 197 } 198 } 199 // 检查基类 200 Class<?> superclass = mapperClass.getSuperclass(); 201 if (superclass == null || superclass == Object.class) { 202 return null; 203 } 204 Type[] typeArguments = null; 205 Type genericSuperclass = mapperClass.getGenericSuperclass(); 206 if(genericSuperclass instanceof ParameterizedType){ 207 typeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments(); 208 if (actualTypeArguments != null && actualTypeArguments.length > 0) { 209 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 210 } 211 } 212 return getEntityClass(superclass, typeArguments); 213 } 214 215 private static void adjustTypeArguments(Class<?> subclass, Type[] subclassTypeArguments, Type[] typeArguments) { 216 for (int i = 0; i < typeArguments.length; i++) { 217 if (typeArguments[i] instanceof TypeVariable) { 218 TypeVariable<?> typeVariable = (TypeVariable<?>) typeArguments[i]; 219 TypeVariable<?>[] typeParameters = subclass.getTypeParameters(); 220 for (int j = 0; j < typeParameters.length; j++) { 221 if (Objects.equals(typeVariable.getName(), typeParameters[j].getName())) { 222 typeArguments[i] = subclassTypeArguments[j]; 223 break; 224 } 225 } 226 } 227 } 228 } 229 230 231 private static TableInfo createTableInfo(Class<?> entityClass) { 232 233 TableInfo tableInfo = new TableInfo(); 234 tableInfo.setEntityClass(entityClass); 235 Reflector reflector = Reflectors.of(entityClass); 236 tableInfo.setReflector(reflector); 237 238 FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig(); 239 240 // 初始化表名 241 Table table = entityClass.getAnnotation(Table.class); 242 if (table == null) { 243 TableRef vo = entityClass.getAnnotation(TableRef.class); 244 if (vo != null) { 245 TableInfo refTableInfo = ofEntityClass(vo.value()); 246 // 设置 VO 类对应的真实的表名 247 if (!config.isIgnoreSchema()) { 248 tableInfo.setSchema(refTableInfo.getSchema()); 249 } 250 tableInfo.setTableName(refTableInfo.getTableName()); 251 // 将 @Table 注解的属性复制到 VO 类当中 252 if (vo.copyTableProps()) { 253 tableInfo.setComment(refTableInfo.getComment()); 254 tableInfo.setCamelToUnderline(refTableInfo.isCamelToUnderline()); 255 tableInfo.setDataSource(refTableInfo.getDataSource()); 256 257 tableInfo.setOnSetListeners(refTableInfo.getOnSetListeners()); 258 tableInfo.setOnInsertColumns(refTableInfo.getOnInsertColumns()); 259 tableInfo.setOnUpdateListeners(refTableInfo.getOnUpdateListeners()); 260 } 261 } else { 262 // 默认为类名转驼峰下划线 263 String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName()); 264 tableInfo.setTableName(tableName); 265 } 266 } else { 267 if (!config.isIgnoreSchema()) { 268 tableInfo.setSchema(table.schema()); 269 } 270 tableInfo.setTableName(table.value()); 271 tableInfo.setCamelToUnderline(table.camelToUnderline()); 272 tableInfo.setComment(table.comment()); 273 274 if (table.onInsert().length > 0) { 275 List<InsertListener> insertListeners = Arrays.stream(table.onInsert()) 276 .filter(listener -> listener != NoneListener.class) 277 .map(ClassUtil::newInstance) 278 .collect(Collectors.toList()); 279 tableInfo.setOnInsertListeners(insertListeners); 280 } 281 282 if (table.onUpdate().length > 0) { 283 List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate()) 284 .filter(listener -> listener != NoneListener.class) 285 .map(ClassUtil::newInstance) 286 .collect(Collectors.toList()); 287 tableInfo.setOnUpdateListeners(updateListeners); 288 } 289 290 if (table.onSet().length > 0) { 291 List<SetListener> setListeners = Arrays.stream(table.onSet()) 292 .filter(listener -> listener != NoneListener.class) 293 .map(ClassUtil::newInstance) 294 .collect(Collectors.toList()); 295 tableInfo.setOnSetListeners(setListeners); 296 } 297 298 if (StringUtil.hasText(table.dataSource())) { 299 tableInfo.setDataSource(table.dataSource()); 300 } 301 } 302 303 // 初始化字段相关 304 List<ColumnInfo> columnInfoList = new ArrayList<>(); 305 List<IdInfo> idInfos = new ArrayList<>(); 306 307 308 String logicDeleteColumn = null; 309 String versionColumn = null; 310 String tenantIdColumn = null; 311 312 // 数据插入时,默认插入数据字段 313 Map<String, String> onInsertColumns = new HashMap<>(); 314 315 // 数据更新时,默认更新内容的字段 316 Map<String, String> onUpdateColumns = new HashMap<>(); 317 318 // 大字段列 319 Set<String> largeColumns = new LinkedHashSet<>(); 320 321 // 默认查询列 322 Set<String> defaultQueryColumns = new LinkedHashSet<>(); 323 324 List<Field> entityFields = getColumnFields(entityClass); 325 326 TypeHandlerRegistry typeHandlerRegistry = null; 327 if (config.getConfiguration() != null) { 328 typeHandlerRegistry = config.getConfiguration().getTypeHandlerRegistry(); 329 } 330 331 for (Field field : entityFields) { 332 333 Class<?> fieldType = reflector.getGetterType(field.getName()); 334 335 // 移除默认的忽略字段 336 boolean isIgnoreField = false; 337 for (Class<?> ignoreColumnType : ignoreColumnTypes) { 338 if (ignoreColumnType.isAssignableFrom(fieldType)) { 339 isIgnoreField = true; 340 break; 341 } 342 } 343 344 if (isIgnoreField) { 345 continue; 346 } 347 348 Column columnAnnotation = field.getAnnotation(Column.class); 349 350 /* 351 * 满足以下 4 种情况,不支持该类型的属性自动映射为字段 352 * 1、注解上未配置 TypeHandler 353 * 2、类型不是枚举 354 * 3、默认的自动类型不包含该类型 355 * 4、没有全局 TypeHandler 356 */ 357 if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) 358 && !fieldType.isEnum() 359 && !defaultSupportColumnTypes.contains(fieldType) 360 && (typeHandlerRegistry == null || !typeHandlerRegistry.hasTypeHandler(fieldType)) 361 ) { 362 // 忽略 集合 实体类 解析 363 if (columnAnnotation != null && columnAnnotation.ignore()) { 364 continue; 365 } 366 // 集合嵌套 367 if (Collection.class.isAssignableFrom(fieldType)) { 368 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 369 if (genericType instanceof ParameterizedType) { 370 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 371 if (actualTypeArgument instanceof Class) { 372 tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument); 373 } 374 } 375 } 376 // 实体类嵌套 377 else if (!Map.class.isAssignableFrom(fieldType) 378 && !fieldType.isArray()) { 379 tableInfo.addAssociationType(field.getName(), fieldType); 380 } 381 // 不支持的类型直接跳过 382 continue; 383 } 384 385 // 列名 386 String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation); 387 388 // 逻辑删除字段 389 if ((columnAnnotation != null && columnAnnotation.isLogicDelete()) 390 || columnName.equals(config.getLogicDeleteColumn())) { 391 if (logicDeleteColumn == null) { 392 logicDeleteColumn = columnName; 393 } else { 394 throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName()); 395 } 396 } 397 398 // 乐观锁版本字段 399 if ((columnAnnotation != null && columnAnnotation.version()) 400 || columnName.equals(config.getVersionColumn())) { 401 if (versionColumn == null) { 402 versionColumn = columnName; 403 } else { 404 throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName()); 405 } 406 } 407 408 // 租户ID 字段 409 if ((columnAnnotation != null && columnAnnotation.tenantId()) 410 || columnName.equals(config.getTenantColumn())) { 411 if (tenantIdColumn == null) { 412 tenantIdColumn = columnName; 413 } else { 414 throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName()); 415 } 416 } 417 418 419 if (columnAnnotation != null && StringUtil.hasText(columnAnnotation.onInsertValue())) { 420 onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim()); 421 } 422 423 424 if (columnAnnotation != null && StringUtil.hasText(columnAnnotation.onUpdateValue())) { 425 onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim()); 426 } 427 428 429 if (columnAnnotation != null && columnAnnotation.isLarge()) { 430 largeColumns.add(columnName); 431 } 432 433 // 主键配置 434 Id id = field.getAnnotation(Id.class); 435 ColumnInfo columnInfo; 436 if (id != null) { 437 columnInfo = new IdInfo(id); 438 idInfos.add((IdInfo) columnInfo); 439 } else { 440 columnInfo = new ColumnInfo(); 441 columnInfoList.add(columnInfo); 442 } 443 444 ColumnAlias columnAlias = null; 445 446 // 属性上没有别名,查找 getter 方法上有没有别名 447 Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName())); 448 if (getterMethod != null) { 449 columnAlias = getterMethod.getAnnotation(ColumnAlias.class); 450 } 451 452 if (columnAlias == null) { 453 columnAlias = field.getAnnotation(ColumnAlias.class); 454 } 455 456 if (columnAlias != null) { 457 columnInfo.setAlias(columnAlias.value()); 458 } 459 460 columnInfo.setColumn(columnName); 461 columnInfo.setProperty(field.getName()); 462 columnInfo.setPropertyType(fieldType); 463 columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore()); 464 465 if (columnAnnotation != null) { 466 columnInfo.setComment(columnAnnotation.comment()); 467 } 468 469 470 // 默认查询列 没有忽略且不是大字段 471 if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) { 472 defaultQueryColumns.add(columnName); 473 } 474 475 476 // typeHandler 配置 477 if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) { 478 TypeHandler<?> typeHandler = null; 479 480 // 集合类型,支持泛型 481 // fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE 482 if (Collection.class.isAssignableFrom(fieldType)) { 483 typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType); 484 } 485 486 // 非集合类型 487 else { 488 Class<?> typeHandlerClass = columnAnnotation.typeHandler(); 489 if (typeHandlerRegistry != null) { 490 Class<?> propertyType = columnInfo.getPropertyType(); 491 JdbcType jdbcType = columnAnnotation.jdbcType(); 492 if (jdbcType != JdbcType.UNDEFINED) { 493 typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType); 494 } 495 if (typeHandler == null || !typeHandlerClass.isAssignableFrom(typeHandler.getClass())) { 496 typeHandler = typeHandlerRegistry.getInstance(propertyType, typeHandlerClass); 497 } 498 } 499 } 500 501 columnInfo.setTypeHandler(typeHandler); 502 } 503 504 // 数据脱敏配置 505 ColumnMask columnMask = field.getAnnotation(ColumnMask.class); 506 if (columnMask != null && StringUtil.hasText(columnMask.value())) { 507 if (String.class != fieldType) { 508 throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName()); 509 } 510 columnInfo.setMaskType(columnMask.value().trim()); 511 } 512 513 // jdbcType 配置 514 if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) { 515 columnInfo.setJdbcType(columnAnnotation.jdbcType()); 516 } 517 518 } 519 520 521 tableInfo.setLogicDeleteColumn(logicDeleteColumn); 522 tableInfo.setVersionColumn(versionColumn); 523 tableInfo.setTenantIdColumn(tenantIdColumn); 524 525 if (!onInsertColumns.isEmpty()) { 526 tableInfo.setOnInsertColumns(onInsertColumns); 527 } 528 529 if (!onUpdateColumns.isEmpty()) { 530 tableInfo.setOnUpdateColumns(onUpdateColumns); 531 } 532 533 if (!largeColumns.isEmpty()) { 534 tableInfo.setLargeColumns(largeColumns.toArray(new String[0])); 535 } 536 537 if (!defaultQueryColumns.isEmpty()) { 538 tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0])); 539 } 540 541 // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column, 542 // 否则会影响 SQL 的字段构建顺序 543 tableInfo.setPrimaryKeyList(idInfos); 544 tableInfo.setColumnInfoList(columnInfoList); 545 546 547 return tableInfo; 548 } 549 550 /** 551 * 创建 typeHandler 552 * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)} 553 * 554 * @param entityClass 555 * @param field 556 * @param typeHandlerClass 557 * @param fieldType 558 */ 559 private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) { 560 Class<?> genericClass = null; 561 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 562 if (genericType instanceof ParameterizedType) { 563 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 564 if (actualTypeArgument instanceof Class) { 565 genericClass = (Class<?>) actualTypeArgument; 566 } 567 } 568 569 try { 570 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class); 571 return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass); 572 } catch (NoSuchMethodException ignored) { 573 } catch (Exception e) { 574 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 575 } 576 try { 577 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class); 578 return (TypeHandler<?>) constructor.newInstance(fieldType); 579 } catch (NoSuchMethodException ignored) { 580 } catch (Exception e) { 581 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 582 } 583 try { 584 Constructor<?> c = typeHandlerClass.getConstructor(); 585 return (TypeHandler<?>) c.newInstance(); 586 } catch (Exception e) { 587 throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); 588 } 589 } 590 591 592 static String getColumnName(boolean isCamelToUnderline, Field field, Column column) { 593 if (column != null && StringUtil.hasText(column.value())) { 594 return column.value(); 595 } 596 if (isCamelToUnderline) { 597 return StringUtil.camelToUnderline(field.getName()); 598 } 599 return field.getName(); 600 } 601 602 603 public static List<Field> getColumnFields(Class<?> entityClass) { 604 List<Field> fields = new ArrayList<>(); 605 doGetFields(entityClass, fields); 606 return fields; 607 } 608 609 610 private static void doGetFields(Class<?> entityClass, List<Field> fields) { 611 ClassUtil.applyAllClass(entityClass, currentClass -> { 612 Field[] declaredFields = currentClass.getDeclaredFields(); 613 for (Field declaredField : declaredFields) { 614 int modifiers = declaredField.getModifiers(); 615 if (!Modifier.isStatic(modifiers) 616 && !Modifier.isTransient(modifiers) 617 && !existName(fields, declaredField)) { 618 fields.add(declaredField); 619 } 620 } 621 return true; 622 }); 623 } 624 625 626 private static boolean existName(List<Field> fields, Field field) { 627 for (Field f : fields) { 628 if (f.getName().equalsIgnoreCase(field.getName())) { 629 return true; 630 } 631 } 632 return false; 633 } 634 635}