JavaFX で Applet その 2

昨日の続き。

Application のオブジェクトをスクリプトの最後に記述すると、なぜか Applet になってしまうわけです。

なんでこうなるのか調べてみました。

まず Applet.fx のソースを見てみると...

Applet.fx は javax.swing.JApplet クラスの派生クラスになっています。JApplet クラスの初期化は init メソッドなので、Applet.fx も init メソッドがコールされます。でも、init は JavaFX Script の予約後なので、使えません。こういうときは、<< >> で囲むと、予約後でもそのまま使えるのです。

ということで、<<init>> メソッドを見てみると...

public class Applet extends JApplet {

    public function <<init>>() {
        DeferredTask {
            action: launchApplication
        }
    }

DeferredTask は無視しておいて、launchApplication メソッドがコールされるといいうことです。で、lauchApplication メソッドを見てみると...

    private function launchApplication() {
        InternalHelper.initDefaultLAF();

        var appClassName = getParameter("ApplicationClass");
        var errorPrefix = "Couldn't launch FX Application";
        if (appClassName != null) {
            try {
                var appClass:Class = Class.forName(appClassName);
                var name = Entry.entryMethodName();
                var args = Sequences.make(java.lang.String.<<class>>) as java.lang.Object;
                var appObject = appClass.getMethod(name, Sequence.<<class>>).invoke(null, args);
                app = appObject as Application;
            }
            catch (e:Throwable) {
                throw new Error("{errorPrefix} {appClassName}", e);
            }
        }
        else {
            throw new Error("{errorPrefix}: no ApplicationClass applet param specified");
        }

        if (app != null) {
            if (app.getClass().getName() == "javafx.ext.swing.SwingApplication") {
                var sapp = app as javafx.ext.swing.SwingApplication;
                setContentPane(sapp.content.getJComponent());
            } else {
                // default to white background for applet
                setBackground(java.awt.Color.WHITE);
                // extract and use stage here
                var canvasStage:CanvasStageImpl = CanvasStageImpl{ stage: bind app.stage };
                setContentPane(canvasStage.jsgPanel);
            }

            if (app.onStart != null) app.onStart();
        }
    }

長いですけど、順々に見ていけばそんなにむずかしくないです。まず、applet タグもしくは jnlpapplet-desc タグのパラメータ param の ApplicationClass を取得し、それをクラス名としてロードしています。。

その後、Entry.entryMethodName クラスが示す "javafx$run$" のメソッドをロードして、実行するわけです。そうすると、Application オブジェクトが得られるということになっているようです。

で、その Application のコンテンツを JApplet のコンテントペインに貼っているわけです。

そして、最後に Application の onStart メソッドをコールするということをやっています。

でも、ApplicationClass で示されるクラスの javafx$run$ メソッドとはなんでしょう。

appletsample.fx は次のようになっていました。

Application {
    stage: Stage {
        content: Circle {
            centerX: 100, centerY: 100
            radius: 40
            fill: Color.RED
        }
    }
}

このスクリプトをコンパイルしてできたクラスファイルを逆コンパイルしてみて、javafx$run$ を見てみると...

    public static Object javafx$run$(Sequence sequence) {
        Application application = new Application(true);
        Stage stage = new Stage(true);
        SequenceBuilder sequencebuilder = new SequenceBuilder(javafx/scene/geometry/Circle$Intf);
        Circle circle = new Circle(true);
        circle.get$centerX().setAsDoubleFromLiteral(100D);
        circle.get$centerY().setAsDoubleFromLiteral(100D);
        circle.get$radius().setAsDoubleFromLiteral(40D);
        circle.get$fill().setFromLiteral(Color.RED.get());
        circle.initialize$();
        sequencebuilder.add(circle);
        stage.get$content().setAsSequenceFromLiteral(sequencebuilder.toSequence());
        stage.initialize$();
        application.get$stage().setFromLiteral(stage);
        application.initialize$();
        return application;
    }

内容を見てみると、javafx$run$ メソッドはスクリプトをそのまま Java に直したものになっているようです。で、最後に見てみると Application オブジェクトを返していることが分かります。

つまり、JavaFX のメソッドで最後にオブジェクトを書いておくと、return を書かなくてもいいというルールがここでも適用されているようです。

というわけで、JavaFX における Applet の仕組みが分かったのでした。