Skip to content

Commit 69a2d92

Browse files
committed
Initialize JavaFx more accurately, do not leave dispatcher in uninitialized state in case of unexpected failure
Fixes Kotlin#816
1 parent bb0ad4a commit 69a2d92

File tree

2 files changed

+26
-23
lines changed

2 files changed

+26
-23
lines changed

ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt

+25-20
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import javafx.event.*
1010
import javafx.util.*
1111
import kotlinx.coroutines.*
1212
import kotlinx.coroutines.internal.*
13-
import java.lang.IllegalStateException
13+
import kotlinx.coroutines.javafx.JavaFx.delay
1414
import java.lang.reflect.*
1515
import java.util.concurrent.*
1616
import kotlin.coroutines.*
@@ -115,31 +115,36 @@ private class PulseTimer : AnimationTimer() {
115115
}
116116
}
117117

118-
internal fun initPlatform() {
118+
internal fun initPlatform(): Boolean {
119119
/*
120120
* Try to instantiate JavaFx platform in a way which works
121121
* both on Java 8 and Java 11 and does not produce "illegal reflective access":
122122
*
123123
* 1) Try to invoke javafx.application.Platform.startup if this class is
124-
* present in a classpath (since Java 9 it is a separate dependency)
125-
* 2) If it is not present, invoke plain old PlatformImpl.startup
124+
* present in a classpath.
125+
* 2) If it is not successful and does not because it is already started,
126+
* fallback to PlatformImpl.
126127
*
127-
* Additionally, ignore ISE("Toolkit already initialized") because since Java 9
128-
* consecutive calls to 'startup' throw it
128+
* Ignore exception anyway in case of unexpected changes in API, in that case
129+
* user will have to instantiate it manually.
129130
*/
130-
val platformClass = runCatching {
131-
Class.forName("javafx.application.Platform") // Java 9+
132-
}.getOrElse {
133-
Class.forName("com.sun.javafx.application.PlatformImpl") // Fallback
134-
}
135-
136-
try {
137-
platformClass.getMethod("startup", java.lang.Runnable::class.java)
138-
.invoke(null, java.lang.Runnable { })
139-
} catch (e: InvocationTargetException) {
140-
val cause = e.cause
141-
if (cause !is IllegalStateException || "Toolkit already initialized" != cause.message) {
142-
throw e
131+
val runnable = Runnable {}
132+
return runCatching {
133+
// Invoke public API if it is present
134+
Class.forName("javafx.application.Platform")
135+
.getMethod("startup", java.lang.Runnable::class.java)
136+
.invoke(null, runnable)
137+
}.recoverCatching { exception ->
138+
// Recover -> check re-initialization
139+
val cause = exception.cause
140+
if (exception is InvocationTargetException && cause is IllegalStateException
141+
&& "Toolkit already initialized" == cause.message) {
142+
// Toolkit is already initialized -> success, return
143+
Unit
144+
} else { // Fallback to Java 8 API
145+
Class.forName("com.sun.javafx.application.PlatformImpl")
146+
.getMethod("startup", java.lang.Runnable::class.java)
147+
.invoke(null, runnable)
143148
}
144-
}
149+
}.isSuccess
145150
}

ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt

+1-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ class JavaFxTest : TestBase() {
1616

1717
@Test
1818
fun testDelay() {
19-
try {
20-
initPlatform()
21-
} catch (e: Exception) {
19+
if (!initPlatform()) {
2220
println("Skipping JavaFxTest in headless environment")
2321
return // ignore test in headless environments
2422
}

0 commit comments

Comments
 (0)