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

mybatis / ibatis-2 / 737

29 Dec 2025 12:30AM UTC coverage: 65.6% (+0.03%) from 65.571%
737

Pull #340

github

web-flow
Merge e5b1c0141 into 14d10ebb6
Pull Request #340: More code cleanup and styling changes

1598 of 2797 branches covered (57.13%)

23 of 42 new or added lines in 2 files covered. (54.76%)

2 existing lines in 1 file now uncovered.

5042 of 7686 relevant lines covered (65.6%)

0.66 hits per line

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

92.21
/src/main/java/com/ibatis/common/beans/ClassInfo.java
1
/*
2
 * Copyright 2004-2025 the original author or authors.
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
 *    https://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.ibatis.common.beans;
17

18
import java.lang.reflect.Constructor;
19
import java.lang.reflect.Field;
20
import java.lang.reflect.InvocationTargetException;
21
import java.lang.reflect.Method;
22
import java.lang.reflect.ReflectPermission;
23
import java.lang.reflect.UndeclaredThrowableException;
24
import java.math.BigDecimal;
25
import java.math.BigInteger;
26
import java.util.ArrayList;
27
import java.util.Collection;
28
import java.util.Date;
29
import java.util.Enumeration;
30
import java.util.HashMap;
31
import java.util.HashSet;
32
import java.util.Hashtable;
33
import java.util.Iterator;
34
import java.util.LinkedList;
35
import java.util.List;
36
import java.util.Locale;
37
import java.util.Map;
38
import java.util.Set;
39
import java.util.TreeMap;
40
import java.util.TreeSet;
41
import java.util.Vector;
42
import java.util.concurrent.ConcurrentHashMap;
43

44
/**
45
 * This class represents a cached set of class definition information that allows for easy mapping between property
46
 * names and getter/setter methods.
47
 */
48
public class ClassInfo {
49

50
  /** The cache enabled. */
51
  private static boolean cacheEnabled = true;
1✔
52

53
  /** The Constant EMPTY_STRING_ARRAY. */
54
  private static final String[] EMPTY_STRING_ARRAY = {};
1✔
55

56
  /** The Constant SIMPLE_TYPE_SET. */
57
  private static final Set SIMPLE_TYPE_SET = new HashSet<>();
1✔
58

59
  /** The Constant CLASS_INFO_MAP. */
60
  private static final Map<Class, ClassInfo> CLASS_INFO_MAP = new ConcurrentHashMap<>();
1✔
61

62
  /** The class name. */
63
  private String className;
64

65
  /** The readable property names. */
66
  private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
1✔
67

68
  /** The writeable property names. */
69
  private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
1✔
70

71
  /** The set methods. */
72
  private HashMap setMethods = new HashMap<>();
1✔
73

74
  /** The get methods. */
75
  private HashMap getMethods = new HashMap<>();
1✔
76

77
  /** The set types. */
78
  private HashMap setTypes = new HashMap<>();
1✔
79

80
  /** The get types. */
81
  private HashMap getTypes = new HashMap<>();
1✔
82

83
  /** The default constructor. */
84
  private Constructor defaultConstructor;
85

86
  static {
87
    SIMPLE_TYPE_SET.add(String.class);
1✔
88
    SIMPLE_TYPE_SET.add(Byte.class);
1✔
89
    SIMPLE_TYPE_SET.add(Short.class);
1✔
90
    SIMPLE_TYPE_SET.add(Character.class);
1✔
91
    SIMPLE_TYPE_SET.add(Integer.class);
1✔
92
    SIMPLE_TYPE_SET.add(Long.class);
1✔
93
    SIMPLE_TYPE_SET.add(Float.class);
1✔
94
    SIMPLE_TYPE_SET.add(Double.class);
1✔
95
    SIMPLE_TYPE_SET.add(Boolean.class);
1✔
96
    SIMPLE_TYPE_SET.add(Date.class);
1✔
97
    SIMPLE_TYPE_SET.add(Class.class);
1✔
98
    SIMPLE_TYPE_SET.add(BigInteger.class);
1✔
99
    SIMPLE_TYPE_SET.add(BigDecimal.class);
1✔
100

101
    SIMPLE_TYPE_SET.add(Collection.class);
1✔
102
    SIMPLE_TYPE_SET.add(Set.class);
1✔
103
    SIMPLE_TYPE_SET.add(Map.class);
1✔
104
    SIMPLE_TYPE_SET.add(List.class);
1✔
105
    SIMPLE_TYPE_SET.add(HashMap.class);
1✔
106
    SIMPLE_TYPE_SET.add(TreeMap.class);
1✔
107
    SIMPLE_TYPE_SET.add(ArrayList.class);
1✔
108
    SIMPLE_TYPE_SET.add(LinkedList.class);
1✔
109
    SIMPLE_TYPE_SET.add(HashSet.class);
1✔
110
    SIMPLE_TYPE_SET.add(TreeSet.class);
1✔
111
    SIMPLE_TYPE_SET.add(Vector.class);
1✔
112
    SIMPLE_TYPE_SET.add(Hashtable.class);
1✔
113
    SIMPLE_TYPE_SET.add(Enumeration.class);
1✔
114
  }
1✔
115

116
  /**
117
   * Instantiates a new class info.
118
   *
119
   * @param clazz
120
   *          the clazz
121
   */
122
  private ClassInfo(Class clazz) {
1✔
123
    className = clazz.getName();
1✔
124
    addDefaultConstructor(clazz);
1✔
125
    addGetMethods(clazz);
1✔
126
    addSetMethods(clazz);
1✔
127
    addFields(clazz);
1✔
128
    readablePropertyNames = (String[]) getMethods.keySet().toArray(new String[getMethods.size()]);
1✔
129
    writeablePropertyNames = (String[]) setMethods.keySet().toArray(new String[setMethods.size()]);
1✔
130
  }
1✔
131

132
  /**
133
   * Adds the default constructor.
134
   *
135
   * @param clazz
136
   *          the clazz
137
   */
138
  private void addDefaultConstructor(Class clazz) {
139
    Constructor[] consts = clazz.getDeclaredConstructors();
1✔
140
    for (Constructor constructor : consts) {
1✔
141
      if (constructor.getParameterTypes().length == 0) {
1✔
142
        if (canAccessPrivateMethods()) {
1!
143
          try {
144
            constructor.setAccessible(true);
1✔
145
          } catch (Exception e) {
×
146
            // Ignored. This is only a final precaution, nothing we can do.
147
          }
1✔
148
        }
149
        if (constructor.isAccessible()) {
1!
150
          this.defaultConstructor = constructor;
1✔
151
        }
152
      }
153
    }
154
  }
1✔
155

156
  /**
157
   * Adds the get methods.
158
   *
159
   * @param cls
160
   *          the cls
161
   */
162
  private void addGetMethods(Class cls) {
163
    Method[] methods = getClassMethods(cls);
1✔
164
    for (Method method : methods) {
1✔
165
      String name = method.getName();
1✔
166
      if (name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2) {
1!
167
        if (method.getParameterTypes().length == 0) {
1✔
168
          name = dropCase(name);
1✔
169
          addGetMethod(name, method);
1✔
170
        }
171
      }
172
    }
173
  }
1✔
174

175
  /**
176
   * Adds the get method.
177
   *
178
   * @param name
179
   *          the name
180
   * @param method
181
   *          the method
182
   */
183
  private void addGetMethod(String name, Method method) {
184
    getMethods.put(name, new MethodInvoker(method));
1✔
185
    getTypes.put(name, method.getReturnType());
1✔
186
  }
1✔
187

188
  /**
189
   * Adds the set methods.
190
   *
191
   * @param cls
192
   *          the cls
193
   */
194
  private void addSetMethods(Class cls) {
195
    Map conflictingSetters = new HashMap<>();
1✔
196
    Method[] methods = getClassMethods(cls);
1✔
197
    for (Method method : methods) {
1✔
198
      String name = method.getName();
1✔
199
      if (name.startsWith("set") && name.length() > 3) {
1✔
200
        if (method.getParameterTypes().length == 1) {
1✔
201
          name = dropCase(name);
1✔
202
          // /------------
203
          addSetterConflict(conflictingSetters, name, method);
1✔
204
          // addSetMethod(name, method);
205
          // /------------
206
        }
207
      }
208
    }
209
    resolveSetterConflicts(conflictingSetters);
1✔
210
  }
1✔
211

212
  /**
213
   * Adds the setter conflict.
214
   *
215
   * @param conflictingSetters
216
   *          the conflicting setters
217
   * @param name
218
   *          the name
219
   * @param method
220
   *          the method
221
   */
222
  private void addSetterConflict(Map conflictingSetters, String name, Method method) {
223
    List list = (List) conflictingSetters.get(name);
1✔
224
    if (list == null) {
1✔
225
      list = new ArrayList<>();
1✔
226
      conflictingSetters.put(name, list);
1✔
227
    }
228
    list.add(method);
1✔
229
  }
1✔
230

231
  /**
232
   * Resolve setter conflicts.
233
   *
234
   * @param conflictingSetters
235
   *          the conflicting setters
236
   */
237
  private void resolveSetterConflicts(Map conflictingSetters) {
238
    for (Iterator propNames = conflictingSetters.keySet().iterator(); propNames.hasNext();) {
1✔
239
      String propName = (String) propNames.next();
1✔
240
      List setters = (List) conflictingSetters.get(propName);
1✔
241
      Method firstMethod = (Method) setters.get(0);
1✔
242
      if (setters.size() == 1) {
1✔
243
        addSetMethod(propName, firstMethod);
1✔
244
      } else {
245
        Class expectedType = (Class) getTypes.get(propName);
1✔
246
        if (expectedType == null) {
1✔
247
          throw new RuntimeException("Illegal overloaded setter method with ambiguous type for property " + propName
1✔
248
              + " in class " + firstMethod.getDeclaringClass() + ".  This breaks the JavaBeans "
1✔
249
              + "specification and can cause unpredicatble results.");
250
        } else {
251
          Iterator methods = setters.iterator();
1✔
252
          Method setter = null;
1✔
253
          while (methods.hasNext()) {
1✔
254
            Method method = (Method) methods.next();
1✔
255
            if (method.getParameterTypes().length == 1 && expectedType.equals(method.getParameterTypes()[0])) {
1!
256
              setter = method;
1✔
257
              break;
1✔
258
            }
259
          }
1✔
260
          if (setter == null) {
1✔
261
            throw new RuntimeException("Illegal overloaded setter method with ambiguous type for property " + propName
1✔
262
                + " in class " + firstMethod.getDeclaringClass() + ".  This breaks the JavaBeans "
1✔
263
                + "specification and can cause unpredicatble results.");
264
          }
265
          addSetMethod(propName, setter);
1✔
266
        }
267
      }
268
    }
1✔
269
  }
1✔
270

271
  /**
272
   * Adds the set method.
273
   *
274
   * @param name
275
   *          the name
276
   * @param method
277
   *          the method
278
   */
279
  private void addSetMethod(String name, Method method) {
280
    setMethods.put(name, new MethodInvoker(method));
1✔
281
    setTypes.put(name, method.getParameterTypes()[0]);
1✔
282
  }
1✔
283

284
  /**
285
   * Adds the fields.
286
   *
287
   * @param clazz
288
   *          the clazz
289
   */
290
  private void addFields(Class clazz) {
291
    Field[] fields = clazz.getDeclaredFields();
1✔
292
    for (Field field : fields) {
1✔
293
      if (canAccessPrivateMethods()) {
1!
294
        try {
295
          field.setAccessible(true);
1✔
296
        } catch (Exception e) {
1✔
297
          // Ignored. This is only a final precaution, nothing we can do.
298
        }
1✔
299
      }
300
      if (field.isAccessible()) {
1✔
301
        if (!setMethods.containsKey(field.getName())) {
1✔
302
          addSetField(field);
1✔
303
        }
304
        if (!getMethods.containsKey(field.getName())) {
1✔
305
          addGetField(field);
1✔
306
        }
307
      }
308
    }
309
    if (clazz.getSuperclass() != null) {
1✔
310
      addFields(clazz.getSuperclass());
1✔
311
    }
312
  }
1✔
313

314
  /**
315
   * Adds the set field.
316
   *
317
   * @param field
318
   *          the field
319
   */
320
  private void addSetField(Field field) {
321
    setMethods.put(field.getName(), new SetFieldInvoker(field));
1✔
322
    setTypes.put(field.getName(), field.getType());
1✔
323
  }
1✔
324

325
  /**
326
   * Adds the get field.
327
   *
328
   * @param field
329
   *          the field
330
   */
331
  private void addGetField(Field field) {
332
    getMethods.put(field.getName(), new GetFieldInvoker(field));
1✔
333
    getTypes.put(field.getName(), field.getType());
1✔
334
  }
1✔
335

336
  /**
337
   * This method returns an array containing all methods declared in this class and any superclass. We use this method,
338
   * instead of the simpler Class.getMethods(), because we want to look for private methods as well.
339
   *
340
   * @param cls
341
   *          The class
342
   *
343
   * @return An array containing all methods in this class
344
   */
345
  private Method[] getClassMethods(Class cls) {
346
    HashMap uniqueMethods = new HashMap<>();
1✔
347
    Class currentClass = cls;
1✔
348
    while (currentClass != null) {
1✔
349
      addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
1✔
350

351
      // we also need to look for interface methods -
352
      // because the class may be abstract
353
      Class[] interfaces = currentClass.getInterfaces();
1✔
354
      for (Class element : interfaces) {
1✔
355
        addUniqueMethods(uniqueMethods, element.getMethods());
1✔
356
      }
357

358
      currentClass = currentClass.getSuperclass();
1✔
359
    }
1✔
360

361
    Collection methods = uniqueMethods.values();
1✔
362

363
    return (Method[]) methods.toArray(new Method[methods.size()]);
1✔
364
  }
365

366
  /**
367
   * Adds the unique methods.
368
   *
369
   * @param uniqueMethods
370
   *          the unique methods
371
   * @param methods
372
   *          the methods
373
   */
374
  private void addUniqueMethods(HashMap uniqueMethods, Method[] methods) {
375
    for (Method currentMethod : methods) {
1✔
376
      if (!currentMethod.isBridge()) {
1✔
377
        String signature = getSignature(currentMethod);
1✔
378
        // check to see if the method is already known
379
        // if it is known, then an extended class must have
380
        // overridden a method
381
        if (!uniqueMethods.containsKey(signature)) {
1✔
382
          if (canAccessPrivateMethods()) {
1!
383
            try {
384
              currentMethod.setAccessible(true);
1✔
385
            } catch (Exception e) {
1✔
386
              // Ignored. This is only a final precaution, nothing we can do.
387
            }
1✔
388
          }
389

390
          uniqueMethods.put(signature, currentMethod);
1✔
391
        }
392
      }
393
    }
394
  }
1✔
395

396
  /**
397
   * Gets the signature.
398
   *
399
   * @param method
400
   *          the method
401
   *
402
   * @return the signature
403
   */
404
  private String getSignature(Method method) {
405
    StringBuilder sb = new StringBuilder();
1✔
406
    sb.append(method.getName());
1✔
407
    Class[] parameters = method.getParameterTypes();
1✔
408

409
    for (int i = 0; i < parameters.length; i++) {
1✔
410
      if (i == 0) {
1✔
411
        sb.append(':');
1✔
412
      } else {
413
        sb.append(',');
1✔
414
      }
415
      sb.append(parameters[i].getName());
1✔
416
    }
417

418
    return sb.toString();
1✔
419
  }
420

421
  /**
422
   * Drop case.
423
   *
424
   * @param name
425
   *          the name
426
   *
427
   * @return the string
428
   */
429
  private static String dropCase(String name) {
430
    if (name.startsWith("is")) {
1✔
431
      name = name.substring(2);
1✔
432
    } else if (name.startsWith("get") || name.startsWith("set")) {
1!
433
      name = name.substring(3);
1✔
434
    } else {
435
      throw new ProbeException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
×
436
    }
437

438
    if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
1!
439
      name = name.substring(0, 1).toLowerCase(Locale.US) + name.substring(1);
1✔
440
    }
441

442
    return name;
1✔
443
  }
444

445
  /**
446
   * Can access private methods.
447
   *
448
   * @return true, if successful
449
   */
450
  private static boolean canAccessPrivateMethods() {
451
    try {
452
      SecurityManager securityManager = System.getSecurityManager();
1✔
453
      if (null != securityManager) {
1!
454
        securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
×
455
      }
456
    } catch (SecurityException e) {
×
457
      return false;
×
458
    }
1✔
459
    return true;
1✔
460
  }
461

462
  /**
463
   * Gets the name of the class the instance provides information for.
464
   *
465
   * @return The class name
466
   */
467
  public String getClassName() {
468
    return className;
×
469
  }
470

471
  /**
472
   * Instantiate class.
473
   *
474
   * @return the object
475
   */
476
  public Object instantiateClass() {
477
    if (defaultConstructor == null) {
1!
UNCOV
478
      throw new RuntimeException("Error instantiating class.  There is no default constructor for class " + className);
×
479
    }
480
    try {
481
      return defaultConstructor.newInstance();
1✔
NEW
482
    } catch (Exception e) {
×
NEW
483
      throw new RuntimeException("Error instantiating class. Cause: " + e, e);
×
484
    }
485
  }
486

487
  /**
488
   * Gets the setter for a property as a Method object.
489
   *
490
   * @param propertyName
491
   *          - the property
492
   *
493
   * @return The Method
494
   */
495
  public Method getSetter(String propertyName) {
496
    Invoker method = (Invoker) setMethods.get(propertyName);
1✔
497
    if (method == null) {
1!
498
      throw new ProbeException(
×
499
          "There is no WRITEABLE property named '" + propertyName + "' in class '" + className + "'");
500
    }
501
    if (!(method instanceof MethodInvoker)) {
1!
502
      throw new ProbeException(
×
503
          "Can't get setter method because '" + propertyName + "' is a field in class '" + className + "'");
504
    }
505
    return ((MethodInvoker) method).getMethod();
1✔
506
  }
507

508
  /**
509
   * Gets the getter for a property as a Method object.
510
   *
511
   * @param propertyName
512
   *          - the property
513
   *
514
   * @return The Method
515
   */
516
  public Method getGetter(String propertyName) {
517
    Invoker method = (Invoker) getMethods.get(propertyName);
1✔
518
    if (method == null) {
1!
519
      throw new ProbeException(
×
520
          "There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
521
    }
522
    if (!(method instanceof MethodInvoker)) {
1!
523
      throw new ProbeException(
×
524
          "Can't get getter method because '" + propertyName + "' is a field in class '" + className + "'");
525
    }
526
    return ((MethodInvoker) method).getMethod();
1✔
527
  }
528

529
  /**
530
   * Gets the sets the invoker.
531
   *
532
   * @param propertyName
533
   *          the property name
534
   *
535
   * @return the sets the invoker
536
   */
537
  public Invoker getSetInvoker(String propertyName) {
538
    Invoker method = (Invoker) setMethods.get(propertyName);
1✔
539
    if (method == null) {
1!
540
      throw new ProbeException(
×
541
          "There is no WRITEABLE property named '" + propertyName + "' in class '" + className + "'");
542
    }
543
    return method;
1✔
544
  }
545

546
  /**
547
   * Gets the gets the invoker.
548
   *
549
   * @param propertyName
550
   *          the property name
551
   *
552
   * @return the gets the invoker
553
   */
554
  public Invoker getGetInvoker(String propertyName) {
555
    Invoker method = (Invoker) getMethods.get(propertyName);
1✔
556
    if (method == null) {
1!
557
      throw new ProbeException(
×
558
          "There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
559
    }
560
    return method;
1✔
561
  }
562

563
  /**
564
   * Gets the type for a property setter.
565
   *
566
   * @param propertyName
567
   *          - the name of the property
568
   *
569
   * @return The Class of the propery setter
570
   */
571
  public Class getSetterType(String propertyName) {
572
    Class clazz = (Class) setTypes.get(propertyName);
1✔
573
    if (clazz == null) {
1✔
574
      throw new ProbeException(
1✔
575
          "There is no WRITEABLE property named '" + propertyName + "' in class '" + className + "'");
576
    }
577
    return clazz;
1✔
578
  }
579

580
  /**
581
   * Gets the type for a property getter.
582
   *
583
   * @param propertyName
584
   *          - the name of the property
585
   *
586
   * @return The Class of the propery getter
587
   */
588
  public Class getGetterType(String propertyName) {
589
    Class clazz = (Class) getTypes.get(propertyName);
1✔
590
    if (clazz == null) {
1!
591
      throw new ProbeException(
×
592
          "There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
593
    }
594
    return clazz;
1✔
595
  }
596

597
  /**
598
   * Gets an array of the readable properties for an object.
599
   *
600
   * @return The array
601
   */
602
  public String[] getReadablePropertyNames() {
603
    return readablePropertyNames;
×
604
  }
605

606
  /**
607
   * Gets an array of the writeable properties for an object.
608
   *
609
   * @return The array
610
   */
611
  public String[] getWriteablePropertyNames() {
612
    return writeablePropertyNames;
1✔
613
  }
614

615
  /**
616
   * Check to see if a class has a writeable property by name.
617
   *
618
   * @param propertyName
619
   *          - the name of the property to check
620
   *
621
   * @return True if the object has a writeable property by the name
622
   */
623
  public boolean hasWritableProperty(String propertyName) {
624
    return setMethods.containsKey(propertyName);
1✔
625
  }
626

627
  /**
628
   * Check to see if a class has a readable property by name.
629
   *
630
   * @param propertyName
631
   *          - the name of the property to check
632
   *
633
   * @return True if the object has a readable property by the name
634
   */
635
  public boolean hasReadableProperty(String propertyName) {
636
    return getMethods.containsKey(propertyName);
1✔
637
  }
638

639
  /**
640
   * Tells us if the class passed in is a knwon common type.
641
   *
642
   * @param clazz
643
   *          The class to check
644
   *
645
   * @return True if the class is known
646
   */
647
  public static boolean isKnownType(Class clazz) {
648
    return SIMPLE_TYPE_SET.contains(clazz) || Collection.class.isAssignableFrom(clazz)
1!
649
        || Map.class.isAssignableFrom(clazz) || List.class.isAssignableFrom(clazz) || Set.class.isAssignableFrom(clazz)
1!
650
        || Iterator.class.isAssignableFrom(clazz);
1!
651
  }
652

653
  /**
654
   * Gets an instance of ClassInfo for the specified class.
655
   *
656
   * @param clazz
657
   *          The class for which to lookup the method cache.
658
   *
659
   * @return The method cache for the class
660
   */
661
  public static ClassInfo getInstance(Class clazz) {
662
    if (!cacheEnabled) {
1!
UNCOV
663
      return new ClassInfo(clazz);
×
664
    }
665
    ClassInfo cached = CLASS_INFO_MAP.get(clazz);
1✔
666
    if (cached == null) {
1✔
667
      cached = new ClassInfo(clazz);
1✔
668
      CLASS_INFO_MAP.put(clazz, cached);
1✔
669
    }
670
    return cached;
1✔
671
  }
672

673
  /**
674
   * Sets the cache enabled.
675
   *
676
   * @param cacheEnabled
677
   *          the new cache enabled
678
   */
679
  public static void setCacheEnabled(boolean cacheEnabled) {
680
    ClassInfo.cacheEnabled = cacheEnabled;
1✔
681
  }
1✔
682

683
  /**
684
   * Examines a Throwable object and gets it's root cause.
685
   *
686
   * @param t
687
   *          - the exception to examine
688
   *
689
   * @return The root cause
690
   */
691
  public static Throwable unwrapThrowable(Throwable t) {
692
    Throwable t2 = t;
1✔
693
    while (true) {
694
      if (t2 instanceof InvocationTargetException) {
1✔
695
        t2 = ((InvocationTargetException) t).getTargetException();
1✔
696
      } else if (t2 instanceof UndeclaredThrowableException) {
1✔
697
        t2 = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
1✔
698
      } else {
699
        return t2;
1✔
700
      }
701
    }
702
  }
703

704
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc