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}