@@ -12,52 +12,65 @@ import kotlin.concurrent.*
12
12
13
13
private val throwableFields = Throwable ::class .java.fieldsCountOrDefault(- 1 )
14
14
private val cacheLock = ReentrantReadWriteLock ()
15
+ private typealias Ctor = (Throwable ) -> Throwable ?
15
16
// Replace it with ClassValue when Java 6 support is over
16
- private val exceptionConstructors : WeakHashMap <Class <out Throwable >, ( Throwable ) -> Throwable ? > = WeakHashMap ()
17
+ private val exceptionCtors : WeakHashMap <Class <out Throwable >, Ctor > = WeakHashMap ()
17
18
18
19
@Suppress(" UNCHECKED_CAST" )
19
20
internal fun <E : Throwable > tryCopyException (exception : E ): E ? {
21
+ // Fast path for CopyableThrowable
20
22
if (exception is CopyableThrowable <* >) {
21
23
return runCatching { exception.createCopy() as E }.getOrNull()
22
24
}
23
-
24
- val cachedCtor = cacheLock.read {
25
- exceptionConstructors[ exception.javaClass]
25
+ // Use cached ctor if found
26
+ cacheLock.read { exceptionCtors[exception.javaClass] }?. let { cachedCtor ->
27
+ return cachedCtor( exception) as E ?
26
28
}
27
-
28
- if (cachedCtor != null ) return cachedCtor(exception) as E ?
29
29
/*
30
30
* Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
31
31
*/
32
32
if (throwableFields != exception.javaClass.fieldsCountOrDefault(0 )) {
33
- cacheLock.write { exceptionConstructors [exception.javaClass] = { null } }
33
+ cacheLock.write { exceptionCtors [exception.javaClass] = { null } }
34
34
return null
35
35
}
36
-
37
36
/*
38
- * Try to reflectively find constructor(), constructor(message, cause) or constructor(cause ).
37
+ * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message ).
39
38
* Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
40
39
*/
41
- var ctor: (( Throwable ) -> Throwable ? ) ? = null
40
+ var ctor: Ctor ? = null
42
41
val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
43
42
for (constructor in constructors) {
44
- val parameters = constructor .parameterTypes
45
- if (parameters.size == 2 && parameters[0 ] == String ::class .java && parameters[1 ] == Throwable ::class .java) {
46
- ctor = { e -> runCatching { constructor .newInstance(e.message, e) as E }.getOrNull() }
47
- break
48
- } else if (parameters.size == 1 && parameters[0 ] == Throwable ::class .java) {
49
- ctor = { e -> runCatching { constructor .newInstance(e) as E }.getOrNull() }
50
- break
51
- } else if (parameters.isEmpty()) {
52
- ctor = { e -> runCatching { (constructor .newInstance() as E ).also { it.initCause(e) } }.getOrNull() }
53
- break
54
- }
43
+ ctor = createConstructor(constructor )
44
+ if (ctor != null ) break
55
45
}
56
-
57
- cacheLock.write { exceptionConstructors [exception.javaClass] = ( ctor ? : { null }) }
46
+ // Store the resulting ctor to cache
47
+ cacheLock.write { exceptionCtors [exception.javaClass] = ctor ? : { null } }
58
48
return ctor?.invoke(exception) as E ?
59
49
}
60
50
51
+ private fun createConstructor (constructor : Constructor <* >): Ctor ? {
52
+ val p = constructor .parameterTypes
53
+ return when (p.size) {
54
+ 2 -> when {
55
+ p[0 ] == String ::class .java && p[1 ] == Throwable ::class .java ->
56
+ safeCtor { e -> constructor .newInstance(e.message, e) as Throwable }
57
+ else -> null
58
+ }
59
+ 1 -> when (p[0 ]) {
60
+ Throwable ::class .java ->
61
+ safeCtor { e -> constructor .newInstance(e) as Throwable }
62
+ String ::class .java ->
63
+ safeCtor { e -> (constructor .newInstance(e.message) as Throwable ).also { it.initCause(e) } }
64
+ else -> null
65
+ }
66
+ 0 -> safeCtor { e -> (constructor .newInstance() as Throwable ).also { it.initCause(e) } }
67
+ else -> null
68
+ }
69
+ }
70
+
71
+ private inline fun safeCtor (crossinline block : (Throwable ) -> Throwable ): Ctor =
72
+ { e -> runCatching { block(e) }.getOrNull() }
73
+
61
74
private fun Class <* >.fieldsCountOrDefault (defaultValue : Int ) = kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
62
75
63
76
private tailrec fun Class <* >.fieldsCount (accumulator : Int = 0): Int {
0 commit comments