Kotlinの仕様を悪用してJavaFXのイベント型を定義する

# あらまし

`JavaFX`でカスタムイベント処理を行う場合、密に結びついた[javafx.event.Event](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/event/Event.html)と[EventType<T extends Event>](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/event/EventType.html)を使用する。

ここで、`EventType`のコンストラクタを見てみると。

> public EventType(String name)
> 指定された名前、およびスーパー・タイプとしてEventType.ROOTを使用して新しいEventTypeを構築します。
> パラメータ:
>     name - 名前
> 例外:
>     IllegalArgumentException - 名前が同じで、親としてROOT/Event.ANYを持つEventTypeの場合 

とあるように、

* 名前を文字列で指定しろ！
* ただし被ったら殺す！

とめんどくさいことをおっしゃってくる。

そこで`kotlin`の機能を使って（悪用して）でなんとかできないか模索してみた。

# 詳細 (2021/7/4改訂)

マウス座標のようなイベント引数を提供する必要がなければ、元のコードでもやりたいことを満たせるが、

* イベント引数をハンドラに提供する
* イベント引数の型・数はイベントによって異なる

場合、元のコードのままでは`EventType`の型引数が固定されているため、静的には判断できない。
もちろん型チェックを通して型キャストすればよいが、来るイベントが分かっているのに型キャストが必要なのは腑に落ちない。

ということを踏まえて書き直したのが以下のコード

```kotlin
interface WindowEvent {
    companion object Type {
        val CONFIG_SHOWING = EventType<WindowEvent.ConfigShowing>(WindowEvent.ConfigShowing::class.java.canonicalName)
        val CONFIG_CLOSING = EventType<WindowEvent.ConfigClosing>(WindowEvent.ConfigClosing::class.java.canonicalName)
    }
    class ConfigShowing(): Event(WindowEvent.Type.CONFIG_SHOWING)
    class ConfigClosing(): Event(WindowEvent.Type.CONFIG_CLOSING)
}  
```

元のコードでは、`EventType`を`enum class`で括っていたが、どう頑張っても`Event`を`sealed class`にできないっぽいため、`enum class`を捨て`companion object`のプロパティに変更した。

イベントを束ねることだけが目的の`WindowEvent`は意地でもインスタンス化させないためにインターフェースにした。[^1]

[^1]: インターフェースの実装はされるかもだが、マーカーインターフェースだしままえやろ。

この構造の問題は、イベントタイプグラフを構成できないことだが、下位のイベントごとに引数が異なる場面を想定しているため諦めた。[^2]

[^2]: つよつよ`Kotlin`使いがなんかいいアイデア出してくれることに期待

## イベント引数が異なる例

```kotlin
interface ConfigEvent {
    companion object Type {
        val LIST_ADD = EventType<ConfigEvent.ListAdding>(ConfigEvent.ListAdding::class.java.canonicalName)
        val LIST_REMOVE = EventType<ConfigEvent.ListRemoving>(ConfigEvent.ListRemoving::class.java.canonicalName)
        val LIST_MOVE_TO = EventType<ConfigEvent.ListMoveTo>(ConfigEvent.ListMoveTo::class.java.canonicalName)
    }
    class ListAdding(val location: Location): Event(ConfigEvent.Type.LIST_ADD)
    class ListRemoving(val location: Location): Event(ConfigEvent.Type.LIST_REMOVE)
    class ListMoveTo(val from: Int, val to: Int): Event(ConfigEvent.Type.LIST_MOVE_TO)
}
```

# 詳細 (Obsolete)

なんやかんややった結果が以下のコード

```kotlin
import javafx.event.EventType
import javafx.event.Event

enum class WindowEventType(val eventType: EventType<WindowEvent>) {
    CONFIG_SHOWING(EventType<WindowEvent>(WindowEvent.ConfigShowing::class.java.canonicalName)),
    CONFIG_CLOSING(EventType<WindowEvent>(WindowEvent.ConfigClosing::class.java.canonicalName))
}

open class WindowEvent(val ev: WindowEventType): Event(ev.eventType) {
    class ConfigShowing(): WindowEvent(WindowEventType.CONFIG_SHOWING)
    class ConfigClosing(): WindowEvent(WindowEventType.CONFIG_CLOSING)
}  
```

`Event`と`EventType`が蜜月であることに着目して、`EventType`と1対1となるよう`Event`のサブクラスを定義し [^3]、そのクラス名をイベントタイプ名とすることで、一意の名前を持たせてみた。

[^3]: サブクラス化するためイベントクラスが大量に定義さることにはなるかもしれないが、誤差だよ誤差。

また、`kotlin`の｀enum｀にはプロパティとして任意の型を持たせられるため、`EventType`をラップした。

本当は、`Event`を`sealed class`にしたかったけど、

> This type is final, so it cannot be inherited from

って、怒られたので断念した。

## 使用例

イベントハンドラやフィルタの割り当ては、

```kotlin
someScene.addEventHandler(WindowEventType.CONFIG_SHOWING.eventType) { println("event triggered") }
```

イベントのトリガーは、

```kotlin
someNode.scene.root.fireEvent(WindowEvent.ConfigShowing())
```

`Event`をインスタンス化する際の煩雑な`EventType`の引き渡しを型に押し込んだおかげで、多少はスッキリ書けるようになった気がする

結局、出来上がってみれば、いうほど悪用という感じでもなかった・・・。無念。