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

ExpediaGroup / bull / #1344

28 Mar 2026 12:58PM UTC coverage: 99.917% (-0.08%) from 100.0%
#1344

Pull #642

fborriello
Extract Kotlin default constructor marker class name into a constant
Pull Request #642: Fix #374: Skip Kotlin synthetic constructors when selecting all-args constructor

8 of 9 new or added lines in 1 file covered. (88.89%)

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
     * Fully qualified class name of the Kotlin synthetic default constructor marker.
94
     */
95
    private static final String KOTLIN_DEFAULT_CONSTRUCTOR_MARKER = "kotlin.jvm.internal.DefaultConstructorMarker";
96

97
    /**
98
     * CacheManager class {@link CacheManager}.
99
     */
100
    private static final CacheManager CACHE_MANAGER = CacheManagerFactory.getCacheManager("classUtils");
2✔
101

102
    /**
103
     * Primitive types list.
104
     */
105
    private static final Set<Class<?>> PRIMITIVE_TYPES = of(String.class, Boolean.class, Integer.class, Long.class,
2✔
106
            Double.class, BigDecimal.class, BigInteger.class, Short.class, Float.class, Character.class, Byte.class, Void.class);
107

108
    /**
109
     * Special type list.
110
     */
111
    private static final Set<Class<?>> SPECIAL_TYPES = of(Currency.class, Locale.class, Temporal.class, Date.class, Properties.class);
2✔
112

113
    /**
114
     * Method Handles lookup.
115
     */
116
    private static final MethodHandles.Lookup METHOD_HANDLES_LOOKUP = lookup();
2✔
117

118
    /**
119
     * Reflection utils instance {@link ReflectionUtils}.
120
     */
121
    private final ReflectionUtils reflectionUtils;
122

123
    /**
124
     * Default constructor.
125
     */
126
    public ClassUtils() {
2✔
127
        this.reflectionUtils = new ReflectionUtils();
2✔
128
    }
2✔
129

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

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

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

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

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

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

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

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

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

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

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

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

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

269
    /**
270
     * Checks if the given type is a String.
271
     * @param type the class to check
272
     * @return true if is String
273
     */
274
    public static boolean isString(final Class<?> type) {
275
        return type == String.class;
2✔
276
    }
277

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

844
    /**
845
     * Returns all the not final fields.
846
     * @param clazz the class containing fields.
847
     * @param skipStatic if true the static fields are skipped.
848
     * @return a list containing all the not final fields.
849
     */
850
    @SuppressWarnings("unchecked")
851
    public List<Field> getNotFinalFields(final Class<?> clazz, final Boolean skipStatic) {
852
        final String cacheKey = "NotFinalFields-" + clazz.getName() + "-" + skipStatic;
2✔
853
        return CACHE_MANAGER.getFromCache(cacheKey, List.class).orElseGet(() -> {
2✔
854
            List<Field> notFinalFields = getDeclaredFields(clazz, skipStatic)
2✔
855
                    .stream()
2✔
856
                    .filter(IS_NOT_FINAL_FIELD).collect(toList());
2✔
857
            CACHE_MANAGER.cacheObject(cacheKey, notFinalFields);
2✔
858
            return notFinalFields;
2✔
859
        });
860
    }
861
}
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