• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ExpediaGroup / bull / #1341

27 Mar 2026 05:51PM UTC coverage: 99.917% (-0.08%) from 100.0%
#1341

push

fborriello
Fix #374: Skip Kotlin synthetic constructors when selecting all-args constructor

Kotlin compiler generates synthetic constructors for data classes with default
parameter values, adding int (bitmask) and DefaultConstructorMarker parameters.
ClassUtils.getAllArgsConstructor() was selecting these synthetic constructors
because they have the most parameters, causing InvalidBeanException.

Filter out constructors containing kotlin.jvm.internal.DefaultConstructorMarker
parameter types before selecting the constructor with the most parameters.

9 of 10 new or added lines in 1 file covered. (90.0%)

1206 of 1207 relevant lines covered (99.92%)

1.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.62
/bull-common/src/main/java/com/expediagroup/transformer/utils/ClassUtils.java
1
/**
2
 * Copyright (C) 2019-2026 Expedia, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.expediagroup.transformer.utils;
17

18
import static java.lang.invoke.LambdaMetafactory.metafactory;
19
import static java.lang.invoke.MethodHandles.lookup;
20
import static java.lang.invoke.MethodHandles.privateLookupIn;
21
import static java.lang.invoke.MethodType.methodType;
22
import static java.lang.reflect.Modifier.isFinal;
23
import static java.lang.reflect.Modifier.isPrivate;
24
import static java.lang.reflect.Modifier.isPublic;
25
import static java.lang.reflect.Modifier.isStatic;
26
import static java.util.Arrays.asList;
27
import static java.util.Arrays.stream;
28
import static java.util.Collections.max;
29
import static java.util.Comparator.comparing;
30
import static java.util.Objects.isNull;
31
import static java.util.Objects.nonNull;
32
import static java.util.Optional.ofNullable;
33
import static java.util.Set.of;
34
import static java.util.stream.Collectors.toList;
35

36
import static com.expediagroup.transformer.constant.Filters.IS_FINAL_AND_NOT_STATIC_FIELD;
37
import static com.expediagroup.transformer.constant.Filters.IS_NOT_FINAL_AND_NOT_STATIC_FIELD;
38
import static com.expediagroup.transformer.constant.Filters.IS_NOT_FINAL_FIELD;
39
import static com.expediagroup.transformer.validator.Validator.notNull;
40

41
import java.lang.annotation.Annotation;
42
import java.lang.invoke.MethodHandle;
43
import java.lang.invoke.MethodHandles;
44
import java.lang.reflect.Constructor;
45
import java.lang.reflect.Field;
46
import java.lang.reflect.InvocationTargetException;
47
import java.lang.reflect.Method;
48
import java.lang.reflect.Parameter;
49
import java.math.BigDecimal;
50
import java.math.BigInteger;
51
import java.time.temporal.Temporal;
52
import java.util.ArrayList;
53
import java.util.Currency;
54
import java.util.Date;
55
import java.util.HashSet;
56
import java.util.LinkedList;
57
import java.util.List;
58
import java.util.Locale;
59
import java.util.Optional;
60
import java.util.Properties;
61
import java.util.Set;
62
import java.util.function.Predicate;
63
import java.util.function.Supplier;
64

65
import com.expediagroup.transformer.base.Defaults;
66
import com.expediagroup.transformer.cache.CacheManager;
67
import com.expediagroup.transformer.cache.CacheManagerFactory;
68
import com.expediagroup.transformer.constant.ClassType;
69
import com.expediagroup.transformer.error.InstanceCreationException;
70
import com.expediagroup.transformer.error.InvalidBeanException;
71
import com.expediagroup.transformer.error.MissingMethodException;
72

73
/**
74
 * Reflection utils for Class objects.
75
 */
76
public final class ClassUtils {
77
    /**
78
     * Default method name used by a Builder for creating an object.
79
     */
80
    public static final String BUILD_METHOD_NAME = "build";
81

82
    /**
83
     * Custom special types.
84
     */
85
    public static final Set<Class<?>> CUSTOM_SPECIAL_TYPES = new HashSet<>();
2✔
86

87
    /**
88
     * Class nullability error message constant.
89
     */
90
    private static final String CLAZZ_CANNOT_BE_NULL = "clazz cannot be null!";
91

92
    /**
93
     * CacheManager class {@link CacheManager}.
94
     */
95
    private static final CacheManager CACHE_MANAGER = CacheManagerFactory.getCacheManager("classUtils");
2✔
96

97
    /**
98
     * Primitive types list.
99
     */
100
    private static final Set<Class<?>> PRIMITIVE_TYPES = of(String.class, Boolean.class, Integer.class, Long.class,
2✔
101
            Double.class, BigDecimal.class, BigInteger.class, Short.class, Float.class, Character.class, Byte.class, Void.class);
102

103
    /**
104
     * Special type list.
105
     */
106
    private static final Set<Class<?>> SPECIAL_TYPES = of(Currency.class, Locale.class, Temporal.class, Date.class, Properties.class);
2✔
107

108
    /**
109
     * Method Handles lookup.
110
     */
111
    private static final MethodHandles.Lookup METHOD_HANDLES_LOOKUP = lookup();
2✔
112

113
    /**
114
     * Reflection utils instance {@link ReflectionUtils}.
115
     */
116
    private final ReflectionUtils reflectionUtils;
117

118
    /**
119
     * Default constructor.
120
     */
121
    public ClassUtils() {
2✔
122
        this.reflectionUtils = new ReflectionUtils();
2✔
123
    }
2✔
124

125
    /**
126
     * Checks if an object is a primitive or special type.
127
     * @param clazz the class to check
128
     * @return true if is primitive or special type, false otherwise
129
     */
130
    public boolean isPrimitiveOrSpecialType(final Class<?> clazz) {
131
        if (isNull(clazz)) {
2✔
132
            return false;
2✔
133
        }
134
        final String cacheKey = "isPrimitiveOrSpecial-" + clazz.getName();
2✔
135
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
136
            final Boolean res = isPrimitiveType(clazz) || isSpecialType(clazz);
2✔
137
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
138
            return res;
2✔
139
        });
140
    }
141

142
    /**
143
     * Checks if an object is a special type.
144
     * @param clazz the class to check
145
     * @return true if is special type, false otherwise
146
     */
147
    public boolean isPrimitiveType(final Class<?> clazz) {
148
        final String cacheKey = "isPrimitive-" + clazz.getName();
2✔
149
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
150
            final Boolean res = clazz.isPrimitive() || PRIMITIVE_TYPES.contains(clazz) || clazz.isEnum();
2✔
151
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
152
            return res;
2✔
153
        });
154
    }
155

156
    /**
157
     * Checks if an object is a primitive type array.
158
     * @param clazz the class to check
159
     * @return true if is primitive type array, false otherwise
160
     */
161
    public boolean isPrimitiveTypeArray(final Class<?> clazz) {
162
        final String cacheKey = "isPrimitiveTypeArray-" + clazz.getName();
2✔
163
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
164
            final Boolean res = clazz.isArray() && isPrimitiveType(clazz.getComponentType());
2✔
165
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
166
            return res;
2✔
167
        });
168
    }
169

170
    /**
171
     * Checks if an object is a special type.
172
     * The label "Special classes" refers to all objects that has to be copied without applying any special transformation.
173
     * @param clazz the class to check
174
     * @return true if is special type, false otherwise
175
     */
176
    public boolean isSpecialType(final Class<?> clazz) {
177
        return clazz.isSynthetic() || SPECIAL_TYPES.stream()
2✔
178
                .anyMatch(specialTypeClazz -> clazz.equals(specialTypeClazz) || specialTypeClazz.isAssignableFrom(clazz))
2✔
179
                || isCustomSpecialType(clazz);
2✔
180
    }
181

182
    /**
183
     * Checks if the given class is a custom provided special type.
184
     * @param clazz the class to check
185
    * @return true if is special type, false otherwise
186
     */
187
    private boolean isCustomSpecialType(final Class<?> clazz) {
188
        return CUSTOM_SPECIAL_TYPES.stream()
2✔
189
                .anyMatch(customTypeClazz -> clazz.equals(customTypeClazz) || customTypeClazz.isAssignableFrom(clazz));
2✔
190
    }
191

192
    /**
193
     * Checks if the given type is a {@link Double}.
194
     * @param type the class to check
195
     * @return true if is Double
196
     */
197
    public static boolean isDouble(final Class<?> type) {
198
        return Double.class.isAssignableFrom(type) || type == double.class;
2✔
199
    }
200

201
    /**
202
     * Checks if the given type is a {@link Float}.
203
     * @param type the class to check
204
     * @return true if is Float
205
     */
206
    public static boolean isFloat(final Class<?> type) {
207
        return Float.class.isAssignableFrom(type) || type == float.class;
2✔
208
    }
209

210
    /**
211
     * Checks if the given type is a {@link Long}.
212
     * @param type the class to check
213
     * @return true if is Long
214
     */
215
    public static boolean isLong(final Class<?> type) {
216
        return Long.class.isAssignableFrom(type) || type == long.class;
2✔
217
    }
218

219
    /**
220
     * Checks if the given type is a {@link Short}.
221
     * @param type the class to check
222
     * @return true if is Short
223
     */
224
    public static boolean isShort(final Class<?> type) {
225
        return Short.class.isAssignableFrom(type) || type == short.class;
2✔
226
    }
227

228
    /**
229
     * Checks if the given type is an {@link Integer}.
230
     * @param type the class to check
231
     * @return true if is Integer
232
     */
233
    public static boolean isInt(final Class<?> type) {
234
        return Integer.class.isAssignableFrom(type) || type == int.class;
2✔
235
    }
236

237
    /**
238
     * Checks if the given type is a {@link Byte}.
239
     * @param type the class to check
240
     * @return true if is Byte
241
     */
242
    public static boolean isByte(final Class<?> type) {
243
        return Byte.class.isAssignableFrom(type) || type == byte.class;
2✔
244
    }
245

246
    /**
247
     * Checks if the given type is a {@link Character}.
248
     * @param type the class to check
249
     * @return true if is Character
250
     */
251
    public static boolean isChar(final Class<?> type) {
252
        return Character.class.isAssignableFrom(type) || type == char.class;
2✔
253
    }
254

255
    /**
256
     * Checks if the given type is a {@link Boolean}.
257
     * @param type the class to check
258
     * @return true if is Boolean
259
     */
260
    public static boolean isBoolean(final Class<?> type) {
261
        return Boolean.class.isAssignableFrom(type) || type == boolean.class;
2✔
262
    }
263

264
    /**
265
     * Checks if the given type is a String.
266
     * @param type the class to check
267
     * @return true if is String
268
     */
269
    public static boolean isString(final Class<?> type) {
270
        return type == String.class;
2✔
271
    }
272

273
    /**
274
     * Checks if the given type is a {@link BigInteger}.
275
     * @param type the class to check
276
     * @return true if is String
277
     */
278
    public static boolean isBigInteger(final Class<?> type) {
279
        return type == BigInteger.class;
2✔
280
    }
281

282
    /**
283
     * Checks if the given type is a {@link BigDecimal}.
284
     * @param type the class to check
285
     * @return true if is String
286
     */
287
    public static boolean isBigDecimal(final Class<?> type) {
288
        return type == BigDecimal.class;
2✔
289
    }
290

291
    /**
292
     * Checks if the given type is a byte[].
293
     * @param type the class to check
294
     * @return true if is String
295
     */
296
    public static boolean isByteArray(final Class<?> type) {
297
        return type == byte[].class;
2✔
298
    }
299

300
    /**
301
     * Return the private final fields of a class.
302
     * @param clazz class from which gets the field
303
     * @return a list of private final fields.
304
     */
305
    @SuppressWarnings("unchecked")
306
    public List<Field> getPrivateFinalFields(final Class<?> clazz) {
307
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
308
        final String cacheKey = "PrivateFinalFields-" + clazz.getName();
2✔
309
        return CACHE_MANAGER.getFromCache(cacheKey, List.class).orElseGet(() -> {
2✔
310
            final List<Field> res = new ArrayList<>();
2✔
311
            if (hasSuperclass(clazz.getSuperclass())) {
2✔
312
                res.addAll(getPrivateFinalFields(clazz.getSuperclass()));
2✔
313
            }
314
            res.addAll(stream(getDeclaredFields(clazz))
2✔
315
                    //.parallel()
316
                    .filter(IS_FINAL_AND_NOT_STATIC_FIELD)
2✔
317
                    .collect(toList()));
2✔
318
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
319
            return res;
2✔
320
        });
321
    }
322

323
    /**
324
     * Checks if a class has a clazz different from {@link Object}.
325
     * @param clazz the class to check.
326
     * @return true if the given class extends another class different from object
327
     */
328
    private boolean hasSuperclass(final Class<?> clazz) {
329
        return nonNull(clazz) && !clazz.equals(Object.class);
2✔
330
    }
331

332
    /**
333
     * Return the total fields matching with the given predicate.
334
     * @param clazz class from which gets the field
335
     * @param predicate the condition that needs to match
336
     * @return the total matching item.
337
     */
338
    public int getTotalFields(final Class<?> clazz, final Predicate<? super Field> predicate) {
339
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
340
        final String cacheKey = "TotalFields-" + clazz.getName() + '-' + predicate;
2✔
341
        return CACHE_MANAGER.getFromCache(cacheKey, Integer.class).orElseGet(() -> {
2✔
342
            List<Field> declaredFields = getDeclaredFields(clazz, true);
2✔
343
            int res = ofNullable(predicate)
2✔
344
                    .map(filter -> (int) declaredFields.stream().filter(filter).count())
2✔
345
                    .orElseGet(declaredFields::size);
2✔
346
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
347
            return res;
2✔
348
        });
349
    }
350

351
    /**
352
     * Return the private fields of a class.
353
     * @param clazz class from which gets the field
354
     * @return a list of private final fields.
355
     */
356
    public List<Field> getPrivateFields(final Class<?> clazz) {
357
        return getPrivateFields(clazz, false);
2✔
358
    }
359

360
    /**
361
     * Return the private fields of a class.
362
     * @param clazz class from which gets the field
363
     * @param skipFinal if true it skips the final fields otherwise all private fields are retrieved.
364
     * @return a list of private fields.
365
     */
366
    @SuppressWarnings("unchecked")
367
    public List<Field> getPrivateFields(final Class<?> clazz, final boolean skipFinal) {
368
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
369
        final String cacheKey = "PrivateFields-" + clazz.getName() + "-skipFinal-" + skipFinal;
2✔
370
        return CACHE_MANAGER.getFromCache(cacheKey, List.class).orElseGet(() -> {
2✔
371
            final List<Field> res = new ArrayList<>();
2✔
372
            if (hasSuperclass(clazz.getSuperclass())) {
2✔
373
                res.addAll(getPrivateFields(clazz.getSuperclass(), skipFinal));
2✔
374
            }
375
            res.addAll(stream(getDeclaredFields(clazz))
2✔
376
                    //.parallel()
377
                    .filter(field -> isPrivate(field.getModifiers())
2✔
378
                            && (!skipFinal || !isFinal(field.getModifiers()))
2✔
379
                            && !isStatic(field.getModifiers()))
2✔
380
                    .collect(toList()));
2✔
381
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
382
            return res;
2✔
383
        });
384
    }
385

386
    /**
387
     * Return the fields of a class.
388
     * @param clazz class from which gets the field
389
     * @param skipStatic if true it skips the static fields otherwise all private fields are retrieved.
390
     * @return a list of class fields.
391
     */
392
    @SuppressWarnings("unchecked")
393
    public List<Field> getDeclaredFields(final Class<?> clazz, final boolean skipStatic) {
394
        final String cacheKey = "DeclaredFields-" + clazz.getName() + "-skipStatic-" + skipStatic;
2✔
395
        return CACHE_MANAGER.getFromCache(cacheKey, List.class).orElseGet(() -> {
2✔
396
            final List<Field> res = new ArrayList<>();
2✔
397
            if (hasSuperclass(clazz.getSuperclass())) {
2✔
398
                res.addAll(getDeclaredFields(clazz.getSuperclass(), skipStatic));
2✔
399
            }
400
            stream(getDeclaredFields(clazz))
2✔
401
                    .filter(field -> !skipStatic || !isStatic(field.getModifiers()))
2✔
402
                    .forEach(field -> {
2✔
403
                        field.setAccessible(true);
2✔
404
                        res.add(field);
2✔
405
                    });
2✔
406
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
407
            return res;
2✔
408
        });
409
    }
410

411
    /**
412
     * Returns the class fields.
413
     * @param clazz the class from which gets the field.
414
     * @return a list of class fields
415
     */
416
    private Field[] getDeclaredFields(final Class<?> clazz) {
417
        final String cacheKey = "ClassDeclaredFields-" + clazz.getName();
2✔
418
        return CACHE_MANAGER.getFromCache(cacheKey, Field[].class).orElseGet(() -> {
2✔
419
            Field[] res = clazz.getDeclaredFields();
2✔
420
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
421
            return res;
2✔
422
        });
423
    }
424

425
    /**
426
     * Returns the concrete class of a field.
427
     * @param field the field for which the concrete class has to be retrieved.
428
     * @param objectInstance the object instance.
429
     * @param <T> the object instance class.
430
     * @return the concrete class of a field
431
     */
432
    public <T> Class<?> getFieldClass(final Field field, final T objectInstance) {
433
        return getConcreteClass(field, reflectionUtils.getFieldValue(objectInstance, field));
2✔
434
    }
435

436
    /**
437
     * Returns the concrete class of a field.
438
     * @param field the field for which the concrete class has to be retrieved.
439
     * @param fieldValue the field value.
440
     * @return the concrete class of a field
441
     */
442
    public Class<?> getConcreteClass(final Field field, final Object fieldValue) {
443
        final String cacheKey = "ConcreteFieldClass-" + field.getName() + "-" + field.getDeclaringClass();
2✔
444
        return CACHE_MANAGER.getFromCache(cacheKey, Class.class).orElseGet(() -> {
2✔
445
            Class<?> concreteType = field.getType();
2✔
446
            boolean isFieldValueNull = isNull(fieldValue);
2✔
447
            if (field.getType().isInterface()) {
2✔
448
                concreteType = isFieldValueNull ? Object.class : fieldValue.getClass();
2✔
449
            }
450
            if (!isFieldValueNull) {
2✔
451
                CACHE_MANAGER.cacheObject(cacheKey, concreteType);
2✔
452
            }
453
            return concreteType;
2✔
454
        });
455
    }
456

457
    /**
458
     * Checks if the destination class has accessible constructor.
459
     * @param targetClass the destination object class
460
     * @param <K> the target object type
461
     * @return true if the target class uses the builder pattern
462
     */
463
    public <K> boolean hasAccessibleConstructors(final Class<K> targetClass) {
464
        final String cacheKey = "HasAccessibleConstructors-" + targetClass.getName();
2✔
465
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
466
            final boolean res = stream(targetClass.getDeclaredConstructors()).anyMatch(constructor -> isPublic(constructor.getModifiers()));
2✔
467
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
468
            return res;
2✔
469
        });
470
    }
471

472
    /**
473
     * Retrieves all classes defined into the given one.
474
     * @param clazz class where we search for a nested class
475
     * @return all classes defined into the given one
476
     */
477
    public Class[] getDeclaredClasses(final Class<?> clazz) {
478
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
479
        String cacheKey = "DeclaredClasses-" + clazz.getName();
2✔
480
        return CACHE_MANAGER.getFromCache(cacheKey, Class[].class).orElseGet(() -> {
2✔
481
            Class[] declaredClasses = clazz.getDeclaredClasses();
2✔
482
            CACHE_MANAGER.cacheObject(cacheKey, declaredClasses);
2✔
483
            return declaredClasses;
2✔
484
        });
485
    }
486

487
    /**
488
     * Returns the builder class.
489
     * @param targetClass the class where the builder should be searched
490
     * @return the Builder class if available.
491
     */
492
    @SuppressWarnings("unchecked")
493
    public Optional<Class<?>> getBuilderClass(final Class<?> targetClass) {
494
        String cacheKey = "BuilderClass-" + targetClass.getName();
2✔
495
        return CACHE_MANAGER.getFromCache(cacheKey, Optional.class).orElseGet(() -> {
2✔
496
            Optional<Class> res = stream(getDeclaredClasses(targetClass))
2✔
497
                    .filter(nestedClass -> {
2✔
498
                        var hasBuildMethod = true;
2✔
499
                        try {
500
                            getBuildMethod(targetClass, nestedClass);
2✔
501
                        } catch (MissingMethodException e) {
2✔
502
                            hasBuildMethod = false;
2✔
503
                        }
2✔
504
                        return hasBuildMethod;
2✔
505
                    })
506
                    .findAny();
2✔
507
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
508
            return res;
2✔
509
        });
510
    }
511

512
    /**
513
     * Get build method inside the Builder class.
514
     * @param parentClass the class containing the builder
515
     * @param builderClass the builder class (see Builder Pattern)
516
     * @return Builder build method if present
517
     */
518
    public Method getBuildMethod(final Class<?> parentClass, final Class<?> builderClass) {
519
        final String cacheKey = "BuildMethod-" + builderClass.getName();
2✔
520
        return CACHE_MANAGER.getFromCache(cacheKey, Method.class).orElseGet(() -> {
2✔
521
            try {
522
                var method = builderClass.getDeclaredMethod(BUILD_METHOD_NAME);
2✔
523
                if (!method.getReturnType().equals(parentClass)) {
2✔
524
                    throw new MissingMethodException("Invalid " + BUILD_METHOD_NAME + " method definition. It must returns a: " + parentClass.getCanonicalName());
2✔
525
                }
526
                method.setAccessible(true);
2✔
527
                CACHE_MANAGER.cacheObject(cacheKey, method);
2✔
528
                return method;
2✔
529
            } catch (NoSuchMethodException e) {
2✔
530
                throw new MissingMethodException("No Builder " + BUILD_METHOD_NAME + " method defined for class: " + builderClass.getName() + ".");
2✔
531
            }
532
        });
533
    }
534

535
    /**
536
     * Creates an instance of the given class invoking the given constructor.
537
     * @param constructor the constructor to invoke.
538
     * @param constructorArgs the constructor args.
539
     * @param <T> the class object type.
540
     * @return the object instance.
541
     * @throws InstanceCreationException in case the object creation fails.
542
     */
543
    @SuppressWarnings("unchecked")
544
    public <T> T getInstance(final Constructor constructor, final Object... constructorArgs) {
545
        try {
546
            return (T) constructor.newInstance(constructorArgs);
1✔
547
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
2✔
548
           throw new InstanceCreationException(e.getMessage(), e);
2✔
549
        }
550
    }
551

552
    /**
553
     * Retrieves the no args constructor.
554
     * @param clazz the class from which gets the all arg constructor.
555
     * @param <K> the object type
556
     * @return the no args constructor
557
     * @throws InvalidBeanException if no default constructor is available
558
     */
559
    @SuppressWarnings("unchecked")
560
    public <K> Supplier<K> getNoArgsConstructor(final Class<K> clazz) {
561
        final String cacheKey = "NoArgsConstructor-" + clazz.getName();
2✔
562
        return CACHE_MANAGER.getFromCache(cacheKey, Supplier.class).orElseGet(() -> {
2✔
563
            try {
564
                var privateLookupIn = privateLookupIn(clazz, METHOD_HANDLES_LOOKUP);
2✔
565
                MethodHandle mh = privateLookupIn.findConstructor(clazz, methodType(void.class));
2✔
566
                Supplier<K> constructor = (Supplier<K>) metafactory(
2✔
567
                        privateLookupIn, "get", methodType(Supplier.class), mh.type().generic(), mh, mh.type()
2✔
568
                ).getTarget().invokeExact();
2✔
569
                CACHE_MANAGER.cacheObject(cacheKey, constructor);
2✔
570
                return constructor;
2✔
571
            } catch (Throwable e) {
2✔
572
                throw new InvalidBeanException("No default constructors available for class: " + clazz.getName());
2✔
573
            }
574
        });
575
    }
576

577
    /**
578
     * Retrieves the all args constructor.
579
     * @param clazz the class from which gets the all arg constructor.
580
     * @param <K> the object type
581
     * @return the all args constructor
582
     */
583
    @SuppressWarnings("unchecked")
584
    public <K> Constructor<K> getAllArgsConstructor(final Class<K> clazz) {
585
        final String cacheKey = "AllArgsConstructor-" + clazz.getName();
2✔
586
        return CACHE_MANAGER.getFromCache(cacheKey, Constructor.class).orElseGet(() -> {
2✔
587
            Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
2✔
588
            var candidates = stream(declaredConstructors)
2✔
589
                    .filter(c -> !isKotlinSyntheticConstructor(c))
2✔
590
                    .collect(toList());
2✔
591
            if (candidates.isEmpty()) {
2✔
NEW
592
                candidates = asList(declaredConstructors);
×
593
            }
594
            final var constructor = max(candidates, comparing(Constructor::getParameterCount));
2✔
595
            constructor.setAccessible(true);
2✔
596
            CACHE_MANAGER.cacheObject(cacheKey, constructor);
2✔
597
            return constructor;
2✔
598
        });
599
    }
600

601
    /**
602
     * Checks if a constructor is a Kotlin synthetic constructor generated for default parameter values.
603
     * Such constructors have {@code kotlin.jvm.internal.DefaultConstructorMarker} as a parameter type.
604
     * @param constructor the constructor to check
605
     * @return true if the constructor is a Kotlin synthetic default constructor
606
     */
607
    private boolean isKotlinSyntheticConstructor(final Constructor<?> constructor) {
608
        for (Class<?> paramType : constructor.getParameterTypes()) {
2✔
609
            if ("kotlin.jvm.internal.DefaultConstructorMarker".equals(paramType.getName())) {
2✔
610
                return true;
2✔
611
            }
612
        }
613
        return false;
2✔
614
    }
615

616
    /**
617
     * Gets all the constructor parameters.
618
     * @param constructor the constructor.
619
     * @return the constructor parameters
620
     */
621
    public Parameter[] getConstructorParameters(final Constructor constructor) {
622
        final String cacheKey = "ConstructorParams-" + constructor.getDeclaringClass().getName() + '-' + constructor.getParameterCount();
2✔
623
        return CACHE_MANAGER.getFromCache(cacheKey, Parameter[].class).orElseGet(() -> {
2✔
624
            final Parameter[] parameters = constructor.getParameters();
2✔
625
            CACHE_MANAGER.cacheObject(cacheKey, parameters);
2✔
626
            return parameters;
2✔
627
        });
628
    }
629

630
    /**
631
     * Checks that the class has a specific field.
632
     * @param target the class where the field should be
633
     * @param fieldName the field name to retrieve
634
     * @return true if the field is available, false otherwise
635
     */
636
    public boolean hasField(final Object target, final String fieldName) {
637
        final String cacheKey = "ClassHasField-" + target.getClass().getName() + '-' + fieldName;
2✔
638
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
639
            var hasField = false;
2✔
640
            try {
641
                target.getClass().getDeclaredField(fieldName);
2✔
642
                hasField = true;
2✔
643
            } catch (final NoSuchFieldException e) {
2✔
644
                final Class<?> superclass = target.getClass().getSuperclass();
2✔
645
                if (hasSuperclass(superclass)) {
2✔
646
                    hasField = hasField(superclass, fieldName);
2✔
647
                }
648
            }
2✔
649
            CACHE_MANAGER.cacheObject(cacheKey, hasField);
2✔
650
            return hasField;
2✔
651
        });
652
    }
653

654
    /**
655
     * Checks if a class has setter methods.
656
     * @param clazz clazz the clazz containing the methods.
657
     * @return true if has at least one setter method, false otherwise
658
     */
659
    public boolean hasSetterMethods(final Class<?> clazz) {
660
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
661
        final String cacheKey = "HasSetterMethods-" + clazz.getName();
2✔
662
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class)
2✔
663
                .orElseGet(() -> {
2✔
664
                    final Boolean res = stream(getDeclaredMethods(clazz)).anyMatch(reflectionUtils::isSetter);
2✔
665
                    CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
666
                    return res;
2✔
667
                });
668
    }
669

670
    /**
671
     * Retrieves the declared methods for the given class.
672
     * @param clazz class from which gets the methods
673
     * @return the class declared methods.
674
     */
675
    private Method[] getDeclaredMethods(final Class<?> clazz) {
676
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
677
        final String cacheKey = "DeclaredMethods-" + clazz.getName();
2✔
678
        return CACHE_MANAGER.getFromCache(cacheKey, Method[].class)
2✔
679
                .orElseGet(() -> {
2✔
680
                    final Method[] res = clazz.getDeclaredMethods();
2✔
681
                    CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
682
                    return res;
2✔
683
                });
684
    }
685

686
    /**
687
     * Checks if a class has any final field.
688
     * @param clazz class from which gets the field
689
     * @return true if it has private final field, false otherwise.
690
     */
691
    public boolean hasFinalFields(final Class<?> clazz) {
692
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
693
        return hasFieldsMatchingCondition(clazz, IS_FINAL_AND_NOT_STATIC_FIELD, "HasFinalNotStaticFields-");
2✔
694
    }
695

696
    /**
697
     * Checks if a class has any public field.
698
     * @param clazz class from which gets the field
699
     * @return true if it has private final field, false otherwise.
700
     */
701
    private boolean hasNotFinalFields(final Class<?> clazz) {
702
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
703
        return hasFieldsMatchingCondition(clazz, IS_NOT_FINAL_AND_NOT_STATIC_FIELD, "HasNotFinalNotStaticFields-");
2✔
704
    }
705

706
    /**
707
     * Checks if a class has any field matching the given condition.
708
     * @param clazz class from which gets the field
709
     * @param filterPredicate the filter to apply
710
     * @param cacheKey the filter to apply
711
     * @return true if it has private final field, false otherwise.
712
     */
713
    private boolean hasFieldsMatchingCondition(final Class<?> clazz, final Predicate<Field> filterPredicate, final String cacheKey) {
714
        return CACHE_MANAGER.getFromCache(cacheKey + clazz.getName(), Boolean.class).orElseGet(() -> {
2✔
715
            boolean res = stream(getDeclaredFields(clazz))
2✔
716
                    //.parallel()
717
                    .anyMatch(filterPredicate);
2✔
718
            if (!res && nonNull(clazz.getSuperclass()) && !clazz.getSuperclass().equals(Object.class)) {
2✔
719
                Class<?> superclass = clazz.getSuperclass();
2✔
720
                res = hasFieldsMatchingCondition(superclass, filterPredicate, cacheKey);
2✔
721
            }
722
            CACHE_MANAGER.cacheObject(cacheKey + clazz.getName(), res);
2✔
723
            return res;
2✔
724
        });
725
    }
726

727
    /**
728
     * Checks if any of the class constructor's parameters are not annotated with the given class.
729
     * @param constructor the constructor to check.
730
     * @param annotationClass the annotation class to retrieve
731
     * @return true if any of the parameter does not contains the annotation, false otherwise.
732
     */
733
    public boolean allParameterAnnotatedWith(final Constructor constructor, final Class<? extends Annotation> annotationClass) {
734
        final String cacheKey = "AllParameterAnnotatedWith-" + constructor.getDeclaringClass().getName() + '-' + annotationClass.getName();
2✔
735
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
736
            final boolean notAllAnnotatedWith = stream(constructor.getParameters())
2✔
737
                    .allMatch(parameter -> nonNull(parameter.getAnnotation(annotationClass)));
2✔
738
            CACHE_MANAGER.cacheObject(cacheKey, notAllAnnotatedWith);
2✔
739
            return notAllAnnotatedWith;
2✔
740
        });
741
    }
742

743
    /**
744
     * Checks if the constructor's parameters names are defined.
745
     * @param constructor the constructor to check.
746
     * @return true if some parameters names are not defined, false otherwise.
747
     */
748
    public boolean areParameterNamesAvailable(final Constructor constructor) {
749
        final String cacheKey = "AreParameterNamesAvailable-" + constructor.getDeclaringClass().getName();
2✔
750
        return CACHE_MANAGER.getFromCache(cacheKey, Boolean.class).orElseGet(() -> {
2✔
751
            final boolean res = stream(getConstructorParameters(constructor))
2✔
752
                    .anyMatch(Parameter::isNamePresent);
2✔
753
            CACHE_MANAGER.cacheObject(cacheKey, res);
2✔
754
            return res;
2✔
755
        });
756
    }
757

758
    /**
759
     * Returns the class type.
760
     * @param clazz the class to check
761
     * @return the class type {@link ClassType}
762
     */
763
    public ClassType getClassType(final Class<?> clazz) {
764
        final String cacheKey = "ClassType-" + clazz.getName();
2✔
765
        return CACHE_MANAGER.getFromCache(cacheKey, ClassType.class).orElseGet(() -> {
2✔
766
            final ClassType classType;
767
            boolean hasFinalFields = hasFinalFields(clazz);
2✔
768
            if (!hasFinalFields) {
2✔
769
                classType = ClassType.MUTABLE;
2✔
770
            } else {
771
                boolean hasNotFinalFields = hasNotFinalFields(clazz);
2✔
772
                if (hasNotFinalFields) {
2✔
773
                    classType = ClassType.MIXED;
2✔
774
                } else {
775
                    classType = ClassType.IMMUTABLE;
2✔
776
                }
777
            }
778
            CACHE_MANAGER.cacheObject(cacheKey, classType);
2✔
779
            return classType;
2✔
780
        });
781
    }
782

783
    /**
784
     * Retrieves all the setters method for the given class.
785
     * @param clazz the class containing the methods.
786
     * @return all the class setter methods
787
     */
788
    public List<Method> getSetterMethods(final Class<?> clazz) {
789
        return getMethods(clazz, "SetterMethods", reflectionUtils::isSetter);
2✔
790
    }
791

792
    /**
793
     * Retrieves all the getters method for the given class.
794
     * @param clazz the class containing the methods.
795
     * @return all the class getter methods
796
     */
797
    public List<Method> getGetterMethods(final Class<?> clazz) {
798
        return getMethods(clazz, "GetterMethods", reflectionUtils::isGetter);
2✔
799
    }
800

801
    /**
802
     * Retrieves all the class methods matching to the given filter.
803
     * @param clazz the class containing the methods.
804
     * @param cacheKeyPrefix the prefix to adopt for the cache key
805
     * @param methodFilter the filter to apply to the retrieved methods
806
     * @return the class methods matching the filter
807
     */
808
    @SuppressWarnings("unchecked")
809
    private List<Method> getMethods(final Class<?> clazz, final String cacheKeyPrefix, final Predicate<Method> methodFilter) {
810
        notNull(clazz, CLAZZ_CANNOT_BE_NULL);
2✔
811
        final String cacheKey = cacheKeyPrefix + "-" + clazz.getName();
2✔
812
        return CACHE_MANAGER.getFromCache(cacheKey, List.class).orElseGet(() -> {
2✔
813
            final List<Method> methods = new LinkedList<>();
2✔
814
            if (hasSuperclass(clazz.getSuperclass())) {
2✔
815
                methods.addAll(getMethods(clazz.getSuperclass(), cacheKeyPrefix, methodFilter));
2✔
816
            }
817
            methods.addAll(stream(getDeclaredMethods(clazz))
2✔
818
                    .filter(methodFilter)
2✔
819
                    .collect(toList()));
2✔
820
            CACHE_MANAGER.cacheObject(cacheKey, methods);
2✔
821
            return methods;
2✔
822
        });
823
    }
824

825
    /**
826
     * Gets the default value of a primitive type.
827
     * @param objectType the primitive object class
828
     * @return the default value of a primitive type
829
     */
830
    public Object getDefaultTypeValue(final Class<?> objectType) {
831
        final String cacheKey = "DefaultTypeValue-" + objectType.getName();
2✔
832
        return CACHE_MANAGER.getFromCache(cacheKey, Object.class).orElseGet(() -> {
2✔
833
            final Object defaultValue = isPrimitiveType(objectType) ? Defaults.defaultValue(objectType) : null;
2✔
834
            CACHE_MANAGER.cacheObject(cacheKey, defaultValue);
2✔
835
            return defaultValue;
2✔
836
        });
837
    }
838

839
    /**
840
     * Returns all the not final fields.
841
     * @param clazz the class containing fields.
842
     * @param skipStatic if true the static fields are skipped.
843
     * @return a list containing all the not final fields.
844
     */
845
    @SuppressWarnings("unchecked")
846
    public List<Field> getNotFinalFields(final Class<?> clazz, final Boolean skipStatic) {
847
        final String cacheKey = "NotFinalFields-" + clazz.getName() + "-" + skipStatic;
2✔
848
        return CACHE_MANAGER.getFromCache(cacheKey, List.class).orElseGet(() -> {
2✔
849
            List<Field> notFinalFields = getDeclaredFields(clazz, skipStatic)
2✔
850
                    .stream()
2✔
851
                    .filter(IS_NOT_FINAL_FIELD).collect(toList());
2✔
852
            CACHE_MANAGER.cacheObject(cacheKey, notFinalFields);
2✔
853
            return notFinalFields;
2✔
854
        });
855
    }
856
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc