Wolfram言語からJavaを呼び出す
序文
J/Link はWolfram言語から直接任意のJavaクラスとインタラクトする能力を提供する.Wolfram言語で直接オブジェクトを作成したりメソッドを呼び出したりすることができるのである.Javaコードや使いたいJavaクラスを作成する必要は全くない.また,Wolfram Symbolic Transfer Protocol (WSTP)について何か知っている必要もない.実質的に,JavaはWolfram言語を透過的に拡張するので,まるで既存のあるいは将来のJavaクラスがすべてWolfram言語で書かれているかのようである.
この機能は「インストール可能なJava」と呼ばれる.これによって,Install関数で他の言語で書かれた拡張機能をもプラグインするという今までのWolfram言語の能力が一般化されるからである.CやC++のような他の言語に比べ,J/Link がどれほどこのJavaのプロセスを簡素化しているかは後で分かるであろう.実際,J/Link にはそのプロセスが全く必要ない.このようなわけで,JavaはWolfram言語を透過的に拡張するといえる.
Javaはしばしばインタプリタ言語と呼ばれるが,それは正しい呼び方ではない.Javaを使うには,完全なプログラムを書き,コンパイルし,実行しなければならない(Javaコードの行をインタラクティブに実行できるようにする環境もあるが,それは特殊ツールで,同様のツールがCのような伝統的な言語にもある).Wolfram言語ユーザは関数を実験し,1度に1行ずつプログラムを組み立ててテストできるという,純粋なインタプリタ型のインタラクティブな環境で作業ができる.J/Link はこれと同じ生産的な環境をJavaプログラマーに提供する.Wolfram言語はJavaのスクリプト言語になるといっても過言ではない.
Wolfram言語ユーザにとっては,J/Link の「インストール可能なJava」機能はWolfram言語の拡張機能としてのJavaクラスの拡充をもたらすものである.また,Javaユーザにとっては,J/Link は非常にパワフルで有用なWolfram言語環境が,Javaクラスをインタラクティブに開発し,実験し,テストすることができるシェルとして使用できるようにするものである.
J/Link パッケージのロード
Javaランタイムの起動
InstallJava
次のステップではJavaランタイムを起動してWolfram言語にインストールする.InstallJavaの関数を使う.
InstallJava[] | Javaランタイムを起動してWolfram言語から使用する準備をする |
ReinstallJava[] | Javaランタイムが起動している場合に,それを終了して再起動する |
JavaLink[] | Javaランタイムとの通信に使用されているLinkObjectを与える |
InstallJavaは1回のセッションで何度でも呼び出すことができるが,2回目以降は何もしない.従って,ユーザがすでに呼び出したかどうかを考えずに,どのプログラムでInstallJavaを呼び出しても安全である.
InstallJavaはコマンドラインを作成する.そのコマンドラインはJavaランタイム(通常「java」と呼ばれるもの)を起動し,その最初の引数を指定するのに使われる.コマンドラインに何を入れるかをユーザが制御する必要がある場合が稀にあるので,InstallJavaにはそのための数多くのオプションがある.しかし,ほとんどのユーザはこのようなオプションを使う必要はないし,実のところ使わない方がよい.Javaランタイムはすでに起動しているということもあり得るので,Javaランタイムの起動を制御することができると思ってはならない.何らかの理由で,Javaランタイムの起動を制御するためにオプションを適用しなければならない場合は,InstallJavaではなくReinstallJavaを使う.
ClassPath->None | Javaランタイムのデフォルトのクラスパスを使用 |
ClassPath->"dirs" | 指定されたディレクトリとjarファイルを使用 |
CommandLine->"cmd" | Javaランタイムを起動するために,"java"の代りに指定されたコマンドラインを使用 |
InstallJavaのオプション
Javaの起動に使われるコマンドをコントロールする
InstallJavaとReinstallJavaの大切なオプションにCommandLineがある.これはJavaを起動するのに使うコマンドラインの最初の部分を指定するものである.このオプションは,システムに複数のJavaがインストールされていて,特定の1つを起動したい場合等に使える.
デフォルトでは,InstallJavaは Mathematica 4.2以降に含まれているJavaランタイムを起動する.Wolfram言語の以前のバージョンをお持ちの場合は,デフォルトのコマンドラインはほとんどのシステムではjavaになる.システムパスにjavaの実行ファイルがないときは,InstallJavaを使ってそれを指すことができる.その他にこのオプションを使うのは,他のオプションではカバーされていないJavaへの引数を指定する場合である.以下は,冗長なガーベジコレクションを設定し,fooという属性が値barを持つように定義する例である.
クラスパスをオーバーライドする
クラスパスとはJavaランタイムがクラスを探すディレクトリの集合のことである.システムのコマンドラインからJavaプログラムを起動すると,Javaが使うクラスパスにはデフォルトの場所と,CLASSPATH環境変数で指定された場所(存在するならば)が含まれる.しかし,場所を指定するために-classpathというコマンドラインを使うと,CLASSPATH環境変数は無視される.InstallJavaとReinstallJavaのClassPathオプションも同様に動作する.ClassPathオプションをデフォルト値のAutomaticのままにすると,J/Link はクラス検索パスにCLASSPATH環境変数の内容を含む.値をNoneあるいは文字列にすると,CLASSPATHの内容は使われない.文字列にする場合は,CLASSPATH環境変数を設定するのに使うのと同じ文法(WindowsとLinux/OSXとで異なる)を使う.
J/Link には,クラス検索パスの非常に柔軟なコントロール方法がある.J/Link はWolfram言語アプリケーションディレクトリのクラスを自動的に検索するだけでなく,Javaランタイムの実行中に新しい検索場所をダイナミックに加えることができる.つまり,Javaが最初に起動したときに,クラスパスを設定するためにClassPathオプションを使うことはそれほど重要ではないということである.ClassPathオプションをNoneに設定すると,J/Link がCLASSPATHの内容からクラスを見付けないようにすることができるので,便利なことがある.開発中のディレクトリに試験的なクラスがあり,J/Link がCLASSPATH上にあった古いクラスではなく,この試験バージョンを確実に使うようにしたい場合に,この設定を使うとよい.「Javaクラスパス」では,J/Link がクラスを検索する方法と,この検索パスに場所を加える方法について詳述する.
クラスのロード
LoadJavaClass
LoadJavaClass["classname"] | JavaとWolfram言語に指定のクラスをロードする |
LoadClass["classname"] | J/Link の以前のバージョンで使われていた古い名前.現在はLoadJavaClassを使う |
Wolfram言語でJavaクラスを使うためには,最初にJavaランタイムにロードしてWolfram言語で定義を設定しなければならない.これはLoadJavaClass関数を使って行うことができる.LoadJavaClassは完全修飾クラス名を指定する文字列(ピリオドを伴った完全な階層名)を引数として取る.
戻り値は頭部がJavaClassの式である.このJavaClass式は J/Link で数多く使われるので,ここに示すように変数を指定するとよい.J/Link でクラスが引数として指定されなければならないところにはすべて,JavaClass式,文字列としての完全修飾クラス名,クラスのオブジェクトのどれでも使うことができる.ただタイプするだけで有効なJavaClass式ができるわけではなく,LoadJavaClassによって返されなければならない.
クラスがロードされたら,staticメソッドを呼び出してオブジェクトを生成し,メソッドを起動すると,オブジェクトのフィールドにアクセスできる.どのような「パブリック」コンストラクタ,メソッド,クラスのフィールドも使える.
StaticsVisible->True | staticメソッドやフィールドに名前でのみアクセスできるようにする(特殊なコンテキストを除く) |
AllowShortContext->False | staticメソッドやフィールドに完全修飾クラスコンテキストでのみアクセスできるようにする |
UseTypeChecking->False | Javaの呼出しの定義に通常挿入されるタイプチェックを抑える |
LoadJavaClassのオプション
「Javaクラスパス」では,J/Link がどこでどのようにクラスを見付けるのかについての詳細を説明する.J/Link は起動中でも,クラスパス,指定のJava拡張ディレクトリ,ユーザがコントロールできる付加的なディレクトリでクラスを見付けることができる.
いつLoadJavaClassを呼び出すか
LoadJavaClassを使って明示的にクラスをロードする必要がないこともよくある.後で説明するように,JavaNewでJavaオブジェクトを作成するときに,文字列としてクラス名を与えることができる.クラスがまだロードされていなければ,JavaNewがLoadJavaClassを内部的に呼び出す.実際,JavaオブジェクトがWolfram言語に返されるときはいつも,クラスは必要に応じて自動的にロードされる.こうなると,LoadJavaClassを使う理由がほとんどないように思えるが,LoadJavaClassを明示的に使った方がよい,あるいは使う必要がある理由もたくさんある.
- クラスのstaticメソッドを呼び出す必要があるが,そのクラスのオブジェクトを生成していないか,生成する予定がない場合.クラスはstaticメソッドが呼び出される前に,ロードされていなければならない.
- LoadJavaClassのオプションの1つを使う必要がある場合.JavaNewはLoadJavaClassを内部的に呼び出すとき,デフォルトのオプション設定で呼び出す
- クラスのロードに関連した初期遅延をコントロールしたい場合.クラス自身,あるいはそのスーパークラスが非常に大きいものである場合,クラスをロードするのに(それほど長くはかかるのはまれであるが)数秒かかることがある.ユーザが速さを期待している関数での不可解な遅延は,避けた方がよいかもしれない.
- LoadJavaClassによって返されるJavaClass式を維持して他の関数で使いたい場合.JavaClassを取る関数はすべてクラス名の文字列を取ることができるが,読みやすさのためには名前の付いたJavaClass変数を使った方がよいかもしれない.その方が文字列を使うよりもわずかに速いが,ループ内で何回も使わない限り,その差は分からない.
J/Link でクラスをロードする操作は1度の J/Link セッションで1度だけである(1セッションとはInstallJavaとUninstallJavaの間).LoadJavaClassはあるクラスについて何度でも呼び出すことができる.2回目以降の呼び出しでは何もせず,その都度即座にJavaClass式を返してくる.これはクラスがすでにロードされているかどうかを心配する必要が全くないということを意味する.はっきり分からない場合は,LoadJavaClassを呼び出してみる.
幅広いユーザを対象にコードを書く開発者は,クラスを必要とするあらゆる関数において,どのクラスについても常にLoadJavaClassを呼び出さなければならない.パッケージコードが読み込まれるときにそのボディで,LoadJavaClassを呼び出すのは適当ではない.というのは,パッケージが読まれた後で,ユーザがJavaランタイムを終了して再起動(つまり,UninstallJavaとInstallJava)するかもしれないからである.安全策として,J/Link を使うユーザレベルの関数ではすべてInstallJava,および必要に応じてLoadJavaClassを呼び出すようにした方がよい.LoadJavaClassの呼出しが必要でない場合は,迅速に実行される.
上で述べたように,クラスのロードには数秒かかる場合もある.クラスがロードされたら,そのスーパークラスも引き続きロードされ,継承階層を作り上げる.クラスは実際には1度しかロードされないので,前にロードしたクラスと同じスーパークラスを持つクラスをロードする場合は,2回目のロードではスーパークラスはもうロードする必要がない.スーパークラスが大きいとしても,2回目は1回目よりもずっと迅速である.その例として,java.awtパッケージのクラスのロードが挙げられる.java.awt.Componentクラスは非常に大きいので,java.awt.Buttonのようにこのクラスを継承しているクラスを初めてロードするとき,遅延しているのが分かる.Componentから派生している他のクラスを引き続きロードするときは,ずっと速くロードできる.
コンテキストとstaticメンバの可視性
LoadJavaClassには2つのオプションがあり,それによりstaticメソッドおよびフィールドの指定や可視性をコントロールすることができる.これらのオプションを理解するためには,これらが解決できる問題を理解しなければならない.まだJavaメソッドの呼出しについての説明をしていないので,この問題を説明するのは,少し先を行くことになる.クラスがロードされるとき,そのクラスのオブジェクトのメソッドを呼び出したり,フィールドにアクセスしたりできるようにする定義がWolfram言語に作らる.staticメンバは非staticメンバとは全く異なった扱いがされる.以下に挙げる問題は非staticメンバには該当しないので,ここでは,staticメンバのみを扱う.例えば,com.foobar.MyClassという名のクラスがあって,fooという名前のstaticメソッドを含んでいるとする.このクラスをロードするときに,fooの定義を作って,foo[args]のように名前で呼ばれるようにしなければならない.そこで問題になるのは,どのようなコンテキストでシンボルfooを定義したいか,またそのコンテキストを可視にするかどうか(つまり,$ContextPath上で)ということである.
J/Link は,常にcom`foobar`MyClass`fooのように,完全修飾クラス名を反映するコンテキストでfooの定義を生成する.これにより,他のコンテキストに存在するかもしれないfooというシンボルとの衝突を避けることができる.しかし,com`foobar`MyClass`foo[args]のように,毎回完全なコンテキスト名を入力してfooを呼び出さなければならないというのは面倒である.AllowShortContext->True(デフォルト設定)オプションで,J/Link は簡略化されたコンテキストでアクセスできるfooの定義,つまり接頭辞として階層パッケージ名の付いていないクラス名だけからなる定義も作ることができる.ここの例では,単にMyClass`foo[args]としてfooを呼び出すことができる.すでにWolframシステムセッションに同じ名前のコンテキストがあるために簡略化されたコンテキストを避ける必要がある場合は,AllowShortContext->Falseとすることもできる.これにより,すべての名前が強制的に「深層」コンテキストにのみ置かれる.AllowShortContext->Trueでも,staticメンバの名前は深層コンテキストに置かれるので,シンボルを参照するときはいつも深層コンテキストを使うことができる.
AllowShortContextによって,シンボル名が定義されているコンテキストがコントロールできる.もうひとつのオプションStaticsVisibleは,このコンテキストを可視にするかどうか($ContextPathに置くかどうか)をコントロールする.デフォルトはStaticsVisible->Falseなので,シンボルを参照するときはMyClass`foo[args]のように,コンテキスト名を使わなければならない.StaticsVisible->Trueとすると,MyClass`が$ContextPathに置かれるので,foo[args]と書くだけで済む.しかしデフォルトをTrueにすることは少し危険を伴う.クラスをロードするたびに,潜在的に多数の名前が突然生成されWolframシステム上で見えるようになり,もし同じ名前のシンボルがすでにあれば,定義が隠されてしまう問題が起る可能性があるからである.この問題は,Wolfram言語のユーザ定義のシンボルと同様に,通常Javaのメソッドやフィールド名が小文字で始まるという理由で,特にJavaでよく起る.Javaクラスの中にはx,y,width等の名前のstaticメソッドやフィールドもあるので,定義が隠されてしまう問題は非常に起りやすくなる(コンテキストと定義の隠蔽については「コンテキスト」を参照).
このような理由で,StaticsVisible->Trueは自分で書いたクラス,あるいは内容をよく知っているクラスにのみお勧めする.この設定によりタイプする手間が省け,コードがもっと読みやすくなり,パッケージ接頭辞をタイプし忘れるというような簡単なバグが防げる.古典的な例は,古い"addtwo" WSTP例題プログラムを実装することである.Javaでは,次のようになる.
デフォルトのStaticsVisible->Falseでは,AddTwo`addtwo[3,4]としてaddtwoを呼び出さなければならない.StaticsVisible->Trueでは,より明確なaddtwo[3, 4]が書ける.
これらはstaticメソッドとフィールドのみのオプションである.後で説明するように,非staticメンバはコンテキストと可視性の問題を完全に排除する形で扱われる.
内部クラス
内部クラスは他のpublicクラスの中で定義されたpublicクラスである.例えば,javax.swing.Boxというクラスは,Fillerという内部クラスを持っている.JavaプログラムでFillerクラスを参照するときは,一般的には「外部クラス名 ピリオド 内部クラス名」と続く表記を使う.
J/Link でも内部クラスを使うことができるが,クラスの本当の内部名を使う必要がある.それは外部クラス名と内部クラス名を区切るのにピリオドでなく,$を使う.
Javaコンパイラで作成されたクラスファイルを見ると,内部クラスに$で区切られているクラス名があるのが分かる.
JavaとWolfram言語間の型の変換
Javaオブジェクトを作成し,メソッドを呼び出す操作に入る前に,Wolfram言語とJavaの間の型のマッピングを調べなければならない.Javaメソッドが結果をWolfram言語に返すとき,その結果は自動的にWolfram言語の式に変換される.例えば,Javaの整数型(例えば,byte,short,int等)はWolfram言語の整数に変換され,Javaの実数型(floatやdouble)はWolfram言語の実数に変換される.下の表は完全な変換表である.この変換はどちらの方向にも当てはまる.例えば,Wolfram言語整数がbyte値を要求するJavaメソッドに送られると,その整数は自動的にJavaのbyteに変換される.
Javaの型
| Wolfram言語の型 |
byte, char, short, int, long | Integer |
Byte, Character, Short, Integer, Long, BigInteger | |
Integer | |
float, double | Real |
Float, Double, BigDecimal | Real |
boolean | True または False |
String | String |
array | List |
ユーザによる制御(「複素数」を参照) | Complex |
Object | JavaObject |
Expr | あらゆる式 |
null | Null |
Javaの配列は適切な深さのWolfram言語リストにマップされる.従って,double[]を取るメソッドを呼び出すとき,それに{1.0,2.0,N[Pi],1.23}を渡す場合がある.同様に,深さ2の整数配列を返すメソッド(つまり,int[][])は,Wolfram言語に式{{1,2,3},{5,3,1}}を返す可能性がある.
ほとんどの場合 J/Link を使うと,実数型(floatあるいはdouble)を取るメソッドに,Wolfram言語整数を与えることができる.同様にdouble[]を引数として取るメソッドには整数と実数が混ざったリストを渡すことができる.これができないのはまれで,メソッドが同じ引数のスロットで実数,整数のタイプのみが違う2つのシグネチャを持っているという場合だけである.例えば,以下のようなメソッドを持つクラスを考えてみる.
public void foo(byte b, Object obj);
public void foo(float f, Object obj);
public void bar(float f, Object obj);
J/Link はメソッドfooについて2つのWolfram言語定義を作成する.ひとつは最初の引数に整数を取り,最初のシグネチャを呼び出し,もうひとつは最初の引数に実数を取り,2つ目のシグネチャを呼び出す.barメソッドに対して作成された定義は最初の引数に整数,実数のどちらでも受け入れる.つまり,あるメソッドについてどちらのシグネチャを呼び出すかが曖昧になってしまう場合以外は,J/Link は自動的に整数を実数に変換する.しかし,これは必ず行われるわけではない.というのは,J/Link は全力を尽くして,実数と整数の曖昧さがすべての引数の位置で問題になるかどうかを判別するわけではないからである.1つの位置で曖昧であると,J/Link はあきらめて,すべての引数位置で正確な型のマッチングを要求する.少し複雑に聞こえるが,ほとんどの場合,J/Link を使うと整数あるいは整数のリストを,それぞれ実数あるいは実数の配列を引数として持つメソッドに渡すことができる.それができない場合は,エラーメッセージが現れ,呼出しは失敗する.このような場合は,Wolfram言語のN関数を使って明示的にすべての整数を実数に変換しなければならない.
オブジェクトの生成
Javaオブジェクトのインスタンスを作成するためには,JavaNew関数を使う.JavaNewの最初の引数はオブジェクトのクラスであり,LoadJavaClassから返されたJavaClass式として,あるいは完全修飾クラス名(つまり,すべてのピリオドを伴った完全なパッケージ接頭辞を持つ形)を与える文字列として指定される.オブジェクトのコンストラクタに引数を与えたい場合は,クラスの後に文字列として続ける.
JavaNew[cls,arg1,…] | 指定されたクラスの新しいオブジェクトを生成し,それをWolfram言語に返す |
JavaNew["classname",arg1,…] | 指定されたクラスの新しいオブジェクトを生成し,それをWolfram言語に返す |
JavaNewからの戻り値は,<<>>でくくられていることを除くと,JavaObjectという頭部を持っているかように見えるおかしな式である.<<>>は,式の表示形式が,内部表現とかなり異なるということを示す.これらの式はJavaObject式として参照される.JavaObject式はクラス名を示す方法で表示されるが,不可視なものだと考えなければならない.つまり,分解したり,中身を見たりすることはできないのである.これが使えるのはJavaObject式を取る J/Link の関数の中だけである.例えば,もし obj がJavaObjectなら,First[obj]を使ってクラス名を得ることはできない.その代りに,ClassName[obj]という J/Link の関数を使うとよい.
JavaNewは引き渡される引数の型に適したJavaコンストラクタを呼び出し,Wolfram言語に実際にオブジェクトの参照となるものを返す.JavaObject式は,Java言語のオブジェクトの参照とよく似たJavaオブジェクトへの参照と考えることができる.どんなタイプのオブジェクトを生成していようとも,Wolfram言語に返されるものは大きいものではない.特に,オブジェクトのデータ(つまり,そのフィールド)はWolfram言語に送り返されない.実際のオブジェクトはJava側に残っていて,Wolfram言語はその参照を取得するのである.
Frameを生成したからといってそれが自然に現れるわけではない.これには,別の手順が必要となる.フレームのshowあるいはsetVisibleメソッドを呼び出して操作することもできるし,後述のように J/Link の特別な関数JavaShowによってJavaウィンドウを表示し,前面に持ってくることもできる.
JavaNewだけがWolfram言語でJavaオブジェクトの参照を取得する方法ではない.多くのメソッドやフィールドがオブジェクトを返し,そのようなメソッドを呼び出すときにJavaObject式が生成される.そのようなオブジェクトはJavaNewで明示的に生成したオブジェクトと同じように使用できる.
この時点で,参照カウンタのようなものやWolfram言語に返されたオブジェクトがどのようにクリーンアップされるのかについての疑問があるかもしれないが,これらの項目は「Wolfram言語でのオブジェクト参照」で扱う.
J/Link にはJavaオブジェクトを生成する他の2つの関数MakeJavaObjectとMakeJavaExprがある.この特別な関数は「MakeJavaObjectとMakeJavaExpr」で説明する.
メソッドの呼出しとフィールドへのアクセス
シンタックス
Javaメソッドを呼び出したりフィールドにアクセスしたりするためのWolfram言語のシンタックスは,Javaのシンタックスと類似している.下の表はコンストラクタ,メソッド,フィールド,staticメソッド,staticフィールドの呼出しについてWolfram言語とJavaとを比較したものである.Javaを使うWolfram言語プログラムはJavaプログラムとほとんど全く同じように書かれていることが分かる.違いは,Wolfram言語では引数に()ではなく[]を使い,「メンバアクセス演算子」として,Javaの「.(ドット)」の代りに@を使うということだけである.
例外は,staticメソッドについて,Javaではドットを使うが,Wolfram言語ではコンテキストマークの「`」を使うということである.しかし,この場合のJavaのドットはスコープ解決演算子(C++の「::」 等)として使われるので,Wolfram言語の使用法はJavaの使用法に対応しているといえる.Wolfram言語にはこのような用語はないが,このスコープ解決演算子はコンテキストマークである.Javaの階層パッケージ名は直接Wolfram言語の階層コンテキストに対応している.
コンストラクタ
| |
Java: | MyClass obj=new MyClass(args); |
Wolfram言語: | obj=JavaNew["MyClass",args]; |
メソッド
| |
Java: | obj.methodName(args); |
Wolfram言語: | obj@methodName[args] |
フィールド
| |
Java: | obj.fieldName=1; value=obj.fieldName; |
Wolfram言語: | obj@fieldName=1; value=obj@fieldName; |
静的メソッド
| |
Java: | MyClass.staticMethod(args); |
Wolfram言語: | MyClass`staticMethod[args]; |
静的フィールド
| |
Java: | MyClass.staticField=1; value=MyClass.staticField; |
Wolfram言語: | MyClass`staticField=1; value=MyClass`staticField; |
@は関数を引数に適用するときのWolfram言語の演算子として,もうすでにお馴染みかもしれない.f@xはより一般的に使われるf[x]と同じである.J/Link は@を特別な操作のためだけに使うわけではない.これは単に通常の関数のアプリケーションが少し形を変えただけのものである.つまり,@を使う必要は全くないのである.以下はメソッドを呼び出す方法で,これらは同様のものである.
最初の型がJavaのシンタックスからWolfram言語のシンタックスへ自然にマップしているので,このチュートリアルではこちらだけを使う.
メソッドやフィールドを呼び出して結果が返されたら,J/Link は「JavaとWolfram言語間の型の変換」の表に従って,自動的に引数や結果をWolfram言語 表現に変換したり,Wolfram言語表現から変換したりする.
メソッドの呼出しはJavaで行われるようにWolfram言語でも続けて行うことができる.例えば,meth1がJavaオブジェクトを返すと,Javaでobj.meth1().meth2()と書くことができる.Wolfram言語では,obj@meth1[]@meth2[]となる.ここで,明らかな問題がある.Wolfram言語の@演算子は右向きにかかり,Javaのドットは左向きにかかるのである.つまり,Javaのobj.meth1().meth2()は実際はobj.meth1()).meth2()ということで,Wolfram言語のobj@meth1[]@meth2[]は通常obj@(meth1[]@meth2[])ということである.「通常」というのは,J/Link は自動的な一連の呼出しをJavaのように左向きにかかるようにするからである.これは@の大域的な動作に影響がないように,@演算子の属性を変えることによってではなく,JavaObject式の規則を定義することで起る.この接続動作はメソッドの呼び出しにのみ適用され,フィールドの呼び出しには適用されない.以下のようなことはできない.
これは2行に分けなければならない.例えば,上の2行目は次のようになる.
他のオブジェクト指向の言語のように,Javaではメソッド名やフィールド名は呼び出されるオブジェクトによってスコープされる.つまり,obj.meth()と書いたら,Javaはたとえ他のクラスにmethという他のメソッドがあったとしても,そのオブジェクトのクラスにあるmethという名のメソッドを呼び出す.J/Link はWolfram言語シンボルのためにこのスコープを維持し,同名の既存のシンボルとの競合が起きないようにしている.obj@meth[]と書いたら,システム中の他のmethというシンボルとの競合はない.この呼出しの評価で,Wolfram言語によって使われるシンボルmethはこのクラスの J/Link によって設定されたシンボルである.以下はフィールドを使った例である.まず,Pointオブジェクトを生成する.
Pointクラスにはxとyという名の座標のフィールドがある.しかし,ユーザのセッションにもxやyという名のシンボルがあることがよくある.xが評価されたときにそのことをユーザに知らせるよう,xの定義を設定してみる.
ここで,xという名のフィールドの値を設定する(Javaではpt.x = 42と書かれる).
"gotcha"は出力されない.Printの定義を持つGlobal`コンテキストのシンボルxと,このコードラインの評価中に使われるシンボルxとの間には何の衝突もないからである.J/Link は@の右側のメソッドやフィールドの名前を保護して,可視のコンテキスト中にあるこれらのシンボルの定義と衝突しないように,あるいはこれに依存しないようにしている.下のメソッドの例は,これを違った形で示している.
たとえ新しいシンボルshowがここで生成されていても,J/Link によって使われるshowはjava`awt`Frameコンテキストにあるもので,それに必要な定義が付けられている.
要するに,静的でないメソッドとフィールドについては,どんなコンテキストであろうと,その時点での$ContextPathが何であろうと,競合やシャドーイングの心配をする必要は全くないということである.しかし,これは静的メンバについてはまた別の話である.静的メソッドやフィールドはオブジェクト参照なしの完全名で呼び出されるので,名前をスコープするためのオブジェクトは前にはない.以下は,Javaのガベージコレクタを起動する簡単な静的メソッドの例である.静的メソッドを呼び出す前に,確実にクラスがロードされているようにするためにLoadJavaClassを呼び出す必要がある.
staticメンバはそれ自身のコンテキスト(この例ではRuntime`)で定義されているので,名前のスコープは通常問題にはならない.このようなコンテキストは通常$ContextPath上にはないので,Global`コンテキストや読み込まれたパッケージの中に同じ名前のシンボルがあることを心配する必要はない.LoadJavaClassにはstaticメソッドが定義されているコンテキストを判別し,それらが$ContextPath上にあるかどうかを決定するオプションがあるので,この問題についてはLoadJavaClassに関してのセクションで詳しく述べる.使用中のセッションにRuntime`というコンテキストがすでに存在し,シンボルgcを持っている場合,staticメンバの完全クラス名に対応する完全階層コンテキスト名を使うことにより,競合を避けることができる.
最後に,Javaと同様に,オブジェクトのstaticメソッドを呼び出すこともできる.この場合,オブジェクトが前に来るので,名前のスコープがある.ここでは,現在のRuntimeオブジェクトを返すRuntimeクラスのstaticメソッドを呼び出してみる(Runtimeはコンストラクタを持っていないので,JavaNewでRuntimeオブジェクトを生成することはできない).オブジェクト上で(static)メソッドgcを起動するので,どのようなコンテキスト接頭辞もなくgcを使うことができる.
Java名のアンダースコア(_)
Javaの名前はWolfram言語シンボルでは合法ではない文字を含むことができる.唯一よく使われるのはアンダースコアである.J/Link はクラス,メソッド,フィールド名のアンダースコアを「U」にマップする.このマッピングは,名前が文字列ではなくシンボル形で使われるとき等,必要なときだけに使われる.例えば,com.acme.My_Classという名のクラスがあるとする.このクラス名を文字列で参照するときは,アンダースコアを使う.
しかし,そのようなクラスでstaticメソッドを呼び出すとき,階層コンテキスト名はシンボルなのでアンダースコアをUに変換しなければならない.
同じ規則がメソッド名とフィールド名についても当てはまる.Javaフィールド名の多くにはjava.awt.Frame.TOP_ALIGNMENTのように,アンダースコアが使われている.コードでこのメソッドを参照するにはUを使う.
クラスとオブジェクトについての情報入手
J/Link には与えられたクラスやオブジェクトに使用可能なコンストラクタ,メソッド,フィールドを示す便利な関数がある.
Constructors[cls] | publicコンストラクタとその引数の表を返す |
Constructors[obj] | このオブジェクトのクラスのコンストラクタ |
Methods[cls] | publicメソッドとその引数の表を返す |
Methods[cls,"pat"] | 名前が文字列パターン pat とマッチするメソッドだけを示す |
Methods[obj] | このオブジェクトのクラスのメソッドを示す |
Fields[cls] | publicフィールドの表を返す |
Fields[cls,"pat"] | 名前が文字列パターン pat とマッチするフィールドだけを示す |
Fields[obj] | このオブジェクトのクラスのフィールドを示す |
ClassName[cls] | cls で示されるクラス名を文字列として返す |
ClassName[obj] | このオブジェクトのクラス名を文字列として返す |
GetClass[obj] | このオブジェクトのクラスを表すJavaClassを返す |
ParentClass[obj] | このオブジェクトの親クラスを表すJavaClassを返す |
InstanceOf[obj,cls] | このオブジェクトが cls のインスタンスであればTrueを,それ以外の場合はFalseを返す |
JavaObjectQ[expr] | expr がJavaオブジェクトへの有効な参照であればTrueを,それ以外の場合はFalseを返す |
Constructors,Methods,Fieldsにはオブジェクトやクラスを与えることができる.クラスは文字列として,あるいはJavaClass式としての完全名で指定できる.
これらの関数によって返された宣言はJavaキーワードのpublic,final(メソッドに関してのみ省略可能.フィールドの場合は該当しない),synchronized,native,volatile,transientを削除することにより,簡素化されている.宣言は常にpublicで,他の修飾子は J/Link での使用にはおそらく関係がない.
MethodsとFieldsはInheritedというオプションを1つ取り,それはスーパークラスとインターフェースから継承したメンバを含むか,クラス内で宣言されたメンバだけを示すかを指定する.デフォルトはInherited->Trueである.
オブジェクトとクラスについての情報を与えてくれる別の関数として,ClassName,GetClass,ParentClass,InstanceOf,JavaObjectQがある.ほとんどの場合,これらの関数は自己説明的である.InstanceOf関数はJava言語のinstanceof演算子を真似たものである.JavaObjectQは有効なJavaオブジェクトにのみマッチするパターンを書くのに便利である.
JavaObjectQは引数がJavaオブジェクトへの有効な参照であるか,JavaのnullオブジェクトにマップするNullシンボルであるときのみ,Trueを返す.
Javaの終了と再起動
WolframシステムセッションでJavaを使い終えたら,UninstallJava[]を呼び出すことで,Javaランタイムを終了することができる.
UninstallJava[] | Javaランタイムを終了する |
ReinstallJava[] | Javaランタイムを再起動する |
UninstallJavaはJavaの終了に加えて,クラスをロードしたときにWolfram言語で生成された多くのシンボルや定義をクリアする.終了していないJavaObject式は,Javaが終了したときに無効になる.それらはもはやJavaObjectQを満たさず,<<JavaObject[classname]>>ではなくJLink`Objects`JavaObject12345678のようなそのままの記号として現れる.
ユーザのほとんどはUninstallJavaを呼び出すことはない.JavaランタイムはWolfram言語システムの統合された部分として考えるとよい.起動したらそのままにしておくのである.J/Link を使うすべてのコードが同じJavaランタイムを共有しており,使っているパッケージの中には気付かないところでJavaを利用しているものもある.Javaをシャットダウンしてしまうとその機能が使えなくなるかもしれないパッケージを書いている開発者は決してそのパッケージでUninstallJavaを呼び出してはならない.自分のアプリケーションが J/Link を使い終えたからといって他のユーザもそうだと想定してはならない.
Javaを終了して再起動する必要があるのは,Wolfram言語から呼び出したいJavaクラスを積極的に開発しているときくらいである.一旦クラスがJavaランタイムにロードされると,アンロードすることはできない.クラスを変更および再コンパイルしたい場合は,Javaを再起動して修正バージョンを再ロードする必要がある.このような場合でも,UninstallJavaを呼び出すことはない.その代りに,ReinstallJavaを呼び出す.これでUninstallJavaの後にInstallJavaを再び呼び出すことになる.
バージョン情報
J/Link にはバージョン情報を提供する記号が3つある.この記号はWolfram言語の記号と同じタイプの情報を与える.異なる点は,J/Link の記号はJLink`Information`コンテキストにあり,このコンテキストは$ContextPath上にはないので,完全パス名で指定しなければならないという点である.
JLink`Information`$Version | すべてのバージョン情報を与える文字列 |
JLink`Information`$VersionNumber | 現在のバージョン番号を与える実数 |
JLink`Information`$ReleaseNumber | リリース番号を与える整数(完全なバージョン仕様xxxの最後の桁) |
ShowJavaConsole[] | コンソールウィンドウでJavaランタイムと J/Link Javaコンポーネントのバージョン情報を表示 |
「Javaコンソールウィンドウ」で説明しているShowJavaConsole[]関数も,便利なバージョン情報を表示する.この関数は使用中のJavaランタイムのバージョンとJavaで書かれている J/Link の部分のバージョンを表示する.J/Link Javaコンポーネントのバージョンは,J/Link Wolfram言語コンポーネントのバージョンと同じでなければならない.
クラスパスのコントロール:J/Link がいかにクラスを見付けるか
Javaクラスパス
クラスパスはJavaランタイム,コンパイラ等のツールに,どこでサードパーティやユーザ定義のクラス(Javaの拡張機能やJavaプラットフォームの一部ではないもののこと)を見付ければよいかを指示するものである.クラスパスはJavaユーザやプログラマーの間でいつも混乱を招くものであった.
Javaは標準Javaプラットフォームの一部であるクラス(いわゆる「ブートストラップ」クラス),拡張機能を使うクラス,CLASSPATH環境変数によって,あるいはJavaの起動時のコマンドラインオプションによってコントロールされるクラスパス上にあるクラスを見付けることができる.J/Link はJavaランタイムがこの通常のメカニズムで見付けられるどのクラスもロードして使うことができる.その上,J/Link はスタートアップ時にクラスパス上で指定されたもの以外に,ユーザ設定可能で別の場所にあるクラス,リソース,ネイティブライブラリを見付けることもできる.この追加の場所は,Javaが起動している間に加えることができる.
J/Link にはJavaがクラスを見付けるのに使う,検索パスを変える方法が2種類ある.1つ目は,ReinstallJavaのClassPathオプションで行う方法で,2つ目は,新しいディレクトリとjarファイルを J/Link が検索する特別な場所に加える方法である.2つ目の方法は,起動時にクラスパスを修正する方法より優れている.この2種類の方法を以下で説明する.
スタートアップクラスパスのオーバーライド
標準的なJavaのクラスパスでアクセスできるクラスは,次のうちの1つの条件を満たしていなければならないり.
「適切にネストされた」というのは,クラスファイルが置かれているディレクトリの階層が,クラスの完全パッケージ名を反映していなければならないということである.例えば,ディレクトリc:\MyClassesがクラスパス上にあるとする.パッケージにない(コードの最初にパッケージの文がない)クラスは,そのクラスファイルを直接c:\MyClassesに置かなければならない.パッケージcom.acme.stuffの中にあるクラスの場合,そのクラスファイルはディレクトリc:\MyClasses\com\acme\stuffになければならない.jarとzipファイルはクラスパス上で明示的に指定されなければならないからである.クラスパス上で名付けられたディレクトリにただ放り込むことはできない.ディレクトリの問題はこれらのファイルには関係ない.つまり, jarファイル内のクラスがどのように階層的に組まれているかにかかわらず,クラスパス上でただjarファイルを指定すれば,その中のすべてのクラスが見付けられるのである.
標準Javaプラットフォームや拡張機能の一部ではないクラスのパスを指定したい場合は,ReinstallJavaのClassPathオプションを使うとよい.ClassPathオプションに与える値は,希望のディレクトリやzipまたはjarファイルを指定する文字列である.この文字列はプラットフォーム依存である.パスはプラットフォームのネイティブスタイルで指定され,セパレータ文字はLinuxではコロン,Windowsではセミコロンである.以下は,通常の設定方法である.
ClassPathのデフォルト設定はAutomaticである.これはCLASSPATH環境変数の値を使うことを意味する.ClassPathを何か別の値に設定すると,J/Link はそのようなクラスが見付けられないので,CLASSPATH環境変数を無視する.つまり,ClassPath設定を使えば,CLASSPATH環境変数は失効するということである.これは,Javaランタイムとコンパイラの-classpathコマンドラインオプションをご存知の方には,その動作と似ているといえる.
ClassPathオプションの使用は避けた方がよい.ClassPathオプションが提供するダイナミックなコントロールが必要なら,よりパワフルで便利なAddToClassPath機能を使うとよい.ClassPathオプションを使うのは,CLASSPATH環境変数の内容が使われるのを特に防ぎたい場合がほとんどである.そのためには,ClassPath->Noneと設定する.
クラスパスのダイナミックな修正
標準的なJavaクラスパスで不便なのは,Javaランタイムが起動した後に変更できないという点である.J/Link には,標準的なJavaクラスパスを超えた特別な場所を検索する独自のクラスローダがある.これにより,J/Link は非常にパワフルかつ柔軟にクラスを見付けることができるのである.この追加のクラス検索パスに他の場所を加える場合は,AddToClassPath関数を使う.
AddToClassPath["location",…] | J/Link のクラス検索パスに,指定されたディレクトリまたはjarファイルを加える |
Javaがスタートしたら,いつでもAddToClassPathを呼び出すことができ,変更は直ちに反映される.この追加のクラス検索パスで便利なのは,ディレクトリを加えるとそのディレクトリのあらゆるjarあるいはzipファイルが検索できるという点である.つまり,標準的なJavaクラスパスでは必要なjarファイルを個別に指定しなくてもよいということである.圧縮されていないクラスファイルの場合,ネストの規則はクラスパスの規則と同じである.これは,クラスがcom.acme.stuffパッケージの中にあってAddToClassPath["d:\\myClasses"]を呼び出すとすると,クラスファイルをd:\MyClasses\com\acme\stuffに置く必要があるということである.
AddToClassPathで加えた検索パスの変更は,現行のJavaセッションにのみ適用される.Javaを終了して,再起動したら,AddToClassPathを再び呼び出さなければならない.
J/Link は,AddToClassPathで加えた場所の他に,標準Wolfram言語アプリケーションの場所($UserBaseDirectory/AddOns/Applications,$BaseDirectory/AddOns/Applications,<Mathematica dir>/AddOns/Applications,<Mathematica dir>/AddOns/ExtraPackages)にある任意のディレクトリのJavaサブディレクトリをすべて自動的に含む.この機能は,実装の一部としてJavaと J/Link を使うWolfram言語アプリケーションを作成する開発者が非常に簡単に J/Link を配備できるよう設計されたものである.詳細は「J/Link を使うアプリケーションを配備する」で説明するが,J/Link で使用するクラスを書いているJavaのプログラマーも,この機能を利用することができる.AddOns/Applicationsのサブディレクトリ(例えばMyStuff)を作って,その中にJavaサブディレクトリを作り,その中にクラスあるいはjarファイルを入れるだけで十分である.J/Link はそれを見付けて使うことができる.もちろん,圧縮されていないクラスファイルは,上記のようにそのパッケージ名に対応したJavaディレクトリ(存在するならば)の適切にネストされたサブディレクトリに置かなければならない.
AddToClassPath関数は J/Link 2.0で導入された.J/Link の前バージョンには追加場所のリストを指定する$ExtraClassPathという変数があった.このリストは次のようにして追加することができる.
$ExtraClassPathは J/Link 2.0でなくなったが,まだ使うことはできる.AddToClassPathよりも$ExtraClassPathの優れている点は,$ExtraClassPathに加えた変更がJavaランタイムを再起動しても有効であるという点である.
クラスパスの検証
JavaClassPath関数は J/Link がクラスを検索するディレクトリおよびjarファイルの集合を返す.これには,標準的なWolfram言語アプリケーションの場所の中にあるアプリケーションディレクトリのJavaサブディレクトリに加え,AddToClassPathあるいは$ExtraClassPathで追加した場所も含まれる.標準的なJavaプラットフォームを構成するjarファイルやJava拡張ディレクトリ内のjarファイルは表示しない.これらのクラスはJavaプログラムで常に見付けることができる.
JavaClassPath[] | J/Link がクラスを検索するディレクトリおよびjarファイルをすべて与える |
J/Link のクラスローダを直接使う
前述のように,J/Link はそれ自身のクラスローダを使って,スタートアップクラスパスを超えたダイナミックな場所にあるクラスやリソースを見付けることができる.基本的に,J/Link を使ってロードするクラスでJavaプラットフォームの一部ではないものは,すべてこのクラスローダでロードされる.このため,Wolfram言語からJavaのClass.forName()メソッドを呼び出しても,動作しないことがよくある.
問題となるのは,Class.forName()は J/Link クラスローダではなく,デフォルトのクラスローダを使うクラスを見付けるが,このデフォルトのクラスローダは J/Link がクラスを探す特殊なディレクトリについて知らない(実際,J/Link がJavaを起動する方法の詳細のため,スタートアップクラスパスについてさえ知らない)という点である.JavaコードをWolfram言語に変換している場合,あるいはあるクラスのClassオブジェクトだけが必要な場合は,この問題に注意する必要がある.これを修正するには,強制的に J/Link のクラスローダが使われるようにする.その方法のひとつとして,Class.forName()の3引数形式を使うことが挙げられる.これで使用するクラスローダを指定することができるようになる.
より簡単な方法に,JLinkClassLoaderのstaticなclassFromNameメソッドを使うというものがある.
このclassFromName()メソッドはClass.forName()の代りである.文字列のクラス名からClassオブジェクトを得たい場合,忘れずにJLinkClassLoader.classFromName()を使わなければならない.
Class.forName()はJavaコードではあまり一般的に使われるものではない.それでも使用されるのは,オブジェクトを生成しなければならないときに,コンパイルの時点でクラスが未知の場合である.例えば,クラス名は初期設定ファイルで付けられていたり,他の方法でプログラムに基づいて決められている可能性がある.以下の行で,クラスのインスタンスを作成する.
// Java code
Class cls = Class.forName("SomeClassThatImplementsInterfaceX");
X obj = (X) cls.newInstance();
このようなコードをWolfram言語プログラムに変換する場合は,JavaNewを呼び出すだけで実行できる.
ポイントは,Class.forName()を一般的に使用する場合,それを1行ごとWolfram言語プログラムに変換する必要がないということである.JavaNewを呼び出すことでその機能を得ることができるのである.
パフォーマンス問題
Java呼出しのオーバーヘッド
Javaプログラムの実行速度はJavaランタイムによる.例えば,複雑な計算ループに時間のほとんどを費やすようなタイプのプログラムでは,Javaの実行速度はコンパイルされ最適化されたC言語の実行速度に近付く.
Javaは計算機能が強化されたプログラムに適している.利用目的が何であろうと,簡単な実行速度テストをする前にどんなタイプのプログラムにもJavaを除外するべきではない.スピードが必要ではない,それほど多くを要求しないプログラムでは,C言語で従来のWSTPの「インストール可能」なプログラムを作成する代りに J/Link を使う方が簡単なため,Javaを選ぶのは適切といえる.
J/Link に関する実行速度の問題はほとんどの場合,Javaの実行速度のことではない.むしろ,ボトルネックはJavaを呼び出すことのできる速度で,主にそれはWSTPの実行速度と,Javaの各呼出しについてWolfram言語で行われなければならない処理の実行速度によって決まる.Java呼出しの最高速度は,使用するオペレーティングシステムやJavaランタイムの種類で異なる.速いWindowsマシンでは,1秒間におよそ5000以上のJavaメソッドを呼び出す.staticメソッドならWolfram言語での前処理が少なくてすむので,もっと多くの回数呼び出せる.オペレーティングシステムの中には,これより少ない結果になるものもある.Javaの呼出しにはその目的にかかわらず,多かれ少なかれ決まったコストがあり,遅いマシンでは,そのコストは.001秒以上にもなる.Javaメソッドの多くはこれよりもかなり少ない時間で実行されるので,呼出しの合計時間はJava自身の実行速度ではなく,しばしば J/Link の呼出しの決まったターンアラウンドタイムに依存する.
通常の使用では,Javaの呼出しのオーバーヘッドは問題ではないが,Javaを500,000回呼び出すループがあったりすると,問題になる(そのプログラムにあまりに長時間かかるので J/Link コストが無視できる場合を除く.その場合はもっと問題が大きくなる).もし,Wolfram言語がJavaをかなりの回数呼び出すことを要求するように構成されているなら,Java側の負担を増やすように再編成して,Java-Wolfram言語の境界を越える回数を減少させる必要がある.この場合,おそらくJavaコードを書くことになるため,Wolfram言語を使って任意のJavaプログラムの機能を書くことができるという J/Link の前提を,残念ながら打ち消すことになる.この方法では書けないJavaの使用法もあるので,そのためにはJavaでもっと機能を書き,Wolfram言語で書く量を減らす必要がある.
大規模配列送信のスピードアップ
ほとんどの「基本的」Javaのデータ型(例えば,byte,short,int,float,double)の配列の送受信はC言語プログラムと同じくらいの速さで行うことができる.迅速に渡されるタイプはWSTPC APIが配列を置くための関数を持つタイプに対応している.Javaのデータ型のlong(64ビット),boolean,Stringは高速なWSTPの関数を持っていないので,これらの型の送受信にはずっと時間がかかる.できれば,このような型の巨大配列(例えば,100,000要素より大きいもの)を使用するのは避けるようにした方がよい.
多次元配列を動かす実行速度に大きな影響を与える設定は,「長方形でない」配列が許されるかどうかを制御するために使われるものである.「不揃いな配列」で説明するように,J/Link のデフォルト動作では,すべての配列が完全に長方形であることが要求される.しかし,Javaではこの制限がなく,長方形でない配列を送受信したいときは,WolframシステムセッションでAllowRaggedArrays[True]を呼び出すとよい.これにより,J/Link は配列を読んだり書いたりするためのメソッドをずっと遅いものに切り替えるので,必要でない限りこの設定は避けなければならない.また,必要がなくなった時点で直ちに切り替えること.
int[][]メソッドを持つクラスをロードするとき,J/Link がこのメソッドを呼び出すために生成するWolfram言語の定義は,引数として整数の二次元配列を要求するパターンテストを使用する.もし,500×500のように配列が非常に大きければ,このテストには実際にJavaに配列を転送するのにかかるのと同じくらい,莫大な時間がかかる.配列引数のテストでかかる時間を節約したければ,変数$RelaxedTypeCheckingをTrueに設定するとよい.こうすれば,送った配列が正しいタイプ,また正しい次元であるかが自分で確認できる.壊れた配列を送ると,WSTPエラーが出るが,(1つの呼出しが$Failedを返す以外は) J/Link には影響ない.
$RelaxedTypeCheckingを長期間Trueのままにしておかない方がよい.もし,他の人のためのコードを書く場合は,その人たちのセッション中に値を変えないようにしなければならない.$RelaxedTypeCheckingは,短期間True値が与えられるBlockコンストラクトでの使用を意図している.
$RelaxedTypeCheckingは,配列への影響しかない.この配列は J/Link が作成するパターンテストが,実際のJavaの呼出しと比較して演算コストが高い唯一のタイプである.
J/Link プログラムをスピードアップする別の最適化に,ReturnAsJavaObjectを使ってWolfram言語とJavaの間における大規模配列や文字列の不必要な行き来を避けるというものがある.ReturnAsJavaObjectは「ReturnAsJavaObject」で説明する.
最適化の例
J/Link プログラムの実行速度をアップする手順の簡単な例を見てみる.Javaには,パワフルなDecimalFormatクラスがあり,それを使ってWolfram言語の数値をファイルへの出力用に望みの方法でフォーマットすることができる.ここでは,数値を小数第4位まで正確にフォーマットするDecimalFormatオブジェクトを生成する.
fmtオブジェクトを使うためには,そのformat()メソッドを呼び出し,フォーマットしたい数を与える.
これで要求したフォーマットの文字列を返す.20000の数字のリストをファイルに書き込む前にフォーマットする機能を使いたいとする.
上記のMap呼出しは,フォーマットメソッドを40000回呼び出すので,あるPCでは56秒かかる(これは掛け時計の時間であり,ほとんどのシステムでWSTPプログラムに対して正確でないTiming関数の結果ではない).明らかにこれは受け入れられない.最初のステップとして,同じメソッドを何回も呼び出すのでMethodFunctionを使う.
MethodFunctionの1つ目の引数としてfmtを使う.1つ目の引数はただクラスを指定するだけである.クラス設定を取る J/Link のすべての関数で実質的にできるように,クラスのオブジェクトを使うこともできる.生成されたMethodFunctionはfmtオブジェクトだけでなく,DecimalFormatクラスのどのオブジェクト上でも使うことができる.
methodFuncを使うと,36秒かかる.わずかにスピードアップしており,J/Link の以前のバージョンよりはずっと速くなっている.これは1秒間におよそ1100回の呼出しをしているということだが,それでもまだ便利といえるほど速くはない.唯一できることは数字の「配列」を取り,すべての数字をフォーマットし,文字列の配列を返すJavaメソッドを自分で書くことである.これにより,Wolfram言語からJavaへの呼出しが40000回から1回に減少する.
これは必要な小さいJavaクラスのコードである.このクラスが J/Link でWolfram言語から呼び出されるということを示しているものはこのコードには何もない.これはJava内でこの機能を使いたいときに書くのと全く同じコードである.
public class FormatArray {
public static String[] format(java.text.DecimalFormat fmt,double[] d) {
String[] result=new String[d.length];
for (int i = 0; i < d.length; i++)
result[i] = fmt.format(d[i]);
return result;
}
}
参照カウントとメモリ管理
Wolfram言語でのオブジェクト参照
先ほどのJavaObject式の部分では,参照カウントや独自性等のより深い問題については説明しなかった.Javaオブジェクト参照がメソッドあるいはフィールドの結果として,またはJavaNewの明示的な呼出しとしてWolfram言語に返されるたびに,J/Link はこのオブジェクトの参照がこのセッションで以前に送られたかどうかを調べる.もし送られていなければ,Wolfram言語にJavaObject式を生成して,その定義を設定する.これは比較的時間のかかるプロセスである.もし,このオブジェクトがすでにWolfram言語に送られていたら,ほとんどの場合 J/Link は単に前に生成されたのと同一のJavaObject式を生成する.これはずっと速い操作である.
この最後の規則には例外がいくつかある.オブジェクトがWolfram言語に返されると,これと同じオブジェクトがWolfram言語に事前に送られていたとしても,新しく異なったJavaObject式が生成される場合がある.特にオブジェクトのhashCode()値がWolfram言語に最後に現れたものと異なっているときはいつも,生成されるJavaObject式は異なる.この詳細については特に知っておく必要はないが,SameQはこれらのJavaObject式が同じオブジェクトを参照してるかどうかを決めるためにそれらを比較する妥当な方法ではない.これを行うためには,SameObjectQ関数を使わなければならない.
SameObjectQ[obj1,obj2] | JavaObject式の obj1 と obj2 が同じJavaオブジェクトを参照していればTrueを返し,そうでなければFalseを返す |
JavaObject式の比較
変数ptはJavaのPointオブジェクトを参照する.後で取り戻せるようにそれをコンテナに入れる.
フィールドのうちの1つの値を変える.Pointオブジェクトでは,そのフィールドの1つの値を変えることによりhashCode()値が変わる.
今度はptによって与えられたJavaObject式と,Vectorの最初の要素がWolfram言語に返されるよう要求したときに生成されたJavaObject式を比較する.これらは両方とも同じJavaオブジェクトへの参照であるが,JavaObject式は異なる.
Wolfram言語の2つのオブジェクト参照が同じJavaオブジェクトを参照してるかどうかを判断するためにSameQ (===)を使うことができないので,J/Link はこの目的のための関数SameObjectQを提供している.
なぜSameObjectQ関数が必要であるか.オブジェクトのequals()メソッドを呼び出してみる.それにより,この例の場合は正しい解答が得られる.
この方法で問題なのは,equals()が常にオブジェクト参照を比較するわけではないということである.どのクラスも自由にequals()をオーバーライドして,そのクラスの2つのオブジェクトを比較するための望み通りの動作を提供することができる.クラスの中には,文字列の比較のためにequals()を使うStringクラスのようにオブジェクトの「内容」を比較するものもある.Javaには2つの異なる等式操作,==演算子とequals()メソッドがある.==演算子はいつも参照を比較し,その参照が同じオブジェクトを指している場合にのみtrueを返すが,equals()はしばしば他のタイプの比較にオーバーライドされる.オブジェクト参照に適用されると,言語の==演算子に似た動作をするメソッド呼出しはJavaにはないので,J/Link にはWolfram言語プログラマーにその動作を提供するSameObjectQ関数が必要なのである.
まれに,等式のオブジェクト参照をかなりの回数に渡り比較する必要があるとき,SameQと比べてSameObjectQの遅さが問題になる.全く同じJavaオブジェクトを参照する2つのJavaObjectt式がSameQでないようにする唯一の場合は,オブジェクトのhashCode()値が,2つのJavaObject式が生成される間に変わった場合である.変わっていないと分かっていれば,SameQを使ってそれらの式が同じオブジェクトを参照しているかどうかをテストしても安全である.
ReleaseJavaObject
Java言語には「ガベージコレクション」という組込み機能がある.これは,プログラムによってもう使われなくなったオブジェクトで占領されているメモリを回収するものである.オブジェクトが,参照されない他のオブジェクトを除いたどこからも参照されなくなると,そのオブジェクトはガベージコレクションの対象となる.オブジェクトがJavaNewの呼出しの結果として,あるいはメソッド呼出しやフィールドへのアクセスの戻り値としてWolfram言語に返されると,J/Link コードはJava側のオブジェクトの特別な参照を持ち,Wolfram言語で使用されている間はガベージコレクションされないように保証する.Wolframシステムセッションで特定のJavaオブジェクトを使う必要がなくなったら,J/Link に明示的にその参照を解放するよう指示できる.これに使う関数がReleaseJavaObjectである.ReleaseJavaObjectはJavaのWolfram言語特有の参照を解放することに加え,Wolfram言語で生成されたオブジェクトの内的な定義も一掃する.その後はWolfram言語でこのオブジェクトを使うことはできない.
JavaにもうWolfram言語からこのオブジェクトを使う必要がないことを伝える.
ReleaseJavaObject[obj] | JavaにWolfram言語で obj を使い終えたことを知らせる |
ReleaseObject[obj] | 古い名称,J/Link 2.0ではReleaseJavaObject |
JavaBlock[expr] | expr の評価中にWolfram言語に返された新しいJavaオブジェクトはすべてexpr が終了すると解放される |
BeginJavaBlock[] | この時点からEndJavaBlock[]マッチまでにWolfram言語に返された新しいJavaオブジェクトはすべて解放される |
EndJavaBlock[] | BeginJavaBlock[]マッチ以降見られる新しいオブジェクトをすべて解放する |
LoadedJavaObjects[] | Wolfram言語で使用中のオブジェクトすべてのリストを返す |
LoadedJavaClasses[] | Wolfram言語にロードされているクラスすべてのリストを返す |
ReleaseJavaObjectを呼び出したからといって,必ずしもオブジェクトがガベージコレクションされるとは限らない.Javaには他の参照も存在しているということが十分考えられる.ReleaseJavaObjectはJavaにオブジェクトを捨てるよう指示するのではなく,そのオブジェクトはWolfram言語のためだけに保管される必要がないということを告げるだけである.
J/Link がWolfram言語に送られたオブジェクトのために維持する参照についての重要な事実は,何回Wolfram言語に返されようとも,参照は1つのオブジェクトにつきただ1つだけしか保管されないということである.各人の責任においてReleaseJavaObjectを呼び出した後,Wolframシステムセッションに存在している可能性のある参照からそのオブジェクトを決して使わないようにしなければならない.
Frameクラスのadd()メソッドは加えられたオブジェクトを返すので,b2はb1と同じオブジェクトを参照する.
ReleaseJavaObject[b1]を呼び出すと,影響を受けるのはWolfram言語シンボルのb1ではなく,b1が参照するJavaオブジェクトである.従って,b2を使うのも(%のような,これと同じButtonオブジェクトを参照するどのような方法も)誤りである.
ReleaseJavaObjectの呼出しは通常の使用においてはほとんど必要ではない.セッションでJavaを頻繁に使わない場合は,どのようなオブジェクトが必要とされるか追跡し続ける必要はない.ただ放っておけばよい.しかし,Javaのメモリ使用が重要になると,ReleaseJavaObjectが提供する制御がもっと必要になる,特別な場合もある.
JavaBlock
ReleaseJavaObjectは主に他の人のためにコードを書いている開発者に提供されている.ある人のコードが他の人にどのように使われるかは予測できないので,開発者はコードが作成する不必要な参照を排除するよう常に確かめていなければならない.おそらく,これに最も便利な関数はJavaBlockであろう.
JavaBlockは数式の評価中に見付かったオブジェクトを解放するプロセスを自動化する.Wolfram言語プログラムではJavaNewでJavaオブジェクトが生成され,そのオブジェクトで操作することによりメソッド呼出しの結果として他のオブジェクトがWolfram言語に返され,最終的に数字や文字列等の結果が返されるようにしなければならないことがよくある.この操作中にWolfram言語が使ったJavaオブジェクトは,BlockやModuleによってWolfram言語に与えられたり,またブロックスコープコンストラクト(例えば{})によってCやC++,Java,その他数多くの言語に与えられたりするローカル変数のようなもので,どれもプログラムの実行中にだけ必要とされる.JavaBlockを使うと,評価中にWolfram言語に返されJavaBlockが終了したら解放される新しいオブジェクトはどれも一時的なものとして扱われるという属性を持つものとして,コードのブロックをマークすることができる.
前の文で「新しいオブジェクト」といっている点に注意しなければならない.JavaBlockにより,評価中に使われたオブジェクトがすべて解放されるわけではなく,最初に使われたもののみが対象となる.すでにWolfram言語が使われていたオブジェクトは何の影響も受けない.つまり,実際にはその評価にとって一時的ではないオブジェクトをJavaBlockが積極的に解放してしまうと心配する必要はない.
多くのJavaメソッドの呼出しでオブジェクトが返されるので,JavaNewで生成したすべてのオブジェクトに対してReleaseJavaObjectをただ単に呼び出すだけでは十分とはいえない.これらの戻り値には興味がなかったり,他の呼出しと接続(obj@returnsObject[]@foo[]のように)しているかもしれないので,戻り値を明示的にある変数に代入しようとはしないであろう.しかし,それでも戻り値を解放する必要がある.JavaBlockを使うのはコードのブロックが終了したときに,すべての新しいオブジェクトが解放されることを保証する簡単な方法である.
JavaBlock[expr]は expr が戻す値をすべて返す.
J/Link Wolfram言語プログラムの多くは次の構造を持つ.
多くのJavaObject式を作成・操作し,そのうちの1つを返して残りを一時的なものにする関数を書くことは一般的である.簡単に言うと,JavaBlockの戻り値が1つのJavaObjectなら,それは解放されない.
J/Link 2.1で新しく加わったKeepJavaObject関数により,JavaBlockが終わっても解放してはならないオブジェクトを単独で,あるいは列で指定することができるようになった.以下の例では,この関数を使って2つのオブジェクトを解放せずにそのリストを返す.単独,あるいは列のオブジェクトに対してKeepJavaObjectを呼び出すと,最初に取り囲んでいるJavaBlockが終了してもそのオブジェクトは解放されない.しかし,その外を取り囲むJavaBlockがある場合は,それが終了するとオブジェクトは解放されるので,オブジェクトがネストされたJavaBlock式の集合をエスケープするようにしたい場合は,各レベルでKeepJavaObjectを呼ばなければならない.あるいはKeepJavaObject[obj,Manual]を呼び出す方法も使える.この方法では,Manual引数によって,取り囲んでいるJavaBlock式はどれもオブジェクトを解放してはならないということが J/Link に指示される.この場合,各オブジェクトにReleaseJavaObjectを手動で呼び出さない限り,解放されない.以下の例では,KeepJavaObjectを使って,2つのオブジェクトを解放せずに,その2つからなるリストが返せるようにする.
BeginJavaBlockとEndJavaBlockは複数の評価に渡ってJavaBlockと同じ機能を提供するのに使用される.EndJavaBlockにより,前にマッチしたBeginJavaBlock以降Wolfram言語に返された新しいJavaオブジェクトはすべて解放される.これらの関数は主に開発中に,セッションでマークを設定し,変更を加え,その時点以降Wolfram言語に返されるすべての新しいオブジェクトを解放したいときに便利である.BeginJavaBlockとEndJavaBlockはネストできる.BeginJavaBlockにはすべてマッチするEndJavaBlockがなければならない.ネストされていても,EndJavaBlockを呼び出し忘れるのは重大な誤りではない.ただいくつかのオブジェクトの解放ができないだけである.
LoadedJavaObjectsとLoadedJavaClasses
LoadedJavaObjects[]は現在Wolfram言語で参照されているすべてのJavaオブジェクトのリストを返す.これにはJavaNewで明示的に生成されたオブジェクト全部とJavaメソッドの呼出しあるいはフィールドアクセスの結果としてWolfram言語に返されたオブジェクトすべてが含まれる.ReleaseJavaObjectやJavaBlockで解放されたオブジェクトは含まない.LoadedJavaObjects[]は主にデバッグのために使われる.作業中の関数の前後に呼び出すと便利である.もしリストが大きくなったら,関数から参照がリークしているので,JavaBlockおよび/またはReleaseJavaObjectの使用について調べる必要がある.
LoadedJavaClasses[]はWolfram言語にロードされた全クラスを表示するJavaClass式のリストを返す.LoadedJavaClassesはLoadedJavaObjectsのように主にデバッグのために使われる.LoadJavaClassを呼び出す前にクラスがロードされているかどうかを調べる必要はない.もしクラスがロードされていたら,LoadJavaClassは適切なJavaClass式を返すだけである.
例外
例外の取扱い
J/Link は自動的にJavaの例外を扱う.Javaの呼出し中にキャッチされなかった例外が投げられたら,Wolframシステムで次のようなメッセージが現れる.以下は,実数を整数にフォーマットしようとする例である.
メソッドがWolfram言語に結果を返す前に例外が投げられたら,上の例のように呼出しの結果は$Failedになる.「Wolfram言語への手動による結果返信」で説明するが,返ってくる前にWolfram言語に手動で結果を送るメソッドを自分で書くこともできる.その場合,結果がWolfram言語に送られた後で,メソッドが戻る前に例外が投げられたら,例外を告げる警告メッセージが出るが,呼出しの結果には影響ない.
Javaコードがデバッグ情報を伴ってコンパイルされているなら,例外の結果として得られるWolframシステムメッセージは,例外が生じた個所をそれぞれのファイル中の実際の行番号で示す.
JavaThrow関数
時には,Javaで例外が投げられるようにしたいことがあるかもしれない.そのときは,JavaThrow関数を使う.JavaThrowは J/Link 2.0の新しい関数で,実験的なものともいえる.将来的には,動作が変更される可能性がある.
JavaThrow[exceptionObj] | Javaにある例外オブジェクトを投げる |
JavaThrowを使うのは,それ自身がJavaから呼び出されるWolfram言語コードの中だけにした方がよい.Wolfram言語で書かれた J/Link プログラムがWolfram言語からJavaへの呼び出しとJavaかWolfram言語へのコールバックの両方を含むことはよくある.そのようなWolfram言語 への「コールバック」は,「ウィンドウやその他のユーザインターフェース要素の作成」の後半でも説明するが,Javaのユーザインターフェースを作成するWolfram言語プログラムでよく使われる.例えば,ユーザがJavaのボタンをクリックしたときにWolfram言語の関数が呼び出されるように関連付けることができる.このWolfram言語の関数はJavaから直接呼び出されるので,その関数がJavaの例外を投げるという機能も含めて,Javaメソッドのように動作させた方がよいかもしれない.
ボタンのクリックのようなユーザインターフェースアクションからのコールバックで例外を投げるというのは,あまり現実的ではない.その理由は,通常Javaにはそのような例外をキャッチするものは何もないので,必然的に無視されるからである.より重要な例として,柔軟性と開発の簡単さという理由から,例外を投げることのできるJavaメソッドの真髄を実装するためにWolfram言語の関数が呼び出されるようになっている,JavaとWolfram言語の両方のコードを含むプログラムがある.具体的な例として,SAX(Simple API for XML)APIを使ってJavaとWolfram言語でXML処理を行っているとする.SAX処理はXMLドキュメントのパース中に,あるイベントが起ると呼び出されるハンドラメソッドに基づいている.そのようなメソッドはそれぞれ,SAXExceptionを投げて,エラーを示し,パースを中止することができる.このようなハンドラメソッドをWolfram言語コードに実装して,Wolfram言語からSAXExceptionを投げる方法がほしいこともあろう.ここでは仮に,そのようなハンドラメソッド,startDocument()があり,ドキュメントの処理が始まるとSAXエンジンによって実行されるとする.
JavaThrowを呼び出した後,残りのWolfram言語関数は通常通りに実行するが,Javaに戻される結果はない.
「値」と「参照」でオブジェクトを返す
参照と値
J/Link はあるWolfram言語の式とそれに対応するJavaとの間のマッピングを実行する.これはWolfram言語の式がWolfram言語とJavaとの間を行き来するときに,それが自動的にJavaの対応式に変換されたり,あるいはJavaの対応式から変換されたりするということである.例えば,Java整数タイプ(long,short,int等)はWolfram言語整数に変換され,Java実数タイプ(floatとdouble)はWolfram言語の実数に変換される.その他のマッピングとしては,JavaオブジェクトがWolfram言語のJavaObject式に変換されるというものもある.これらのJavaObject式はJavaオブジェクトへの「参照」である.J/Link によって操作されるという以外はWolfram言語において何の意味も持たない.しかし,Javaオブジェクトの中にはWolfram言語において意味をなす値を持っているものもあり,これらのオブジェクトはデフォルトで値に変換される.そのようなオブジェクトの例として文字列と配列が挙げられる.
従って,Javaオブジェクトはデフォルトで特別な場合を除き「参照」でWolfram言語に返されるということができる.この特別な場合というのは,文字列,配列,複素数(後で説明する),BigDecimal,BigInteger,「ラッパー」クラス(例えばjava.lang.Integer)である.このような例外的な場合は「値」によって返されるといえる.「JavaとWolfram言語間の型の変換」の表はどのようにこの特別なJavaオブジェクトタイプがWolfram言語の値にマップされるかを示している.
要するに,Wolfram言語上で意味をなす値で表現できるJavaオブジェクトがこの値に変換されるのは,単に便宜上のことである.しかし,このデフォルトの動作をオーバーライドしたいこともある.その最も一般的な理由はWSTP上での大規模な数式の不必要なトラフィックを避けるためである.
ReturnAsJavaObject[expr] | expr によって返されるJavaオブジェクトは参照の形を取る |
ByRef[expr] | 古い名称,J/Link 2.0ではReturnAsJavaObject |
JavaObjectToExpression[obj] | オブジェクト参照 obj の値をWolfram言語式として取得する |
Val[obj] | 古い名称,J/Link 2.0では,JavaObjectToExpression |
ReturnAsJavaObject
MyClassクラスにarrayAbs()というstaticメソッドがあり,それがdoubleの配列を取り,それぞれの要素が引数配列の対応する要素の絶対値である新しい配列を返すとする.このメソッドの宣言はdouble[] arrayAbs(double[] a)のようになる.Wolfram言語からそのようなメソッドを呼び出すときは次のようにする.
上記の例はどのようにメソッドが動作すればよいかの例である.Wolfram言語にリストを渡すと,リストが返される.abs()ではなくsqrt()関数を実行する以外はarrayAbs()のように動作する,arraySqrt()という別のメソッドがあるとする.
この演算では,もとのリストはWSTPでJavaに送られ,その値でJavaの配列が生成される.その配列は引数としてarrayAbs()に渡され,それは自身で別の配列を生成し返す.この配列はWSTPでWolfram言語に返され,リストを作り,それはarraySqrt()の引数として素早くJavaに返される.Wolfram言語に配列データを返すのは時間の無駄に見える.Java側に完全な配列(arrayAbs()メソッドによって返されたもの)があり,arraySqrt()に渡される準備ができているのに,同じ値の新しい配列として直ちにまたJavaに送り返させるためだけに,その内容をWolfram言語に送り返すのである.この例では,演算コストは無視できるほどであるが,200,000要素を持つ配列だったらどうであろうか.
必要なのは,配列データをJava内に保存し,実際のデータではなく配列への参照だけを返す方法である.これを実行するのにReturnAsJavaObject関数を使う.
JavaObjectのクラス名は"[D"で,多少不可解ではあるが,一次元のdoubleの配列の実際のJavaクラス名である.以下はReturnAsJavaObjectを使った演算例である.
先ほどは,arraySqrt()がWolfram言語のrealリストであった引数で呼ばれていた.ここでは,一次元のdoubleの配列であるJavaオブジェクトへの参照で呼ばれている.配列を取るメソッドやフィールドはすべて,Wolfram言語 リストあるいは適切なタイプのJava配列への参照でWolfram言語から呼び出すことができる.
ReturnAsJavaObjectは文字列にも便利である.配列のように,文字列には2つの属性がある.まず,文字列はオブジェクトとしてJavaで表示されるが,意味をなすWolfram言語の値も持つということである.もう1つは,文字列は大規模になる可能性があるということである.従って不必要にデータを往復させることを避けることができるのは便利である.例として,MyClassクラスがstaticメソッドを持ち,それがJavaからコントロールしている外部デバイスから読み取られた数字を文字列に加えるとする.そのメソッドは文字列を取り新しい文字列を返すので,そのシグネチャはstatic String appendDigit(String s)である.長い文字列を持つveryLongStringという名のWolfram言語変数があり,この文字列に100回加えたいとする.このコードにより,文字列の内容がWolfram言語とJavaとの間を100回往復することになる.
ReturnAsJavaObjectを使うと文字列はJava側に残り,実質的にはWSTP上のトラフィックはほとんど生じない.
大きくなる文字列に繰り返し加えていくことはあまり効率のよいプログラミングではないので,この例は幾分不自然であるが,この問題を例示している.
上記のDoループが実行されると,veryLongStringにはWolfram言語の文字列ではなく,Javaにある文字列を参照するJavaObject式である値が割り当てられる.つまり,appendString()は1回目はWolfram言語の文字列で呼び出されるが,2回目以降はJavaObject式で呼び出される.配列と同様に,文字列を取るあらゆるJavaメソッドやフィールドは,文字列あるいは文字列を参照するJavaObject式のどちらででもWolfram言語から呼び出すことができる.veryLongString変数は初めは文字列を持っているが,ループの最後ではJavaObject式を持っている.
時には,この文字列のオブジェクト参照ではなく実際のWolfram言語の文字列が必要となる.どのように値を取り戻せるであろうか.後でJavaObjectToExpression関数を紹介するときにまたこの例に戻る.
要するに,通常Wolfram言語の値に変換されるオブジェクトを返すメソッドやフィールドが,代りにReturnAsJavaObject関数によって参照を返すようになる.Wolfram言語とJavaとの間で大規模なデータを不必要に往復させるのを避けることは最適化につながる.また,この関数は主に大規模配列や文字列に便利である.すべての最適化でそうであるように,受け入れられない実行速度で起動しているコードがあったり,書いているコードがそれにより多大な恩恵を受けるということが前もって分かっていたりする場合以外は,ReturnAsJavaObjectのことはそれほど考える必要はない.ほとんどのJavaクラスオブジェクトはWolfram言語での意味をなす「値」として表示されず,いつも「参照」で返される.このような場合,ReturnAsJavaObjectでは何の効果もない.
最後にReturnAsJavaObjectはJavaプログラマーが手動でWolfram言語に結果を返すメソッドには何の影響もない(このトピックは後ほど出てくる).手動で結果を返すと,J/Link の通常の結果取扱いのルーチンは回避されるので,ReturnAsJavaObjectの要求が満たされることがない.
JavaObjectToExpression
上では,通常Wolfram言語に値で返されるオブジェクトを,参照で返すためにどのようにReturnAsJavaObject関数を使うかについて説明した.その逆を行う関数も必要である.つまり,参照を取ってそれを値表示に変換するものである.その関数がJavaObjectToExpressionである.
上記のappendStringの例に戻ると,WSTP上で演算コストの高い文字列のデータの往復を避けるためにReturnAsJavaObjectを使った.この結果,veryLongString変数は直接的なWolfram言語の文字列ではなく,JavaObject式を持った.JavaObjectToExpressionはこの文字列オブジェクトの値をWolfram言語の文字列として取得するのに使われる.
Javaオブジェクトの大多数にはWolfram言語で意味をなす値表示がない.これらのオブジェクトはJavaObject式としてWolfram言語で表示され,JavaObjectToExpressionを使っても何の効果もない.
通常は値としてWolfram言語に返されるオブジェクトのためのJavaObject式を得る方法は,ReturnAsJavaObject関数だけではない.JavaNew関数はいつも参照を返す.
Wolfram言語の文字列や配列からJavaオブジェクトを生成するときにJavaNewを使うより簡単な,MakeJavaObject関数については別のページで説明する.
MakeJavaObjectとMakeJavaExpr
序文
クラスのコンストラクタを呼び出すJavaNewに加えて,J/Link はWolfram言語の式からJavaオブジェクトを生成する2つの便利な関数,MakeJavaObjectとMakeJavaExprを提供している.名前はよく似ているが,混同してはならない.MakeJavaObjectはある特殊なタイプのオブジェクトを構築するのに広く使われる関数である.MakeJavaExprは任意のWolfram言語式を示す J/Link のExprクラスのオブジェクトを生成する高度な関数である.
MakeJavaObject
MakeJavaObject[val] | Wolfram言語式 val(数値,文字列,リスト等)を表すための適切なタイプのオブジェクトを構築する |
例えばWolfram言語からJava Stringオブジェクト等を取るJavaメソッドを呼び出すときは,Wolfram言語の文字列で呼び出すことができる.J/Link の内部はWolfram言語の文字列と同じ文字を持つJava文字列を構築し,その文字列をJavaメソッドに渡す.しかし,Objectを取るメソッドに文字列を渡したいこともある.そのようなメソッドはWolfram言語から文字列を引数にして呼び出すことはできない.というのは,J/Link はWolfram言語の文字列がJava文字列に対応していると認識しても,Wolfram言語の文字列がJava Objectに対応しているとは認識しないからである.Javaの呼出しでの型保証のために意図的に行うのである.この例では,MyClassクラスが次のシグネチャのメソッドを持っていると仮定する.
また,theObjがこのクラスのオブジェクトで,JavaNewで生成されたと仮定する.直接的な文字列でfooを呼び出してみる.
これは上記の理由で失敗した.Objectを取るJavaメソッドを文字列で呼び出すためには,適切な値を持つJava文字列オブジェクトを明示的に生成しなければならない.JavaNewを使うと,これを生成することができる.
引数がJavaObject式なので,今度は成功する.
Java文字列オブジェクトを生成するためにJavaNewを呼び出さなくてもよいように,J/Link はMakeJavaObject関数を提供する.
文字列の場合,MakeJavaObjectはJavaNewを呼び出すだけである.もちろん,それがStringオブジェクトしか構築できないとしたら,あまり役には立たない.これと同じ問題が,Wolfram言語の値の直接表現であるJavaオブジェクトでも起る.これにはjava.lang.Integer,java.lang.Boolean等のような「ラッパー」クラスと配列クラスが含まれる.java.lang.Integerを引数として取るJavaメソッドを呼び出したいときはWolfram言語から通常の整数型で呼び出すことができる.しかし,Objectを取るメソッドに整数を渡したければ,明示的にjava.lang.Integerタイプのオブジェクトを生成しなければならない.J/Link は整数の引数から自動的にそれを構築しない.この場合にはJavaNewよりもMakeJavaObjectを呼び出す方が簡単である.
整数の引数が与えられたら,MakeJavaObjectはいつもjava.lang.Integerを構築し,java.lang.Short,java.lang.Longや他の「整数」Javaラッパーオブジェクトは構築しない.同様に,実数でMakeJavaObjectを呼び出せば,java.lang.Doubleを生成し,java.lang.Floatは生成しない.他のタイプのオブジェクトを要求するときは,JavaNewを明示的に呼び出さなければならない.
MakeJavaObjectはBoolean値にも使える.
MakeJavaObjectがJavaNewを呼び出すショートカットであるだけなら,それほど便利なものではない.しかし,配列クラスのオブジェクトを生成するのになくてはならないものになっている.Javaでは配列はオブジェクトでそれはクラスに属している.このクラスには暗号的な名前あるが,もしそれが分かっていればJavaNewで配列オブジェクトを生成することができる.配列オブジェクトを生成するときに,JavaNewの2つ目の引数はそれぞれの次元の長さを与えるリストである.ここではintの2×3の配列を作る.
JavaNewで配列オブジェクトを作ることができるが,その配列の要素の初期値を与えることはできない.一方,MakeJavaObjectはWolfram言語リストを取り,同じ値を持つJava配列オブジェクトに変換する.
このように,MakeJavaObjectは特に配列オブジェクトを作る上で便利である.それにより配列要素の初期値を与えることができ,Java配列クラスの名前(intの二次元配列には[[I,doubleの一次元配列には[D等)を学んだり覚えたりする必要がなくなるからである.MakeJavaObjectは二次元の深さまでのinteger,double,string,booleansオブジェクトの配列を作ることができる.
JavaObjectToExpression関数については「JavaObjectToExpression」で説明したが,MakeJavaObjectはJavaObjectToExpressionの逆と考えることができる.MakeJavaObjectは,値を表示できる対応するJavaオブジェクトを持つWolfram言語の式を取り,そのオブジェクトを生成する.実際にそれをJavaオブジェクトにするのである.JavaObjectToExpression関数はその逆を行う.Wolfram言語で意味をなす表示をするJavaオブジェクトを取り,その式に変換するのである.MakeJavaObjectが操作できるどのxについても,下記がいつもいえる.
MakeJavaObjectはあまり使われない関数である.Javaメソッドに渡すためだけに,明示的にJavaオブジェクトをWolfram言語の文字列,配列等から構築する必要はない.J/Link が自動的に構築する.しかし,ほとんどの場合にある引数から J/Link が自動的にオブジェクトを生成しても,引数が汎用Objectの場合はオブジェクトの生成はしない.この場合,自分でJavaObjectを生成しなければならない.MakeJavaObjectはそのための最も簡単な方法である.
「SetInternetProxy」で説明しているSetInternetProxy関数のコードはMakeJavaObjectの具体的な使用例を示している.プロキシ情報をファイアウォールの後ろのユーザのために指定するためには,Propertiesクラスを使ってシステム属性を設定する必要がある.このクラスはHashtableのサブクラスで,以下のシグネチャを持ったメソッドを持っている.
文字列の形式でPropertiesのキーと値をいつも指定しなければならない.これをWolfram言語で行うこともできる.
これがうまく動作するには,MakeJavaObjectを使って,JavaのStringオブジェクトを作らなければならない.
MakeJavaExpr
MakeJavaExpr関数を理解するためには,「Exprクラスの動機付け」で説明する,J/Link のExprクラスの動機付けを理解しなければならない.基本的に,Exprは任意のWolfram言語の式を表わすことのできるJavaオブジェクトである.それは主に,JavaでWolfram言語式を調べ,操作したいと思っているJavaのプログラマーに利用される.時にはJavaからではなくWolfram言語でExprオブジェクトを生成する方法があるのも便利である.MakeJavaExprはこのようなニーズを満たす関数である.
MakeJavaExpr[expr] | Wolfram言語の式を表す J/Link のExprクラスのオブジェクトを構築する |
Exprを取るJavaメソッドを呼び出しているなら,Exprオブジェクトを構築するためにMakeJavaExprを呼び出す必要はない.J/Link は,他の自動変換と同様に,引数として与えられる式をすべて自動的にExprオブジェクトに変換する.MakeJavaObjectと同様に,MakeJavaExprは,Exprではなく汎用のObjectを取るメソッドを呼び出していて,J/Link が自動変換を行わない場合に使われる.このような場合,あるWolfram言語式からExprオブジェクトを明示的に生成しなければならない.これを行うのは,後で取り出せるようにWolfram言語式をJavaに保存するためである.
以下に示すのは,MakeJavaExprの簡単な例である.これは式の一部を検査,変更,抽出するためのWolfram言語のようなメソッドをたくさん持つExprクラスからのメソッドをいくつか示している.もちろん,これは短縮した例である.式の長さが知りたい場合は,Wolfram言語のLength[]関数を呼び出してみるとよい.ここで示すExprメソッドは,通常Wolfram言語ではなく,Javaから呼び出されるものである.
Exprオブジェクトは,Wolfram言語式のように不変である.上記のようにinsert()を呼び出してもeを修正しなかった.その代りに新しいExprを返した.
Wolfram言語プログラムでMakeJavaExprを使った方がよい理由がよく分からなくても,心配はいらない.これは利用するプログラマーがほとんどいないほどの高度な関数なのである.
ウィンドウやその他のユーザインターフェース要素の作成
序文
J/Link の最も便利なアプリケーションのひとつは,Wolfram言語プログラムのためのユーザインターフェース要素を書くということである.このような要素の例として,演算の完了を監視するプログレスバー,画像やアニメーションを表示するウィンドウ,ユーザに入力を求めたり,馴染みの薄い関数の呼出しを補助したりするダイアログボックス,ユーザに解析の段階を示していくミニアプリケーション等が挙げられる.このようなユーザインターフェースは,ユーザがWolfram言語を呼び出したときに現れるという点で,バックグラウンドでWolfram言語を使うJavaプログラム用に書くものとは異なる.ノートブックフロントエンドの代替となるのではなく,それを拡張するものである.このように,ユーザインターフェースはフロントエンドで作成できるパレット,あるいは他の特別なノートブック要素の拡張機能のようなものなのである.
J/Link を伴ったWolfram言語は,ユーザインターフェースの作成のための非常にパワフルで生産的な環境である.ユーザインターフェースコードの複雑さは J/Link 開発におけるインタラクティブな1度に1行という性質で扱うのに理想的である.動作中のインターフェースを文字通り構築,修正,実験できるのである.
Wolfram言語プログラムのユーザインターフェースを書きたい場合は,GUIKit も参照するとよい.GUIKit は J/Link を基盤としており,非常に高度なインターフェース作成方法を提供する.GUIKit の詳細はこのマニュアルの範疇を超えてしまうが,GUIKit は,ここで説明するように J/Link をそのまま使うよりも,簡単にユーザインターフェースが作成できる方法を提供するよう特別に設計されたアドオンである.
インタラクティブインターフェースと非インタラクティブインターフェース
Javaウィンドウを作成するWolfram言語プログラムを書くためには,さまざまなタイプのユーザインターフェース間の重要な相違点を理解する必要がある.これらの違いはそのユーザインターフェースがどのようにWolfram言語カーネルとインタラクトするかに関連している.
カテゴリの最上部には,「インタラクティブ」と「非インタラクティブ」インターフェースの違いがある.ここでいうインタラクティブというのは,Wolfram言語カーネルとのことであって,ユーザとのことではない.非インタラクティブユーザインターフェースとは,通常Wolfram言語によってコントロールされるが,Wolfram言語に送信し返す必要がないものを言う.このインターフェースはユーザ入力を受け入れないこともある.それらはWolfram言語コードによって作成され,操作され,破壊される.例として,プログレスバーを示すウィンドウがある(完全なプログレスバープログラムは「プログレスバー」で示す).プログレスバーはWolfram言語に結果を返さず,少なくともWolfram言語とインタラクトすることによってユーザアクションに応答する必要もない.ウィンドウはクローズボックスがクリックされたら(ユーザアクション)閉じるが,Wolfram言語に結果を返したり,Wolfram言語へのコールバックを引き起したりしないので,Wolfram言語には関係ない.プログレスバーは完全にWolfram言語プログラムによって駆動される.情報の流れは単方向である.
このユーザインターフェースの寿命は,プログレスバーの場合のように,通常単一のWolfram言語プログラムによって定められる.しかし,必ずしもそうとは限らない.「アプレットをホストする」で説明するように,アプレットウィンドウでアプレットをホストすることは,ウィンドウを作成したコードが終了した後でもウィンドウが残る例のひとつである.アプレットウィンドウはユーザがクローズボックスをクリックしたときにのみ閉じる.しかし,重要なことはアプレットはWolfram言語とインタラクトする必要はないということである.
Wolfram言語とのインタラクションを必要としないこのタイプのユーザインターフェースは,ここで取り上げなくてはならないような問題を起すことはない.このインターフェースを作成し,起動し,破壊するプログラムは,Javaの一連の呼出しで達成される非GUI Java演算のようなものである.ただ,偶然視覚効果を生み出しただけに過ぎない.具体的な例が必要であれば,「プログレスバー」のプログレスバーコードを参照するとよい.
もっと一般的な「インタラクティブ」タイプのユーザインターフェースはWolfram言語に応答する必要がある.これは典型的なモーダル入力ダイアログのように結果を返したり,ユーザがボタンをクリックした結果として演算を始めたりするものである.このタイプのユーザインターフェースによる特別な問題を理解するためには,入力を取り込み,それを評価し,すべての出力を送り返すカーネルの「メインループ」についての基本を考えると役に立つ.
Wolfram言語カーネルがフロントエンドで使用されているときは,フロントエンドと通信するために使用するWSTPリンクに入力が届くのを待つのにそのほとんどの時間が費やされる.このリンクは$ParentLinkによって与えられる.この$ParentLinkがカーネルの「注意」を引くのである.入力が$ParentLinkに届くと評価され,結果がリンクに送られ,カーネルは$ParentLinkへの更なる入力を待つ状態に戻る.J/Link が使用されているとき,カーネルはJavaランタイムに接続する別のリンクを開く.Javaを呼び出すコードを実行すると,カーネルはJavaに何かを送り,Javaからの戻り値を受け取るまでブロックする.カーネルがJavaからの戻り値を待っている間,Javaリンクはカーネルの注意を引く.カーネルがJavaのリンクに注意を向けるのはこの時だけである.もっと一般的な言葉でいうと,カーネルは特別に指示されたときだけJavaから届く入力に耳を傾けるのである.その他の時間は,通常ノートブックフロントエンドである$ParentLinkのみに注意している.
ユーザがJavaウィンドウのボタンをクリックし,そのボタンがWolfram言語を呼び出すコードを実行しようとするとどうなるであろうか.Java側はWolfram言語に何かを送り結果を待つが,カーネルはJavaリンクに注意を払っていないので,この要求を受け取らない.従って,カーネルにJavaリンクから届く入力を探すよう指示する手段を使わなければならない.J/Link はカーネルをJavaリンクに注目させ,Java側で発生した評価の要求の受入れ態勢をコントロールする3種類の方法を提供する.
この3つの方法は「モーダル」,「モードレス」,「マニュアル」と呼ばれる.モーダルインタラクションでは,Wolfram言語の関数のDoModalを使用するが,カーネルはJava側がJavaリンクを解放するまで,それを監視する.カーネルは完全にJava側に従属しているので,他の演算には使用できない.Wolfram言語の関数のShareKernelを使うモードレスインタラクションでは,カーネルはノートブックフロントエンドとJavaのどちらにも平等に注目することで,どちらから届く評価要求も受け入れる状態に保たれる.最後に,マニュアルモードでは,Wolfram言語の関数のServiceJavaを使う.このモードは,いくつかの点でモーダル操作とモードレス操作の中間といえる.ここでは,大規模プログラムの実行中に,Javaからの要求を許可するように手動でカーネルに指示する.
Javaから要求される演算にどのようにカーネルを対処させるかという問題は,ユーザがボタンをクリックするというようなユーザの動作によってJavaで「開始される」演算にのみ関連している.Wolfram言語からのJavaの呼出しを含む,一連の双方向のやり取りの一部としてのJavaからのWolfram言語の呼出しは関係ない.Wolfram言語がJavaを呼び出すときはいつも,Wolfram言語は積極的にJavaから届く結果を聞く.これが分かり辛いのは,おそらくWolfram言語から呼び出すJavaメソッドを書くことについては,後のセクションで説明しているからであろう.そのメソッドは結果を返す前に演算のためにWolfram言語へコールバックすることができる(典型的な例は,ノートブックウィンドウに何かを出力したり,メッセージを表示したりすること).これはWolfram言語への本当の「コールバック」で,Wolfram言語はいつもそれを扱うことができる.これとは対照的に,Java側のユーザアクションの結果として起るWolfram言語の呼出しは,実際にはWolfram言語にとっては予想外のことで,通常は受け入れる準備ができていない.
モーダル操作とモードレス操作
よくあるタイプのユーザインターフェース要素はモーダルダイアログのようなものである.ダイアログが一旦表示されると,Wolfram言語プログラムは停止してユーザがダイアログウィンドウを閉じるのを待つ.これはウィンドウがWolfram言語に結果を返すからで,ウィンドウが閉じるまでWolfram言語が続行するのは意味がない.このウィンドウの例として,ユーザにある値の入力を求め,OKボタンがクリックされたらWolfram言語にそれを返す単純な入力ウィンドウがある.
このようなウィンドウを説明するために,ここでは「モーダル」という言葉を少し一般化して使っている.閉じるまでユーザインターフェースで他の作業ができないという従来の意味では,これはモーダルとはいえないであろう.そうではなく,Wolfram言語カーネルに関してモーダルなのである.カーネルはウィンドウが閉じるまで何もできない.作成したJavaウィンドウはモニタ上の他のJavaウィンドウに関してはモーダルではないかもしれない(つまり,ダイアログがisModal属性集合を持っていないかもしれない)が,閉じるまでカーネルの注意を引いている.
別のタイプのユーザインターフェース要素はモードレスダイアログのようなものである.ダイアログが表示された後,それを作成したWolfram言語プログラムは終了するが,ユーザがノートブックフロントエンドで作業を続けている間もウィンドウが存在し,使用することができる.これは前述の最初のタイプのユーザインターフェース要素によく似ているが,このウィンドウが表示されている間でもWolfram言語とのインタラクションを始めることができるという点が異なる.例として,ユーザがスクロールリストからパッケージを選んでWolfram言語にロードできるようにするウィンドウが挙げられる.このウィンドウを作成し,表示し,戻る J/Link プログラムを書くとする.そのウィンドウはユーザがクローズボックスをクリックするまで開いており,使用可能な状態のままとなる.その間,ユーザはフロントエンドで自由に作業が続けられ,都合のよいときにJavaウィンドウに戻ることができる.
このウィンドウはフロントエンドの別のタイプのノートブック,あるいはパレットウィンドウのようなものである.フロントエンドやJavaウィンドウを一度にいくつでも開きアクティブにすることができる.つまり,それらはWolfram言語での演算を始めるのに使うことができるのである.その各々が,同じカーネル上の小さなインターフェースになっている.異なる点は,Javaウィンドウはノートブックウィンドウより一般的で,重要なことにはフロントエンドとは異なったアプリケーション層にあるということである.この事実により,Javaウィンドウはノートブックフロントエンドの拡張機能というよりもむしろ,事実上第2のフロントエンドとなっている.2つ目のフロントエンドを持つためには,カーネルは特別な状態に保たれ,ノートブックフロントエンドとJavaのどちらからの評価の要求が扱えるようになっていなければならない.
モーダルウィンドウとモードレスウィンドウの実装の方法を説明する前に,少し先取りしてJavaのユーザインターフェース要素がWolfram言語と通信するメカニズムについて説明する.
Wolfram言語コードでイベントを扱う:「MathListener」クラス
ユーザインターフェース要素はクリックされたときに動作を引き起すボタン,スクロールバー,メニュー,テキストフィールドのようなアクティブなコンポーネントを持つ.Javaのイベントモデルでは,コンポーネントがユーザのアクションに応答してイベントを発生させ,他のコンポーネントはイベントリスナとして登録することでこれらのイベントへの関心を示す.しかし,実際は,コンポーネントは通常直接イベントリスナとしては動作しない.従って,プログラマーは望ましいイベントリスナインターフェースを実装し,さまざまなイベントに対応したコンポーネントの特定のメソッドを呼び出すアダプタクラスを書く.これにより,ただイベントリスナとして動作させるためだけに応答するコンポーネントをサブクラス化する必要がなくなる.特別なコードだけがアダプタクラスに行き,イベントを終了したりそれに応答したりするコンポーネントが一般化される.
例えば,標準的なJavaプログラムを書いていて,テキストエリアの外観をコントロールするために使いたいボタンがあるとする.ボタンをクリックすると,白い背景の黒いテキストと黒い背景の白いテキストとが交互に切り替わるようにする.ボタンをクリックするとActionEventsが発生する.クリックの通知を受け取るクラスはActionListenerインターフェースを実装し,そのaddActionListenerメソッドを呼び出すことにより,ボタンに登録しなければならない.このためにはActionListenerを実装するMyActionAdapterというような名前のクラスを書くことになろう.ボタンをクリックしたときに呼び出されるactionPerformed()メソッドでは,適切なメソッドを呼び出して,テキストエリアの前景色と背景色を設定することができる.
コンポーネントをフォームにドロップして,イベントでつなぐことによってアプリケーションを作ることができるJava GUIビルダを使ったことがあれば,生成されるコードは,イベントがソースオブジェクトにより起動されたときに,ターゲットオブジェクトの特定のメソッドを呼び出すロジックを扱うアダプタクラスの大部分で構成される.
GUIのコンポーネントをつなぎ合わせるには,通常はさまざまなイベントリスナインターフェースを実装するクラスの形式で多くのJavaコードを書かなければならない.J/Link プログラマーは標準Javaイベントモデルを使うGUIを書きたがるが,そのためにJavaコードを書くべきではない.解決方法は簡単である.J/Link は標準イベントリスナインターフェースを実装し,Wolfram言語へコールバックしてユーザ定義のコードを実行する完全なクラスセットを提供する.これにより,すべてのイベントハンドリングロジックはWolfram言語内に行き,プログラムの他の部分のように書かれる.
この解決法は複雑なJava GUIの「純粋なWolfram言語」の属性を保存するだけではなく,Javaで従来のアプリケーションを書くより融通が利く.Javaで書くとき,あるいは便利なドラッグアンドドロップのGUIビルダを使うとき,イベントロジックがハードコードされる.コンパイル時に各クリック,スクロール,キーストロークが何をするかを決めなければならない.しかし,J/Link を使うときは,実行時にどのようにプログラムをつなぎ合わせるかを決める.実行中にコードを数行タイプするだけで,進行中の動作を変えることさえできる.
J/Link はすべての標準AWTイベントリスナクラスの実装を提供する.これらのクラスには,「Math」の後に実装するインターフェースの名が付いている.従って,ActionListenerを実装するクラスはMathActionListenerである(おそらくこれらのクラスにはMathXXXAdapter等もっとよい名前が付けられるであろう).下の表はすべてのMathListenerクラス,それが実装するメソッド,Wolfram言語ハンドラ関数に送られる引数の総括表である.
クラス
|
メソッド
| Wolfram言語ハンドラの引数 |
MathActionListener | actionPerformed(ActionEvent e) | e, e.getActionCommand() (String) |
MathAdjustmentListener | adjustmentValueChanged( AdjustmentEvent e) | e, e.getAdjustmentType(),(Integer) e.getValue() (Integer) |
MathComponentListener | componentHidden(ComponentEvent e) componentShown(ComponentEvent e) componentResized(ComponentEvent e) componentMoved(ComponentEvent e) | e |
MathContainerListener | componentAdded(ContainerEvent e) componentRemoved(ContainerEvent e) | e |
MathFocusListener | focusGained(FocusEvent e) focusLost(FocusEvent e) | e |
MathItemListener | itemStateChanged(ItemEvent e) | e, e.getStateChange() (Integer) |
MathKeyListener | keyPressed(KeyEvent e) keyReleased(KeyEvent e) keyTyped(KeyEvent e) | e, e.getKeyChar(),(Integer) e.getKeyCode() (Integer) |
MathMouseListener | mouseClicked(MouseEvent e) mouseEntered(MouseEvent e) mouseExited(MouseEvent e) mousePressed(MouseEvent e) mouseReleased(MouseEvent e) | e, e.getX(), (Integer) e.getY(), (Integer) e.getClickCount() (Integer) |
MathMouseMotionListener | mouseMoved(MouseEvent e) mouseDragged(MouseEvent e) | e, e.getX(), (Integer) e.getY(), (Integer) e.getClickCount() (Integer) |
MathPropertyChangeListener | propertyChanged( PropertyChangeEvent e) | e |
MathTextListener | textValueChanged(TextEvent e) | e |
MathVetoableChangeListener | vetoableChange( PropertyChangeEvent e) | e(ハンドラからFalseを返すことにより変更を拒否する) |
MathWindowListener | windowOpened(WindowEvent e) windowClosed(WindowEvent e) windowClosing(WindowEvent e) windowActivated(WindowEvent e) windowDeactivated(WindowEvent e) windowIconified(WindowEvent e) windowDeiconified(WindowEvent e) | e |
MathKeyListenerクラスを例にとってこの表の見方を説明する.MathKeyListenerは,keyPressed(),keyReleased(),keyTyped()の3つのメソッドを含むKeyListenerインターフェースを実装する.KeyEventを発生させるコンポーネントにMathKeyListenerを登録したら,この3つのメソッドはそれぞれの名前を取ったキーイベントに反応して呼び出される.このうちどのメソッドが呼び出されても,Wolfram言語を呼び出し,ユーザ定義の関数を実行し,それに3つの引数を渡す.その3つの引数とは,KeyEventオブジェクトと,それに続くイベントオブジェクトのgetKeyChar(),getKeyCode()メソッドの結果である2つの整数である.MathListenerクラスはすべてハンドラ関数にイベントオブジェクト自身を送る.このクラスのように,一般的に必要とされる値である付加的な整数の引数を渡すものもある.これにより,Javaへコールバックしてこの付加的な値を得る手間が省ける.
MathListenerオブジェクトのメソッドに関連したWolfram言語の関数を指定するためには,このオブジェクトのsetHandler()メソッドを呼び出す.setHandler()は2つの文字列を取る.最初のものはイベントハンドラメソッドの名前(「actionPerformed」や「keyPressed」等),そして2つ目は応答として呼び出されるWolfram言語の関数である.Wolfram言語の関数は「myButtonFunction」のような名前あるいは純関数(文字列として指定)である.リスナインターフェースで実際のJavaメソッドの名前を与えるのは,リスナの多くが複数のメソッドを持っているからである.もしハンドラが正しく設定されていたらsetHandler()はTrueを返し,そうでない場合(名付けたメソッドが正しく綴られていない場合等)はFalseを返す.
obj@setHandler["methodName","funcName"] | |
MathListenerオブジェクト obj のイベントハンドラメソッドmethodName()が呼び出されたときに呼び出されるWolfram言語関数を設定する. |
イベント通知に応答して呼び出されるWolfram言語の関数の割当て
これらのクラスの使用法については,以下のモーダルおよびモードレスウィンドウの簡単な例と,「簡単なモーダル入力ダイアログ」および「ピアノのキーボード」のより詳細に渡る例を参照のこと.
ユーザアクションによって引き起されるWolfram言語の呼出しを行うために J/Link のMathListenerクラスを使う必要はない.これは,便利なように提供されている.自分でクラスを書いてイベントを扱い,コードに直接Wolfram言語の呼出しを入れることができる.J/Link のMathListenerクラスは,MathListenerと呼ばれる抽象基底クラスに由来している.MathListenerのコードはWolfram言語とのインタラクションの詳細すべてを取扱い,イベントとWolfram言語コードとを関連付けるのに使うsetHandler()メソッドも提供する.自分でMathListenerスタイルのクラス(例えば,J/Link が提供しないSwing特有のイベントリスナインターフェース)を書く場合は,すべての機能を継承するために,そのクラスをMathListenerのサブクラスにするとよい.MathListenerから派生した具象クラス(おそらくMathActionListener isが最も簡単なもの)のソースコードを調べて,どのようにそれが書かれているか調べるとよいであろう.それを自分の実装の出発点にすることができる.自分のクラスをMathListenerのサブクラスにせず,代りにWolfram言語を呼び出すイベントハンドラコードを書く場合は,1.2.18の「自分のイベントハンドラコードを書く」を必ず参照いただきたい.
Javaウィンドウを前面に持ってくる
Wolfram言語プログラムでJavaウィンドウを生成している場合,作業中のノートブックの前面にポップアップさせてその存在がはっきり分かるようにしたいことがある.このためにはJavaのWindowクラスのtoFront()メソッドが使えるように思えるが,Macintoshでは動作せず,WindowsでもJavaランタイムによって多少異なった動作をする.この違いがあるため,すべてのプラットフォームやJavaバーチャルマシンで,Javaウィンドウが他のすべてのウィンドウの前に同じように見えるようにするWolfram言語プログラムを書くのは困難である.
この問題を回避するため,J/Link はすべての環境設定で適切なステップを実行するWolfram言語関数のJavaShowを提供している.window@setVisible[True],window@show[],window@toFront[]の代りにJavaShow[window]を使わなければならない.すべてのプログラムの例題で,JavaShowが使われている.JavaShowの引数はトップレベルのウィンドウを表示することのできるクラスのインスタンスであるJavaオブジェクトでなければならない.中でも,java.awt.Windowクラス,あるいはそのサブクラスでなければならない.これにはAWT Frame,Dialogウィンドウ,トップレベルのウィンドウに使われるSwingクラス(JFrameとJWindowとJDialog)が含まれる.
JavaShow[windowObj] | 指定のJavaウィンドウが見えるようにし,ノートブックウィンドウを含むどのウィンドウよりも前に持ってくる |
モーダルウィンドウ
以下は簡単な「モーダル」ウィンドウの例である.ウィンドウにはボタンとテキストフィールドが含まれている.テキストフィールドは値1の表示から始まり,ボタンがクリックされるたびに値が1つずつ増える.それを包囲するウィンドウにはcom.wolfram.jlink.MathFrameクラスを使う.MathFrameは,クローズボックスがクリックされるとdispose()を呼び出すjava.awt.Frameの単純な拡張機能である(標準Frameクラスは何もしない).
ここで,左側にボタン右側にテキストフィールドのある小さいフレームが現れる.ボタンにラベルを付け,フィールドの最初のテキストを設定する.
テキストフィールドの値を増やすようにする動作をボタンに加える.ボタンはActionEventを発生させるので,MathActionListenerのインスタンスが必要になる.
addActionListenerを呼び出して,ボタンに登録しなければならない.
この時点では,++ボタンをクリックしたら,MathActionListenerのactionPerformed()メソッドが呼び出される(ここではまだボタンをクリックしないこと).既出のMathListener表から,actionPerformed()メソッドが2つの引数を取るユーザ定義のWolfram言語の関数を呼び出すことが分かる.この2つの引数とは,ActionEventオブジェクトと,イベントのgetActionCommand()メソッドの結果の整数値である.
まだactionPerformed()メソッドによって呼び出されるユーザ定義のコードを設定していないが,setHandler()メソッドを使うと,すべてのMathListenerクラスについて設定することができる.このメソッドは2つの文字列を取る.1つ目はイベントリスナインターフェースのメソッドの名前で,2つ目は呼び出したい関数の名前である.
ここで,buttonFuncを定義する必要がある.これは引数を2つ取るように定義しなければならないが,この例ではどちらも重要ではない.
まだボタンを試す準備はできていない.もし今ボタンをクリックしたら,Javaユーザインターフェーススレッドは停止してしまう.というのは,それはWolfram言語を呼び出してbuttonFuncを評価し,結果を待つが,カーネルがJavaリンクに入力が届くのを待っていないため結果が返ってこないからである.ここではカーネルを常にJavaリンクからの情報を読み込む状態にする方法が必要である.これにより,ウィンドウが「モーダル」,つまりウィンドウが閉じるまでカーネルが何もできない状態になる.このモーダルの状態を実装する関数がDoModalである.
DoModal[] | カーネルの注意がJavaリンクにのみ向けられている状態にする |
EndModal[] | モーダル状態を終了してDoModal関数が戻るようにするためにJavaプログラムが呼び出さなければならないもの |
DoModalは,JavaプログラムがWolfram言語へコールバックしてEndModal[]を評価するまで返らない.DoModalが実行中は,カーネルはJavaから(例えば,MathListenerオブジェクトから)のコールバックを扱う準備ができている.Java側にEndModal[]を呼び出させるには通常,MathListenerを使う.例えば,ウィンドウにOKとCancelボタンがあるとすると,これらはウィンドウを閉じなければならないので,MathActionListenerオブジェクトを作成し,それをこの2つのボタンに登録しなければならない.このMathActionListenerオブジェクトはactionPerformed()メソッドでEndModal[]を呼び出すように設定される.
DoModalはEndModal[]を呼び出すコードのブロックが返すものは何でも返す.通常はこの戻り値で,どのようにウィンドウを閉じるか(例えば,OKボタンか,Cancelボタンか)を決める.それから,適切なアクションを取ることができる.DoModalの戻り値の使用方法の例は「簡単なモーダル入力ダイアログ」を参照のこと.
この例では,ウィンドウを閉じるにはクローズボックスをクリックするしかない.クローズボタンのクリックでwindowClosingイベントが発生するので,MathWindowListenerを使って通知を受ける.
クローズボックスがクリックされたら呼び出されるWolfram言語の関数を指定する.EndModal[]を呼び出すだけでよいので,引数を無視しEndModal[]を実行する以外は何もしない純関数が指定できる.
以上は,ウィンドウのクローズボックスがクリックされたときにMathWindowListenerを使ってEndModal[]を呼び出すよい例である.明示的なCloseボタンが必要な場合は,MathActionListenerを使うことを除いてこれと似たものを使用する.しかし,これにはもっと簡単な方法がある.MathFrameクラスは,クローズボックスがクリックされたときにdispose()を呼び出す以外は通常のAWT Frameであることは先に述べたが,これ以外に便利な属性がもう1つある.クローズボックスがクリックされると,EndModal[]を実行することもできるのである.従って,MathFrameをインターフェースのトップレベルのウィンドウクラスとして使うと,毎回モーダルループを終了するのにMathWindowListenerを手動で作成する必要がなくなる.このMathFrameの動作を可能にするには,setModalメソッドを呼び出さなければならない.
DoModalを使っていないなら,setModalを呼び出してはならないい.というのはsetModalが呼び出された後,MathFrameは閉じられるときにWolfram言語を呼び出そうとするので(EndModalを実行するため),Wolfram言語はJavaからの呼出しを待っている状態でなければならないからである.作成したどのMathListenerについても同じ問題が存在する.
これですべての準備が整ったので,モーダル状態に入ってウィンドウを使うことができる.
ウィンドウを使い終わったら,フレームのクローズボックスをクリックする.これによりEndModal[]を呼び出すWolfram言語へのコールバックが起る.DoModalが戻り,カーネルはフロントエンドから使用する準備ができる.MathFrameのクローズボックスをクリックしたら,DoModal[]はNullを返す.
この例を1つのプログラムにパッケージすると,次のようになる(SimpleModalのコードはJLink/Examples/Part1ディレクトリのSimpleModal.nbというファイルにある).
DoModalはJava側がEndModalを呼び出すまで戻らない.DoModalを呼び出すときは,Java側でEndModalの呼出しを引き起す方法がすでに確立しているかどうかに少し気を付けなければならない.通常は前述のように,フレームウィンドウとしてMathFrameを使ってそのsetModalを呼び出したり,ユーザアクション(OKあるいはCancelボタンをクリックすること等)に反応してEndModalを呼び出すMathListenerを生成し,登録 たりすることにより確立する.DoModalが一旦実行されると,カーネルはフロントエンドに反応しなくなるので,何かを設定するには遅すぎる.
直接は J/Link に関連していないが,SimpleModalのコード内の小さな点にお気付きだろうか.buttonListener@setHandlerを呼び出す行では,ボタン関数の名前を直接的な文字列"buttonFunc"としてではなく,ToString[buttonFunc]として渡すという点である.これはbuttonFuncがModuleのローカル名で,その実名はbuttonFuncではなく,例えばbuttonFunc$42のようなものだからである.確実に実際のランタイム名を捕えるために,記号的な名前のToStringを呼び出すのである.buttonFuncという名前をModuleのローカルにしなければこれを避けることができるが,ここで行った方法を使うと,Moduleが終了したときにbuttonFuncの定義を自動的にクリーンアップすることができる.
MathFrameとMathJFrame
ここでMathFrameクラスが出てきたが,これは3つの特殊な属性を持っているので,J/Link プログラマーにとって便利なトップレベルのウィンドウクラスである.その3つの属性うちの2つはすでに説明してある.閉じられたときに,dispose()を呼び出すということと,DoModalとの使用においてそのクラスをサポートするsetModal()メソッドを持つというものである.3つ目の属性は,ウィンドウが閉じられたときに実行されるWolfram言語コードを指定するために使うことができるonClose()メソッドを持つというものである.onClose()メソッドは「フロントエンドの共有:パレットタイプのボタン」のパレットの例で使われている.J/Link にはSwing JFrameクラスのサブクラスであるMathJFrameクラスもあり,このクラスにも3つの特殊な属性がある.AWTコンポーネントの代りにSwingコンポーネントでインターフェースを作成したいプログラマーは,MathJFrameをトップレベルのウィンドウクラスとして使用することができる.
モードレスウィンドウ:カーネルをJavaと共有する
上ではJavaウィンドウを表示する J/Link プログラムの書き方と,DoModal関数を使ってウィンドウが閉じられるまでカーネルを待たせる方法を例示した.DoModalの実行中に,カーネルはJava側からの演算の要求を受け取り,処理することができる.このコンテキストでは,「モーダル」という言葉は,カーネルがJavaリンクのサービスに従事しているため,DoModalが戻るまでノートブックフロントエンドはカーネルを使用できないということを意味する.
この処置はJavaウィンドウの多くでうまく働く.カーネルはウィンドウが閉じられるまで先に進めないので,これはWolfram言語に結果を返すウィンドウには必要なものである.しかし残念ながら,ユーザインターフェース要素の大規模クラスには限定的すぎる.例えば,フロントエンドのパレットウィンドウの概念をJavaにコピーしようとしているとする.クリックしたらWolfram言語での演算が始まるボタンのウィンドウが必要であろう.フロントエンドパレットウィンドウのように,このウィンドウがいつまでも表示され,アクティブであり続けるようにしたいと思うであろう.ボタンをクリックしたいときはいつも最初にDoModal[]を実行しなければならない(そしてそれぞれのボタンが,引き起す演算の一部としてEndModal[]を呼び出すような処置もしなければならない)としたら,あまり便利なものではない.いちいち手動でカーネルを特別な状態にしたりそれを解除したりする必要なく,フロントエンドのノートブックウィンドウとJavaウィンドウの間を往復できるようになればよいと思うことであろう.
ノートブックフロントエンドのリンクに加え,Javaリンクから届く入力にもカーネルが自動的に注意を払うようにすることが必要になる.ここにあるのは,カーネルの注意を得ようとして張り合う2つのフロントエンドである.J/Link はカーネルがいくつのリンクの入力でも同時に聞ける状態になる簡単な方法を導入することによりこの問題を解決する.これを実行する関数がShareKernelである.
注意:Mathematica 5.1以降では,カーネルは常にJavaと共有されるようになっている.従って,関数のShareKernelとUnshareKernelは必要ではなくなり,また,全く動作しなくなった.Mathematica 5.1以降でしか実行する必要のないプログラムでは,ShareKernelとUnshareKernelを呼び出す必要はない(ShareFrontEndとUnshareFrontEndはまだ利用できる).Wolfram言語の全バージョンで動作しなければならないようなプログラムの場合は,以下のようにして,ShareKernelおよびUnshareKernelを使う必要がある.
ShareKernel[] | Javaとカーネルの共有を始める |
ShareKernel[link] | link とカーネルの共有を始める |
UnshareKernel[id] | id を返す共有要求を取り消す.カーネル共有は他に未解決の要求があればオフにならない |
UnshareKernel[link] | link とのカーネルの共有を終了する |
UnshareKernel[] | Javaとのカーネルの共有を終了する |
KernelSharedQ[] | カーネルが現在共有されていればTrue,さもなければFalse |
SharingLinks[] | 現在カーネルを共有しているリンクのリスト |
ShareKernelはLinkObjectを引数として取り,そのリンクと現在の$ParentLink(通常,ノートブックフロントエンド)との間でカーネルの共有を開始する.引数なしでShareKernelを呼び出すと,Javaへのリンクと見なされる.大部分のユーザは引数なしで呼び出す.
カーネルが共有されている間,入力のプロンプトには「(sharing)」が付加される.付加される文字列はShareKernelのSharingPromptオプションにより指定される.
共有はユーザに透過的である.入力プロンプトの変化の他には,何も変化を示すものはない.フロントエンド,あるいはJavaプログラムからカーネルに送られた入力は,評価され,入力を送ったプログラムに結果が送り返される.カーネルがリンクから届いた入力を計算している間,そのリンクはカーネルの$ParentLinkになる.つまり,ShareKernelは入力が届くたびに,リンク間で$ParentLink値をあちこちに動かすのである.
カーネルがすでに共有されていたらShareKernelを呼び出してもよい.ユーザがすでに共有を始めたかもしれないと心配することなく,作成中のプログラムでカーネルが呼び出せるということである.Javaとカーネルを共有する必要がなくなったら,UnshareKernelを呼び出すことができる.これにより,カーネルは通常の操作モードに戻り,フロントエンドにだけ注意を払うようになる.
引数なしで呼び出された場合,UnshareKernelは共有をシャットダウンする.共有を必要とするJavaベースのプログラムが他にも起動しているかもしれないので,ほとんどの場合これは望ましいことではない.他の人が使うためのコードを書く場合,ただ自分のコードが共有の必要がなくなったからといってユーザとの共有をシャットダウンすることは決してできない.この問題を解決するために,ShareKernelは共有機能の要求を反映するトークン(単なる整数であるが,その表示について心配する必要はない)を返す.つまり,ShareKernelは共有の要求を登録し,まだオンになっていなければオンにし,その要求を提示するトークンを返す.UnshareKernelを呼び出すと,その共有の要求を「登録解除」するためにそのトークンが渡される.他の未解決の要求がない場合にのみ,共有は実際にオフになる.
ShareKernelの特殊なところは,ShareKernelとUnshareKernelを同じセル内で呼び出すことができないという点である.もしこれをすると,カーネルが停止してしまう.もちろん,カーネル共有は複数の評価に渡るとき(厳密にいえば,複数セルの評価)に関係があるだけなので,この操作をする理由はない.1つの演算のスコープ内では共有をオンにしたりオフにしたりする必要はない.
ShareKernelを使う重要なユーザインターフェースの例は「Real-Time Algebra:ミニアプリケーション」にある.
フロントエンドの共有
J/Link の目的のひとつはJavaユーザインターフェース要素をノートブックやパレットのような,第一級のノートブックフロントエンド環境にできるだけ近付けることである.カーネル共有機能は,Javaランタイムが別のプログラムであり,カーネルは通常フロントエンドからの入力だけを待つという事実を隠して,ノートブックフロントエンド環境の重要な一面を模倣するものである.
パレットからできる機能のひとつで,Javaからでもできると便利なフロントエンドとインタラクトする重要な機能がもうひとつある.クリックするとPrint["hello"]コードを評価するパレットボタンを作成することができる.これは J/Link でも簡単にできるが,大きな違いが1つある.それは,パレットボタンをクリックしたときにhelloはアクティブなノートブックに現れるが,Javaボタンをクリックすると「hello」はJavaプログラム(要するに,その時点でのカーネルの$ParentLink)に送り返される点である.Javaリンクではなくフロントエンドリンクに「hello」を含むTextPacketをカーネルに書き込ませようとしても,フロントエンドは演算の結果を待っていないときはカーネルリンクに注意を払っていないので何も起らない.フロントエンドが休んでいるときに出力を求めても無駄である.
J/Link はこの問題の解決策としてShareFrontEnd関数を提供する.ShareFrontEnd[]はPrint出力やJavaユーザインターフェース要素によって生成された画像が,フロントエンドで見えるようにする.また,Java側でノートブックの要素を操作するWolfram言語の関数を呼び出したり,Wolfram言語の関数(例えば,NotebookRead,NotebookWrite,SelectionEvaluate等)をフロントエンドで適切に動かしたりすることができるようになる.共有中もフロントエンドは通常に動き,編集,計算その他何にでも使い続けることができる.共有は透過的である.
ShareFrontEnd[] | Javaとのフロントエンドの共有を始める |
UnshareFrontEnd[id] | id を返す共有の要求(ShareFrontEndの呼出し)の登録を解除する.フロントエンド共有はほかに未解決の要求があればオフにならない |
UnshareFrontEnd[] | Javaとのフロントエンド共有を終了する |
FrontEndSharedQ[] | フロントエンドがJavaと共有されていればTrue,さもなければFalse |
ShareFrontEndは現在リモートカーネルでは動かない.同じマシンでカーネルとフロントエンドを起動していなければならない.
ShareFrontEndは,Javaユーザインターフェースがあたかも特殊タイプのノートブックウィンドウであるかのように,ノートブックフロントエンドで直接ホストされるのにかなり近いものである.この種の強固な統合が将来的に可能になることが期待される.
モーダルのJavaウィンドウで生成されるPrint出力,グラフィックス,メッセージは,ShareFrontEndを呼び出さなくてもフロントエンドに表示される.これは$ParentLinkがDoModalの間フロントエンドリンクに残っているためである(これら「二次的」パケットは常に$ParentLinkに送らる).また,フロントエンドは演算の途中であり,DoModalを呼び出したコードの結果を待っているので,カーネルから届くさまざまなパケットを処理することができるためでもある.ShareFrontEndはShareKernelでモードレスインターフェースを作成する能力を得たときに失われた機能を再生する方法である.ShareFrontEndは,Javaで引き起される演算により作成される二次的な出力がノートブックフロントエンドに現れるようにする,ShareKernelを超えたステップとして考えることができる.ShareFrontEndは,たとえそのコードがShareFrontEndはの特別な機能を必要としない場合でも,ShareKernelを使う必要のあるコードの開発中に特に便利である.というのは,Javaイベントによって喚起される演算で生成されるWolframシステムエラーメッセージはShareKernelとともになくなってしまうからである.メッセージはフロントエンド共有がオンになっていればフロントエンドに現れる.
フロントエンド共有の必要がなくなったらUnshareFrontEndを呼び出す.ShareKernel/UnshareKernelの関数ペアのように,ShareFrontEndはフロントエンド共有の要求を登録解除するために,UnshareFrontEndに送らなければならないトークンを返す.ShareFrontEndへのすべての呼出しがUnshareFrontEndの呼出しで登録解除されたときのみ,フロントエンド共有はオフになる.引数なしでUnshareFrontEndを呼び出すことで直ちにフロントエンド共有をシャットダウンすることができるが,この方法が自分のコードの開発中に便利だからといって,他の人が使用することを意図したコードで呼び出してはならない.というのは,自分のコードがフロントエンド共有を終えたからといって,ユーザもそうだとは限らないからである.代りにShareFrontEndから返されたトークンを保存し,UnshareFrontEndに渡す.
ShareFrontEndはカーネルの共有を要求するので,ShareKernelを内部的に呼び出す.引数なしでUnshareKernelを呼び出すと,カーネルの共有は直ちにストップし,同時にフロントエンド共有もオフになる.このように,すべての共有を直ちにシャットダウンする素早いショートカットとしてUnshareKernel[]を使うことができる.
ShareFrontEndを使う簡単なパレットタイプボタンの例は「フロントエンドの共有:パレットタイプのボタン」にある.
ShareFrontEndの重要な使い方のひとつに,Javaのポップアップユーザインターフェースにタイプセット式を含む画像が表示できるようにするというものがある.カーネルがタイプセット式を含む画像(例えばPlotLabel->Sqrt[z]のプロット)を作成するよう指示を受けたら,プロットのためにはPostScriptを生成するが,タイプセットラベルのためのPostScriptの生成はできない.そこで,フロントエンドに特別な要求を送り返してPostScript表示を求める.タイプセット式の取扱いはノートブックフロントエンドだけにしかできなので,他のインターフェースがカーネルを駆動しているとき,インターフェースはカーネルに,画像中の何もタイプセットしないように指示するよう注意している(ShareKernelが自動的にこれを扱う).これはうまくいくが,Javaインターフェースでのタイプセット式の画像を得ることはできなくなる.
ShareFrontEndにはこの制限を取り払うために2つのことを実行する.1つは,Javaランタイムはノートブックフロントエンドであり,「PostScriptへの変換」の特別な要求を扱う能力があるとカーネルに思わせることである.もう1つは,その要求をフロントエンドにフォワードすることによりこの約束を果たす能力をJavaに与えることである.「GraphicsDlg:ウィンドウへのグラフィックスとタイプセット出力」 では,ShareFrontEndを使ったタイプセット式を表示するJavaダイアログボックスの例を説明する.
モーダル操作とモードレス操作のまとめ
以上のモーダルおよびモードレス操作,ShareKernel,ShareFrontEndの説明は複雑だったかもしれないが,実際は,この技術の原理と使用は単純なものである.もっと例題を見てみるとそのことがはっきりする.「例題プログラム」のプログラム例題の多くはShareKernelかShareFrontEndを使っている.それらが提供する能力を理解して,作成するプログラムでどのように利用するかを考え始めることが大切である.
ユーザがカーネルを終了するまで,ユーザインターフェース要素(通常ウィンドウ)をカーネルにつないでおきたい場合は,setModal/DoModal/EndModalを使う.モーダル状態の内部的な働きはモードレス状態よりも単純なので,モードレスウィンドウの特徴を必要としないプログラムにはモーダルを使うべきである.ユーザが値を入力してOKをクリックするダイアログボックスの作成中等,実行中のWolfram言語プログラムに結果を返す必要がある場合は,いつもこのタイプのウィンドウを使うとよい.「簡単なモーダル入力ダイアログ」にこのタイプのダイアログの例が挙げてある.
ユーザがフロントエンドでの作業に戻っている間,ウィンドウを可視状態でアクティブにしておきたい場合は,ウィンドウを「モードレス」で起動しなければならない.これには,カーネルがノートブックフロントエンドとJavaのどちらから届く入力も同時に受け入れる状態であるよう,ShareKernelの呼出しが必要である.この時点では,カーネルの注意は2つの独立した,実質的に等価のフロントエンドに分けられている.この状態の欠点(見方によっては特徴)は,Javaコードで生成されるPrint出力,メッセージ,プロットのようなすべての二次的な出力がフロントエンドではなく,Javaに送られるということである(そして,標準MathListenerクラスはこれらすべての出力を捨て去る).従って,フロントエンドのパレットボタンのように,クリックしたときにノートブックウィンドウに何かを出力するボタンを作成することはできない.ノートブックやパレットウィンドウで行うように,Javaプログラムにフロントエンドとインタラクトする機能を与えたい場合は,ShareKernelの拡張機能と考えられるShareFrontEndを使う.
Javaウィンドウを生成し,あるイベントでWolfram言語へコールバックするMathListenerクラスをつなぎ合わせ,DoModalやShareKernelを呼び出す前にイベントを引き起すというのはよくある間違いである.これにより,Javaユーザインターフェーススレッドが停止してしまう.UIスレッドが停止する兆候は,Javaウィンドウでのコントロールが視覚的に反応がない(例えば,ボタンをクリックしても押されたように見えない)ということである.もしこのような状態になってしまったら,ShareKernelを呼び出して,Javaからの待ちの呼出しが続行できるようにする.
「マニュアル」インターフェース:ServiceJava関数
前述のモーダルおよびモードレスタイプのインターフェースに加え,いくつかの点でその中間的な別のタイプのインターフェースがある.次のシナリオを考えてみる.Javaウィンドウを表示するWolfram言語プログラムを作成し,そのウィンドウにプログラム使用中に変化するものを表示したいとする.今のところ,これは「非インタラクティブ」インターフェースの例のようである.これについてはこのセクションの冒頭ですでに説明してあり,プログレスバーがその代表的な例であると述べた.しかし,ここではウィンドウにインタラクティブな要素を加えたいとする.つまり,ウィンドウでのユーザアクションでWolfram言語を呼び出したいので,プログレスバーの例のように,プログラムを終了する「Abort」ボタンを加えたいとする.JavaイベントがWolfram言語への呼出しを誘発するようにするには,どのようにカーネルの注意をJava側に向ければよいであろうか.
モーダル状態ではカーネルは演算ではなく,DoModalを実行しているので,モーダルタイプのインターフェースではうまくいかない.つまり,カーネルはただJavaに注目しているだけなのである.モードレスタイプのインターフェースでもうまくいかない.というのは,モードレスではカーネルはフロントエンドとJavaとに交互に注目し,それぞれに交代で完全な演算を実行させるからである.1つの演算のコンテキスト内での共有はない.
カーネルがJavaから届く1つの演算をサービスできるようにする関数が必要になるということは明白である.その関数がServiceJavaである.プログラムでServiceJavaを呼び出すと,カーネルはJava側から演算の要求を1つだけ受け入れる.カーネルは演算を実行し,プログラムにコントロールを返す.要求がない場合は,ServiceJavaは直ちに戻る.
以下は,「Abort」ボタンを持つプログレスバーを表示し,ユーザのボタンクリックを処理するためにServiceJavaを定期的に呼び出し,要求されたら演算を中止するプログラムの構造を示す擬似コードである.
... プログレスバーを作成する ...
progressBar@addActionListener[
JavaNew["com.wolfram.jlink.MathActionListener", "(userCancelled = True)&"]
];
JavaShow[progressBar];
While[i < 100 && !userCancelled,
... 反復を1回計算する ...
... プログレスバーをアップデートする ...
ServiceJava[];
i++
];
... プログレスバーを破棄する ...
ServiceJavaはDoModalに密に関連してる.これが実際の実装ではないが,下記のようにDoModalをServiceJavaで書けるものと考えることができる.
このように見ると,DoModalはServiceJavaの特殊な使用方法で,ここではWolfram言語はJavaからの要求のサービスしかしない.Wolfram言語で他のことをする必要がある場合でも,Javaから届く要求を処理することができなければならない.そのようなときに,ServiceJavaを呼び出す.DoModalのように,ServiceJavaが呼び出されると,$ParentLinkのシフト操作はない.従って,Javaの演算で起る,画像,メッセージ,Print出力のような二次的な出力が,あたかもServiceJavaを呼び出すWolfram言語プログラムにハードコードされているかのように,ノートブックに現れるのである.
「BouncingBalls: ウィンドウへの描画」で示されているBouncingBallsの例では,ServiceJavaを使っている.
GUIビルダを使う
モーダルとモードレスインターフェースについての以上の説明では,完全にWolfram言語コードで作成された例題を使用した.複雑なユーザインターフェースでは,ほとんどの市販のJava開発環境にある,ドラッグアンドドロップのGUIビルダでウィンドウをレイアウトし,イベントをつなぎ合わせる方が便利かもしれない.ネイティブJavaでインターフェースのコードを書きたいだけ書くこともできれば,書く量をできるだけ少なくすることもできる.GUIのイベントでWolfram言語を呼び出したければ,Wolfram言語コードから使うのと同様に,JavaコードからどのMathListenerクラスでも使うことができる.または,適時にWolfram言語を呼び出すJavaコードを書くこともできる.Wolfram言語へコールバックするJavaコードの書き方は「インストール可能なJavaクラスを書く」を参照のこと.「GraphicsDlg:ウィンドウへのグラフィックスとタイプセット出力」では,GUIビルダで作成されWolfram言語コードで起動およびコントロールされるダイアログボックスの簡単な例を挙げている.
Java ウィンドウでWolfram言語の画像を描画,表示する
MathCanvasクラスとMathGraphicsJPanelクラス
J/Link を使うと簡単にWolfram言語からJavaウィンドウに描画したり,Wolfram言語のグラフィックスやタイプセット式を表示したりすることができる.MathCanvasクラスとMathGraphicsJPanelクラスはこの目的のために提供されている.「Wolfram言語を使うJavaプログラムを書く」で説明するように,Wolfram言語カーネルを使う純粋なJavaプログラムでこの2つのクラスを使うことができるが,Wolfram言語で作成され,書かれたJavaウィンドウにも便利である.MathGraphicsJPanelクラスは J/Link 2.0で新しく加わったクラスである.
MathCanvasはAWT Canvasクラスのサブクラスで,MathGraphicsJPanelクラスはSwing JPanelクラスのサブクラスである.特別に加わったWolfram言語のグラフィックス機能という点では,この2つは同じといえる.これらのクラスは,2種類の方法で画像を表示する.最初の方法は,出力を表示するWolfram言語コードの一部を提供することである.出力はグラフィックスオブジェクトでも,あるいはタイプセットされる非グラフィックスオブジェクト式でも構わない.これにより,JavaウィンドウでのWolfram言語のグラフィックスやタイプセット式の表示が簡単になる.表示をコントロールする2つ目の方法は,描画したいJava Imageオブジェクトを提供することである.このImageは通常,Wolfram言語の生データからビットマップを作成するコード,あるいはJavaのグラフィックスルーチンへの呼出しを使用して何かを描画するコードのようなWolfram言語コードによって作成される.
MathCanvasとMathGraphicsJPanelは共にJavaクラスであり,Wolfram言語プログラムからでもJavaプログラムからでも使えるので,JLink/Documentation/JavaDocディレクトリにこの2つのクラスについてのJavaDoc形式の完全ドキュメントが含まれている.詳細は,そのドキュメントに記載されている.
Wolfram言語のグラフィックスとタイプセット式の表示
これはWolfram言語プロットを示すウィンドウを表示する簡単な例である.以下の例ではMathCanvasを使っているが,関係のある部分はMathGraphicsJPanelを使っても同様になる.ここではずっとこのウィンドウを使うので,コードの評価をする場合は,ウィンドウを閉じないようにしなければならない.
これはキャンバスのsetMathCommand()メソッドを呼び出すのと同じくらい単純である.setMathCommand()の引数は評価されるコードを与える文字列である.このコードはグラフィックス式を作成するだけでなく,返さなければならない.例えば,setMathCommand["Plot[x,{x,0,1}];"]は動作しない.それは最後のセミコロンにより,式がNullと評価されるからである.画像は自動的に正しいサイズで描画され,Wolfram言語によって作成された実際の画像サイズが希望の範囲を完全に満たしていないなら(タイプセット出力によくあるように),キャンバスの中心に置かれる.
setMathCommand()の呼出しで,再び画像がリセットされる.
プロットのコマンドがWolframシステムセッションの変数に依存する場合は,recompute()を呼び出してグラフィックスを再計算し描画することができる.例えば,これはウィンドウでゆっくりとしたアニメーションを表示する.
文字列として式を与えるときは,文字列内のクォーテーションマークはバックスラッシュでエスケープする.
MathCanvasはタイプセット式も表示できる.MathCanvasはデフォルトで,setMathCommand()に与えられた式の評価の結果はグラフィックスオブジェクトになり描画されるものと想定する.setMathCommand()の戻り値をタイプセット表示するためには,定数TYPESETを与えて,setImageType()メソッドを呼び出す.
グラフィックス表示に切り替えるには,mathCanvas@setImageType[MathCanvas`GRAPHICS]を呼び出す.タイプセット出力のデフォルト形式はStandardFormであり,TraditionalFormへの変換には,setUsesTraditionalForm()メソッドを使う.出力タイプを変えても画像は強制的に再描画されるわけではないので,ここでrecompute()を呼び出す.
グラフィックスはWolfram言語のDisplayコマンドを使って描画される.このコマンドは速く,実行にノートブックフロントエンドの起動を必要としない.しかし,特に3Dグラフィックスのような高質のものには,描画のサービスのためにフロントエンドを使う別のメソッドもある.setUsesFE()メソッドを呼び出すと,この方法が使えるようになる.
setUsesFE[True]とsetUsesFE[False]でのプロットの結果を比べてみる.
描画にフロントエンドを使う重要なポイントは,画像を作成する演算が実行されるとき,フロントエンドはカーネルからのサービスの要求を受け入れる状態になければならない点である.これには,2つの場合が考えられる.1つ目はフロントエンドのセルが現在評価中(Wolfram言語プログラムからsetMathCommand()かrecompute()を呼び出しているとき)である場合で,2つ目はShareFrontEndが呼び出された場合である.別の角度から見ると,動作しないのはShareKernelが使用中であるがShareFrontEndは使用中ではなく,演算がJavaのイベントで始まるときである.描画にフロントエンドを使用し,モードレスインターフェースのユーザアクションの応答でJavaからsetMathCommand()かrecompute()を呼び出したい場合は,ShareFrontEndを使う必要がある.ShareKernelでは十分ではない.モーダル,モードレスインターフェース,ShareFrontEndについては「ウィンドウやその他のユーザインターフェース要素の作成」で説明してある.
Javaのグラフックス関数を使用した描画
前述のように,MathCanvasクラスとMathGraphicsJPanelクラスのsetMathCommand()メソッドを使うと,Wolfram言語式の出力が表示できる.MathCanvasあるいはMathGraphicsJPanelのsetMathCommand()の代りにsetImage()メソッドを使っても,Java Imageを表示することができる.
Wolfram言語からJavaウィンドウに描画する簡単な例を見てみる.今まで使ってきたのと同じウィンドウとMathCanvasを使う.このプログラムで,代りにMathGraphicsJPanelを使ったとしても,コードの描画に関連した部分は全く同じである.MathCanvasに描画するには,同じサイズのオフスクリーン画像を生成し,その上に描画するグラフィックスコンテキストを得て描画し,MathCanvasのsetImage()メソッドを使ってそのオフスクリーン画像を表示する.オフスクリーン画像に描画してスクリーンにブリットするのは,ちらつきのない描画のための標準的なテクニックである.
Wolfram言語からJavaウィンドウに手動で描画するプログラムは,通常これと同じ構造である.MathCanvasを走り書き(Scribble)プログラムに変えるのに,ほんの数行のコードしか必要ない.以下は完全なプログラム例である(このコードはJLink/Examples/Part1ディレクトリのScribble.nbファイルとしても提供されている).
プログラムを実行して,マウスをクリックしたまま動かし,ウィンドウに描画する.プログラムの終了にはウィンドウを閉じる.Scribble関数が描画された点のリストを返す.
返された点のリストを見てみると,それがJavaの座標系,つまり左上に(0, 0)がある系に基づいていることが分かる.Wolfram言語グラフィックスで点をプロットしたい場合は, の値を逆にしなければならない.これについては,例題ノートブックのScribble.nbで例示している.
このプログラムには,新しいMathCanvasメソッドのrepaintNow()が例示してある.このような演算量の多いプログラムでは,イベントはユーザインターフェーススレッドで素早く起動され,これらのイベントのハンドラは実行するのにかなりの時間を必要とするので,Javaはウィンドウの再描画を遅らせることがある.描画は塊のようになり,しばらく視覚的変化はなく,それから突然最後の数秒で描画された線が現れる.新しいポイント毎に標準repaint()メソッドを呼び出しても,必ずしもウィンドウが適時にアップデートされる訳ではない.この問題を解決するためにrepaintNow()メソッドが提供されており,これはキャンバスの再描画を直ちに強制する.直ちに起動するユーザイベントからのスムーズな視覚的フィードバックにプログラムが依存している場合は,システムでは必要ないように見えてもrepaintNow()も呼び出さなければならない.スクリーンの更新メカニズムの反応性はプラットフォーム,Javaランタイム間で顕著な違いがある.
MathCanvasあるいはMathGraphicsJPanelのイベントに応答して描画する機能により,インタラクティブなデモンストレーション,チュートリアル等の可能性が開かれる.大規模なプログラム例の2つである「BouncingBalls:ウィンドウへの描画」と,「スパイログラフ」は,Wolfram言語からMathCanvasに描画するものである.
ビットマップ
オフスクリーン画像を使ったMathCanvasあるいはMathGraphicsJPanelへの描画方法はすでに説明した通りである.Wolfram言語コードを使って作成し,setImage()を使って表示することができるもう1つのタイプの画像はビットマップである.この例では,Wolfram言語データからインデックスカラービットマップを作成し,表示する.ここでは8ビットのカラーテーブルを使用する.つまり,画像のすべてのデータポイントは256要素の色リストのインデックスとして扱われる.もっと大きなカラーテーブルを使うこともできる.
上のScribbleの例ではフレームウィンドウを閉じたので,まずビットマップ用の新しいフレームとキャンバスを作成する.
以下はカラーテーブルである.これは{r,g,b}の3つ1組の配列で,それぞれの色要素は0…255の範囲内にある.この例では,インデックスが小さな数字の色は大体青で,大きな数字の色はほとんど赤である.
データは0…255の範囲(256要素のカラーテーブルのインデックスであるから)の400×400の整数の行列である.実際のアプリケーションでは,このデータはファイルから読み込まれるか,もっと洗練された方法で計算されることがある.データの数の範囲が0…255の範囲でなければ,その範囲になるようにし,より大きなカラーテーブルを使いたければもっと大きな範囲にスケールしなければならない.
ここでは,カラーモデルとビットマップを表すJavaオブジェクトを生成する.これらのクラスについての更なる情報は標準Javaドキュメントに記載されている.
Javaコンソールウィンドウ
J/Link はJavaの「コンソール」ウィンドウを表示する,便利な方法を提供する.標準的なSystem.outおよびSystem.errストリームに書かれるすべての出力がこのウィンドウに向けられる.System.outあるいはSystem.errの診断的な情報を書くJavaコードを呼び出しているなら,プログラムが実行している間にこの出力を見ることができる.ほとんどの J/Link の機能と同様に,コンソールウィンドウもWolfram言語もしくはJavaプログラムから簡単に使うことができる(Javaコードからの使用方法は「Wolfram言語を使うJavaプログラムを書く」で説明する).Wolfram言語から使うためには,ShowJavaConsole関数を呼び出す.
ShowJavaConsole[] | Javaコンソールウィンドウを表示し,System.outとSystem.errに書かれた出力を獲得し始める |
ShowJavaConsole["stream"] | Javaコンソールウィンドウを表示し,指定されたストリーム(System.outは"stdout",System.errは"stderr")に書かれた出力を獲得し始める |
ShowJavaConsole[None] | 出力の獲得を中止する |
出力の獲得はShowJavaConsoleを呼び出したときだけ始まる.従って,ウィンドウが最初に現れたときは,以前にSystem.outあるいはSystem.errに書かれた,いかなる内容も持っていない.J/Link コンソールウィンドウは J/Link JavaコンポーネントとJavaランタイムについてのバージョン情報を表示する.ウィンドウがすでに開いている状態でShowJavaConsoleを呼び出すとウィンドウが前面に現れる.
例を示す.Wolfram言語からある出力を書くことができる.上記のShowJavaConsole[]を実行すると,ウィンドウに「Hello from Java」と出力される.
このようにWolfram言語コードを使ってウィンドウに書き込む例を示すのは便利であるが,通常はJavaコードから行われる.実際には,Wolfram言語コードから書かれる診断的出力にJavaコンソールウィンドウを使うと非常に便利な場合が1つある.これは「モードレス」のJavaユーザインターフェース(「ウィンドウやその他のユーザインターフェース要素の作成」で説明した)があり,ShareFrontEnd関数をまだ使っていない場合である.この場合,Wolfram言語のPrintへの呼出しからの出力は,ノートブックフロントエンドには現れない.しかし,上記の例のように,System.outに書くと,いつも出力を見ることができる.出力のデバッグでノートブックをいっぱいにすることを避けるために,他の場合にもこのようにした方がよい.
JavaBeansを使う
JavaBeansはJavaのコンポーネントアーキテクチャである.Beansはビルダツールで視覚的に操作できる再利用可能な要素である.コードレベルでは,Beanは本質的には通常のJavaクラスで,そのメソッドがどのように指定されるか,またどのようにそれがイベントやパーシステンスをサポートするかに関しての特殊なデザインパターンに従う.
この時点までJavaBeansについて触れなかったのは,それについて特筆すべきことがないからである.Beansは単なるJavaクラスで,他のクラスのように使われたり呼ばれたりするだけである.明言されていなくても,Wolfram言語から使う多くのJavaクラスはBeansである.特にユーザインターフェースコンポーネントについてはそうである.
Beansは視覚的なビルダツールで使われるよう設計されており,プログラマーは直接コードを書いたり,指定されたメソッドを呼び出したりしない.その代り,Beansは「属性」をビルダツールに提示する.これはプロパティエディタウィンドウを使って検査・設定される.典型的な簡単な例として,任意のBeanがsetColorとgetColorいうメソッドを持っており,そのために「color」という名の属性を持っているとする.プロパティエディタは「color」の名を示すラインと色を入力できるエディットフィールドを持つ.その上,視覚的に望みの色が選べるような色選択ウィンドウを作る便利なエディタさえ持つかもしれない.
視覚的なビルダツール,あるいは他のタイプの自動操作のために,Beansは実際のメソッド名の低レベルの詳細を隠そうとする.Wolfram言語コードでBeanクラスのメソッドを呼び出したい場合,クラスの「Bean-ness」を考えることなく,いつもの方法で名前を使って呼び出すことができる.
Bean属性を明示的にサポートするWolfram言語の関数を J/Link に加えることは可能である.例えば,Beanオブジェクト,文字列としての属性名,属性を設定する値を取るBeanSetProperty関数を書くことができる.今までに要求されているもの
BeanSetProperty関数を使うと,Beanクラスで特定のメソッドを呼び出さずに属性という漠然としたものを操作するコードを書くことができる.BeanSetPropertyスタイルに特に長所を見出せない場合は,J/Link のこれらのラインに沿った特別なBeanサポートがなぜないかが分かるであろう.直接のメソッド呼出しの代りに属性を扱う長所は,ビルダツールを使っていて,実際に手でコードを書いていない場合にのみ生じる.
これがBeanSetPropertyとBeanGetPropertyの簡単な実装である.
JavaBeanが発生するイベントを利用するには,「ウィンドウやその他のユーザインターフェース要素の作成」で説明した標準MathListenerクラスのひとつを使うとよい.JavaBeansはしばしばPropertyChangeEventsを発生させるので,MathPropertyChangeListenerあるいはMathVetoableChangeListenerを使うことによって,Wolfram言語コードがこれらのイベントに応答して実行されるようにアレンジすることができる.
アプレットをホストする
J/Link を使うと,ほとんどのアプレットがWolfram言語から直接そのウィンドウで起動できる.作成された多数のアプレットを考えれば,これは非常に便利に見えるが,ほとんどのアプレットは便利なpublicメソッドをエキスポートしない.アプレットは一般的に,スタンドアロンの機能なので,J/Link が提供するスクリプト機能の恩恵を受けることはほとんどない.それでも,Wolfram言語プログラムから起動するのに便利なアプレットも数多くある.
ここで説明するのは,Wolfram言語カーネルを使用するアプレットを書くことについてではない.それに関しては「アプレットを書く」で説明する.
AppletViewer["applet class"] | 指定のアプレットクラスをそのウィンドウ内で起動する.デフォルトの幅と高さは300ピクセル |
AppletViewer["applet class",params] | 指定のアプレットクラスをそのウィンドウ内で起動し,HTMLページで使われるような「名前=値」指定のリストであるパラメータを与える |
J/Link にはアプレットの起動のためにAppletViewer関数がある.この関数は,アプレットインスタンスの作成,それを保持するフレームウィンドウの提供やその起動等,すべてのステップを扱う.AppletViewerの1つ目の引数はアプレットクラスの完全修飾名である.2つ目の引数はそれをホストしているHTMLページのアプレットへ与えられるパラメータに対応した,「名前=値」という形式でのパラメータのリストであり,これはオプショナルである.例えば,アプレットをホストしているWebページの<applet>タグが次のように見える場合
このようにAppletViewerを呼び出す.
アプレットウィンドウの幅と高さをコントロールするのに,少なくとも「WIDTH=」と「HEIGHT=」の設定を与える.このパラメータを指定しない場合,デフォルトの幅と高さは300ピクセルである.
Wolfram言語ユーザに便利なアプレットの例は,Martin Krausによって書かれたLiveGraphics3Dである.LiveGraphics3DはWolfram言語の3Dグラフィックスのインタラクティブなビューアである.それにより,画像を回転・ズームしたり,立体的に見たりすることができるようになる.下記の例題を試す場合は,LiveGraphics3Dの資料をhttp://wwwvis.informatik.uni-stuttgart.de/~kraus/LiveGraphics3Dからダウンロードする.例を試す前にlive.jarがCLASSPATHに置いてあることをご確認しなければならない.あるいはlive.jarを使用可能にするために J/Link のAddToClassPath機能を使う.
まず,PolyhedronOperations`パッケージをロードして表示するグラフィックスを作成する.LiveGraphic3Dのドキュメントでは,Wolfram言語のグラフィックス式を適切なLiveGraphics3Dアプレットへの入力に変換する,より一般的な目的の関数を提供しているが,多くの例題ではToString,InputForm,Nを使用すれば十分である.
グラフィックスのInputForm表示をする文字列を取るINPUTパラメータで,表示する画像を指定する.
Liveアプレットには画像操作のためのキーボードおよびマウスコントロールが数多くある.LiveGraphics3Dドキュメントでそれについて読むことができる.Alt+Sで立体視に変えてみる.
アプレットを使い終えたら,ウィンドウのクローズボックスをクリックする.
アプレットが他のファイルを参照する必要がある場合,AppletViewerがドキュメントベースを"user.dir"Javaシステム属性によって指定されるディレクトリに設定する.これは通常InstallJavaが呼び出されたときのWolfram言語の現行のディレクトリ(Directory[]によって与えられる)である.
ほとんどのアプレットは,Wolfram言語からコントロールするのに便利なpublicメソッドを示さないので,アプレットはAppletViewerで起動し,使い終えたらユーザがウィンドウを閉める他ない.Liveアプレットは例外である.それが完全なメソッドを提供するので,ビューポイント,スピン等がWolfram言語 コードで修正できるようになる.これらのメソッドはLiveクラスにあるので,それを呼び出すにはLiveクラスのインスタンスが必要である.先ほどのAppletViewerの使い方では,アプレットクラスのインスタンスを与えない.アプレットインスタンスの構築と破壊はAppletViewerの内部に隠されている.クラス名の代りにアプレットクラスのインスタンスでAppletViewerを呼び出すこともできる.これにより,アプレットインスタンスの寿命を管理することができる.
これで,アプレットインスタンスのメソッドを呼び出すことができる.LiveGraphics3Dドキュメントで完全なメソッド群を見てみる.このスクリプト機能はオブジェクトの「フライバイ」ビューのプログラミングや,画像をある方向やスピンにジャンプさせるボタンの作成等,多くの可能性を開く.
終わったら,ReleaseJavaObjectを呼び出してアプレットインスタンスを解放する.これはアプレットウィンドウが閉じる前でも後でも行える.
定期タスク
「ウィンドウやその他のユーザインターフェース要素の作成」ではShareKernel関数についてと,それによりJavaとノートブックフロントエンドがどのようにカーネルの注意を共有できるようになるかについて説明した.この機能を使うと,ユーザがセッション中に定期的にWolfram言語プログラムを実行するよう任意にスケジュールできる方法を簡単に提供できるようになる.例えば,継続的に最新の金融データを提供するソースがあり,Wolfram言語の変数が現在の値を反映するようにしたいとする.情報を得るために出て行ってソースを読み込むプログラムを書いても,作業中ずっとこのプログラムを手動で起動しなければならない.15秒ごとにソースからデータを引き出して変数を設定する定期的なタスクを設定すると,その必要はなくなる.
AddPeriodical[expr,secs] | カーネルがアイドルの間,secs 秒ごとに expr を評価させる |
RemovePeriodical[id] | id で指定される定期スケジュールを中止する |
Periodical[id] | id で指定される定期スケジュールに関連した式や時間の間隔を示すリスト{{HoldForm[expr],secs}}を返す |
Periodicals[] | 現行の定期スケジュールすべての id 番号のリストを返す |
SetPeriodicalInterval[id] | id で示される定期タスクの間隔を再設定する |
$ThisPeriodical | 現在実行中の定期タスクの id を保持する |
AddPeriodical関数を使ってそのようなタスクを設定することができる.
AddPeriodicalはタスクを識別するのに使用しなければならない(例えば,RemovePeriodicalを呼び出すことでそのスケジュールをやめるとき)整数のID番号を返してくる.AddPeriodicalはカーネルの共有に依存しているので,まだShareKernelが呼び出されていなければ,それを呼び出す.設定できる定期タスクの数に限りはない.
上記のタスクをスケジュールした後,カーネルがアイドルの間,updateFinancialData[]が15秒ごとに実行される.定期タスクはカーネルがビジーでないときのみ実行される.他の評価を遮ることはない.割り当てられた15秒が経過しても,カーネルが他の評価の途中なら,タスクは演算が終了する直後まで実行を待つ.そのように遅れた定期タスクはカーネルが現在の演算を終了したら直ちに実行されることが保証されている.ユーザがフロントエンドあるいはJavaで多数の演算に忙しい場合でも,タスクが無限に遅延されることはない.この逆もまた真である.ユーザがフロントエンドのセルを評価するときに定期タスクが実行中なら,評価はタスクが終わるまで始まらないが,終了後すぐに始まることは保証されている.
1つの定期タスクを削除するには,引数としてその定期タスクのID番号を渡してRemovePeriodicalを使う.すべての定期タスクを削除するにはRemovePeriodical[Periodicals[]]を使う.また,UnshareKernel[]を引数なしで呼び出しても,カーネル共有をオフにすることができる.AddPeriodicalをもう一度使うと,定期タスクを再設定することができる.
定期タスクのスケジュール間隔を再設定するのに,新しく J/Link 2.0で加わったSetPeriodicalIntervalを呼び出すこともできる.以下の行は,金融データを上記のように15秒間隔ではなく10秒間隔で定期的に実行する.
時々は定期タスクの間隔を変更したり,タスク自身のコード内から定期タスクをすべて削除したりした方がよいこともある.$ThisPeriodicalは現在実行中の定期タスクのIDを保持する変数である.これは定期タスクの実行中だけに値を持つ.RemovePeriodicalあるいはSetPeriodicalIntervalが呼び出せるように定期タスクのIDを取得するためには,定期タスク内から$ThisPeriodicalを使う.
定期タスクはJavaとは何の関係もないし,Javaを使う必要もない.Javaは起動していなくてもよい(しかし,JavaはCPUを産出するためにShareKernelの内部によって使用されるので,Javaが起動していなかったら,定期タスクの設定によりカーネルがCPUを継続的にビジーにする).定期タスク機能はShareKernelの単純な拡張機能なので,J/Link に含まれ,Java関連の優れた使用法も有している.
最後になったが,定期タスクは出力がフロントエンドで見えるようにはしない.その例である.
プログラマーは,10秒ごとにhelloがノートブックに出力されることを期待しているが,何も起らない.定期タスクが実行されている間は,$ParentLinkはフロントエンド(あるいはJava)に割り当てられない.結果やPrint出力,メッセージ,グラフィックスのような二次的出力は消えてなくなる.
特殊数字クラス
序文
Javaには,J/Link がWolfram言語の数値表現にマップする特別な数字関係のクラス群がある.文字列や配列のように,この数字クラスのオブジェクトには,重要な特性がある.これらはJavaのオブジェクトであるが,Wolfram言語に意味をなす「値」で表現することができるという属性を持っている.従ってJavaからWolfram言語にオブジェクトが返されたときに J/Link が自動的に数字に変換し,Wolfram言語からJavaに送られるときにオブジェクトに戻すことができ,便利である.
このようなクラスには,基本的なタイプ(Byte,Integer,Long,Double等),BigDecimal,BigIntegerのいわゆる「ラッパー」クラス,それに複素数を表すのに使うクラスがある.このクラスの扱いをここで説明する.
「ラッパー」クラス:Integer,Float,Boolean他
Javaには基本データ型に対応する,いわゆる「ラッパー」クラスがある.これらのクラスにはByte,Character,Short,Integer,Long,Float,Double,Booleanがある.ラッパークラスはそれぞれの基本データ型の値を1つずつ保持しており,JavaのすべてのものがObjectのサブクラスとして表現されることが必要である.これによって,オブジェクトを扱うさまざまなユーティリティメソッドやデータ構造で簡単に基本データ型が扱えるようになる.それはまた,Javaのリフレクション機能にとっても必要なことである.
これらのオブジェクトの1つを返すJavaメソッドを持っていたら,そのオブジェクトは整数(Byte,Character,Short,Integer,Longの場合),実数(FloatとDoubleの場合)あるいはTrueかFalseのシンボル(Booleanの場合)としてWolfram言語に届く.同様に,このオブジェクトの1つを引数として取るJavaメソッドは適切な標準のWolfram言語の値でWolfram言語から呼び出すことができる.値のリストにマップされるこれらのオブジェクトの配列にも同じことがいえる.
これら自動の「値渡し」の意味を無効にしたい場合は,「参照と値」で説明したReturnAsJavaObjectとJavaObjectToExpression関数を使うとよい.
複素数
Java数値型(byte,int,double等)はWolfram言語にInteger,あるいはRealとして返され,Integer,Realは引数としてJavaへ送られたら,適切な型に変換されるということを見てきた.では,複素数はどうだろうか.Wolfram言語とJavaとの間でやり取りされるときに自動的に変換されるように,Wolfram言語のComplex型に直接マップされる複素数を表現するJavaクラスがあったら非常に便利である.Javaは複素数の標準クラスを持っていないので,J/Link によってこのマッピングに入れたいクラスを指定することができる.
SetComplexClass["classname"] | クラスをWolfram言語の複素数にマップするよう設定する |
GetComplexClass[] | 現在複素数に使われているクラスを返す |
次の属性を持っていれば,どのようなクラスも使用することができる.
1. 2つのDouble(実数部,虚数部の順番で)を取るpublicコンストラクタ
Javaで複素数を使った演算をしていて,Wolfram言語から上のようなメソッドとインタラクトしたいとする.netlibから使用できる複素数クラスを使いたいとする.このクラスはORG.netlib.math.complex.Complexという名前で,http://www.netlib.org/java/から入手できる.SetComplexClass関数を使ってクラスの名前を指定する.
ORG.netlib.math.complex.Complex型の引数を取るどのメソッドやフィールドもWolfram言語複素数を受け入れ,メソッドやフィールドから返されたORG.netlib.math.complex.Complexクラスのどのオブジェクトも,Wolfram言語で自動的に複素数に変換される.複素数の配列についても同じことがいえる.
クラスのメソッドを呼び出す前だけでなく,複素数を使用するクラスをロードする前にもSetComplexClassを呼び出さなければならない.
BigIntegerとBigDecimal
Javaには任意精度の浮動小数点数や任意精度の整数の標準クラスがある.そのクラスはそれぞれ,java.math.BigDecimalとjava.math.BigIntegerである.Wolfram言語はそのような「bignum」を楽に扱うことができるので,J/Link はBigIntegerをWolfram言語整数に,BigDecimalをWolfram言語実数にマップする.つまり不揃いな配列,例えばBigIntegerを取るJavaメソッドやフィールドは整数を送ることによりWolfram言語から呼び出されるのである.同様に,BigDecimalを返すメソッドやフィールドは実数としてWolfram言語に返される値を持つ.
不揃いな配列
Javaを使うと,二次元以上の配列が「不揃い」,すなわち長方形ではなくなることがある.つまり,同じレベルのすべての位置で同じ長さではないということである.例えば,{{1,2,3},{4,5},{6,7,8}}は不揃いの2D配列である.J/Link を使うと,不揃いな配列を送ったり受け取ったりできるが,それはデフォルトの動作ではない.この理由は単に効率である.WSTPライブラリにはほとんどの基本データ型(byte,int,double等)の長方形配列を効率的に転送することができる関数があるが,不揃いのものはすべての要素が拾われるように個々に呼び出されなければならない.これはすべて,J/Link の内部深くで起ることなので,その方法については知る必要はないが,実行速度への影響は莫大なものがある.実行速度を最大化するために,J/Link は基本データ型の配列は長方形だと想定する.AllowRaggedArrays関数をTrueかFalseで呼び出すことにより,不揃いな配列を受け入れるか拒否するかを決めることができる.
AllowRaggedArrays[True] | 不揃いな配列(つまり,長方形ではないもの)がJavaに送られることを許可する |
AllowRaggedArrays[True]を使うと,二次元以上の配列の送信が非常に遅くなる.以下は配列の動作と,それがどうのように影響を受けるかの例である.Testingクラスがintsの二次元配列を取って,単純にそれを返す下のようなメソッドを持っているとする.
Testing`intArrayIdentity()関数のWolfram言語における定義では引数が整数の二次元長方形配列でなければならないので,エラーが生じる.そのため,Wolfram言語からの呼出しは起らない.
ここで,不揃いな配列のサポートをオンにすると,呼出しが始まる.これにはWolfram言語側のメソッドの引数のタイプチェックとJava側の配列読込みルーチン両方での修正が必要である.
不揃いな配列のサポートは配列を非常に遅くするので,必要がなくなったら,すぐにオフにした方がよい.
JavaインターフェースをWolfram言語コードで実装する
J/Link でどのように既存のJavaクラスを使うプログラムを書くことができるかは,すでに説明した.また,MathListenerクラスでのWolfram言語へのコールバックによりJavaユーザインターフェースの動作をまとめる方法も見た.MathListenerクラス(例えばMathActionListener)はどれも,任意のユーザ定義のWolfram言語コードにその動作を近付けるクラスとして考えることができる.これはまるで,実装がWolfram言語コードで書かれたJavaクラスであるかのようなものである.この機能は,自分でJavaクラスを書かなくても,純粋にWolfram言語だけで書くことのできるプログラムを大きく拡張するので,非常に便利である.
ImplementJavaInterface["interfaceName",{"methName"->"mathFunc",…}] | |
JavaメソッドのWolfram言語関数への指定されたマッピングに従い,Wolfram言語にコールバックすることにより指定されたJavaインターフェースを実装するJavaクラスのインスタンスを生成する |
この動作を利用してそれを一般化できると便利である.そうすることで,すべてのJavaインターフェースを利用し,Wolfram言語関数へのコールバックでそのメソッドを実装することができ,しかもこれがすべてJavaコードを全く書かずにできるようになる.J/Link 2.0で新しく導入されたImplementJavaInterface関数で,これを実行することができる.この関数は,具体例を見た方が理解しやすい.例えば,SwingメニューでJavaウィンドウを表示するために J/Link を使うWolfram言語プログラムを書いているときに,そのメニューの動作をWolfram言語で書きたいとする.Swing JMenuクラスは,登録されたMenuListenerにイベントを発生させるので,必要なのはWolfram言語を呼び出すことでMenuListenerを実装するクラスである.MenuListenerに関するセクションを見てみると,J/Link はMathMenuListenerクラスを提供していない.自分でそのようなクラスの実装を書くとよい.これをMathListenerのサブクラスにして必要な関数を実質的にすべて継承させるので,実際は,自分で書いても非常に簡単で,些細なことですらある.ここでの説明のために,Javaのことを知らない,あるいは自分で書くのに必要な余計な手順を踏みたくないから,そのようなクラスを書かないことにしたとしてみる.この場合は,ImplementJavaInterfaceを使って1行のWolfram言語コードでそのようなJavaクラスを作成することができる.
ImplementJavaInterfaceの第1引数は実装したいJavaインターフェース,あるいは,インターフェースのリストである.第2引数は,インターフェースのうちの1つからのJavaメソッドの名前を,そのメソッドを実装するために呼び出すWolfram言語関数の名前と関連付ける規則のリストである.Wolfram言語関数はJavaメソッドが取るのと同じ引数で呼び出される.ImplementJavaInterfaceは指定されたインターフェースを実装する,新たに作成されたクラスのJavaオブジェクトを返す.このオブジェクトはJavaNewを呼び出したり他の手段で入手したりしたJavaObjectと同じように使う.これは,関連付けられたWolfram言語関数を呼び出すことによって,指定されたインターフェースを実装するJavaクラスを自分で書いて,そのクラスのインスタンスを生成するためにJavaNewを呼び出したのと同じである.
インターフェースの中のすべてのメソッドをWolfram言語関数と関連付ける必要はない.マッピングのリストからはずしたJavaメソッドにはすべて,nullを返すデフォルトのJavaの実装が与えられる.そのメソッドにとってこれが適切な戻り値ではなく(例えば,メソッドがintを返すとき),ある時点でそのメソッドが呼び出されなければならない場合,例外が投げられる.一般的に,この例外はJavaの呼出しのスタックの上に伝播し無視されるが,Javaインターフェースのすべてのメソッドを実装した方がよい.
ImplementJavaInterface関数はJava 1.3で導入された「ダイナミックプロキシ」機能を利用する.従って,1.3より前のバージョンのJavaでは動作しない.Mathematica 4.2以降にバンドルされているJavaランタイムはすべて1.3以降である.Mathematica 4.0あるいは4.1をお持ちの場合には,ImplementJavaInterface関数が使えるように,使用中のシステムに最新のJavaランタイムをインストールしておく必要がある.
ImplementJavaInterface関数は,一見任意のJavaクラスをWolfram言語で書けるようにしてくれるもののようで,これはある程度は本当である.しかし,重要なことは,既存のJavaクラスを拡張したり,サブクラスに分類したりすることはできないという点である.また,実装しているインターフェースに存在しないメソッドを加えることもできない.この機能が役に立つクラスの例としてイベントハンドラクラスがある.MathListenerクラスはImplementJavaInterfaceがあるために古いものとなったとお考えかもしれない.確かに重複する機能もある.しかし,MathListenerクラスはJavaの1.3より前のバージョンでは役に立ち,さらに重要なことには,Wolfram言語を呼び出す純粋なJavaプログラムを書く場合に役に立つ.Wolfram言語を呼び出すJavaプログラムのImplementJavaInterfaceでWolfram言語に実装されたクラスを使うことは可能であるが,非常に面倒である.Javaから使うのと同じくらい簡単にWolfram言語から使える二重目的のクラスが必要なら,MathListenerのサブクラスを自分で書かなければならない.カスタムのJavaクラスを書くのではなく,ImplementJavaInterfaceを使う理由をあえて挙げるとすれば,アプリケーションにWolfram言語コードとそれ自身のJavaクラスを含ませることで,アプリケーションを複雑にすることが心配だからといえる.「J/Link を使うアプリケーションを配備する」で記述してあるように,アプリケーションにサポートするJavaクラスを含むことは,非常に簡単である.ユーザは余計なインストールのステップを踏む必要もなければ,Javaのクラスパスを触る必要もない.
インストール可能なJavaクラスを書く
序文
既存のJavaクラスをロードすると,Wolfram言語のプログラマーはすぐにすべてのJavaクラスにアクセスできるようになる.しかし,既存のJavaクラスだけでは不十分で,自分で書かなければならないこともある.
J/Link は基本的にJavaとWolfram言語との境界をなくすので,どのようなタイプの式も交換しあうことができ,Wolfram言語でJavaオブジェクトを有意義に使うことができるようになる.つまり,Wolfram言語から呼び出すためのJavaクラスを書くときに,通常は特別なことをする必要がないというのである.Javaからのみクラスを使いたいときに書くのと全く同じようにコードを書くことができる.(例外としては,Wolfram言語からJavaを呼び出すのは比較的遅いので,Wolfram言語からメソッドを呼び出し過ぎなくてよいようにクラスを設計する必要があるかもしれないということがある.この問題は「Java呼出しのオーバーヘッド」で詳しく説明してある.)
Wolfram言語とのインタラクションをもっと直接的に制御したくなることがあるかもしれない.例えば,メソッド自身が返すものとは違うものをWolfram言語に返したい場合である.あるいは,メソッドが何かを返すだけでなく,Wolfram言語に二次的出力(ある特定の条件のもとで何かを出力したり,メッセージを表示したり等)を起すようにしたい場合もそうである.メソッドが返る前に,Wolfram言語で複数の演算を実行したりその結果を読み込んだりと,Wolfram言語との拡張した「対話」をすることさえできる.Javaで起きたイベントの結果としてWolfram言語を呼び出すMathListener型のクラスを書きたいこともあろう.
このようなことに関心がない場合は,ここの説明を飛ばしても結構である.J/Link の利点はWSTPでのWolfram言語とのインタラクションについて考える必要がなくなることである.Wolfram言語から使えるJavaクラスを書きたいプログラマーの大部分は,Wolfram言語や J/Link のことを考えずにJavaクラスが書ける.もっと制御したい,あるいは J/Link でできることをもっと知りたい方は,この後も読み進んでいただきたい.
ここで説明する問題には,「Wolfram言語を使うJavaプログラムを書く」で扱うWSTPプログラミング(正確には,WSTPを使うJavaメソッドを使う J/Link プログラミング)の知識が必要になる.ここでこのようなメソッドや問題に直面するのは,「イントロダクション」で述べたように,WSTPを使ってWolfram言語から呼び出される「インストール可能な」関数を書くことと,WSTPを使ってWolfram言語のフロントエンドを書くことの,誤ってはいるが便利な2分法のためである.WSTPはどちらの場合も常に同様に使われる.インストール可能な場合,実質的にそのすべてが内部的に処理されるのである.このセクションではこのデフォルト動作以上のことについて説明するので,直接 J/Link を呼び出して,リンクを読んだりリンクに書いたりする.従って「Wolfram言語を使うJavaプログラムを書く」で初めて説明するような概念,クラス,メソッドが出てくる.
ここでの説明の中には,Cでインストール可能なプログラムを書くプロセスを比較対照しているところもある.これは経験のあるWSTPプログラマーがどのように J/Link が動作するのかを理解する助けになり,また J/Link はC,C++,FORTRANを使うよりも優れたソリューションであることを納得していただくことを意図している.
インストール可能な関数―旧来の方法
C言語でいわゆる「インストール可能な」あるいは「テンプレート」プログラムを書くには,数多くのステップが必要である.関数fooを含むファイルfoo.cがあるとする.関数fooをWolfram言語から呼び出すには,どのように呼び出したいのか,関数fooはどのような引数を取るのか,何を返すのかを説明するテンプレートエントリを含むテンプレートファイル(.tm)をまず最初に書かなければならない.それからこの.tmファイルをmprepというツールに渡す.このツールはプログラムの中でWSTP関連の一部あるいは全部を管理するCコードのファイルを書く.また,いつも同じである簡単なmainルーチンを書く必要もある.それからこのファイルをすべてコンパイルすると,1つのプラットフォームでのみ実行可能なファイルになる.
このメソッドには大きな欠点が2つある.それは呼び出したい関数のそれぞれについてテンプレートエントリを書かなければならないこと(関数ライブラリすべてについて書くこと等想像できない)と,コンパイルされたプログラムは他のプラットフォームでは使えないということである.しかし最大の欠点は,最も単純なタイプ以外への自動サポートがないということである.整数のリストを返すような基本的なことがしたい場合でも,自分でそれを実行するWSTPの呼出しを書く必要がある.Wolfram言語に「オブジェクト」を渡す方法はないので,オブジェクト指向のプログラミングについては忘れなければならない.
Javaのインストール可能な関数
J/Link を使うとそのようなステップが必要なくなる.このチュートリアルで見てきたように,文字通り何の準備もなくどのクラスのどのメソッドも呼び出すことができる.
特別なJavaコードを書く必要があるのは,メソッドを呼び出したりその結果を受け取ったりするデフォルトの動作が十分ではない場合だけである.以下では,使用できる特殊なテクニックを示す.
クラスロード時のWolfram言語での定義設定
Cで書かれたインストール可能なWSTPプログラムに必要な.tmファイルのテンプレートエントリには,J/Link にはないような2つの機能がある.1つ目はプログラムが最初に「インストール」されたときに,評価される任意のWolfram言語コードを指定する機能である.これにはテンプレートエントリの:Evaluate:ラインを使う.2つ目の機能は,関数がWolfram言語から呼び出される方法を指定する機能である.その方法とは,C関数にマップするWolfram言語の関数の名前,その引数の列,どのようにその引数がC関数に与えられたものとマップされるか,送られる前に終了していなければならない処理等である.この情報は,テンプレートエントリの:Pattern:行と:Arguments:行で指定される.
この2つの機能は両方とも,外部プログラムがインストールされたときにロードされるWolfram言語コードを指定する機能に依存しているので,お互いに関連しているといえる.J/Link はonLoadClass()とonUnloadClass()という2つの特殊なメソッドにより,この能力あるいはそれ以上のものを与えてくれる.クラスがLoadJavaClassから直接,あるいはJavaNewを呼び出して間接的にWolfram言語にロードされると,そのクラスに次のシグネチャを持ったメソッドがあるかどうかが確認される.
そのようなメソッドがあれば,そのクラスのすべてのメソッドとフィールドの定義がWolfram言語で設定された後で,呼び出される.クラスは1回のJavaセッションで1度しかロードされないので,このメソッドは1度のWolfram言語カーネルの寿命では2度以上呼び出されても(ユーザはJavaランタイムを繰り返し起動したり終了したりすることができるので),1度のJavaランタイムの寿命では1度しか呼び出されない.このメソッドの引数として与えられるKernelLinkはもちろんWolfram言語へ戻るリンクである.
この機能はクラスのメソッドによって出されるエラーメッセージのテキストを定義するのによく使われる.これがその例である.
public static void onLoadClass(KernelLink ml) throws MathLinkException {
ml.evaluate("MyClass::sun = \"The foo() method can only be called on Sunday.\"");
ml.discardAnswer();
}
このメソッドはMathLinkExceptionを投げている.onLoadClass()メソッドはどのような例外も投げることができる(MathLinkExceptionが典型的である).これによりonLoadClass()の予期されたシグネチャのマッチングが妨害されることはない.onLoadClass()中に例外が投げられても,LoadJavaClassの通常の操作には影響がない.この規則の唯一の例外は,コードがカーネルへのリンクとインタラクトしている間,とりわけコードがカーネルに演算を送ってその結果を読み始めるまでの間に例外を投げる場合である.つまり,投げた例外はLoadJavaClassのメカニズムを壊しはしないが,終了していないものを起動することによりリンクの状態を台無しにしていないかどうかはユーザ自身で確認しなければならない.
onLoadClass()を使うもう1つの理由は,好みの名前や引数の列を与えて,ユーザが呼び出せるstaticメソッドの呼出しを「ラップした」Wolfram言語の関数を作成したい場合である.public static void myMethod(double[a])メソッドを持つMyClassというクラスがあるとすると,Wolfram言語で自動的に生成される定義では,引数に実数か整数のリストを要求する.従来のWolfram言語の大文字を持つMyMethodという定義を加えたいとする.また,この関数が自動的にその引数にNを使い,{Pi, 2Pi, 3Pi}のように,評価すると数のリストになるどのようなものについても動作するようにしたいとする.以下がそのような付加的な定義を設定する方法である.
public static void onLoadClass(KernelLink ml) throws MathLinkException {
ml.evaluate("MyMethod[x_] := myMethod[N[x]]");
ml.discardAnswer();
}
言い換えれば,Wolfram言語で自動的に生成されるクラスのインターフェースに満足できないならば,onLoadClass()を使ってJavaインターフェースを変えることなく希望の定義を設定することができるのである.
onLoadClass()が呼び出されたときのWolfram言語のコンテキストにはすべてのクラスのstaticメソッドやフィールドが定義されている.そのような理由で,前の例ではMyClass`MyMethodではなくMyMethodの定義を作ったのである.Javaコードの正確なコンテキストは,LoadJavaClassのAllowShortContextオプションでユーザによって決定されるためJavaコードの正確なコンテキストは分からないので,これは重要である.
Wolfram言語に数多くのコードを送るためにonLoadClass()を使うのは一般的によいとはいえない.Wolfram言語コードは隠れているのでクラスの動作がユーザに理解しにくくなるし,埋め込まれたWolfram言語のコードを変更するためにはそれをコンパイルし直さなければならないため,柔軟性がなくなるからである.Javaクラスを伴わなければならないコードが多数ある場合,自分またはユーザがロードするWolfram言語パッケージファイルにそのコードを入れておくとよい.つまり,Wolfram言語に多くのコードをダンプするクラスをユーザにロードさせるより,ユーザに自分のクラスをロードするWolfram言語パッケージをロードさせるということである.これにより,将来の変更や維持に最高の柔軟性が提供される.
最後に,onLoadClass()メソッドを J/Link の呼出しをするだけにとどめる理由はない.例えば,Javaコンソールウィンドウにデバッグの情報を書き出したり,ファイルを開いて書いたりというJava側特有の操作を行うことができる.
onUnloadClass()メソッドはonLoadClass()メソッドと操作性が似ており,クラスがアンロードされるときに呼び出される.ロードされたクラスはすべて,Javaランタイムを終了する直前にUninstallJavaにより自動的にアンロードされる.onUnloadClass()を使うと,onLoadClass()によって生成された定義を削除したり,その他のクリーンアップを実行したりすることができる.onUnloadClass()のシグネチャは次のようなものであるが,例外を投げることがある.
ここでクラスのロード,アンロードというのは,直接あるいは間接的にLoadJavaClassを使ってWolfram言語によりロードされることを指している.Javaランタイムにより内部的にクラスをロード,アンロードすることは指していない.Javaランタイムによるクラスのロードは,クラスが最初に使われるときに起るので,それはWolfram言語からLoadJavaClassが呼び出されたときよりもずっと前である可能性がある.
Wolfram言語への手動による結果返信
Wolfram言語から呼び出されるJavaメソッドのデフォルトの動作は,メソッドが返すものをそのままWolfram言語に返すことである.しかし,他のものを返したいときもあろう.例えば,ある状況では整数を,また他の状況ではシンボルを返したいとき等である.あるいは,メソッドがJavaから呼び出されたときにはあるものを返し,Wolfram言語には別ものを返すようにしたいということもあろう.このような場合は,メソッドが戻る前にWolfram言語に手動で結果を送る必要がある.
Wolfram言語から呼び出したいファイル読込みクラスを書いているとする.標準クラスjava.io.FileInputStreamとほとんど同一の動作をさせたければ,書くクラスはそのサブクラスということになる.もっとWolfram言語のような動作を与えたいとする.ひとつの例として,readメソッドがファイルの最後に達したときに-1ではなく,Wolfram言語の組込みのファイル読込み関数が返す,EndOfFileシンボルを返すようにしたい場合がある.
import java.io.*;
import com.wolfram.jlink.*;
public class MyFileReader extends FileInputStream {
<<constructors, other methods deleted>>
public int read() {
int i = super.read();
if (i == -1) {
KernelLink link = StdLink.getLink();
if (link != null) {
link.beginManual();
try {
link.putSymbol("EndOfFile");
} catch (MathLinkException e) {}
}
}
return i;
}
}
ファイルが最後に到達したら,iは-1になり,手動でWolfram言語に何かを返したいとする.まず,Wolfram言語と通信するために使うKernelLinkオブジェクトを取得しなければならない.これはstaticメソッドのStdLink.getLink()を呼ぶとよい.C言語でインストール可能なWSTPプログラムを書いたことがあれば,ここでの名前の選択に覚えがあるはずである.CプログラムはWolfram言語に戻るリンクを保持するstdlinkというグローバル変数を持っている.J/Link はこのリンクオブジェクトに関連したいくつかのメソッドを持つStdLinkクラスを持っている.
ここで,getLink()がnullを返すかどうかを確認する.メソッドがWolfram言語から呼び出されていればnullにはならないので,このテストによりメソッドがWolfram言語から呼び出されているのか,通常のJavaプログラムの一部なのかが分かる.このようにして,Wolfram言語カーネルがどこにも見当たらないときはいつもの方法でJavaから使われるメソッドを持つことができる.getLink()の呼出しは,メソッドがWolfram言語から直接呼び出されいようと,Wolfram言語からの呼出しによって起きた一連のメソッドの一部として間接的に呼ばれていようと,うまく動作する.
カーネルへのリンクバックがあることを確認したら,自分でWolfram言語に結果を送り返すことを J/Link に告げ,J/Link がメソッドの戻り値を自動的に送らないようにする.これにはKernelLinkオブジェクトのbeginManual()メソッドを呼び出すとよい.
Wolfram言語に結果を送り返す前にbeginManual()を呼び出さなければならない.そうしなかったら,リンクは同期からはずれ,次にWolfram言語から J/Link を呼び出そうとしても停止してしまう.beginManual()は2度以上呼び出しても安全なので,すでにbeginManual()を呼び出した別のメソッドからこのメソッドが呼び出されても心配には及ばない.
例題のプログラムに戻ると,beginManual()の次にすべきことは,必要な「put」型の呼出し(この場合,ただputSymbol())を実行し,Wolfram言語にその結果を送り返すことである.いつものように,この呼出しはMathLinkExceptionを投げる可能性があるので,それをtry/catchブロックでラップする必要がある.WSTPエラーによる思いもよらないイベントにおいてはできることはほとんどないため,catchハンドラは空である.すべてのメソッド呼出しをラップする J/Link の内部コードは,クリーンアップとputSymbol()を呼び出して起ったWSTPエラーからの回復を扱う.手動で結果をputしている間に起るMathLinkExceptionについては何もする必要はない.メソッド呼出しは自動的にWolfram言語に$Failedを返す.
Cで書かれたインストール可能なプログラムでもまた,手動で結果を送り返すことができる.関数のテンプレートエントリに,キーワードManualを使うことで示される.Cプログラムについては,手動/自動の決定はコンパイル時に行われなければならないが,J/Link を使うとランタイムの交換で済む.J/Link では,前の例で示したように,ある状況では通常の自動返送,またその他の状況では手動返送と,両方が可能である.
Wolfram言語による評価の要求
これまで,JavaメソッドがWolfram言語と単純なインタラクションをする場合のみを見てきた.Javaメソッドは手動あるいは自動で呼び出され,結果を返す.しかし,Wolfram言語ともっと複雑なインタラクションをさせたい場合もある.メッセージがWolframシステムや他のPrint出力に現れるようにしたり,Wolfram言語に何かを評価させて答を返させたいことがあるかもしれない.これはメソッドの終りにWolfram言語に何を返したいかとは全く異なる問題である.メソッドが最終結果を手動で返しても返さなくても,メソッドのボディから評価を要求することができる.
ある意味では,Wolfram言語とこの種のインタラクションを行うとき,一時的に「マスター」と「スレーブ」を逆にすることで,JavaとWolfram言語の形勢を逆転させることになる.Wolfram言語がJavaを呼び出すときJavaコードはスレーブとして動作し,演算を行いWolfram言語にコントロールを返す.しかし,Javaメソッドの最中で,Wolfram言語を一時的にJava側の演算サーバにすることにより,Wolfram言語にコールバックすることができる.従って,「Wolfram言語を使うJavaプログラムを書く」で説明するのと同じ問題に必然的に直面することになり,Java側の J/Link APIをすべて理解する必要がある.
MathLink とKernelLinkインターフェースについては「Wolfram言語を使うJavaプログラムを書く」で詳しく説明する.ここでは,特に「インストールされた」メソッドによって使用されるKernelLinkの特殊なメソッドについて説明する.そのうちの1つbeginManual()メソッドは,もうすでに出てきた.ここでは,message(),print(),evaluate()メソッドを扱う.
JavaメソッドからWolframシステムメッセージを出したり,Print出力を引き起したりするタスクは一般的に行われているので,KernelLinkインターフェースにはこのような操作のための特別なメソッドがある.message()メソッドはWolframシステムのメッセージを出す全段階を行う.これには2つのシグネチャがある.
最初の形式はメッセージテキストに入れる文字列の引数が1つだけの場合のもので,2つ目の形式はメッセージテキストが2つ以上の引数を必要とする場合のものである.メッセージテキストが引数を必要としないなら,2つ目の引数としてnullを渡すことができる.
print()メソッドはWolfram言語のPrint関数を起動するのに必要な全段階を行う.
これは,両方を使用するメソッドの例である.次のメッセージがWolframシステムで定義されていると仮定する(これはパッケージのロードによるか,あるいはこのクラスのonLoadClass()メソッド実行中だと考えられる).
public static double foo(double x, double y) {
KernelLink link = StdLink.getLink();
if (link != null) {
link.print("inside foo");
if (x < 0)
link.message("Foo::arg", "first");
if (y < 0)
link.message("Foo::arg", "second");
}
return Math.sqrt(x) * Math.sqrt(y);
}
print()とmessage()は必要なコードをWolfram言語に送り,リンクからの結果(常に記号Null)を読む.これらはMathLinkExceptionを投げないので,try/catch ブロックでラップする必要はない.
Javaからの浮動小数点の結果がNaN(“Not-a-Number”)のとき,Wolfram言語には自動的にIndeterminateが返ってくる.
print()とmessage()メソッドは,メソッドが結果を返す前にWolfram言語に中間評価を送るという,より一般的な概念の2つの特殊な場合について便利な関数である.この一般的な方法は,Wolfram言語に送るものは何でもEvaluatePacketでラップすることである.これにより,これは最終結果ではなく,評価してJavaに結果を送り返さなければならないものだということをカーネルに知らせる.頭部のEvaluatePacketを明示的に送ることも,EvaluatePacketを使うKernelLinkのメソッドの中の1つを使うこともできる.そのメソッドは,以下の通りである.
このメソッドについては「Wolfram言語を使うJavaプログラムを書く」で説明する(実際は,他の引数の列を用いてさまざまな使い方を説明する).以下は簡単な例である.
public static double foo(double x, double y) {
KernelLink link = StdLink.getLink();
if (link != null) {
try {
link.evaluate("2+2");
// 答を待って読む.
link.waitForAnswer();
int sum1 = link.getInteger();
// evaluateToOutputFormにより,結果がOutputFormでフォーマットされた
// 文字列として戻る.これがすべて1ステップで行われる
// (waitForAnswerを呼び出す必要はありません).
String s = link.evaluateToOutputForm("3+3");
int sum2 = Integer.parseInt(s);
// EvaluatePacketの頭部を含めて,
// 全評価を部分ごとに分けることもできる.
link.putFunction("EvaluatePacket");
link.putFunction("Plus", 2);
link.put(4);
link.put(4);
link.waitForAnswer();
int sum3 = link.getInteger();
} catch (MathLinkException e) {
// 手に入れようとしているものが,
// 待っている式の種類ではない場合に最も起りがちな
// 唯一のWSTPエラーは,「get」関数からのものである.
// エラーが起きたら,エラー状態をクリアし,読んでいるパケットを捨て,
// メソッドが通常通り終了するようにする.
link.clearError();
link.newPacket();
}
}
return Math.sqrt(x) * Math.sqrt(y);
}
例外を投げる
J/Link はメソッドが投げるどのような例外もうまく扱い,Wolframシステムに例外を説明するメッセージを出力する.これは「例外の取扱い」で説明した.Wolfram言語に演算を送るなら,例外によって不意にコードが妨害されないように注意する必要がある.つまり,Wolfram言語とのトランザクションを始めたら,それを完了させなければならない.さもなければ,リンクを同期から外すことになり,以降のJavaの呼出しが停止してしまう.
メソッドを割込み可能にする
終えるのにしばらくかかりそうなメソッドを書くときは,Wolfram言語から割込み可能にするのも一案である.C WSTPプログラムでは,このためにWSAbortというグローバル変数が与えられている.J/Link プログラムでは,KernelLinkインターフェースでwasInterrupted()メソッドを呼び出す.
以下は長い演算を実行するメソッドの例である.100回の反復毎に,評価メニューの評価を放棄コマンドを使ってユーザが演算を放棄しようとしたかどうかをチェックする.
public int foo() {
KernelLink link = StdLink.getLink();
for (int i = 0; i < 10000, i++) {
... perform one step ...
if (i % 100 == 0 && link.wasInterrupted())
return 0; // Mathematicaは戻り値を見ない.
}
return 42;
}
このメソッドは,ユーザによる放棄の試みを察知すると0を返すが,Wolfram言語がこの値を知ることはない.これは,J/Link により放棄されるメソッドあるいはコンストラクタの呼出しがAbort[]を返すようになるからで,コードの中の放棄を察知するかどうかにはかかわらない.従って,放棄を察知し,ユーザの要求に応じたいと思った場合は,すぐに何らかの値を返せばよい.J/Link がAbort[]を返すとき,Abort[]がWolfram言語コードに組みこまれているかのように,ユーザの計算はすべて放棄される.つまり,Wolfram言語に放棄を伝搬する詳細にはかかわらなくてもよい.放棄の要求があったら早めに返しさえすれば,後は自動的に扱われる.
Javaメソッドが放棄を察知したときに,Wolfram言語の演算全体を放棄させるのではなく,別のことをさせたい場合がある.例えば,ループを中止して,その時点までの結果を返すようにしたいということがある.しかし,これは一般的に勧められるものではない.ユーザは放棄要求を出したときに,プログラムが放棄され,$Abortedが返されるものだと思っている.しかし,多くの人に使われることを意図していないコード等では特に,放棄するときに,それが計算を放棄させるだけのものというのではなく,Javaコードに何らかの情報を伝達する「メッセージ」として使うと便利なこともある.この考え方はWolfram言語のCheckAbort関数に似ており,この関数を使うと,放棄を察知してそれを吸収し.それ以上伝搬することがないため演算全体を放棄することがない.J/Link がAbort[]を返さないように放棄をJavaコードの中に吸収するためには,clearInterrupt()メソッドを呼び出す.
public int foo() {
KernelLink link = StdLink.getLink();
for (int i = 0; i < 10000, i++) {
... perform one step ...
if (i % 100 == 0 && link.wasInterrupted()) {
link.clearInterrupt();
return resultSoFar; // This is the value that will be returned to Mathematica
}
}
...
return 42;
}
イベントハンドラコードを書く
「Wolfram言語コードでイベントを扱う:「MathListener」クラス」ではボタンのクリック等,Javaで起ったイベントへの反応としてのWolfram言語の呼出しについて紹介した.J/Link ではこの目的のために,MathListenerから派生したクラスが提供されている.もちろん提供されたMathListenerクラスを使う必要はない.イベントを扱うコードを書き,その中でWolfram言語を直接呼び出すこともできる.J/Link のすべてのイベントハンドラクラスは抽象基底クラスのMathListenerから派生したものである.このMathListenerはWolfram言語とのインタラクションの詳細を扱い,イベントとWolfram言語コードとを関連付けるのに使うsetHandler()メソッドを提供する.自分でMathListenerスタイルのクラス(例えば, J/Link が提供していないSwing特有のイベントリスナインターフェース)を書きたいユーザは,MathListenerの機能が継承できるようにこれをサブクラスにするよう強くお勧めする.MathListenerのソースコードとそれに由来する具象クラス(MathActionListenerが最も簡単なもの)も調べ,どのように書いてあるか見てみるとよい.自分の実装のための出発点としてこれを使うことができる.
ここで言及したい J/Link 2.0の新機能がある.それは,Wolfram言語関数のImplementJavaInterfaceで,これによりどのようなJavaインターフェースも完全にWolfram言語コードで実装することができる.ImplementJavaInterfaceは「JavaインターフェースをWolfram言語コードで実装する」で詳細に渡り説明してあるが,その一般的な使用目的は,J/Link が組込みのMathListenerを持たない「リスナ」タイプのインターフェースを実装するイベントハンドラクラスを作成することである.これは「JavaインターフェースをWolfram言語コードで実装する」で詳述してあるので,この関数を使う場合は,このセクションで取り扱う問題について心配する必要はない.
Javaクラスを書くつもりで,MathListenerからクラスを作らない場合は,Wolfram言語コードを呼び出すイベントハンドラを書く際に守らなければならない重要な規則が2つある.もっと正確に言うと,これらの規則はWolfram言語コードがJavaを呼び出していない時点でWolfram言語コードを呼び出さなければならないコードを書いているときはいつも適用される.分かりにくいかもしれないが,実際には非常に簡単である.「Wolfram言語コードによる評価の要求」では,Javaメソッド内部からWolfram言語コードによる評価を要求する方法が記載してある.この場合,Wolfram言語コードはJavaメソッドを呼び出し,Wolfram言語コードが結果を待っている間コードは演算を実行するためにコールバックする.コードがWolfram言語コードへコールバックする時点ではWolfram言語コードはJavaを呼び出している最中なので,これはうまくいく.これは本当の「コールバック」である.つまり,Wolfram言語コードはJavaを呼び出し,この呼出しの取扱い中にJavaがWolfram言語コードへコールバックするのである.対照的に,Javaコードがボタンクリックの反応で実行する場合を考えてみる.ボタンクリックイベントが起る場合,Wolfram言語コードはおそらくJavaの呼出し中ではないと考えられる.
後者の場合,特別な配慮が必要である.というのは,Javaランタイムでは2つのスレッドがWSTPリンクを使用しているからである.最初のスレッドは J/Link の内部で生成・使用されていて,このチュートリアルで説明しているようにWolfram言語からJavaへの標準的な呼出しを扱う.2つ目は,Javaユーザインターフェーススレッド(AWTスレッドと呼ばれることもあるもの)で,イベントハンドラコードを呼び出すものである.ユーザインターフェーススレッドでのカーネルへのリンクの使用が J/Link の内部スレッドを妨害しないようにする必要がある.
下記のコードは理想的なMathActionListenerクラスのactionPerformed()メソッドを示している.この作業はスーパークラスであるMathListenerに任せるように作られているので,MathActionListenerの実際のコードとは異なる.しかし,この例は正しい操作の手順を示している.これが関連したオブジェクトのアクション(例えば,ボタンクリック)が起るときに実行されるコードである.
public void actionPerformed(ActionEvent e) {
KernelLink ml = StdLink.getLink();
StdLink.requestTransaction();
synchronized (ml) {
try {
// ユーザが要求した操作を実行するためにコードを送る.
ml.putFunction("EvaluatePacket", 1);
... code to put rest of expression to evaluate goes here ...
ml.endPacket();
ml.discardAnswer();
} catch (MathLinkException exc) {
...
}
}
}
このコードで特筆すべき最初の規則は,評価のためにコードを送ったり結果を完全に読んだりすることを含む,Wolfram言語との完全なトランザクションがsynchronized(ml)ブロックでラップされるということである.このようにして,ユーザインターフェーススレッドが全トランザクションのためにリンクに独占的にアクセスできることを保証する.2つ目の規則は,synchronized(ml)文はStdLink.requestTransaction()の呼出しに続かなければならないということである.この呼出しは,カーネルがJavaからの評価を受け入れる準備ができる時点までブロックする.呼出しはsynchronized(ml)ブロックが始まる前に起らなければならない.また,一旦呼び出したら,Wolfram言語に何かを送らなければならない.つまり,requestTransaction()が戻ったら,カーネルはJavaリンクからの読込みを試みてブロックをしているということである.カーネルは何かが送られるまでこの状態のままなので,requestTransaction()を呼び出した後で何かを送る前に,Javaの例外が投げられないようにしなければならない.通常,synchronized(ml)ブロックが始まり,何かを送り始める直前にただ単にrequestTransaction()を呼び出すことによりこれを行うことができる.
StdLink.requestTransaction()はカーネルがJavaで始まった評価を受け入れる準備ができるまでブロックすると上で述べた.具体的には,それは次の条件の1つが満たされるまでブロックする.
- Wolfram言語がDoModalを実行する
- JavaがWolfram言語から使われていない(InstallJavaが呼び出されていない)
これらの条件は「ウィンドウやその他のユーザインターフェース要素の作成」のユーザインターフェース要素生成についての説明で理解できよう.DoModal,ShareKernel,ServiceJavaの3つの方法でカーネルの注意をJavaリンクに向け,入ってくる演算要求を検出することができる.
DoModalやShareKernelを呼び出す前にJavaからWolfram言語をうっかり呼び出してしまうと,Javaユーザインターフェーススレッドは停止する.しかし,これはDoModal,ShareKernel,ServiceJavaを後で呼び出すことにより簡単に修復できる(2つ以上のイベントコールバックが待っている場合は,ServiceJavaは2度以上呼び出さなければならないかもしれない).
StdLink.requestTransaction()やsynchronized(ml)をいつ使えばよいかが分かりにくければ,Wolfram言語を呼び出すどのコードでこのコンストラクトを使ってもよいと思うとよい.それが必要ではないコードでは無意味であるが,無害なので呼出しスレッドがブロックすることはない.Wolfram言語を呼び出す必要があるJavaメソッドを書いていて,そのメソッドがユーザインターフェーススレッドから呼び出される可能性がある場合,StdLink.requestTransaction()とsynchronized(ml)を加える.
Javaクラスのデバッグ
Wolfram言語から呼び出されるJavaコードをデバッグするのに希望のデバッガを使うことができる.この際の唯一の問題は,デバッガ内部でJavaプログラムを起動しなければならないということである.起動しなければならないJavaプログラムは通常InstallJavaを呼び出したときに起動されるものである.J/Link のmain()メソッドを含むクラスは,com.wolfram.jlink.Installである.従って,InstallJavaによって内部的に実行される J/Link をスタートさせるコマンドラインは一般的に次のようなものである.
これには,InstallJavaのオプション次第で追加や変更があるかもしれない.また,付加的なWSTP特有の引数が最後に付くかもしれない.デバッガを使うには,手動でWolfram言語へのリンクを作ることができるようにする適切なコマンドライン引数でJavaを起動させるだけである.
統合されたデバッガを持つ開発環境を使う場合,デバッガにはメインクラス(main()メソッドが起動されるクラス)が使用する設定とコマンドライン引数の設定がある.例えば,WebGain Visual Caféでは,Project/OptionsダイアログのProjectパネルでこれらの値を設定することができる.メインクラスをcom.wolfram.jlink.Installにセットし,引数は次のように設定する.
次にデバッグセッションを始める.J/Link コピーライト表示が出力され,JavaはWolfram言語が接続するのを待つ.このためには,Wolframシステムセッションに行き,JLink.mパッケージが読み込まれていることを確認して実行する.
これはうまくいく.というのも,ReinstallJavaは引数にLinkObjectを取ることができるからである.しかし,この場合,Javaを起動しようとはしない.これで手動でJavaとWolfram言語間のWSTP接続を作り,そのリンクをReinstallJavaにフィードし,そのリンクがWolfram言語とJavaがお互いにインタラクションする準備に必要な残りの仕事をできるようにするのである.
jdbのようなコマンドラインデバッガを使いたいときは,次のようにする.
C:\>jdb
Initializing jdb...
> run com.wolfram.jlink.Install -linkmode listen -linkname foo
running ...
main[1] J/Link (tm)
Copyright (C) 1999-2018, Wolfram Research, Inc. All Rights Reserved.
www.wolfram.com
Version 4.9.1
Current thread "main" died. Execution continuing...
>
「Current thread "main" died.」というメッセージは正常である.jdbはコマンドの準備ができた.しかし,まず,Wolframシステムセッションで先ほど示されたLinkConnectとReinstallJava行を実行しなければならない.
J/Link を使うアプリケーションを配備する
ここでは J/Link を使うWolfram言語のアドオンを作成する開発者に関係のある問題を扱う.
J/Link はカスタムクラスローダを使い,スタートアップクラスパスを超えた場所のクラスを見付けることができる. 「クラスパスのダイナミックな修正」での説明のように,AddToClassPath関数を呼び出すことにより,クラスを検索する場所を追加することができる.カスタムクラスローダがあると便利な理由のひとつは,一部がJavaで実装されているアプリケーションを,開発者が容易に配信できるからである.アプリケーションディレクトリを適切に構成すると,ユーザはそれをWolfram言語アプリケーションの標準的な場所にコピーするだけでインストールすることができる.ユーザがクラスパス関係の操作をしたり,Javaを再起動したりしなくても,J/Link はJavaクラスを直ちに見付けることができる.
自分のWolfram言語アプリケーションが J/Link を使い,Wolfram言語自身のJavaコンポーネントを含む場合,アプリケーションディレクトリにJavaサブディレクトリを作成しなければならない.このJavaサブディレクトリには,自分のアプリケーションで必要なjarファイルを置くことができる.圧縮されていないクラスファイルがある場合(jarファイルにバンドルされていない場合)は,Javaディレクトリの適切にネストされたサブディレクトリに置かれなければならない.「適切にネストされた」というのは,クラスがJavaパッケージのcom.somecompany.mathにあるとすると,そのクラスファイルはJavaディレクトリのcom/somecompany/mathサブディレクトリに置かれるということである.クラスがパッケージ内にない場合は,直接Javaディレクトリに置かれる.J/Link はアプリケーションが必要とするネイティブライブラリとリソースを見付けることもできる.ネイティブライブラリは,それがインストールされているプラットフォームの$SystemIDにちなんだ名前のJava/Librariesディレクトリのサブディレクトリになければならない.以下は,J/Link を使うアプリケーションのディレクトリ構造の例である.
MyApp/
... other files and directories used by the application ...
Java/
MyAppClasses.jar
MyImage.gif
Libraries/
Windows/
MyNativeLibrary.dll
MacOSX-x86-64/
libMyNativeLibrary.jnilib
Linux/
libMyNativeLibrary.so
... and so on for other platforms
自分のアプリケーションディレクトリは,Wolfram言語アプリケーションの標準的な場所のうちのどれかになければならない.この表記の中で$InstallationDirectory/AddOns/Applicationsは,「値がWolfram言語変数の$InstallationDirectoryによって与えられるディレクトリのAddOns/Applicationsサブディレクトリ」を意味する.
$UserAddOnsDirectory/Applications
コードのヒント
ここでは高品質のアプリケーションを作成するためのヒントを紹介する.このヒントは,これまでに開発者サイドに見られた間違いを参考にしている.
InstallJavaはパッケージが読み込まれるときではなく,単独の関数・関数群のボディで呼び出す.パッケージの読込み中は二次的な影響を避けた方がよい.パッケージの読込みは迅速に実行され,定義のロードの他ならないと思われがちである.しかし,このときにJavaを起動して失敗したら,ロードのプロセスで不可解な停止が起きてしまう可能性がある.1つまたはそれ以上の関数のコードでInstallJavaを呼び出すとよい.Javaを使う関数すべてでInstallJavaを呼び出す必要はない.ほとんどのアプリケーションにはユーザがほとんど独占的に,少なくともセッションの最初で使う傾向のある「主要」関数がいくつかある.もしアプリケーションにこの属性がなければ,ユーザが最初に呼び出さなければならない初期化関数を提供し,その中でInstallJavaを呼び出すようにする.
引数なしでInstallJavaを呼び出す.ユーザのシステムではJavaのどのオプションが必要かは分からないので,ユーザが設定したものをオーバーライドしないようにする.SetOptionsを呼び出し,必要に応じてInstallJavaのオプションをカスタマイズするのはユーザの責任である.一般にこれは,init.mファイルで行われる.
オブジェクト参照のリークを防ぐために,必ずJavaBlockと(あるいは)ReleaseJavaObjectを使う.他の人がどのようにコードを使うかは分からないので,潜在的に多数の不必要なオブジェクトでユーザのセッションを乱すことを避けるよう注意する.ビューアウィンドウのようにWolfram言語の関数の寿命を超えて存在するオブジェクトを生成する必要があることもある.そのような場合,トップレベルウィンドウとしてMathFrameあるいはMathJFrameを使い,そのonClose()メソッドを使ってすべての未解決のオブジェクトを解放し,使ったかもしれないカーネルあるいはフロントエンド共有の登録を解除するWolfram言語コードを指定する.これができなければ,ユーザが手動で呼び出せるクリーンアップ関数を与える.LoadedJavaObjectsを使って関数が起動する前後にWolfram言語で参照されたオブジェクトのリストを見る.それは長くはならないはずである.
ShareKernelやShareFrontEndを使う場合は,これらの関数からの戻り値を必ず保存しそれを引数としてUnshareKernelとUnshareFrontEndに渡す.UnshareFrontEndやUnshareKernelを引数なしで呼び出さない.引数なしで呼び出すと,他のアプリケーションが使用していても共有をシャットダウンしてしまう.
アプリケーションの実行中はJavaランタイムは再起動されないと思わない.ユーザはUninstallJavaやReinstallJavaを呼び出さないように言われていても,呼び出してしまうこともある.Javaランタイムが不適当なとき(Javaウィンドウが表示されているとき等)にシャットダウンすると,アプリケーションが失敗することは避けられないが,Javaのシャットダウン・再起動の際のアプリケーションの強固さを高める手段がある.第1点目は,上記の最初のヒントですでに挙げられているが,「主要」関数のスタート時にInstallJavaを呼び出すということである.その他のヒントとしては,JavaClassやJavaObject式を不必要に保存しないということである.これらはJavaが再起動する場合に無効となるからである.パッケージファイルが読み込まれ,パッケージが有効な間その結果をプライベート変数で保存するときに,InstallJavaを呼び出してからLoadJavaClassとJavaNewを数回呼び出すことが,この例といる.これは,Javaが再起動する際に問題となる.JavaClass式は決して保存してはならない.クラスが現行のJavaランタイムにロードされているかどうかが明確でない場合は,LoadJavaClassを呼び出す.クラスがすでにロードされている場合,LoadJavaClassを呼び出すのは,非常に高価な方法である.作成するコストが高いJavaObjectがあるため,ユーザのセッションで長期に渡りそれをキャッシュしておく必要がある場合は,それを使うたびに次のイディオムを使ってまだ有効かどうかをテストした方がよい.オブジェクトが最後に生成されてからJavaがシャットダウンあるいは再起動していたらJavaObjectQテストは失敗する.その場合はJavaを再起動して,オブジェクトの新たなインスタンスを生成し,保存する.
アプリケーションでUninstallJavaあるいはReinstallJavaを呼び出さない.Javaを使用中の他のアプリケーションとうまく共存する必要がある.パッケージがJavaを使い終えたからといって,ユーザもそうだとは限らない.ユーザだけがUninstallJavaを呼び出すべきだが,呼び出さないこともあり得る.Javaを起動したままにしておいても演算コストはかからない.同様に,積極的にJavaの開発を行っており,編集したバージョンのクラスを再ロードする必要でもない限り,ユーザがReinstallJavaを呼び出すこともほとんどない.
例題プログラム
はじめに
ここではプログラム例題を見ていく.この例題は広範囲に及ぶ技術やちょっとした点を例示することを目的としている.実装におけるニュアンスを説明し,J/Link プログラミングの主要問題の大部分に触れる.
ここでは比較的厳しいアプローチを取り,特に参照のリークを避けるよう注意する.「JavaBlock」で説明したように,JavaBlockとReleaseJavaObjectはこのためのツールであるが,この話題について混乱していない場合は,この項は完全に無視してもよい.J/Link をたまに私用で使う場合は,メモリ管理問題は多くの場合関係ないのでJavaオブジェクトを積み重ねていくだけでよい.
J/Link には,ここで開発されているプログラムのほとんどを含む例題プログラムが記載されたノートブックが数多く含まれている.これらのノートブックは<Mathematica dir>/SystemFiles/Links/JLink/Examples/Part1ディレクトリにある.
Beep関数
次は,Wolfram言語のBeep関数のような,システムの注意を与える簡単な例である.
最初にJavaBeep[]が実行されたときにはわずかな遅延がある.それはLoadJavaClassの呼出しのためであり,任意の指定されたクラスに対して初めて呼び出されたときにだけ,ある程度の時間がかかる.
これは完全なビープ関数なので,多くのユーザにはこれ以上のものは必要ない.他のユーザのためのコードを書いている場合は,このコードに少し色を付けたいこともある.次が同じ関数のより高度にしたものである.
まずInstallJavaを呼び出す.少なくとも他のユーザのためのコードを書く場合は,J/Link を使う関数でInstallJavaを呼び出すことはよい習慣である.InstallJavaがすでに呼び出されていたら,次からの呼出しでは何も起らず素早く戻る.プログラム全体はJavaBlockでラップされている.「JavaBlock」で説明したように,JavaBlockはWolfram言語に返されたオブジェクトへの参照を解放するプロセスを自動化する.getDefaultToolkit()メソッドはToolkitオブジェクトを返すので,Wolfram言語で生成されるJavaObjectを解放する.getDefaultToolkit()メソッドは呼び出されるたびに同じToolkitオブジェクトへの参照を返すので,JavaBlockを呼び出さなくても,セッション全体でリークするオブジェクトは1つだけで済む.ReleaseJavaObjectを明示的に呼び出してBeepを書くこともできる.
JavaBlockの利点はどのメソッドがオブジェクトを返すのかを考えなくてもよく,それらを変数に割り当てなくてもよいという点である.
日付のフォーマット
これはJavaで実行される演算の例である.Javaは多くのパワフルな日付/カレンダー指向のクラスを提供している.例えば,時間と日付を示すようきれいにフォーマットされた文字列を作成したいとする.まず最初に,現在の日付と時間を表示する新しいJavaのDateオブジェクトを生成する.
次にDateFormatクラスをロードし,日付をフォーマットする能力のあるフォーマッタを作成する.
format()メソッドを呼び出し,引数としてDateオブジェクトを渡す.
ユーザの現在地を考慮することを含め,日付や時間のフォーマットには多くの異なる方法がある.Javaには便利な番号フォーマットクラスもあり,その例題は「最適化の例」にある.
プログレスバー
Wolfram言語プログラムのポップアップユーザインターフェースの簡単な例はプログレスバーである.これはWolfram言語にコールバックしたり,Wolfram言語に結果を返したりする必要がないので,「インタラクティブと非インタラクティブインターフェース」で説明したように「非インタラクティブ」なユーザインターフェースの例である.その実装にはSwingユーザインターフェースクラスを使う.Swingはプログレスバーの組込みのクラスを持っているからである(Swingをインストールしていなければこの例題を実行することはできない.SwingはJava1.2以降には標準装備で付いてくるが,Java1.1.x用には単独で手に入れることもできる.まだバージョン1.1.xの大部分のJava開発ツールにはSwingが付いてくる).この例題の完全コードもJLink/Examples/Part1ディレクトリのProgressBar.nbファイルで提供されている.
このコードには一般的な構造を示すよう注釈が付いている.このコードではあまり見慣れないクラスやメソッドが使われているかもしれない.J/Link の標準表記を使ってWolfram言語に変換された完全に標準的なJavaコードであることに変わりない.同じように動くJavaプログラムと各行同一である.
このコードは完全なプログラムとして提示されているが,そのように開発されなければならないというわけではない.J/Link のインタラクティブな性質により,思った通りになるまでJavaオブジェクトを一度に一行操作し,実験することが可能である.もちろん,Wolfram言語プログラムは通常このように書かれるのであるが,J/Link を使うとJavaオブジェクトとメソッドについても同じように書けるようになる.
ここではプログレスバーダイアログを作成し表示するShowProgressBar関数を作成する.バーは演算の完了度を示すのに使われる.初期の完了パーセントを与えることも,デフォルト値の0を使うこともできる.バーはsetValue()を呼び出して後でアップデートされなければならないので,ShowProgressBarはJProgressBarオブジェクトを返す.barオブジェクトはJavaBlockから返されるので,このJavaBlock内で生成された他の新しいJavaオブジェクトのように解放されない.これは J/Link 2.0での新しいJavaBlockの動作である.もしJavaBlockがオブジェクトのリスト等ではなく正確に1つのJavaオブジェクトを返す場合,そのオブジェクトは解放されない.JavaBlockについては「JavaBlock」を参照のこと.
プログレスダイアログを閉じてその後でクリーンアップをする関数も必要である.これは2つのステップしか必要ない.まず,バーを含むトップレベルのフレームウィンドウでdispose()メソッドを呼び出す.次に,オブジェクト参照のリークを避けたければ,バーオブジェクトに対してReleaseJavaObjectを呼び出す.というのは,これはShowProgressBarのJavaBlockを拡張した唯一のオブジェクト参照だからである.ShowProgressBarで生成したJFrameオブジェクトに対してdispose()を呼び出す必要があるが,その参照を保存していない.SwingUtilitiesクラスには,バーオブジェクトが与えられるとこのフレームを取り戻す便利なwindowForComponent()メソッドがある.
バーダイアログにはクローズボックスがあるので,早目にダイアログを閉じることもできる.これでダイアログの処理ができるが,まだバーオブジェクトを解放する必要がある.DestroyProgressBar(およびそのバーのsetValue()メソッド)はユーザがダイアログを閉じていてもいなくても安全に呼び出すことができる.
以下は,演算でプログレスバーを使用する方法である.ShowProgressBarの呼出しにより,バーダイアログが表示され,バーオブジェクトへの参照が返される.演算中は定期的にsetValue()メソッドを呼び出し,バーの状況をアップデートする.演算終了後にはDestroyProgressBarを呼び出す.
コードでオブジェクト参照がリークしているかどうかは,演算の前後にLoadedJavaObjects[]を呼び出すことで簡単にテストできる.オブジェクトのリストが長くなれば,ReleaseJavaObjectを使い忘れているか,不適切にJavaBlockを使ったということである.
この例題で使われているSwingクラスをすべてロードするには数秒かかるかもしれない.つまり,最初にShowProgressBarが呼び出されるときに,顕著な遅延があるかもしれないということである.JavaNewの文に現れるクラスを明示的にロードする前にLoadJavaClassを使うと,この遅延を避けることができる.
ダイアログは座標(400, 400)でスクリーンの左上に現れる.練習問題として,どのようにしたらスクリーンの中央に持ってくることができるかを考えてみていただきたい(ヒント:java.awt.ToolkitクラスにはgetScreenSize()メソッドがある).
最後に,プログレスバーはSwingクラスを使うので,Swingが提供するルックアンドフィールオプションを使うこともできる.特に,ランタイムでテーマを変えることができる.このプログレスバーウィンドウはあまり複雑ではないので,1つのルックアンドフィールテーマから次のものに移行するのにほとんど変化がない.次はテーマを変更する方法である.複雑なウィンドウほど効果も大きい.
staticメソッドを呼び出す必要のあるクラスをロードする.
デフォルトのルックアンドフィールのテーマは「metal」である.次のようにすると,使用中のプラットフォーム用のネイティブスタイルに変えることができる(これを行うときにウィンドウが見えるとやりやすい).
簡単なモーダル入力ダイアログ
「モーダルウィンドウ」には簡単なモーダルダイアログの例が説明してあるが,ここでは,別のモーダルダイアログを作ってみる.これは,ユーザに角度を度数かラジアンで指定して入力するように求める基本的なものである.これは終了したときに起動中のWolfram言語プログラムに値を返すダイアログの例であり,戻る前にユーザからの文字列を求めるWolfram言語の組込み関数Inputによく似ている.このようなダイアログは,終了しないと他のJavaウィンドウが使えないという従来の意味では「モーダル」ではないが,終了するまで(つまり,DoModal[]が返ってくるまで)ビジーの状態であるカーネルについてモーダルである.「ウィンドウやその他のユーザインターフェース要素の作成」ではモーダルおよびモードレスJavaウィンドウについての詳細を説明している.
コードは単純なので,注釈の必要はほとんどないであろう.ウィンドウとその中でのコントロールの作成では,Javaでプログラムを書いているときに使うようなJavaコードと全く同じようになる.ここで示すテクニックのひとつは,ダイアログを閉じるのにOKボタンとCancelボタンのどちらがクリックされたかを判別することである.これは,EndModal[]を呼び出すことに加え,2つのボタンに割り当てられたMathActionListenerオブジェクトが別のものを返すようにすることで実行できる.DoModal[]はEndModal[]を呼び出すコードが返すものは何でも返す.従ってここでは,OKボタンには引数を無視し,EndModal[]を呼び出し,Trueを返す純関数(EndModal[]; True)&を実行させ,Cancelボタンには(EndModal[]; False)&を実行させる.これでDoModal[]はOKボタンがクリックされるとTrueを返し,CancelボタンがクリックされるとFalseを返す.また,ウィンドウのクローズボックスがクリックされるとNullを返す(この動作はMathFrameによる).
最初にGetAngle[]が呼び出されたときは,ダイアログの表示に数秒かかるかもしれない.これは必要な大きいAWTクラスをいくつかロードするときの1回限りのコストである.次回からはGetAngle[]の呼出しはずっと迅速に行われるようになる.
この例の完全コードはJLink/Examples/Part1ディレクトリのModalInputDialog.nbファイルでも提供されている.
ファイル選択ダイアログ
Wolfram言語プログラムの便利な機能は,OpenあるいはSaveダイアログボックスのような,ファイル選択ダイアログを作成できることである.そのようなダイアログを使うと,ユーザに入力ファイルやデータを書き込むファイルを求めることができる.これはJava,特に標準SwingライブラリのJFileChooserクラスを使ったクロスプラットフォームの方法で簡単に作成できる.このようなダイアログボックスのコードはJLink/Examples/Part1ディレクトリのFileChooserDialog.nbファイルでも提供している.
Mathematica 4.0ではファイルブラウザをフロントエンドで表示するFileBrowse[]と呼ばれる「実験的な」新しい関数が導入された.この関数は使用できるものではあるが,下で示すJava技術に比べるといくつか欠点がある.そのひとつは,フロントエンドが使用中でなければならないということである.また,この関数はカスタマイズできないため,常にSave file as:ダイアログボックスとそれに付随した動作が出てくる.これはOpen型のダイアログボックスには不適切である.ここで使用するJFileChooserクラスは非常に洗練されたカスタマイズを可能にする.例えば,初期ディレクトリの設定,名前や属性に基づいたファイルのマスク,さまざまなボタンのタイトルとテキストのコントロール,ダイアログボックスを閉じてよいとされる前の選択を有効にするための関数の供給,複数ファイルの選択,ファイルではなくディレクトリの選択等ができるようになる.
この例は短いプログラムであるが,そのコードには不幸にも複雑な部分(不恰好な部分)がある.それはこの特殊なタイプのダイアログウィンドウをすべてのプラットフォームで前面に持ってくるようにしたために生じたものである.よって,コードはここでは示さない.その代り,プログラムコードのトピックについて言及する.実装の詳細に関心がある場合は,例題ファイルで全コードと関連コメントを読むことができる.
FileChooserDialog関数は3つの文字列引数を取る.1つ目は,ダイアログボックスのタイトル(例えば,Select a data file to import),2つ目は実際にOKボタンに表示されるテキスト(通常OpenかSave),3つ目は開始するディレクトリである.引数を与えずに,カーネルの現在のディレクトリで始まるデフォルトのOpenダイアログボックスにしてもよい.
これは「モーダル」ダイアログボックスであるが,ユーザがダイアログボックスを閉じるまでshowDialog()メソッドは返ってこないので,DoModalを使う必要はない.DoModalはダイアログボックスあるいは他のウィンドウが閉じるまで,強制的にWolfram言語を機能停止にする.ここでは,showDialog()でこの動作を実行することができる.また,DoModalは,Javaから入力を受け取る準備のできているループにカーネルを置く.このため,Wolfram言語へのコールバックによりダイアログの機能のスクリプトを書くことができる.このファイル選択ダイアログボックスでは,選ばれたファイルを返すまでWolfram言語を使う必要がないので,この機能を使う必要はない.
もうひとつの注目すべき点は,ユーザがCancelボタンの代りにSaveあるいはOpenボタンをクリックしたことを示す,showDialog()が返す定数の名前である.Javaではこの定数の名前は,JFileChooser.APPROVE_OPTIONである.Javaの名前はWolfram言語シンボルにマップされるので,アンダースコアのようにWolfram言語シンボルでは合法ではない文字を含んでいれば変換される.アンダースコアはシンボルになると“U”に変換されるので,この定数のWolfram言語名はJFileChooser`APPROVEUOPTIONである.「Java名のアンダースコア(_)」で詳しく説明してある.
フロントエンドの共有:パレットタイプのボタン
「ウィンドウやその他のユーザインターフェース要素の作成」で説明したように,J/Link の目標のひとつは,Javaのユーザインターフェース要素をノートブックやパレットウィンドウのようなノートブックフロントエンド環境の第一級のメンバにできるだけ近付けることである.これを達成する方法のひとつはShareKernel関数を使うことで,これによりJavaウィンドウはカーネルの注意をノートブックウィンドウと共有することができる.このようなJavaウィンドウを「モードレス」と呼ぶ.これは,他のJavaウィンドウがアクティブな状態であるという従来の意味ではない.むしろ,カーネルに対してモードレスということで,カーネルはJavaウィンドウが開いている間ビジー状態ではないという意味である.
Javaウィンドウがフロントエンドとカーネルを共有できるという機能の他にも,Javaでのアクションにより結果の出力やグラフの表示等のノートブックウィンドウの使用,あるいはNotebookApply,NotebookPrint,SelectionEvaluate,SelectionMove等のノートブック操作コマンドの実行ができるとさらに便利である.この例として,パレットボタンがある.パレットボタンで現在の選択範囲を何か別のもので置き換えたり,結果の式をその場で評価したりすることができる.
ShareFrontEnd関数を使うと,パレットボタンやノートブックで手動で評価するWolfram言語コードで行われるように,Javaモードレスウィンドウのアクションでノートブックウィンドウのイベントを引き起すことができる.「モーダル」ダイアログを使用中(DoModalが起動中)は,フロントエンドとインタラクトする能力を自動的に得ることができる.Javaがモーダルで起動中は,カーネルの$ParentLinkは常にフロントエンドを指しているため,すべての二次的な出力は自動的にフロントエンドに送られる.モーダルウィンドウはパレットの例では使えない.というのは,パレットはWolfram言語環境を控えめに向上させるものだからである.カーネルが生きている間にロックアップしてはならない.ShareKernelで,Javaウィンドウはカーネルを縛ることなくWolfram言語を呼び出すことができ,ShareFrontEndはShareKernelの拡張機能で(内部的にShareKernelを呼び出す),そのような「モードレス」Javaウィンドウがフロントエンドとインタラクトすることを可能にする.ShareFrontEndは「フロントエンドの共有」で詳しく説明する.
下のPrintButtonの例では,アクティブノートブックの現在のカーソル位置でラベルを出力する,簡単なJavaのパレットタイプボタンを作成する.ShareFrontEndの現在の制約のため,この例はリモートカーネルではうまくいかない.カーネルとフロントエンドは同じマシンで起動しなければならない.
PrintButton関数を呼び出して,パレットを作成・表示する.ボタンをクリックして現在のカーソル位置に挿入されているボタンのラベル(ここではfoo)を見る.終ったら,ウィンドウのクローズボックスをクリックする.
コードの大部分は簡単である.いつも通り,フレームウィンドウにMathFrameクラスを使う.クローズボックスがクリックされると,MathFrameは終了し自己処理をするからである.buttonFuncを呼び出すMathActionListenerを生成し,それにボタンを割り当てる.「Wolfram言語コードでイベントを扱う:『MathListener』クラス」の表から,buttonFuncは2つの引数で呼び出されることが分かる.1つ目の引数はActionEventオブジェクトである.このオブジェクトから,クリックされるボタンとそのラベルを生成する.このラベルは標準NotebookApply関数を使って現在のカーソル位置に挿入する.小さいことであるが,引数としてノートブックを取るNotebookApply,NotebookWrite,NotebookPrintのようなノートブック操作に対するターゲットとしてSelectedNotebook[]を指定する必要がある.ShareFrontEndの実装詳細のため,EvaluationNotebook[]によって与えられるノートブックは正しいターゲットではないのである(結局,ボタンがクリックされたときにフロントエンドで実行中の評価はない).
PrintButtonについて大切なことは,ShareFrontEndとUnshareFrontEndを使用するということである.前述のように,ShareFrontEndによりJavaは演算の結果以外は何でもフロントエンドに送る状態になり,フロントエンドはそれを受け取ることのできる状態になる.このような訳で,通常はJavaに送られそこで捨てられるJavaボタンのクリックで起るPrint出力が,フロントエンドに現れるのである.フロントエンド共有(およびカーネル共有)は必要でなくなったらオフにするべきである.しかし,他のユーザのためのコードを書いている場合は,ユーザが共有を必要とするJavaウィンドウを開いている可能性があるので,むやみに共有をシャットダウンしてはならない.この問題を扱うために,ShareFrontEnd(およびShareKernel)は登録・登録解除の原則に作用する.ShareFrontEndは呼び出されるに,フロントエンド共有の要求を表すトークンを返す.フロントエンド共有がオンでなければ,オンになる.プログラムがもうフロントエンド共有を必要としなくなると,UnshareFrontEndを呼び出し,ShareFrontEndからのトークンを引数として渡す.このようにして共有の要求が登録解除された時のみ,実際に共有がオフになる.
MathFrameクラスのonClose()メソッドを使うと,フレームが閉じたときに実行されるWolfram言語コードを指定することができる.このコードはすべてのイベントリスナに通知された後で実行されるので,ここで共有をオフにしても安全である.onClose()コードでは,ShareFrontEndによって返されたトークンを使ってUnshareFrontEndを呼び出す.onClose()メソッドをこのように使うと,パレットを使い終えた後手動で呼び出さなければならないクリーンアップ関数を書かなくて済む.フロントエンド共有をオンにしておいても問題はないが,プログラムがユーザのセッションをできるだけ変えないようにした方がよい.
この例を拡張して,異なる操作をするボタンをもっと入れてみる.この例の完全コードはJLink/Examples/Part1ディレクトリのPalette.nbファイルにある.
まず,ボタンを含むフレームを管理するコードとボタンを作成するコードとを分ける.これで,異なるボタンをいくつでも含むことのできる再利用可能なパレットフレームができる.下のShowPalette関数はボタンのリストを取り,フレームウィンドウにボタンを垂直に並べ,ShareFrontEndを呼出し,ユーザのノートブックウィンドウの前にフレームを表示する.
ShowPalette関数からは何も返ってこない.特にフレームオブジェクトは返らない.これはフレームを再び参照する必要がないからである.このフレームは,クローズボックスがクリックされたときに自動的に破壊される(これがMathFrameクラスの特徴である).生成したJavaオブジェクトのどれについての参照も維持する必要がないので,ShowPalette全体はJavaBlockでラップされる.
ボタンを作成する再利用可能なPaletteButton関数を作ってみる.ボタンに書かれるラベルテキストと,ボタンがクリックされたときに呼び出される関数(文字列として)の2つのものを渡すだけである.ボタンの全機能は2つ目の引数として渡すボタン関数にまとめられているので,これだけで完全に任意のボタン動作を可能にすることができる.
PaletteButton関数を使って4つのボタンを作成する.最初のものは上で定義された出力ボタンで,その動作はprintButtonFuncによって指定される.
2つ目は標準AlgebraicManipulationフロントエンドパレットのボタンの機能をコピーしたものである.これらのボタンは現在の選択範囲の周りの関数(例えばExpand)をラップし,その場で次のように結果の式を評価する.以下がそのボタンを作成し,その操作のボタン関数を定義する方法である.
3つ目のボタンはプロットを作成するものである.これはただプロット関数を呼び出しさえすればよい.ShareFrontEndでフロントエンド共有をオンにすると,J/Link は内部的に最前面のノートブックの新しいセルにグラフィックスを出力する.
最後のボタンは,テキストを現在のカーソル位置に挿入する別のメソッドである.最初のメソッドのprintButtonFuncではNotebookApplyを使った.ただPrintを呼び出してもよい.グラフィックスでそうなように,Print出力はフロントエンド共有がオンのときは J/Link によって最前のノートブックウィンドウに自動的にまわされる.この速くて簡単なPrintメソッドはノートブックウィンドウに何かを表したいとき,大体うまくいく.しかしNotebookApplyの使用はもっと厳密な方法である.StandardFormセルに挿入ポイントを置いて試すと,この2つのボタンの効果の違いが分かる.
この例ではいくつかの便利なテクニックを示しているが,ShareFrontEndは特別価値のある方法ではない.単純なボタンのパレットを作成するとき,フロントエンド自体でできないことは何もしていない.ShareFrontEndが実際に効果を発揮するのは,より洗練されたダイアログボックスやその他のユーザインターフェース要素のようにフロントエンド内でコピーできないものがある場合である.
Real-Time Algebra:ミニアプリケーション
この例では,モーダルとモードレスのJavaユーザインターフェースについて説明してきたことをすべて組み合せ,同じ「ミニアプリケーション」(単なるダイアログボックス)をモーダル,モードレスの両方で実装してみる.このアプリケーションは古典的なWSTP例題であるRealTimeAlgebraにヒントを得たものである.これはもともとTheodore GrayによってNeXTコンピュータのために書かれ,Doug SteinとJohn BonadiesによってHyperCardで書かれたものである.オリジナルのRealTimeAlgebraは,ユーザが特定のパラメータに依存する数式をタイプする入力ウィンドウ,演算の結果を表示する出力ウィンドウ,パラメータの値を変えるために使われるスライダーを提供している.出力ウィンドウはスライダーが動くとアップデートするのでRealTimeAlgebraという名前が付いている.ここでのRealTImeAlgebraの実装は,1つのパラメータの値を変更するスライダーが1つだけの,非常に単純なものである.
この例題の完全コードはJLink/Examples/Part1ディレクトリのRealTimeAlgebra.nbファイルにある.
sliderFunc関数はスライダーの位置が変わるたびにMathAdjustmentListenerによって呼び出される.この関数はinputTextボックスにテキストを入れ,それをaがスライダーの位置の値(スライダーを生成するJavaNewの呼出しで設定されているように,その範囲は0…20)を持つ環境で評価し,outTextボックスに結果の文字列を入れる.それからReleaseJavaObjectを呼び出し,1つ目の引数であるAdjustmentEventオブジェクト自身を解放する.これは引数として渡される唯一のオブジェクトである(他の2つは整数).sliderFuncの引数の列は「Wolfram言語コードでイベントを扱う:『MathListener』クラス」の MathListenerの表から分かる.sliderFuncの中の入力および出力ボックスは名前で参照しなければならないので,その名前をCreateWindowのModule内のローカル変数にすることはできないし,もちろんその関数のJavaBlock内で生成することもできない.
コードについて面白いことが1つある.フレームに3つのコンポーネントを加えるラインで,ReturnAsJavaObjectは何をしているのであろうか.ここで呼び出しているメソッドはFrameクラスにあり,次のシグネチャを持っている.
2つ目の引数,constraintsの型はObjectとだけ指定されている.渡す値は使用中のレイアウトマネージャに依存するが,ここでのように一般的には文字列である(例えば,BorderLayout`NORTHは単に文字列「NORTH」となる).問題は,J/Link は2つ目の引数にJavaObjectを取るaddのシグネチャの定義を生成するのであるが,Wolfram言語の文字列が送られるときにJavaの文字列オブジェクトに変換されても,それではJavaObjectQを満足しないということである.つまり,文字列はString型の引数を取るメソッドにしか渡せないのである.まれにJavaメソッドの型がObjectを取るので,Wolfram言語からの文字列を渡したいという場合があるが,このようなときは,まず希望の値でJavaのStringオブジェクトを生成し,生のWolfram言語の文字列の代りにそのオブジェクトを渡さなければならない.この問題にはもうすでに何回か遭遇しており,生の文字列をJavaのStringオブジェクトへの参照に変換するトリックとしてMakeJavaObjectを使っている.MakeJavaObject[BorderLayout`NORTH]はここではうまく動作するが,異なるテクニックを使った方がよい(そうするとJavaを呼び出さなくても済む).BorderLayout`NORTHはJavaを呼び出し,BorderLayout.NORTHのstaticフィールドの値を得るが,この文字列オブジェクトをWolfram言語に返している間に,それは生のWolfram言語の文字列に変換される.生の文字列ではなくオブジェクト参照が必要なので,通常値によって返される文字列を引き起すReturnAsJavaObjectのアクセスをラップし,参照の形で返されるようにする.
RealTimeAlgebraダイアログボックスに戻る.モーダルウィンドウとして起動する準備ができている.内部的にCreateWindowを使う特別なモーダルバージョンを書く.
関数全体がJavaBlockでラップされている.これにより,ダイアログ実行中にWolfram言語で生成されたすべてのオブジェクト参照を一時的なものとして扱い,DoModalが終了したら解放するということが簡単に確実にできる.これでMathListenerオブジェクトに使われるすべてのハンドラ関数のJavaBlockとReleaseJavaObjectが適切に使える(これらの呼出しはsliderFunc関数にはない).
ダイアログを実行する.RealTimeAlgebraModal関数はRealTimeAlgebraウィンドウを閉じるまで戻らない.この意味で,「モーダル」インターフェースなのである.
初めてウィンドウが現れるときは数秒かかるかもしれない.いつものように,これは必要なクラスをすべてロードするときの1回限りのコストである.スライダーをドラックしてみたり,入力ボックスのテキストをN[Pi,2a]等に変える等の操作を試していただきたい.
Wolfram言語がDoModal[]を評価している間にJavaから起る演算の結果以外のあらゆるPrint出力,メッセージ,グラフィックス,その他の出力,コマンドはフロントエンドに送られる.これを評価中に見るためには,入力テキストボックスにPrint[a]を入れる(スライダーをドラッグしている間にノートブックウィンドウが見られるように,スクリーンのウィンドウを並べ替えるとよい).次にPlot[Sin[a x],{x,0,4 Pi}]を実行してみる.
ウィンドウのクローズボックスをクリックしてRealTimeAlgebraを終了する.ウィンドウを閉じて処分することに加え,これによりWolfram言語でEndModal[]が実行され,DoModalが返ってくる.ウィンドウの処分が行われるのは,ウィンドウにMathFrameクラスを使ったためであり,EndModal[]の実行は「モーダルウィンドウ」で説明したようにMathFrameのsetModal()メソッドを呼び出した結果である.
次にRealTimeAlgebraをモードレスウィンドウとして実装してみる.CreateWindow関数はそのまま使うことができる.唯一異なる点は,スライダーをドラッグすることで起きた演算をどのようにWolfram言語が処理できるようにするかということだけである.モーダルウィンドウでは,DoModalを使ってWolfram言語がJavaリンクだけに注意を払うように強制した.この欠点は,DoModalが終了するまでノートブックフロントエンドからカーネルを使うことができないということである.ノートブックフロントエンドとJavaがカーネルの注意を共有できるようにするには,ShareKernelを使う.
RealTimeAlgebraModelessは,フロントエンドとRealTimeAlgebraウィンドウが演算のためにカーネルを使用できる状態にしたままで,ウィンドウが表示されるとすぐに戻る.
しかしながら,まだモードレスバージョンには少し磨きを書ける必要がある.まず,オブジェクト参照のリークを防ぐために,sliderFuncを変えなければならない.モーダルバージョンでは,DoModalをJavaBlockでラップしたので,sliderFuncでJavaBlockやReleaseJavaObjectを使う必要がなかった.sliderFuncや他のMathListenerハンドラ関数の呼出しはすべて,完全にDoModalのスコープ内で起るので,このレベルで解放するオブジェクトはすべて扱うことができる.モードレスインターフェースでは,ウィンドウの寿命と同じ長さの関数の呼出しはもうない.従って,ハンドラ関数にメモリ管理関数を置く.これが新しいsliderFuncである.
ここではラップされるコードは新しいオブジェクト参照を生成していないので,JavaBlockは必要ない.しかし,習慣でJavaBlockのこのようなハンドラをラップする.evtに対してAdjustmentEventオブジェクトであるReleaseJavaObjectを明示的に呼び出す必要がある.というのはその参照はsliderFuncが入力される前にWolfram言語で生成されるので,JavaBlockによって解放されないからである.typeとscrollPos引数は整数で,オブジェクトではない.
入力テキストをPrint[a]に設定する.モーダルの場合とは対照的に,スライダーを動かしたときにはフロントエンドでは何も見えない.モードレスインターフェースでは,カーネルがJavaサイドから始まった要求をサービスしている間は,Javaリンクはカーネルの$ParentLinkである.従って,Printとグラフィックスからの出力はノートブックフロントエンドではなく,Javaに行く(ちなみに,Java側はこの出力を無視する).代りにこの出力をフロントエンドに送るにはShareFrontEndを使う.
入力テキストを例えばPrint[a]あるいはPlot[a x,{x,0,a}]に設定すると,フロントエンドに現れるテキストやグラフィックスを見ることができる.
終わったら,クローズボックスをクリックしてRealTimeAlgebraを終了する.前の入力でオンになったフロントエンド共有をオフにする.
モーダルインターフェースはWolfram言語をどのように使うかという点でモードレスよりも簡単である.従って,特にモードレスの属性が必要でない限りモーダルの方が好まれる.ShareKernelとShareFrontEndはカーネルを通常とは違った状態に置く複雑な関数である.これらはうまく動作するが,不必要な場合に使わないようにしなければならない.
GraphicsDlg:ウィンドウでのグラフィックスおよびタイプセットの出力
JavaユーザインターフェースでもWolfram言語のグラフィックスとタイプセット式が表示できると便利である.これは J/Link のMathCanvasクラスを使うと簡単にできる.ここの例では,ユーザがWolfram言語の式をタイプし,絵図としてその出力を見ることができる簡単なダイアログボックスを示す.式がプロットやその他のグラフィックス関数なら,結果の画像が表示される.式がグラフィックスではない場合は,TraditionalFormでタイプセットされ,絵図として表示される.まず,モーダル形式での例を示し,次にShareKernelとShareFrontEndを使ったモードレス形式での例を挙げる.
この例では,ほとんどのJava開発環境に存在するタイプのドラッグアンドドロップGUIビルダによって作成したJavaコードを使った小さな例も見せる.単純なウィンドウのレイアウトでは,簡単にWolfram言語からすべてのことを行うことができる.このチュートリアルではすべての例題でこの方法を使い,Javaコードを書かずにWolfram言語がJavaを呼び出すウィンドウのコントロールの作成とレイアウトを書いた.これにはどのJavaクラスを書くこともコンパイルすることも必要ないという利点がある.しかし,より複雑なウィンドウに対しては,コントロールを作成したり,並べたり,GUIビルダの属性を設定したりそれでJavaコードを生成したりするのが簡単かもしれない.手書きで付加的なJavaコードをいくつか書きたいこともあるかもしれない.
この方法を選ぶとしたら,問題は「このように生成されたJavaコードをどのようにWolfram言語と接続させればよいか」ということである.publicフィールドやメソッドはどれもWolfram言語から直接呼び出すことができるが,GUIビルダでは使う必要のあるものがpublicになっていないかもしれない.このようなフィールドやメソッドをpublicにしたり,それらを提示する新しいpublicメソッドを加えたりすることができる.GUIビルダが書いたコードを修正すると,ビルダを混乱させたり将来の修正でこの変更を上書きしたりする可能性があるので,修正の必要のない後者の方が望ましい.
この例の完全コードはJLink/Examples/Part1/GraphicsDlgディレクトリにある.コードのいくつかはJavaで書かれている.
この例は WebGain Visual Café Java開発環境のGUIビルダを使っている.ビルダは3つのコントロールを持ったフレームウィンドウを作成するために使った.setModal()メソッドを継承したいのでフレームウィンドウはMathFrameのサブクラスにした.左上には数式の入力ボックスとしての役割を果たすAWT TextAreaがある.その右にはEvaluateボタンがある.その他の部分を占めるのはMathCanvasである.
この時点まで,手書きのコードはひとつもない.コンポーネントをフレームに入れてその属性を設定したら自動的にすべてが完了した.後はクリックすると入力テキストを取り,setMathCommand()メソッドでMathCanvasに渡すようにボタンをつなぎ合わせるだけである.このイベントをつなぎ合わせるためにVisual CaféのInteraction Wizard(他のJava GUIビルダにも同様の機能がある)を使ってJavaでコードを書くことができる.コードのロジックはイベントハンドラを作成するグラフィックスツールが扱えるものより複雑なので,いくつかのコードは手で書かなければならない.
それをするより,残りの動作を書くためにより簡単で柔軟性があるWolfram言語を使う.Wolfram言語からTextArea,Button,MathCanvasオブジェクトにアクセスする必要があるが,GUIビルダがすでにフレームクラスのこれらのnonpublicフィールドを作っている.従って,フレームクラスにこれらのオブジェクトを返す3つのpublicメソッドを加える必要がある.
public Button getEvalButton() {return evalButton;}
public TextArea getInputTextArea() {return inputTextArea;}
public MathCanvas getMathCanvas() {return mathCanvas;}
これがGUIビルダによって作成されたJavaコードに必要なすべてのものである.
GUIビルダはGraphicsDlgと指定したMathFrameのサブクラスを作成した.また,フレームのインスタンスを作りそれを可視にするだけのmain()メソッドも与えた.フレームの参照が必要なので,main()メソッドは気にせず,この2つのステップを手動で行う.
コードを実行する前に,クラス検索パスにダイナミックにディレクトリを加えることができるという,J/Link のもう1つの機能を例示する.この例のためにJavaクラスをロードする必要があるが,それらはJavaクラスパスにはない.J/Link では,AddToClassPathを呼び出すことにより,クラスがあるディレクトリを検索パスに加えることができる.これはMathematica 4.2以降で書かれているのと全く同じように動作する.それより前のバージョンのWolfram言語では,パスを変更しなければならない.
これがWolfram言語コードの最初の実装で,グラフィックスダイアログを作成し起動する.これはモーダルループでダイアログを実行する.
「ウィンドウやその他のユーザインターフェース要素の作成」でノートブックフロントエンドだけがタイプセット式(すなわち「box」)を取り,その画像表現をフロントエンドに作成できると説明した.従って,MathCanvasは適切な表示をする役割を請け負わせることができれば,タイプセット式を描画することができる.この例を実行するためにフロントエンドを使っているが,すべてがあたりまえに動くのはJavaダイアログを「モーダル」で実行しているからである.ダイアログが終ったらフロントエンドは演算(DoModal[])からの結果を待つので,カーネルからのいろいろなサービスの要求を受け入れる.フロントエンドに関する限り,実際にはJavaボタンのクリックで起きたものでも,タイプセットの要求はDoModalのコードが引き起している.
ダイアログをモーダルで実行するという制限に満足できなかったらどうであろうか.ここでフロントエンドからのカーネルの通常使用を妨げない一方,ダイアログを開いたままアクティブな状態にしておきたいとする.「モーダルウィンドウ」と「Real-Time Algebra:ミニアプリケーション」で説明したように,Javaインターフェースをモーダルで起動すると,フロントエンドに関するたくさんの便利な動作を自由に得ることができる.そのうちのひとつは,フロントエンドはカーネルが送ることのできるいろいろな種類の要求(例えば,タイプセットサービス)を受け入れられるようになっていることである.ShareKernelを使ってJavaユーザインターフェースを「モードレス」で起動することもできるが,そうするとJavaのアクションで始まった演算中にカーネルがフロントエンドを使用する能力をあきらめなければならない.しかし,幸いにも,ShareFrontEnd関数があるので,モードレスウィンドウでもこの機能を復元することができる.
上のコードは最後の数行以外はDoGraphicsDialogModalと全く同じである.ここではsetModalとDoModalの代りにShareFrontEndを呼び出す.これが唯一の違いで,その他の部分は(btnFuncを含めて)全く同じである.MathCanvasのonClose()メソッドを使ってウィンドウが閉じたときにフロントエンド共有の要求を解除するコードを実行する.
モードレスバージョンを実行する.ウィンドウがアクティブな間にどのようにフロントエンドで演算を実行し続けることができるかご覧いただきたい.
この新バージョンはフロントエンドを演算の途中で停止したままにしておかないこと以外はモードレスバージョンと全く同じである.フロントエンド共有をオフにしたらどうなるかと対照させると面白いであろう(しかしカーネル共有をオンにしておかないと,Javaダイアログが完全に壊れてしまう).DoGraphicsDialogModelessのShareFrontEndとUnshareFrontEndをShareKernelとUnshareKernelで置きかえることで比較することができる.こうするとダイアログを使っても,タイプセット式は返されず,ただ空白のウィンドウができるだけである,(プロットラベルのようなタイプセット要素がなければ)グラフィックスは正しく描画される.カーネルがタイプセットサービスのためにフロントエンドを利用する能力以外はすべての機能を損なわずに済む.
BouncingBalls: ウィンドウへの描画
ここではWolfram言語から直接JavaグラフィックスAPIを使ってJavaウィンドウで描画してみる.また,ServiceJava関数を使ってイベントハンドラが定期的にJavaからWolfram言語へコールバックできるようにするのも示す.ServiceJavaについての問題や,DoModalやShareKernelとどのように比べられるのかは「『マニュアル』インターフェース:ServiceJava関数」でより詳しく説明する.
コードはここで全部入れると長すぎるが,JLink/Examples/Part1ディレクトリのBouncingBalls.nbサンプルファイルにある.これがServiceJavaの使用を示す抜粋である.
...
mwl = JavaNew["com.wolfram.jlink.MathWindowListener"];
mwl@setHandler["windowClosing", "(keepOn = False)&"];
mathCanvas@addWindowListener[mwl];
keepOn = True;
While[keepOn,
g@setColor[bkgndColor];
g@fillRect[0, 0, 300, 300];
drawBall[g, #]& /@ balls;
mathCanvas@setImage[offscreen];
balls = recomputePosition /@ balls;
ServiceJava[]
];
...
MathWindowListenerはウィンドウが閉じられたらkeepOn=Falseに設定するために使われている.これによりループが終了する.ウィンドウが開いている間,マウスクリックで新しいボールが生成され,ballsリストに加わり,動き出す.これはMathMouseListener(上には示されていない)で実行できる.従って,Wolfram言語はJavaのユーザアクションから始まった呼出しを扱うことができなければならない.「ウィンドウやその他のユーザインターフェース要素の作成」の説明のように,それにはDoModal(モーダルインターフェース),ShareKernelあるいはShareFrontEnd(モードレスインターフェース),ServiceJava(マニュアルインターフェース)の3つの方法がある.カーネルはJavaからの呼出しにサービスするのと同時に何かを演算する必要がある(ここではボールの新しい位置を計算して描画する)ので,DoModalでのモーダルループは適切ではない.また,演算中ではなく,フロントエンドから始まった演算と演算の間にJavaがカーネルにアクセスできるようになるので,ShareKernelはここでは役に立たない.
もし保留中の要求があるなら,定期的にカーネルの注意をJavaに向けて要求をサービスさせ,カーネルをまた他の仕事に戻さなければならない.これを行う関数がServiceJavaで,上のコードは毎回ServiceJavaを呼び出すループを持っているという点において典型的なものである.ServiceJavaが扱うJavaからの呼出しは,新しいボールを生成するためのマウスクリックからのものと,ウィンドウが閉じられるときのものである.
スパイログラフ
最後の例では面白く非凡なアプリケーションを作成する.単純なスパイログラフタイプの描画プログラムの実装である.モーダルウィンドウとして起動するが,さまざまなイベントコールバックのための数多くのMathListenerオブジェクトともにWolfram言語からJavaウィンドウへの描画を示す.これにはJavaGraphics2D APIを使うのでJava1.1.xランタイムしかないシステムでは起動できない.
この例のコードはJLink/Examples/Part1ディレクトリのSpirograph.nbファイルにある.
妥当な速さのマシンでは実行速度は十分に許容範囲内である.アプリケーションの全機能がWolfram言語から書かれてることを示すものは何もない.多数のWolfram言語へのコールバックが起るという事実にもかかわらずそれは素早く反応する.例えば,ウィンドウのいろいろな領域上にマウスを持っていくとカーソルが変わる(リサイズカーソルになるところもある)ので,マウスを動かすにつれていつもWolfram言語へのコールバックがある.この例では,完全にWolfram言語で洗練されたアプリケーションを書くことが可能であることを示す.
このアプリケーションはWolfram言語で書かれているが,完全にJavaで書くことも,JavaとWolfram言語とを組み合せて書くことも可能である.Wolfram言語で書くことの利点はずっと生産的になれることである.スパイログラフはJavaで書くと2倍の長さになる.一度に一行ずつプログラムを書きテストできること,またプログラム起動中にデバッグと修正ができることは非常に貴重である.たとえ最終的にコードを純粋Javaにポートするつもりでも,開発のスクリプトモードを利用してWolfram言語で書き始めるのは非常に便利である.
このようなモーダルプログラムはShareFrontEndを使って開発すると最もうまくいく.完了したときにはじめてモーダルにするのである.開発中にモードレスにすることはインタラクティブに構築,デバッグすることができるためには必要である.というのは起動中にフロントエンドを使ってコードの修正をし続けたり,新しい定義を作ったり,デバッグの文を加えたりすることができるからである.モードレス操作のためにShareKernelの代りにShareFrontEndを使うと,イベントコールバックによって生成されたWolframシステムエラーや警告メッセージと,デバッグのために挿入されたPrint文が,ノートブックウィンドウに現れる.すべてが望み通りに動いているときだけ,モーダルウィンドウにするためにDoModal[]の呼出しを加えるのである.
ピアノのキーボード
Java 1.3以降にJava Sound APIが加わったことで,MIDIの再生等,サウンドを使って洗練されたことを行うJavaプログラムを書くことが可能になった.JLink/Examples/Part1ディレクトリのPiano.nbの例題は,キーボードを表示してユーザのマウスクリックでそれを弾いてもらうというものである.上部のポップアップメニューには使用可能なMIDIがリストされている.この例は,今までのWolfram言語のプログラミングの限界を大きく超えたものという理由で作成された.J/Link を使うと,Wolfram言語だけで短く完全にポータブルなプログラムを実際に書くことができ,そのプログラムでMIDIキーボードを表示して弾いてもらうことができる.もう少し手を加えるだけで,コードを編集して弾いたサウンドを録音し,それをWolfram言語に返すようにすることができ,Wolfram言語でテンポを入れ替えたり,変化させたりしてそのサウンドを操作することもできる.