IntelliJとGradleでJava11とJavaFX 11を組み合わせてHello Worldモジュールを作ってみました

JavaFXの環境づくりと入門のため、「[IntelliJ IDEAとGradleでのJavaFXアプリケーション開発 〜環境構築からサンプルコードまで〜](https://qiita.com/maru2213/items/c81905304c4c3ca09aa2)」をHello Worldの経典として写経してみたら意外に躓きました。

# Hello Worldを表示させるまでの道のり

JavaFXを使ってHello Worldを表示させるまでの試行錯誤を書きます。
なお、Gradleを使ったのは単に好き（私には相性・印象が良い）だからです。Java 11を使ったのはJava 8（歴史のターニングポイント）の次のLTSでJava 8までしか知らない私には変化が少なく入りやすいだろうと思ったからです。JavaFX 11を使ったのはJavaFX 13がダウンロードできず（2020-4-10時点）、最新版はJava FX 16だからJava 11と相性の良さそうLTSを使いたかったからです。

## JavaFX 11 SDKをダウンロード

JetBrainさんに[JavaFXのプロジェクトを作る方法](https://pleiades.io/help/idea/javafx.html#download-javafx)が書いてあったので従います。

JDKFX 11 SDKを[Gluon](https://gluonhq.com/products/javafx/)からダウンロードして展開します。この時私がダウンロードしたSDKは、openjfx-11.0.2_linux-x64_bin-sdk.zipです。

JavaFXプラグインは既に有効だったので何もしていません。

JetBrainさんのチュートリアルに従うのはここまでです。私はGradleを使いたいので。


## IntelliJでGradleプロジェクトを新規作成する

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72237/5c9877bc-2844-5370-0f92-1738e82ea2f8.png)

Project SDKはJava 11を選びます。プロジェクト名はjavafx05としました。

念の為Project SDKを確認すると以下のような感じです。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72237/1d323afc-1a6c-04ff-0c55-24279bf5042a.png)


# 経典に従いソースコードを作成

build.gradleを以下のように追記（編集）しました。

```gradle:build.gradle
plugins {
    id 'java'
    id 'application'    // 追加
    id 'org.openjfx.javafxplugin' version '0.0.8'   // 追加
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'

    runtimeOnly "org.openjfx:javafx-base:$javafx.version:linux"     // 追加
    runtimeOnly "org.openjfx:javafx-controls:$javafx.version:linux" // 追加
    runtimeOnly "org.openjfx:javafx-fxml:$javafx.version:linux"     // 追加
    runtimeOnly "org.openjfx:javafx-graphics:$javafx.version:linux" // 追加
}

test {
    useJUnitPlatform()
}

// --------------------
// 以下追加
// --------------------
javafx {
    version = "11"
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

mainClassName = 'org.example.Launcher'
jar {
    manifest {
        attributes 'Main-Class': 'org.example.Launcher'
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

```

```bash:ビルド成功
$ gradle build
Starting a Gradle Daemon (subsequent builds will be faster)

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.8.3/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 8s
6 actionable tasks: 6 executed

```

新規作成直後、すでにGradleで4つのワーニングが有りますが、Hello Worldを表示させるための障害にはならないので気にしないことにします。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72237/a9538f75-d669-79c0-7a75-e4137f3e6135.png)

## Cannot resolve symbol 'Exception'を解消する

gradle buildは問題ないのですが、IntelliJでは不可解な状態になっていました。Exception等極基本的な予約語が認識されないようです。この状態ではIntelliJからRunができません。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72237/8d875875-6fb1-546f-b083-999a50276376.png)

原因は不明ですが、Project SDKを選び直すと解消しました。IntelliJとは別にSDKMAN!でインストールしておいたJava 11ならエラーになりませんでした。

SDKとエラーの関係は以下です。

* エラー：JetBrains/Toolbox/apps/IDEA-U/ch-0/203.7717.56/jbr（備え付けのJava 11）
* エラーにならない：/home/devel/.sdkman/candidates/java/current（IntelliJとは別にSDKMAN!でインストールしておいたJava 11）
* エラーにならない：jdks/adopt-openjdk-13.0.2（Java 13なので使わない）

JetBrainのページにはVMオプションでSDKへのパスを設定すべし、と書いてありましたが私の場合は設定しなくてもRunできました。

```text:VMオプションは設定しなくてもRunできた
--module-path /path/to/javafx/sdk --add-modules javafx.controls,javafx.fxml
```

## ついでにモジュール化

勉強ついでにモジュール化するため、以下のようなファイルを追加しました。

```java:module-info.java
module javamoduleexample {
    requires transitive javafx.graphics;
    requires transitive javafx.controls;
    requires transitive javafx.fxml;

    // FXMLLoaderがリフレクションを使うためにopensする必要がある
    opens org.example;
    exports org.example;
}
```

## 実行できなくなった問題を解消

モジュール化した途端にRun(gradle run)できなくなりました。デバッガで様子をうかがうと、scene.fxml等resources/のファイルがgetResource()で読めなくなっていました。

[参考にしたページ](https://qiita.com/opengl-8080/items/5f6a405b1193820cc68d)によると、Gradle と IntelliJ では、 src/main/java と src/main/resources のビルド結果が、それぞれ別のディレクトリに出力されることが原因とのこと。このページに記されている解決法を適用してたら私のところでも問題が解消しました。

```gradle:build.gradle最終版
plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}

apply plugin: 'idea'    // さらに追加

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'

    runtimeOnly "org.openjfx:javafx-base:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-controls:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-fxml:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-graphics:$javafx.version:linux"
}

test {
    useJUnitPlatform()
}

// さらに追加
sourceSets {
    main {
        output.resourcesDir = java.outputDir
    }
}

// さらに追加
idea.module.outputDir file("org/classes")

test {
    useJUnitPlatform()
}

javafx {
    version = "11"
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

mainClassName = 'org.example.Launcher'
jar {
    manifest {
        attributes 'Main-Class': 'org.example.Launcher'
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
```

ようやくHello World画面が表示されました。

# 環境

* Ubuntu 18.04
* IntelliJ Ultimate 2020.3.3
* Java 11.0.10
* JavaFX 11
* Gradle 6.8.3

# 参考

* [IntelliJ IDEAとGradleでのJavaFXアプリケーション開発 〜環境構築からサンプルコードまで〜](https://qiita.com/maru2213/items/c81905304c4c3ca09aa2)
* [新しい JavaFX プロジェクトを作成する](https://pleiades.io/help/idea/javafx.html#download-javafx)
* [Gradle および IntelliJ でモジュールを使ったときにリソースが取得できない問題](https://qiita.com/opengl-8080/items/5f6a405b1193820cc68d)