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.isNotBlank(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        // 初始化表名
239        Table table = entityClass.getAnnotation(Table.class);
240        if (table == null) {
241            TableRef vo = entityClass.getAnnotation(TableRef.class);
242            if (vo != null) {
243                TableInfo refTableInfo = ofEntityClass(vo.value());
244                // 设置 VO 类对应的真实的表名
245                tableInfo.setSchema(refTableInfo.getSchema());
246                tableInfo.setTableName(refTableInfo.getTableName());
247                // 将 @Table 注解的属性复制到 VO 类当中
248                if (vo.copyTableProps()) {
249                    tableInfo.setComment(refTableInfo.getComment());
250                    tableInfo.setCamelToUnderline(refTableInfo.isCamelToUnderline());
251                    tableInfo.setDataSource(refTableInfo.getDataSource());
252
253                    tableInfo.setOnSetListeners(refTableInfo.getOnSetListeners());
254                    tableInfo.setOnInsertColumns(refTableInfo.getOnInsertColumns());
255                    tableInfo.setOnUpdateListeners(refTableInfo.getOnUpdateListeners());
256                }
257            } else {
258                // 默认为类名转驼峰下划线
259                String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
260                tableInfo.setTableName(tableName);
261            }
262        } else {
263            tableInfo.setSchema(table.schema());
264            tableInfo.setTableName(table.value());
265            tableInfo.setCamelToUnderline(table.camelToUnderline());
266            tableInfo.setComment(table.comment());
267
268            if (table.onInsert().length > 0) {
269                List<InsertListener> insertListeners = Arrays.stream(table.onInsert())
270                    .filter(listener -> listener != NoneListener.class)
271                    .map(ClassUtil::newInstance)
272                    .collect(Collectors.toList());
273                tableInfo.setOnInsertListeners(insertListeners);
274            }
275
276            if (table.onUpdate().length > 0) {
277                List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate())
278                    .filter(listener -> listener != NoneListener.class)
279                    .map(ClassUtil::newInstance)
280                    .collect(Collectors.toList());
281                tableInfo.setOnUpdateListeners(updateListeners);
282            }
283
284            if (table.onSet().length > 0) {
285                List<SetListener> setListeners = Arrays.stream(table.onSet())
286                    .filter(listener -> listener != NoneListener.class)
287                    .map(ClassUtil::newInstance)
288                    .collect(Collectors.toList());
289                tableInfo.setOnSetListeners(setListeners);
290            }
291
292            if (StringUtil.isNotBlank(table.dataSource())) {
293                tableInfo.setDataSource(table.dataSource());
294            }
295        }
296
297        // 初始化字段相关
298        List<ColumnInfo> columnInfoList = new ArrayList<>();
299        List<IdInfo> idInfos = new ArrayList<>();
300
301
302        String logicDeleteColumn = null;
303        String versionColumn = null;
304        String tenantIdColumn = null;
305
306        // 数据插入时,默认插入数据字段
307        Map<String, String> onInsertColumns = new HashMap<>();
308
309        // 数据更新时,默认更新内容的字段
310        Map<String, String> onUpdateColumns = new HashMap<>();
311
312        // 大字段列
313        Set<String> largeColumns = new LinkedHashSet<>();
314
315        // 默认查询列
316        Set<String> defaultQueryColumns = new LinkedHashSet<>();
317
318        List<Field> entityFields = getColumnFields(entityClass);
319
320        FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig();
321
322        TypeHandlerRegistry typeHandlerRegistry = null;
323        if (config.getConfiguration() != null) {
324            typeHandlerRegistry = config.getConfiguration().getTypeHandlerRegistry();
325        }
326
327        for (Field field : entityFields) {
328
329            Class<?> fieldType = reflector.getGetterType(field.getName());
330
331            // 移除默认的忽略字段
332            boolean isIgnoreField = false;
333            for (Class<?> ignoreColumnType : ignoreColumnTypes) {
334                if (ignoreColumnType.isAssignableFrom(fieldType)) {
335                    isIgnoreField = true;
336                    break;
337                }
338            }
339
340            if (isIgnoreField) {
341                continue;
342            }
343
344            Column columnAnnotation = field.getAnnotation(Column.class);
345
346            /*
347             * 满足以下 4 种情况,不支持该类型的属性自动映射为字段
348             * 1、注解上未配置 TypeHandler
349             * 2、类型不是枚举
350             * 3、默认的自动类型不包含该类型
351             * 4、没有全局 TypeHandler
352             */
353            if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class)
354                && !fieldType.isEnum()
355                && !defaultSupportColumnTypes.contains(fieldType)
356                && (typeHandlerRegistry == null || !typeHandlerRegistry.hasTypeHandler(fieldType))
357            ) {
358                // 忽略 集合 实体类 解析
359                if (columnAnnotation != null && columnAnnotation.ignore()) {
360                    continue;
361                }
362                // 集合嵌套
363                if (Collection.class.isAssignableFrom(fieldType)) {
364                    Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
365                    if (genericType instanceof ParameterizedType) {
366                        Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
367                        if (actualTypeArgument instanceof Class) {
368                            tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument);
369                        }
370                    }
371                }
372                // 实体类嵌套
373                else if (!Map.class.isAssignableFrom(fieldType)
374                    && !fieldType.isArray()) {
375                    tableInfo.addAssociationType(field.getName(), fieldType);
376                }
377                // 不支持的类型直接跳过
378                continue;
379            }
380
381            // 列名
382            String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation);
383
384            // 逻辑删除字段
385            if ((columnAnnotation != null && columnAnnotation.isLogicDelete())
386                || columnName.equals(config.getLogicDeleteColumn())) {
387                if (logicDeleteColumn == null) {
388                    logicDeleteColumn = columnName;
389                } else {
390                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
391                }
392            }
393
394            // 乐观锁版本字段
395            if ((columnAnnotation != null && columnAnnotation.version())
396                || columnName.equals(config.getVersionColumn())) {
397                if (versionColumn == null) {
398                    versionColumn = columnName;
399                } else {
400                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
401                }
402            }
403
404            // 租户ID 字段
405            if ((columnAnnotation != null && columnAnnotation.tenantId())
406                || columnName.equals(config.getTenantColumn())) {
407                if (tenantIdColumn == null) {
408                    tenantIdColumn = columnName;
409                } else {
410                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
411                }
412            }
413
414
415            if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) {
416                onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim());
417            }
418
419
420            if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) {
421                onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim());
422            }
423
424
425            if (columnAnnotation != null && columnAnnotation.isLarge()) {
426                largeColumns.add(columnName);
427            }
428
429            // 主键配置
430            Id id = field.getAnnotation(Id.class);
431            ColumnInfo columnInfo;
432            if (id != null) {
433                columnInfo = new IdInfo(id);
434                idInfos.add((IdInfo) columnInfo);
435            } else {
436                columnInfo = new ColumnInfo();
437                columnInfoList.add(columnInfo);
438            }
439
440            ColumnAlias columnAlias = null;
441
442            // 属性上没有别名,查找 getter 方法上有没有别名
443            Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName()));
444            if (getterMethod != null) {
445                columnAlias = getterMethod.getAnnotation(ColumnAlias.class);
446            }
447
448            if (columnAlias == null) {
449                columnAlias = field.getAnnotation(ColumnAlias.class);
450            }
451
452            if (columnAlias != null) {
453                columnInfo.setAlias(columnAlias.value());
454            }
455
456            columnInfo.setColumn(columnName);
457            columnInfo.setProperty(field.getName());
458            columnInfo.setPropertyType(fieldType);
459            columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore());
460
461            if (columnAnnotation != null) {
462                columnInfo.setComment(columnAnnotation.comment());
463            }
464
465
466            // 默认查询列 没有忽略且不是大字段
467            if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) {
468                defaultQueryColumns.add(columnName);
469            }
470
471
472            // typeHandler 配置
473            if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) {
474                TypeHandler<?> typeHandler = null;
475
476                // 集合类型,支持泛型
477                // fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE
478                if (Collection.class.isAssignableFrom(fieldType)) {
479                    typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType);
480                }
481
482                // 非集合类型
483                else {
484                    Class<?> typeHandlerClass = columnAnnotation.typeHandler();
485                    if (typeHandlerRegistry != null) {
486                        Class<?> propertyType = columnInfo.getPropertyType();
487                        JdbcType jdbcType = columnAnnotation.jdbcType();
488                        if (jdbcType != JdbcType.UNDEFINED) {
489                            typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
490                        }
491                        if (typeHandler == null || !typeHandlerClass.isAssignableFrom(typeHandler.getClass())) {
492                            typeHandler = typeHandlerRegistry.getInstance(propertyType, typeHandlerClass);
493                        }
494                    }
495                }
496
497                columnInfo.setTypeHandler(typeHandler);
498            }
499
500            // 数据脱敏配置
501            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
502            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
503                if (String.class != fieldType) {
504                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
505                }
506                columnInfo.setMaskType(columnMask.value().trim());
507            }
508
509            // jdbcType 配置
510            if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) {
511                columnInfo.setJdbcType(columnAnnotation.jdbcType());
512            }
513
514        }
515
516
517        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
518        tableInfo.setVersionColumn(versionColumn);
519        tableInfo.setTenantIdColumn(tenantIdColumn);
520
521        if (!onInsertColumns.isEmpty()) {
522            tableInfo.setOnInsertColumns(onInsertColumns);
523        }
524
525        if (!onUpdateColumns.isEmpty()) {
526            tableInfo.setOnUpdateColumns(onUpdateColumns);
527        }
528
529        if (!largeColumns.isEmpty()) {
530            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
531        }
532
533        if (!defaultQueryColumns.isEmpty()) {
534            tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0]));
535        }
536
537        // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column,
538        // 否则会影响 SQL 的字段构建顺序
539        tableInfo.setPrimaryKeyList(idInfos);
540        tableInfo.setColumnInfoList(columnInfoList);
541
542
543        return tableInfo;
544    }
545
546    /**
547     * 创建 typeHandler
548     * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)}
549     *
550     * @param entityClass
551     * @param field
552     * @param typeHandlerClass
553     * @param fieldType
554     */
555    private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) {
556        Class<?> genericClass = null;
557        Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass);
558        if (genericType instanceof ParameterizedType) {
559            Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
560            if (actualTypeArgument instanceof Class) {
561                genericClass = (Class<?>) actualTypeArgument;
562            }
563        }
564
565        try {
566            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class);
567            return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass);
568        } catch (NoSuchMethodException ignored) {
569        } catch (Exception e) {
570            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
571        }
572        try {
573            Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class);
574            return (TypeHandler<?>) constructor.newInstance(fieldType);
575        } catch (NoSuchMethodException ignored) {
576        } catch (Exception e) {
577            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
578        }
579        try {
580            Constructor<?> c = typeHandlerClass.getConstructor();
581            return (TypeHandler<?>) c.newInstance();
582        } catch (Exception e) {
583            throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
584        }
585    }
586
587
588    static String getColumnName(boolean isCamelToUnderline, Field field, Column column) {
589        if (column != null && StringUtil.isNotBlank(column.value())) {
590            return column.value();
591        }
592        if (isCamelToUnderline) {
593            return StringUtil.camelToUnderline(field.getName());
594        }
595        return field.getName();
596    }
597
598
599    public static List<Field> getColumnFields(Class<?> entityClass) {
600        List<Field> fields = new ArrayList<>();
601        doGetFields(entityClass, fields);
602        return fields;
603    }
604
605
606    private static void doGetFields(Class<?> entityClass, List<Field> fields) {
607        ClassUtil.applyAllClass(entityClass, currentClass -> {
608            Field[] declaredFields = currentClass.getDeclaredFields();
609            for (Field declaredField : declaredFields) {
610                int modifiers = declaredField.getModifiers();
611                if (!Modifier.isStatic(modifiers)
612                    && !Modifier.isTransient(modifiers)
613                    && !existName(fields, declaredField)) {
614                    fields.add(declaredField);
615                }
616            }
617            return true;
618        });
619    }
620
621
622    private static boolean existName(List<Field> fields, Field field) {
623        for (Field f : fields) {
624            if (f.getName().equalsIgnoreCase(field.getName())) {
625                return true;
626            }
627        }
628        return false;
629    }
630
631}