Wolfram言語を使うJavaプログラムを書く
はじめに
このユーザガイドの前半では,J/Link を使ってWolfram言語からJavaを呼び出すことで,Wolfram言語環境を拡張して既存のあるいは将来のJavaクラスの機能性を取り込む方法について説明した.このチュートリアルはそれとは反対の方向で,Wolfram言語カーネルを演算エンジンとして使うJavaプログラムを書く手段としての J/Link の使い方を説明する.
J/Link はプログラム間でデータやコマンドを送り合うWolfram ResearchのプロトコルWolfram Symbolic Transfer Protocol (WSTP)を使う.J/Link プログラミングの多くの概念や技術はWSTP C言語APIでのプログラミングのものと同じである.J/Link ドキュメントはWSTPを使うJavaプログラムを書くために知っておく必要のあることの百科事典となることを意図しているのではない.プログラマーは「WSTP プログラミング」の一般的なドキュメントに少しは頼らなければならないかもしれない.C言語には J/Link が提供する関数の多くに対応する全く同じ,あるいはほとんど同じものがある.
「Wolfram言語からJavaを呼び出す」をまだ読んでいない場合は,さっと目を通しておいた方がよい.Java「フロントエンド」はWolfram言語コードからJavaメソッドを呼び出すのと,プログラマーがノートブックフロントエンドからカーネルを起動しているときに使う引数としてJavaオブジェクトを渡すのに同じ技術を使うことができる.これにより,JavaとWolfram言語間の非常に高レベルなインターフェースが可能になる.CでWSTPプログラムを書いているときは,文字列や整数のような単純なものを渡したり返したりすることについて考えなければならない.J/Link を使うと,JavaとWolfram言語との間でJavaオブジェクトをやり取りすることができる.J/Link は本当にJavaとWolfram言語との間の境界を取り除くのである.
ユーザガイドの後半は次のように構成されている.「WSTPとは?」はWSTPの簡単なイントロダクションである.「序文」では最も重要な J/Link インターフェースとクラスを紹介する.「サンプルプログラム」では簡単な例題を見せる.「MathLinkFactoryでリンクを作成する」ではWolfram言語の起動方法とリンクの作成方法を説明する.「MathLinkインターフェース」と「KernelLinkインターフェース」では大規模で重要なMathLinkとKernelLinkインターフェースメソッドのリストを提供する.メソッドは関数でグループ分けされて,コメントが添えてある.しかしこれはJLink/Documentation/JavaDocディレクトリにある,J/Link の実際のJavaDocヘルプファイルに代るものではない.JavaDocファイルは J/Link の主なメソッドごとのリファレンスであり,プログラマーが使うクラスやインターフェースをすべて含んでいる.この他,例外の扱い方やグラフィックス,タイプセット出力の取得方法を含む J/Link プログラミングの多くの重要なトピックを説明する.
このテキストの閲覧中や,JavaあるいはWolfram言語でのプログラミング中は,J/Link の全ソースコードが提供されていることを覚えておいていただきたい.どのように動作するか(あるいはなぜ動作しないか)が知りたければ,直接ソースコードを見ることができる.
WSTPとは?
Wolfram Symbolic Transfer Protocol (WSTP)とはプログラム間のコミュニケーションのためのプラットフォーム非依存のプロトコルである.具体的にいえば,Wolfram言語の式を送ったり受け取ったりする手段といえる.WSTPはノートブックフロントエンドとカーネルがお互いに通信し合う手段なのである.それはまた,Wolfram言語と他のプログラムあるいは言語をリンクする多数の市販あるいは無料のアプリケーション,ユーティリティによっても使われている.
WSTPはC言語関数のライブラリとして実装される.Javaのような他の言語から使うには通常データ型間の変換をする「グルー」コードを書き,その言語とCの規則を呼び出すことが必要である.J/Link の中心にはJavaのJNI (Java Native Interface)仕様を使って構築されたライブラリである,そのような変換層がある.
WSTPは以前 MathLink と呼ばれていたため, J/Link クラスやインターフェースでこのレガシーの名前が現れるのである.
J/Link の主要インターフェースとクラスの概要
序文
J/Link クラスはユーザのコード変換を必要とすることなく,将来における拡張性を最大化することを意図してオブジェクト指向型で書かれている.これにはインターフェースと実装のはっきりした区別が必要である.これは主要なリンク機能をクラスではなくインターフェースで示すことにより達成できる.プログラマーはこのようなインターフェースを実装する具象クラス名を知ったり気にしたりする必要はないので,これについてはほとんど言及していない.むしろ,インターフェースタイプのひとつに属するオブジェクトを使う.直接インスタンスを生成することはないので,実際のクラスが何であるかを知る必要はない.その代りリンククラスのインスタンスを作成するために「factory メソッド」を使う.このことについては後ほどはっきり分かる.
MathLinkとKernelLink
最も重要なリンクインターフェースはMathLinkとKernelLinkである.MathLinkインターフェースは実質的にWSTP C APIをJavaにポートしたものである.メソッドの名前のほとんどは,経験のあるWSTPプログラマーにはお馴染みであろう.KernelLinkはMathLinkを拡張して,もう一方のリンク先がWolfram言語カーネルのときにだけ意味をなす,例えば,もう一方のリンク先は定義された一連のパケットに反応すると仮定するwaitForAnswer()メソッドのような重要で高レベルの便利なメソッドを加える.
MathLinkインターフェースは,リンクのもう一方にはどのプログラムがあるかについて仮定することなく,リンクに対して実行されるすべての操作を囲み込む.KernelLinkは,もう一方がWolfram言語カーネルだという仮定を加える.将来,MathLinkを拡張し,リンクを介した通信のその他の規則をカプセル化する他のインターフェースも加えられることであろう.
ほとんどのプログラマーがKernelLinkだけを使用しているという意味で,KernelLinkは最も重要なインターフェースである.もちろんKernelLinkはMathLink拡張するので,KernelLinkオブジェクトで使うメソッドの多くはMathLinkインターフェースで宣言されドキュメントされている.
MathLinkを実装する最も重要なクラスはNativeLinkである.これはネイティブメソッドを使ってWolfram ResearchのWSTPライブラリを呼び出すのでそのような名前が付いている.将来,ネイティブメソッドに依存しない,例えばネットワーク上での通信にRMIを使ったりするような,他のクラスが加えられる.前述のように,ほとんどのプログラマーはコードにリンククラスをタイプすることはないので,これらがどのクラスかを心配する必要はない.
MathLinkFactory
MathLinkFactoryはリンクオブジェクトを生成するのに使用するクラスである.いろいろな引数の列を取るcreateMathLink(),createKernelLink(),createLoopbackLink()というstaticメソッドが含まれる.CプログラムでWSOpenを呼び出すのと同じである.MathLinkFactoryメソッドについては「MathLinkFactoryでリンクを作成する」で詳しく説明する.
MathLinkException
MathLinkExceptionはMathLinkとKernelLinkの多くのメソッドによって投げられる例外クラスである.J/Link APIは,エラーを示すのにWSTP C APIのような関数の戻り値ではなく例外を使う.Cでは,以下のように戻り値をチェックするコードを書く.
J/Link では,WSTP呼出しをtryブロックでラップしてMathLinkExceptionを受け取る.
Expr
ExprクラスはJavaでのWolfram言語の式の直接表現を提供する.Exprは式の構造についての情報を与え,コンポーネントを抽出できるようにする多くのメソッドを持っている.これらのメソッドには,length(),part(),numberQ(),vectorQ(),take(),delete()等,Wolfram言語プログラマーにお馴染みの名前や動作がある.入ってくる式の構造や属性を見付けるのに低レベルのMathLinkインターフェースメソッドを使う代りに,リンクから読むときにはgetExpr()を使ってリンクから式全体を読み,Exprメソッドを使ってそれを点検し分解することができる.リンクに書くには,Exprは最も重要ないくつかのKernelLinkメソッドの引数として使うことができる.Exprクラスについては「Exprクラスの動機付け」で詳しく説明する.
PacketListener
標準C WSTPプログラムの中心的コンポーネントはパケット読込みループで,それは通常希望のパケットが見付かるまでWSTP API関数のWSNextPacketとWSNewPacketを呼び出すということで構成される.J/Link プログラムは通常そのようなループは含んでおらず,代りに内部にパケットループを隠すKernelLinkメソッドのwaitForAnswer()かdiscardAnswer()を呼び出す.これで同じコードをどのプログラムにも書かなければならない手間が省けるだけではない.状況によっては J/Link が内部的に扱う必要のある特殊なパケットが到着するかもしれず,プログラマーが正しいパケットを書くことができないこともあるので,これは必要なことでもある.従って,プログラマーからパケットループの詳細を隠す必要がある.しかし,プログラマーがパケット到着を観察したり,その操作を行いたい場合もある.その典型的な例は,演算によって生成されたPrint出力やメッセージの表示である.これらは演算の2次的な出力で「解答」ではないので,通常はwaitForAnswer()によって捨てられる.
この必要性を満たすために,KernelLinkオブジェクトは内部パケットループを起動している間に見付かるそれぞれのパケットについてPacketArrivedEventを発生させる.PacketListenerインターフェースを実装するクラスを作成し,このクラスのオブジェクトをKernelLinkオブジェクトに登録することにより,これらのパケットの通知を受け取るよう登録することができる.PacketListenerインターフェースはpacketArrived()というメソッドを1つだけ持ち,それは各パケットについて呼び出される.packetArrived()メソッドは内部パケットループに影響を与えることなくパケットを使ったり無視したりできる.非常に上級のプログラマーは内部パケットループがパケットを見てはならないということを選択的に示すことができる.
PacketListenerインターフェースについては「PacketListenerインターフェースの使用」で詳しく説明する.
高レベルなユーザインターフェースクラス
J/Link にはユーザインターフェースを持つプログラムを作成するのに便利なクラスがいくつか含まれている.その中で,MathCanvasとMathGraphicsJPanelクラスはWolfram言語グラフィックスとタイプセット式を簡単に表示できる方法を提供する.これらのクラスは「MathCanvasとMathGraphicsJPanelクラス」での説明のように,Wolfram言語コードから使われることがよくあるが,Javaプログラムでも大変有用である.これについては「MathCanvasとMathGraphicsJPanel」で述べる.Javaコードからは,ユーザインターフェースに動きがあるとWolfram言語での評価を実行するいろいろな「MathListener」クラス(「Wolfram言語コードでイベントを扱う:「MathListener」クラス」)を使うことができる.
com.wolfram.jlink.uiパッケージ内のクラスは,J/Link 2.0で新しく加わったものである.これらのクラスは非常に高レベルのユーザインターフェース要素を提供する.ConsoleWindowクラスというのがあり,これは出力のコンソールウィンドウを表示する(これは「Javaコンソールウィンドウ」で述べた,Wolfram言語関数のShowJavaConsoleを実装するために使うクラスである).InterruptDialogクラスは,計算を中断したり放棄したりするのに使える評価を中断ダイアログを表示する.MathSessionPaneクラスはWolframシステムのIn/Outセッションウィンドウを表示する.これにはカット,コピー,ペースト,取消し,やり直し,グラフィックスのサポート,文法の色付け,カスタマイズできるフォントスタイルを含む編集機能一式が含まれる.補助的なクラスのSyntaxTokenizerとBracketMatcherはMathSessionPaneによって使われるが,自分のプログラムにこのようなサービスを提供するために別々に使うこともできる.上記のクラスはすべて「特殊なユーザインターフェースクラス:はじめに」で説明する.
サンプルプログラム
これはWolfram言語カーネルを起動して,それを演算に使い,終了する基本的なJavaプログラムである.このプログラムは,JLink/Examples/Part2ディレクトリでソースコードとコンパイルされた形式で提供されている.カーネルへのパスを含む通常のWSTPの引数は,プログラムを実行するのに使うコマンドラインで与える.以下に典型的な例が示してある.自分のシステムに合うように,Wolfram言語カーネルのパスを修正する必要がある.CLASSPATH環境変数をJLink.jarを含むように設定してある場合は,このコマンドラインの-classpath指定は省いても構わない.ここでは,これらのコマンドはJLink/Examples/Part2ディレクトリから実行されるものと想定している.
(Windows)
java -classpath .;..\..\JLink.jar SampleProgram -linkmode launch -linkname "c:\program files\wolfram research\mathematica\10.0\mathkernel.exe"
(Linux)
java -classpath .:../../JLink.jar SampleProgram -linkmode launch -linkname 'math -mathlink'
(Mac OS X from a terminal window)
java -classpath .:../../JLink.jar SampleProgram -linkmode launch -linkname '"/Applications/Mathematica.app/Contents/MacOS/MathKernel" -mathlink'
以下はSampleProgram.javaからのコードである.このプログラムではMathLinkFactory.createKernelLink()でカーネルを起動する方法を示している.また,Wolfram言語へ演算を送って結果を読むいくつかの異なる方法も示している.
import com.wolfram.jlink.*;
public class SampleProgram {
public static void main(String[] argv) {
KernelLink ml = null;
try {
ml = MathLinkFactory.createKernelLink(argv);
} catch (MathLinkException e) {
System.out.println("Fatal error opening link: " + e.getMessage());
return;
}
try {
// カーネルが起動したときに送る
// 最初のInputNamePacketを捨てる.
ml.discardAnswer();
ml.evaluate("<<MyPackage.m");
ml.discardAnswer();
ml.evaluate("2+2");
ml.waitForAnswer();
int result = ml.getInteger();
System.out.println("2 + 2 = " + result);
// 同じ入力を,文字列としてではなく送る方法.
ml.putFunction("EvaluatePacket", 1);
ml.putFunction("Plus", 2);
ml.put(3);
ml.put(3);
ml.endPacket();
ml.waitForAnswer();
result = ml.getInteger();
System.out.println("3 + 3 = " + result);
// 結果を文字列として得たい場合は,evaluateToInputForm
// あるいはevaluateToOutputFormを使う.どちらの場合も
// 2番目のargは文字列のフォーマットのために要求されるページ幅である.
// PageWidth->Infinityとするには0を渡す.これらのメソッドは,
// 1ステップで結果を返す.waitForAnswerを呼び出す必要はない.
String strResult = ml.evaluateToOutputForm("4+4", 0);
System.out.println("4 + 4 = " + strResult);
} catch (MathLinkException e) {
System.out.println("MathLinkException occurred: " + e.getMessage());
} finally {
ml.close();
}
}
}
MathLinkFactoryでリンクを作成する
実装の詳細から J/Link クラスのクライアントを分けるには,クライアントがコードで明示的にリンククラスを指定していてはならない.つまりプログラムはリンククラスのインスタンスを作成するためにnewを決して呼び出さないということである.その代り,渡す引数によって適切なインスタンスを作成してくれるいわゆる「ファクトリメソッド」が与えられる.このファクトリメソッドはCプログラムのWSOpenの呼出しに代るものである.
KernelLinkを作成するメソッドはMathLinkFactoryクラスのcreateKernelLink()というstaticメソッドである.
public static KernelLink createKernelLink(String cmdLine) throws MathLinkException
public static KernelLink createKernelLink(String[] argv) throws MathLinkException
. . . plus a few more of limited usefulness
同じ引数を取るがKernelLinkではなくMathLinkを作成するcreateMathLink()という関数が2つある.これを使うのはWolfram言語カーネル以外にプログラムを接続しているときなので,createMathLink()を使う必要のあるプログラマーはほとんどいない.メソッドの完全リストはJavaDocファイルをご覧いただきたい.
createKernelLink()の2つ目のシグネチャは,プログラムを起動したコマンドラインパラメータを使用している場合に便利である.これはもちろん引数の配列としてmain()関数に与えられている.この使い方の例は「サンプルプログラム」の例題にある.例えば次のように1つの引数としてパラメータを指定するのに便利なときもある.
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'c:\\program files\\wolfram research\\mathematica\\10.0\\mathkernel'");
リンク名の引数がシングルクォート(')でラップされていることにご注意いただきたい.これはWSTPがこの文字列を完全なコマンドラインとして解析するからで,シングルクォートでラップすることでその中をファイル名として読ませるようにできるのである.また,CやWolfram言語のようにJavaも\を後続する文字をクォートするメタ文字として扱うので,Javaコードに直接的の文字列をタイプしているときに,Windowsディレクトリセパレータ記号を示すときは2つのバックスラッシュをタイプしなければならない.
以下に示すのは,さまざまなプラットフォームにおいて,createKernelLink()の引数が文字列で与えられる場合の一般的な引数である.ここでクォート文字の「'」と「"」が使ってある.
// Windowsでの一般的な起動
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'c:\\program files\\wolfram research\\mathematica\\10.0\\mathkernel.exe'");
// Linuxでの一般的な起動
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'math -mathlink'");
// Mac OS Xでの一般的な起動
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname '\"/Applications/Mathematica.app/Contents/MacOS/MathKernel\" -mathlink'");
// 全プラットフォームでの一般的な「listen」リンク
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode listen -linkname foo");
以下はcreateKernelLink()の引数が文字列の配列として与えられる場合の一般的な引数である.
// Windowsでの一般的な起動
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'c:\\program files\\wolfram research\\mathematica\\10.0\\mathkernel.exe'");
// Linuxでの一般的な起動
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'math -mathlink'");
// Mac OS Xでの一般的な起動
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname '\"/Applications/Mathematica.app/Contents/MacOS/MathKernel\" -mathlink'");
// 全プラットフォームでの一般的な「listen」リンク
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode listen -linkname foo");
createKernelLink()とcreateMathLink()の引数(例えば -linkmode,-linkprotocol等)はWSTP C APIのWSOpenに使われるものと同一である.詳細はWSTPドキュメントに記載されている.
createKernelLink()とcreateMathLink()メソッドはいつもnullではないリンクオブジェクトを返すか,MathLinkExceptionを投げる.返ってきたリンクがnullかどうかをテストする必要はない.これらのメソッドは失敗のときにMathLinkExceptionを投げるので,呼出しをtryブロックでラップする必要がある.
KernelLink ml = null;
try {
ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'c:\\program files\\wolfram research\\mathematica\\10.0\\mathkernel'");
} catch (MathLinkException e) {
// これはCプログラムでWSOpenがNULLを返すのと同じことである.
System.out.println(e.getMessage());
System.exit(1);
}
createKernelLink()が成功したからといってリンクが繋がり,適切に機能するというわけではない.正しく機能しない可能性があるものもたくさんある.例えば,WSTPについて何も知らないプログラムを起動しても,createKernelLink()は成功する.リンクを作成すること(ユーザ側の設定が必要)とリンクを繋げること(もう一方が生きていることの確認)とは違う.
まだリンクが繋がっていないなら,最初に何かを読んだり書いたりしようとしたときに,WSTPは自動的にリンクを繋げようとする.代替策として,リンクを作成した後,明示的に繋げるためにconnect()メソッドを呼び出すこともできる.自分で明示的に繋ごうとしたのであれ,WSTPが内部的に繋ごうとしたのであれ,もしリンクが繋がらなければ接続の試みは失敗するか,あるいはいつまでも停止してしまう.接続が成功するかリンクについての致命的な問題を見付けるかするまで,接続の試みはブロックするので,停止することがある.このどちらも起らないこともある.例えば,WSTPが認知しないプログラムを誤って起動した場合等である.J/Link メソッドでのブロックの扱いについては後で詳しく述べるが,リンクを接続する場合,簡単な解決方法がある.connect()メソッドには2つ目のシグネチャがあり,connect(long timeoutMillis)に接続する試みを止めるまでに何ミリ秒待つかを指定する引数longを取る.リンクで明示的にconnect()を呼び出す必要はない.初めて何かを読もうとしたときに自動的に接続される.明確に定義された場所での失敗を捕らえるため,あるいは自動タイムアウト機能を使いたい場合に,connect()への呼出しを使うことができる.以下は,connect()にタイムアウトを実装する方法を例示するコードの一部である.
KernelLink ml = null;
try {
ml = MathLinkFactory.createKernelLink("-linkmode launch -linkname 'c:\\program files\\wolfram research\\mathematica\\10.0\\mathkernel'");
} catch (MathLinkException e) {
System.out.println("Link could not be created: " + e.getMessage());
return; // あるいはその他適切なもの
}
try {
connect(10000); // 最大限10秒待つ
} catch (MathLinkException e) {
// タイムアウトが終了したら,MathLinkExceptionが投げられる.
System.out.println("Failure to connect link: " + e.getMessage());
ml.close();
return; // あるいはその他適切なもの
}
リンクを使い終えたら,close()メソッドを呼び出す.リンクオブジェクトのファイナライザは,呼び出されるとリンクを閉じるが,適時に呼び出される保証もなければ,呼び出されること自体の保証もないので,終わったら常に手動でリンクを閉じなければならない.
ListenとConnectモードを使う
すでに実行中のプログラムに接続したい場合,起動する代りにlistenとconnectのリンクモードを使うことができる.J/Link でのlistenとconnectのリンクモードはC WSTPプログラムで使うのと同じように動作する.詳細は MathLink チュートリアル(http://library.wolfram.com/infocenter/TechNotes/174/) ,または「WSTPと外部プログラム通信」に記載されている.
リモートカーネルを使う
リモートのWolfram言語カーネルを J/Link プログラムに接続するためには,listen/connectスタイルを使ってリンクを開く.リモートマシンでは,Wolfram言語を起動し,コマンドラインで以下を実行することにより,リンク上でlistenにする.
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode connect -linkprotocol tcpip -linkname 1234@remotemachinename");
listen/connect技術の欠点は,手動でリモートマシンにログインしてWolfram言語を起動しなければならないという点である.rshまたはsshクライアントプログラムを使うと,JavaプログラムがリモートマシンのWolfram言語を自動的に起動するよう設定することができる.LinuxおよびOSXマシンにはrshとsshが組み込まれており,Windows用のWolfram言語にはwinrshクライアントプログラムが含まれている.以下は,winrshを使ってリモートのLinuxマシンのWolfram言語を起動して接続する例である.
KernelLink ml = MathLinkFactory.createKernelLink("-linkmode listen -linkprotocol tcpip -linkname 1234");
Runtime.exec("c:\\program files\\wolfram research\\mathematica\\10.0\\systemfiles\\frontend\\binaries\\windows\\winrsh -m -q -h -l YourUsername -'math -mathlink -linkmode connect -linkprotocol tcpip -linkname 1234@localmachinename'");
MathLinkインターフェース
MathLinkは J/Link のすべてのリンクオブジェクトのルートである低レベルのインターフェースである.MathLinkのメソッドは大まかにC言語WSTP APIのメソッドのサブセットに対応している.ほとんどのプログラマーは,MathLinkを拡張しリンクの反対側のプログラムがWolfram言語カーネルであるという仮定を組み込む,高レベルなインターフェースであるKernelLinkのオブジェクトを扱う.
これらのメソッドの大部分はほとんどの点でC APIのように動作するので,これについてはここではあまり述べることはない.JavaDocヘルプファイルはすべての J/Link クラスとインターフェースのメソッドごとの主要ドキュメントである.これはJLink/Documentation/JavaDocディレクトリにある.ここは従来の印刷リストにざっと目を通したい方に適している.
これはすべてpublicメソッドである(publicは行を短くするために省略してある).
リンク管理
void close();
void connect() throws MathLinkException;
// 接続されるのをtimeoutMillisまで待ってからMathLinkExceptionを投げる.
void connect(long timeoutMillis) throws MathLinkException;
// 接続のシノニムである.これは新しい名称である.
void activate() throws MathLinkException;
パケット関数
// Catchブロックの中で必要とされることが多いので,例外を投げない.
void newPacket();
int nextPacket() throws MathLinkException;
void endPacket() throws MathLinkException;
エラー処理
リンク状態
put
リンクへ式をputすることは,Javaでは関数がオーバーロードできるという点でCとは少し異なる.従ってC関数のWSPutIntegerやWSPutDoubleのような名前のメソッドを持つ必要がない.それぞれの引数タイプに異なる定義を持つput()という名の関数1つで十分なのである.唯一の例外は,引数が特殊な方法で解釈されなければならない場合である.例えば,引数に1つの文字列を取る「put」メソッドが3つある.その3つとは,put()(C言語関数のWSPutUCS2Stringと同じ),putSymbol()(WSPutUCS2Symbolと同じ),putByteString()(WSPutByteStringと同じ)である.
数値のタイプには,以下のようなメソッドがある(byte,char,shortタイプは自動的にintにプロモートされるので,put()メソッドを与える必要はない).
void put(int i) throws MathLinkException;
void put(long i) throws MathLinkException;
void put(double d) throws MathLinkException;
void put(String s) throws MathLinkException;
void putByteString(byte[] b) throws MathLinkException;
void putSymbol(String s) throws MathLinkException;
文字列をputしたりgetしたりする J/Link メソッドはすべてJava文字列のネイティブフォーマットであるユニコードを使う.
ブーリアンでは,JavaのtrueがWolfram言語シンボルのTrueとして送られ,JavaのfalseはFalseとして送られる.
任意のJavaオブジェクトにもまたput()メソッドがある.デフォルトの実装では,これはobj.toString()を送るだけで,ほとんどのオブジェクトについて役に立つことは何もしない.しかし,オブジェクトの中にはWolfram言語にとって意味をなす表現を持つものも少しある.これらは配列,文字列,Exprオブジェクト(他でも説明してある),1つの数値をホールドするいわゆるラッパークラスInteger,Double,Character等のインスタンスである.配列はリストとして,文字列はWolfram言語の文字列として,そしてラッパークラスはその数値として送られる.最後の事例は複素数についてで,後で述べる.
配列の頭部をそれぞれの次元で指定できる,配列のための特別なメソッドがある.頭部は文字列の配列として渡される.C(WSPutInteger32Array,WSPutReal64Array等)とは違い,深さや次元は配列自身から推定されるので,それを指定する必要はない.
数式を1つのリンクから別のリンクに移す(「この」リンクがデスティネーション).
void transferExpression(MathLink source) throws MathLinkException;
void transferToEndOfLoopbackLink(LoopbackLink source) throws MathLinkException;
void putNext(int type) throws MathLinkException;
void putArgCount(int argCount) throws MathLinkException;
void putSize(int size) throws MathLinkException;
int bytesToPut() throws MathLinkException;
void putData(byte[] data) throws MathLinkException;
void putData(byte[] data, int len) throws MathLinkException;
get
戻り値型に基づいてメソッドをオーバーロードすることはできないので,put()メソッドのように,リンクからの読込みのためのあらゆるものをgetできるget()メソッドはない.その代り,各データ型について別々のメソッドがある.C APIとは違って,これらのメソッドはエラーコードではなく,実際に読まれるデータを返す(すべてのメソッドでそうであるように,エラーの場合は例外が使われる).
int getInteger() throws MathLinkException;
long getLongInteger() throws MathLinkException;
double getDouble() throws MathLinkException;
String getString() throws MathLinkException;
byte[] getByteString(int missing) throws MathLinkException;
String getSymbol() throws MathLinkException;
boolean getBoolean() throws MathLinkException;
複素数だけでなく9つの基本形(boolean,byte,char,short,int,long,float,double,String)の配列もgetXXXArrayN()の型のメソッドで読まれる.ここでXXXはデータ型でNは配列の深さを指定する.それぞれのタイプについて,次のintの例のように2つのメソッドがある.これらの関数を使って配列の頭部(通常はどのレベルでも「List」)をgetする方法はない.もし頭部もgetする必要があるなら,getExpr()を使ってExprとして式を読み,Exprメソッドを使って調べなければならない.
int[] getIntArray1() throws MathLinkException;
int[][] getIntArray2() throws MathLinkException;
... and others for all the eight primitive types and String and the complex class
これらの関数を使うために配列の深さを正確に知る必要はない.例えばgetFloatArray1()を呼び出したときにリンクに実際にあるのが実数の行列ならば,データは要求された深さ(この場合一次元配列)になる.残念ながらこれをするとデータのもとの深さが分からなくなる.リンク上の配列の実際の深さよりも深い配列が必要な関数を呼び出すと,MathLinkExceptionが投げられる.
深さが2より大きい(最大5の)配列を読む必要がある場合,getArray()メソッドを使うことができる.前述のgetXXXArrayN()メソッドはgetArray()を内部的に使う便利なメソッドである.type引数はTYPE_BOOLEAN,TYPE_BYTE,TYPE_CHAR,TYPE_SHORT,TYPE_INT,TYPE_LONG,TYPE_FLOAT,TYPE_DOUBLE,TYPE_STRING,TYPE_EXPR,TYPE_BIGINTEGER,TYPE_BIGDECIMAL,TYPE_COMPLEXの中のどれかでなければならない.
Object getArray(int type, int depth) throws MathLinkException;
Object getArray(int type, int depth, String[] heads) throws MathLinkException;
C WSTP APIとは異なり,文字列や配列を「解放する」ためのメソッドはない.その必要がないのである.リンクから文字列や配列を読むとき,プログラムはデータのコピーを作るので,それに書き込むこともできる(Java文字列は変えることができない).
getFunction()メソッドは頭部と引数カウントの2つを返さなければならない.この両方の情報をカプセル化するMLFunctionという特殊なクラスがあるので,getFunction()はこれを返す.MLFunctionクラスについては後で説明する.
MLFunction getFunction() throws MathLinkException;
// 関数の引数の数を返す.関数が指定されたものでなければ
// MathLinkExceptionを投げる.
int checkFunction(String f) throws MathLinkException;
// 入ってくる関数にこの頭部および引数の数が含まれていない場合に例外を投げる.
void checkFunctionWithArgCount(String f, int argCount) throws MathLinkException;
これらのメソッドはリンクから読むための低レベルのインターフェースをサポートする.
int getNext() throws MathLinkException;
int getType() throws MathLinkException;
int getArgCount() throws MathLinkException;
int bytesToGet() throws MathLinkException;
byte[] getData(int len) throws MathLinkException;
Exprオブジェクトを読む.
public Expr getExpr() throws MathLinkException;
// リンクから式を受け取り,リンクがその式を読み込む前の
// 状態にリセットする.リンクからは何も使わずに
// 「覗き見」することもできる.
public Expr peekExpr() throws MathLinkException;
メッセージ
以下の関数が参照するメッセージはWolframシステムの警告メッセージではなく,主に中止や放棄の要求を送るために使われる低レベルのWSTPのコミュニケーションである.getMessage()とmessageReady()の両メソッドは J/Link 2.0以降ではもはや機能しない.Wolfram言語からメッセージを受け取りたい場合はsetMessageHandler()を使わなければならない.
int getMessage() throws MathLinkException;
void putMessage(int msg) throws MathLinkException;
boolean messageReady() throws MathLinkException;
マーク
long createMark() throws MathLinkException;
// 次の2つは,キャッチハンドラのクリーンアップ操作でよく使われるので,例外を投げない.
void seekMark(long mark);
void destroyMark(long mark);
複素数クラス
setComplexClass()メソッドを使うとWolfram言語の複素数にマップするクラスを割り当てることができる.「マップ」とは,complexクラスのオブジェクトでWolfram言語複素数を呼び出したときに,put(Object)メソッドはそれを送り,getComplex()がこのクラスのインスタンスを返すということである.これについての詳細とcomplexクラスとして使用できるクラスの制限については「複素数」を参照のこと.
public boolean setComplexClass(Class cls);
public Class getComplexClass();
public Object getComplex() throws MathLinkException;
Yieldとメッセージハンドラ
setYieldFunction()とaddMessageHandler()の両メソッドはクラス,オブジェクト,メソッド名を文字列として取る.クラスは指定されたメソッドを含むクラスで,オブジェクトはそのメソッドを呼び出すクラスのオブジェクトである.もしそれがstaticメソッドなら,オブジェクトにはnullを渡す.setYieldFunction()で使うメソッドのシグネチャはV(Z)で,addMessageHandler()のシグネチャはII(V)でなければならない.詳細と例題は「スレッド,ブロック,Yield」を参照のこと.
public boolean setYieldFunction(Class cls, Object obj, String methName);
public boolean addMessageHandler(Class cls, Object obj, String methName);
public boolean removeMessageHandler(String methName);
定数
MathLink クラスはWSTP.hからのユーザレベル定数もすべて含んでいる.Javaの定数はCのものと全く同じ名前である.これに加え,J/Link 特有の定数もある.
static int ILLEGALPKT;
static int CALLPKT;
static int EVALUATEPKT;
static int RETURNPKT;
static int INPUTNAMEPKT;
static int ENTERTEXTPKT;
static int ENTEREXPRPKT;
static int OUTPUTNAMEPKT;
static int RETURNTEXTPKT;
static int RETURNEXPRPKT;
static int DISPLAYPKT;
static int DISPLAYENDPKT;
static int MESSAGEPKT;
static int TEXTPKT;
static int INPUTPKT;
static int INPUTSTRPKT;
static int MENUPKT;
static int SYNTAXPKT;
static int SUSPENDPKT;
static int RESUMEPKT;
static int BEGINDLGPKT;
static int ENDDLGPKT;
static int FIRSTUSERPKT;
static int LASTUSERPKT;
// 下の2つはJ/Link特有のものである.
static int FEPKT;
static int EXPRESSIONPKT;
static int MLTERMINATEMESSAGE;
static int MLINTERRUPTMESSAGE;
static int MLABORTMESSAGE;
static int MLTKFUNC;
static int MLTKSTR;
static int MLTKSYM;
static int MLTKREAL;
static int MLTKINT;
static int MLTKERR;
// getArray()で使う定数である.
static int TYPE_BOOLEAN;
static int TYPE_BYTE;
static int TYPE_CHAR;
static int TYPE_SHORT;
static int TYPE_INT;
static int TYPE_LONG;
static int TYPE_FLOAT;
static int TYPE_DOUBLE;
static int TYPE_STRING;
static int TYPE_BIGINTEGER;
static int TYPE_BIGDECIMAL;
static int TYPE_EXPR;
static int TYPE_COMPLEX;
KernelLinkインターフェース
KernelLinkはプログラムのリンクで使うインターフェースである.Javaインターフェースがいつもそうであるように,これらはすべてpublicメソッドである.ここではKernelLinkメソッドの簡単な概要のみをご紹介するので,習慣的なリストにざっと目を通したい方に向いている.全 J/Link クラスやインターフェースについてのメソッドごとの主要ドキュメントはJavaDocヘルプファイルで,JLink/Documentation/JavaDocディレクトリにある.
評価
evaluate()メソッドは,式を文字列あるいはExprとしてWolfram言語にputし,式として答をgetするのに必要なステップをカプセル化する.これは内部的には式を送るのにEvaluatePacketを使う.waitForAnswer()メソッドがReturnPacketを開くが,答はReturnPacketに入って返ってくる.ただその内容を読みさえすればよい.ReturnPacketを待ってパケットループを回さずに,いつもwaitForAnswer()かdiscardAnswer()を使うこと.詳しくは「WSTPの「パケットループ」」を参照のこと.
結果を待つ
evaluate()の直後に(あるいは手動でEvaluatePacketでラップされた計算を送ったら)waitForAnswer()を呼び出す.結果を保管しているReturnPacketが見付かるまで,waitForAnswer()はリンクからパケットを読み込む.「WSTPの「パケットループ」」を参照のこと.
discardAnswer()メソッドが計算の結果をすべて捨てるので,リンクは次の計算の準備ができる.お察しの通り,これはwaitForAnswer()に続けてnewPacket()を使う以上の何ものでもない.
「evaluateTo」メソッド
次に挙げるメソッドは,結果のputや読込みを実行するevaluate()の拡張機能である.waitForAnswer()を呼び出し,自分で結果を読む必要はない.これらのメソッドはMathLinkExceptionも投げず,エラーがあると自分でクリーンアップしてnullを返す.名前の「evaluateTo」はメソッドが全プロセスを自分で実行することを示している.evaluateToInputForm()は指定されたページ幅でInputFormでフォーマットされた文字列を返す.Infinityを得るにはページ幅を0とする.evaluateToOutputForm()は,OutputFormでフォーマットされた文字列を返す以外はevaluateToInputForm()と全く同じである.結果の表示にはOutputFormの方が好まれるようだが,Wolfram言語に次の演算で使う文字列を渡したければInputFormが必要である.evaluateToImage()メソッドは,画像を返す式(例えばPlotコマンド)を与えたらGIFデータのbyte[]を返す.Wolfram言語のAutomatic設定が必要ならdpi,int,width引数に0を渡す.evaluateToTypeset()は,StandardFormかTraditionalFormでタイプセットされた,演算の結果であるGIFデータのbyte[]を返す.これらのメソッドは「EvaluateToImage()とEvaluateToTypeset()」で詳しく説明する.
String evaluateToInputForm(String s, int pageWidth);
String evaluateToInputForm(Expr e, int pageWidth);
String evaluateToOutputForm(String s, int pageWidth);
String evaluateToOutputForm(Expr e, int pageWidth);
byte[] evaluateToImage(String s, int width, int height);
byte[] evaluateToImage(Expr e, int width, int height);
byte[] evaluateToImage(String s, int width, int height, int dpi, boolean useFrontEnd);
byte[] evaluateToImage(Expr e, int width, int height, int dpi, boolean useFrontEnd);
byte[] evaluateToTypeset(String s, int pageWidth, boolean useStdForm);
byte[] evaluateToTypeset(Expr e, int pageWidth, boolean useStdForm);
// 一番新しい「evaluateTo」メソッドがnullを返すようにした例外を返す.
Throwable getLastError();
Javaオブジェクトの参照を送る
JavaオブジェクトをWolfram言語に「参照」で送って,Wolfram言語コードが「Wolfram言語からJavaを呼び出す」の機能を使ってJavaランタイムにコールバックできるようにしたい場合,まずenableObjectReferences()メソッドを呼び出さなければならない.これは「Wolfram言語にオブジェクト参照を送る」で説明する.
MathLinkインターフェースのように,KernelLinkにはオブジェクトを送るput()メソッドがある.このメソッドのMathLinkバージョンはオブジェクトを「値」だけで送る.KernelLinkバージョンは値で送ることのできるオブジェクトについてMathLinkバージョンと全く同じように動作する.しかし,他のオブジェクトは参照として送る.参照として送られるオブジェクトに対してput()を呼び出す前に,enableObjectReferences()を呼び出さなければならない.「Wolfram言語にオブジェクト参照を送る」を参照のこと.
次の2つのメソッドは参照でオブジェクトをputおよびgetするためのものである(これらのメソッドを使うためにはenableObjectReferences()を呼び出しておかなければならない).
public void putReference(Object obj) throws MathLinkException;
Object getObject() throws MathLinkException;
// 以下の2つのMathLinkインターフェースのメソッドは高度になっていて,
// Javaオブジェクト参照が読まれるのを待っている場合にはMLTKOBJECTを返す.
int getNext() throws MathLinkException;
int getType() throws MathLinkException;
評価の割込み,中断,放棄
これらのメソッドは評価を中断および評価に割り込むためのものである.これらについては,「演算の放棄と中断」で述べる.
PacketListenersのサポート
これらのメソッドはPacketListenerオブジェクトの登録と通知をサポートする.これについては「PacketListenerインターフェースの使用」で述べる.
void addPacketListener(PacketListener listener);
void removePacketListener(PacketListener listener);
boolean notifyPacketListeners(int pkt);
handlePacket() メソッド(上級者向け)
handlePacket()メソッドはwaitForAnswer(),discardAnswer(),あるいは「evaluateTo」メソッドを呼び出さず,自分でパケットループを書く上級ユーザ向きである.詳しくはJavaDocsを参照のこと.
「StdLinks」にのみ有効なメソッド
最後に,「Wolfram言語からJavaを呼び出す」の機能でWolfram言語から呼び出されるメソッドでのみ意味をなすメソッドがある.これらのメソッドは「インストール可能なJavaクラスを書く」に挙げてある.Wolfram言語を演算エンジンとして使用するプログラムを書いている場合は,このようなメソッドを使うことはない.
public void print(String s);
public void message(String symtag, String[] args);
public void message(String symtag, String arg);
public void beginManual();
public boolean wasInterrupted();
public void clearInterrupt();
計算の送信と結果の読込み
WSTPパケット
Wolfram言語カーネルとのコミュニケーションは一般的に「パケット」形式で行われる.Wolfram Symbolic Transfer Protocol (WSTP)パケットは,WSTPによって特別に認識され扱われる集合からのパケットであるが単にWolfram言語の関数である.Wolfram言語に何かを送って評価させるときに,これは演算の要求であるということを告げ,どのように演算しなければならないかも告げるパケットでこれをラップする.結果やメッセージ,Print出力,グラフィックスのような2次的出力を含む,Wolfram言語から受け取る出力もすべて,パケットでラップした状態で届く.パケットのタイプでその内容が分かる.
WSTPプログラムは通常,演算を特別なパケットでラップしてWolfram言語に送り,演算の結果が入ったパケットが届くまでカーネルから届く一連のパケットを読む.そのときに,結果を含まないパケットは中身を見ないで捨てられるか,「開かれて」操作される.結果を含まないパケットには,Print出力を含むTextPacket式,Wolframシステムの警告メッセージを含むMessagePacket式,PostScriptを含むDisplayPacket式をはじめとして,さまざまなタイプがある.
Wolfram言語に何かを送るためのさまざまなパケットのタイプについての情報や,Wolfram言語が送り返すものについては既存のWSTPドキュメントを参照のこと.特に MathLink チュートリアル(http://library.wolfram.com/infocenter/TechNotes/174/)はぜひご覧いただきたい.ほとんどの場合,J/Link はパケットタイプの詳細やその送受信の方法を隠している.J/Link が提供する組込みの動作を超えるものが必要な場合のみパケットタイプについて読む必要がある.これは,多くのプログラムで役に立つ.
WSTPの「パケットループ」
C WSTPプログラムでは,Wolfram言語に演算を送り結果を捨てるための典型的なコードは次のようなものである.
// C コード
WSPutFunction(ml, "EvaluatePacket", 1);
WSPutFunction(ml, "ToExpression", 1);
WSPutString(ml, "Needs[\"MyPackage`\"]");
WSEndPacket(ml);
while (WSNextPacket(ml) != RETURNPKT)
WSNewPacket(ml);
WSNewPacket();
演算を(EvaluatePacketでラップして)送った後,コードは結果(ここではシンボルNull)を含むReturnPacketが見付かるまでパケットを読んで捨てるwhileループに入る.それからまたWSNewPacketを呼び出してReturnPacketを捨てる.
WSTPプログラムは通常これと同じ基本的操作を何度も繰り返すので,J/Link はKernelLinkインターフェースの高レベルのメソッド内にそれを隠す.J/Link では次のようになる.
discardAnswer()メソッドは結果を含むパケットが見付かるまで演算によって生成されたパケットをすべて捨て,結果を含むものも捨てる.結果が見付かるまですべてを捨てるwaitForAnswer()という関連メソッドがある.waitForAnswer()が返ってきたら,ReturnPacketは開かれ内容を読むことができる.discardAnswer()はwaitForAnswer()にnewPacket()が続いたものに過ぎないと推測できよう.
waitForAnswer()とdiscardAnswer()内でパケットループを隠すのは便利なだけではなく,J/Link が内部的に扱わなければならない特別なパケットが届くかもしれないのでそれが必要な状況もある.J/Link にはnextPacket()とnewPacket()メソッドがあるが,プログラマーは最後のCコードにあるループのようなnextPacket()/newPacket()ループを書くべきではない.waitForAnswer()あるいはdiscardAnswer()を呼び出すか,次に説明する「evaluateTo」メソッドを使う.プログラムに到着するパケットすべてについて知りたい場合は,「PacketListenerインターフェースの使用」で説明するPacketListenerインターフェースを使う.
評価を送る
J/Link は評価のためにWolfram言語に式を送るのに,主に3通りの方法を提供している.この3つの方法はすべて「サンプルプログラム」の例題で示されている.
評価結果に関心がない,あるいは文字列や画像以外の形式で結果を受け取りたい場合は,evaluate()メソッドを使う.
従来のC WSTPプログラムでのように,「手動」で式を送ることができる.このためには,MathLinkインターフェースからの低レベルのメソッドを使って,頭部EvaluatePacketとその後に式の一部を置く.
結果を文字列あるいは画像で受け取りたい場合は,高レベルで便利なインターフェースを提供する「evaluateTo」メソッドを使う.
「evaluateTo」メソッドが提供する形式の中のどれかで結果を得たい場合は,「evaluateTo」メソッドを使うのが便利である.これらのメソッドについては「「evaluateTo」メソッド」で説明している.評価したい式が文字列あるいはExpr(Exprクラスは「Exprクラスの動機付け」で解説する)形式である場合,あるいはこのどちらかに簡単に変換できる場合は,evaluate()メソッドを使うとよい.上記のような便利なメソッドが適当ではない場合は,通常のC WSTPプログラムのように式を部分ごとに置いていく.これを行うには,式のFullFormを反映する構造で式の部分を送る.以下で,NIntegrate[x2+ y2,{x,-1,1},{y,-1,1}]という計算を送るための上記の3つの方法を比較する.
String strResult = ml.evaluateToInputForm("NIntegrate[x^2 + y^2, {x,-1,1}, {y,-1,1}]");
ml.evaluate("NIntegrate[x^2 + y^2, {x,-1,1}, {y,-1,1}]");
ml.waitForAnswer();
double doubleResult1 = ml.getDouble();
// 構造を示すのにインデントを使うと便利である.
ml.putFunction("EvaluatePacket", 1);
ml.putFunction("NIntegrate", 3);
ml.putFunction("Plus", 2);
ml.putFunction("Power", 2);
ml.putSymbol("x");
ml.put(2);
ml.putFunction("Power", 2);
ml.putSymbol("y");
ml.put(2);
ml.putFunction("List", 3);
ml.putSymbol("x");
ml.put(-1);
ml.put(1);
ml.putFunction("List", 3);
ml.putSymbol("y");
ml.put(-1);
ml.put(1);
ml.endPacket();
ml.waitForAnswer();
double doubleResult2 = ml.getDouble();
結果を読む
リンクから式を読む前に,結果を文字列あるいは画像で得たいなら,次で説明する「evaluateTo」メソッドの中のどれかを使った方がいい.これらのメソッドは演算を送り,答を文字列あるいは画像として返すので,自分でリンクから読む必要がないのである.また,結果に関心がない場合は,discardAnswer()を使う.これによって,結果を読む必要がなくなるからである.
J/Link はリンクから式を読むためのメソッドをたくさん提供している.その多くはWSTP C APIの関数と本質的に同じなので,標準的なWSTPドキュメントを読めばこれらのメソッドの使い方がある程度理解できる.詳細情報については J/Link JavaDocファイルも参照のこと.読込みのメソッド名は,通常「get」で始まる.その例として,getInteger(),getString(),getFunction(),getDoubleArray2()というメソッドがある.また,タイプテストのメソッドとしてgetType()とgetNext()の2つがある.これらは,次にリンクから読まれるのを待っているもののタイプを知らせる.
前述のように,nextPacket()は通常呼び出さない.waitForAnswer()が返ったときには,nextPacket()は答を持っているReturnPacketについてすでに内部的に呼び出されているので,この最後のパケットはすでに開かれていて,すぐにその内容を読み始めることができるのである.
J/Link プログラムのMathLinkExceptionの大多数は,式のタイプに不適切な方法で式を読もうとすることにより起る.典型的な例として,数を返すと想定されているWolfram言語関数を呼び出すのに誤った引数で呼び出したため,未評価で返ってくるということがある.整数を読むためにgetInteger()を呼び出しても,リンクで待っているのはfoo[badArgument]のような関数である.このような問題にはいくつかの対処方法がある.最初の方法は,待っている式のタイプを判別するのにgetNext()を使って例外を避けることである.
ml.evaluate("SomeFunction[]");
ml.waitForAnswer();
int result;
int type = ml.getNext();
if (type == MathLink.MLTKINT) {
result = ml.getInteger();
} else {
// ここでは何をしても構わない.
System.out.println("Unexpected result: " + ml.getExpr().toString());
// パケットの内容を捨てる.
ml.newPacket();
}
関連した方法として,Exprとして結果を読み,Exprメソッドを使ってそれを調べるというものがある.Exprクラスは「Exprクラスの動機付け」で説明している.
ml.evaluate("SomeFunction[]");
ml.waitForAnswer();
int result;
Expr e = ml.getExpr();
if (e.integerQ()) {
result = e.asInt();
} else {
// ここでは何をしても構わない.
System.out.println("Unexpected result: " + e.toString());
}
最後の方法は,想定している形式で式を読み,MathLinkExceptionをすべて捕らえて対処することである.以下のコードはすべて,MathLinkExceptionオブジェクトのためにtry/catchブロックでラップしなければならない.この例では,読んでいる間に投げられることが分かっているMathLinkExceptionオブジェクトのための内部的なtry/catchブロックだけを見る.
ml.evaluate("SomeFunction[]");
ml.waitForAnswer();
int result;
try {
result = ml.getInteger();
} catch (MathLinkException e) {
ml.clearError();
System.out.println("Unexpected result: " + ml.getExpr().toString());
ml.newPacket(); // 上にgetExpr()があるため,必ずしも必要ではない.
}
リンクから読むコードのバグを避ける別のヒントとして,newPacket()メソッドを使うということがある.MathLinkExceptionが起る原因で2番目に多いのは,次の計算をする前にパケットの内容すべてを読んでしまうのを忘れるということである.newPacket()メソッドにより,現在開かれているパケットが捨てられる.別の言い方をすれば,newPacket()メソッドは現在読まれている式の読まれていない部分をすべて捨てるということである.これは,次の評価に移ったときに最後のパケットからの残り物がないようにする,クリーンアップメソッドである.次のコードを見てみる.
ml.evaluate("SomeFunction[]");
ml.waitForAnswer();
int result;
try {
result = ml.getInteger();
} catch (MathLinkException e) {
ml.clearError();
System.out.println("Unexpected result");
// newPacket()を呼び出して内容を捨てるのを忘れていた.
}
// 前のgetInteger()の呼出しが失敗したら,次の行でMathLinkExceptionが起る.
// 前のパケットが空になる前にnextPacket()が呼び出されるからである.
ml.evaluate("AnotherFunction[]");
ml.discardAnswer();
プログラマーが結果を読み終えること,もしくはnewPacket()を呼び出すことを忘れたので,getInteger()の以前の呼出しが失敗したのであれば,このコードにより,MathLinkExceptionは指示されたポイントで捨てられる.以下はこのエラーの簡単な例である.
ml.evaluate("SomeFunction[]");
ml.waitForAnswer();
// 結果を読む,あるいは捨てるのを忘れていた.
// 恐らくwaitForAnswer()ではなくdiscardAnswer()を呼び出すつもり
// だったのであろう.
ml.evaluate("AnotherFunction[]");
ml.discardAnswer(); // ここで,MathLinkExceptionが出された.
「evaluateTo」メソッド
J/Link にはその中でパケットループを隠す便利なメソッドが他にもある.これらのメソッドはWolfram言語に何かを送り何らかの形式で結果を返す全過程を実行する.それは「evaluateTo」で始まる名前を持ち,evaluate()メソッドのように結果を送るだけでなく実際に返すということを示す.
String evaluateToInputForm(String s,int pageWidth);
String evaluateToInputForm(Expr e,int pageWidth);
String evaluateToOutputForm(String s,int pageWidth);
String evaluateToOutputForm(Expr e,int pageWidth);
byte[] evaluateToImage(String s, int width, int height);
byte[] evaluateToImage(Expr e, int width, int height);
byte[] evaluateToImage(String s, int width, int height, int dpi, boolean useFE);
byte[] evaluateToImage(Expr e, int width, int height, int dpi, boolean useFE);
byte[] evaluateToTypeset(String s, int width, boolean useStdForm);
byte[] evaluateToTypeset(Expr e, int width, boolean useStdForm);
ここではevaluateToInputForm()とevaluateToOutputForm()だけを説明し,evaluateToImage()とevaluateToTypeset()は「グラフィックスとタイプセット出力」の「evaluateToImage()とevaluateToTypeset()」で解説するevaluateToInputForm()とevaluateToOutputForm()メソッドは文字列としてコードを送りフォーマットされた文字列として結果を得るという一般的なニーズをカプセル化する.違いは文字列がInputFormでフォーマットされているかOutputFormでフォーマットされているかだけである.OutputFormはユーザに文字列を表示するのに適していて,InputFormはWolfram言語に式を送り返す必要があったり,ファイルに保存あるいは他の式に接続する必要があったりするときに適している.これらのメソッドはpageWidth引数を取り,行の最大の長さを何字幅にしたいかを指定することができる.無限のページ幅には0を渡す.
evaluateToメソッドはMathLinkExceptionを投げない.その代りnullを返して問題が起きたことを知らせる.カーネルが不意に終了した等の重大な問題ではない限り,これはあまり起りえない.これらのメソッドのどれかからnullが返されたら,getLastError()を呼び出して予想外の結果を引き起すのに投げられた例外を表わすThrowableオブジェクトを得ることができる.一般的に,その例外はMathLinkExceptionであるが,それ以外である場合もまれにある(例えば,大きすぎて扱えない画像が返されたときはOutOfMemoryErrorである).
evaluateToメソッドはすべて,文字列かExprの形式で評価するための入力を取る.Exprクラスの詳しい説明は「Exprクラスの動機付け」まで待つが,どのように,またなぜ入力をExprとして送った方がよいかについて簡単に説明する.Wolfram言語入力は特にテキストフィールドの内容のようにユーザから直接行われるれる場合,文字列として指定するのが便利である.しかし,文字列で作業するのが難しかったり扱いにくかったりすることもある.特に評価する式がプログラムに従って構築されていたり,1つのリンクから読み込まれカーネルへのリンクに書き込まれたりしている場合はそうである.このような状況に対応する方法のひとつは,例えばevaluateToOutputForm()のような便利なものを使用せずに,答がOutputFormでフォーマットされて返ってくるように入力の送信の全操作をハンドコードで行うことである.文字列として出力を得るには,頭部のEvaluatePacketを送りToString関数を使わなければならない.
// 再現する.
// String output = ml.evaluateToOutputForm("Integrate[5 x^n a^x, x]", 0);
// 式として,ToString[Integrate[5 x^n a^x, x], PageWidth->Infinity]を送る.
ml.putFunction("EvaluatePacket", 1);
ml.putFunction("ToString", 2);
ml.putFunction("Integrate", 2);
ml.putFunction("Times", 3);
ml.put(5);
ml.putFunction("Power", 2);
ml.putSymbol("x");
ml.putSymbol("n");
ml.putFunction("Power", 2);
ml.putSymbol("a");
ml.putSymbol("x");
ml.putSymbol("x");
ml.putFunction("Rule", 2);
ml.putSymbol("PageWidth");
ml.putSymbol("x");
ml.endPacket();
ml.waitForAnswer();
String output = ml.getString();
このバージョンは非常に冗長であるが,ほとんどのコードは式を1つの文字列としてではなく,1つずつ送ろうと決めたためにそうなっている.答が適切にフォーマットされた文字列として返ってくることと,リンクから結果を読むことを保証する数行の付加的な行がある.すべてを手動で行っても大きな損失はない.しかし同じことをevaluateToTypeset()で行ったらどうであろうか.プラグラマーのほとんどは,望みの形で答を得るためにどのように作業を行えばよいか分からないであろう.evaluateToメソッドがすべて文字列だけしか取らないなら,J/Link プログラマーは文字列としてすべての入力を構成しなければならないか,さまざまなevaluateToメソッドの内部によってすでに扱われた難しいステップが分からなければならないかのどちらかである.
この解決策はExpr引数を文字列の代りにできるようにすることである.Exprにはコンストラクタがあるが,複雑なコンストラクタを作成する最も簡単な方法は,ループバックリンクに入力式を構築し,Exprとしてリンクからそれを読み,そのExprを望みのevaluateToメソッドに渡すことである.
LoopbackLink loop = MathLinkFactory.createLoopbackLink();
// ループバックリンクに,EvaluatePacket[Integrate[5 x^n a^x, x]]という式を構築する.
loop.putFunction("Integrate", 2);
loop.putFunction("Times", 3);
loop.put(5);
loop.putFunction("Power", 2);
loop.putSymbol("x");
loop.putSymbol("n");
loop.putFunction("Power", 2);
loop.putSymbol("a");
loop.putSymbol("x");
loop.putSymbol("x");
loop.endPacket();
// これでループバックリンクからExprを読むようになった.
Expr e = loop.getExpr();
// これでループバックリンクの終了である.
loop.close();
String result = ml.evaluateToOutputForm(e, 0);
e.dispose();
このようにすると,一連の「put」呼出しで式を手動で構築しながらも,高レベルのevaluateToメソッドを使うことによる便利さも享受することができる.
PacketListenerインターフェースの使用
標準C WSTPプログラムの中心的コンポーネントはパケット読込みループであり,これは通常望みのパケットが見付かるまでWSTP API関数のWSNextPacketとWSNewPacketを呼び出し続けることで構成される.J/Link プログラムは通常そのようなループを含まず,KernelLinkメソッドのwaitForAnswer(),discardAnswer(),あるいはパケットループを中に隠してしまう「evaluateTo」メソッドのうちの1つを呼び出す.しかし,プログラマーは入ってくるパケットを見たり操作したりしたいと思うことがある.典型的な例は,演算によって生成されたPrint出力やメッセージを表示することである.これは演算の2次的な出力で,「解答」の一部ではないので,通常は J/Link の内部パケットループによって捨てられる.
このようなニーズを満たすため,内部パケットループがパケットを読み込んだとき(つまり,nextPacket()が呼び出された直後)KernelLinkオブジェクトはPacketArrivedEventを発生させる.PacketListenerインターフェースを実装するクラスを作成し,それをKernelLinkオブジェクトに登録することにより,パケットが到着したときに通知を受け取るよう登録することができる.このイベント通知はイベントとイベントリスナの標準Javaデザインパターンによって行われる.PacketListenerを実装するクラスを作成し,KernelLinkメソッドのaddPacketListener()を呼び出してこのオブジェクトを通知の受取りのために登録する.
PacketListenerインターフェースはpacketArrived()メソッドだけしか含んでいない.
PacketListenerオブジェクトで入ってくるパケットすべてにつきpacketArrived()メソッドが呼び出される.packetArrived()が呼び出されるときには,パケットはnextPacket()で開かれている.コードはパケットの内容を読み始めることができる.packetArrived()の引数はPacketArrivedEventで,そこからリンクとパケットタイプを抽出することができる(以下の例を参照).
packetArrived()の実装のよいところは内部的パケットループに影響を与えることなく,パケットを使うことも無視することもできるところである.他のどのPacketListenerや J/Link 自身のパケットの内部操作の妨害も心配する必要がない.パケットの内容はすべてあるいは一部だけ読むこともできるし,まったく読まないこともできる.
packetArrived()メソッドは,J/Link 内部コードがパケットを見せないようにするかどうかを示すブーリアンを返す.これは非常に高度なオプションで,完全に J/Link 自身のパケット処理をオーバーライドすることができる.現在のところ,J/Link のパケット処理の内部は文書化されていないので,このオーバーライド機能はプログラマーの役に立たない.packetArrived()メソッドはいつもtrueを返す.
以下はpacketArrived()の実装のサンプルである.これはTextPacket式だけを探し,内容をSystem.outストリームに出力する.
public boolean packetArrived(PacketArrivedEvent evt) throws MathLinkException {
if (evt.getPktType() == MathLink.TEXTPKT) {
KernelLink ml = (KernelLink) evt.getSource();
System.out.println(ml.getString());
}
return true;
}
受け取ったすべてのパケットについてコールバックされるイベントリスナを使用するというデザインパターンにより,プログラムがパケットの処理について非常に柔軟性に富んだものになっている.結果ではないパケットを無視したり,それらをSystem.outストリームに出力したり,ファイルに書いたり,ウィンドウに表示したり等,異なったポリシーを実装するためにプログラムを大きく変える必要がない.ただ異なった動作は異なるPacketListenerオブジェクトを組み込んで,すべてのプログラムロジックをそのままにしておく.PacketListenerオブジェクトは好きなだけ使うことができる.
デバッグのためのPacketPrinterクラス
J/Link は J/Link プログラムのデバッグを簡素化するようにデザインされたPacketListenerインターフェースの実装を提供する.PacketPrinterクラスは指定するストリームにそれぞれのパケットの内容を出力する.これがコンストラクタである.
PacketListener stdoutPrinter = new PacketPrinter(System.out);
ml.addPacketListener(stdoutPrinter);
...
String result = ml.evaluateToOutputForm("Integrate[x^n a^x, x]", 72);
これは演算中に生成されたWolframシステムメッセージを見るのに特に便利である.PacketPrinterを使ってWolfram言語が送り返しているものを正確に見ることは,非常に役に立つデバッグ方法である.J/Link プログラムの問題の大多数は,PacketPrinterを生成し,インストールするコードを1行加えるだけで解決するといっても過言ではない.プログラムが期待通りに動作したら,addPacketListener()ラインを取り除く.その他のコードの変更は必要ない.
EnterTextPacketの使用
前述のように,Wolfram言語に何かを送って評価させるときは,パケットでラップする.Wolfram言語は演算を送るのに3つの異なるパケットをサポートするが,大切なのはEvaluatePacketとEnterTextPacketの2つである.これまでいくつかのコードでEvaluatePacketを手動で使ったが,それらはevaluate()とevaluateToメソッドで内部的に使われている.Wolfram言語はEvaluatePacketでラップされたものを受け取ったら,それを評価し,結果をReturnPacketに送り返す.PrintやグラフィックスのPostScriptのような2次的な出力はReturnPacketに先行してそれ自身のパケットで送られる.対照的に,EnterTextPacketでラップされたものを受け取ったら,Wolfram言語は完全な「メインループ」を実行する.それにはInとOutのプロンプトの生成,$Pre,$PrePrint,$Post関数の適用,入出力の記録等が含まれる.ノートブックフロントエンドはこのようにカーネルを使うのである.MathSource で入手可能な拙著「MathLink Tutorial」でこのようなパケットの属性についての詳しい説明をご覧いただきたい.
もしカーネルを演算エンジンとして使っているのであれば,EvaluatePacketを使うとよい.前の出力が数か%で取り出せるインタラクティブな「セッション」をユーザに示したければ,代りにEnterTextPacketを使う.ノートブックフロントエンドに似た機能や,カーネルのスタンドアロン「ターミナル」インターフェースを提供している場合等がこれに当たる.演算のためのラッパーパケットとしてのEnterTextPacketはめったに使われることがないので,J/Link ではサポートされていない.evaluateToメソッドはEvaluatePacketを使うので,使用することはできない.
EnterTextPacketの演算から代りに取得したパケットの列にはいつもReturnTextPacketがあるとは限らない.もし演算がNullを返したら,あるいはシンタックスエラーがあったら,ReturnTextPacketは送られない.常に送られる最終パケットはInputNamePacketで,次の演算に使う入力プロンプトを含んでいる.このことはwaitForAnswer()メソッドが,ほとんどの演算では解答はReturnTextPacketの中にあるが,解答がまったくない場合もあるという,2つの状態を満たさなければならないということを意味している.waitForAnswer()はReturnTextPacketかInputNamePacketが見付かったときに返ってくる.こういう訳でwaitForAnswer()はintを返すのである.これはwaitForAnswer()が返ってくるようにするパケットである.もしwaitForAnswer()の呼出しでMathLink.RETURNTEXTPKTが返ってきたら,解答(文字列)を読むことができ,もう一度waitForAnswer()を呼び出して後で来るInputNamePacketを受け取ることができる.getString()でプロンプト文字列(「In[1]:=」のようなもの)を読むこともできる.もし,もとのwaitForAnswer()がMathLink.INPUTNAMEPKTを返したら,表示する結果がないので,ただgetString()を呼び出して入力プロンプト文字列を読むことができる.ReturnTextPacketが来る最初の場合,次のInputNamePacketから読むためにwaitForAnswer()を2回呼び出す代りに,ただnextPacket()を呼び出すことができる.というのは,InputNamePacketはいつもReturnTextPacketの直後にくるからである.少し変に見えるかもしれないが,waitForAnswer()を呼び出すと,すべての登録されたPacketListenerオブジェクトの通知を引き起すという利点がある.これはnextPacket()で手動でパケットを呼んだときには起らない.つまり,すべてのパケットを J/Link の内部ループで読まれるようにした方がよいということである.
String inputString = getStringFromUser();
ml.putFunction("EnterTextPacket", 1);
ml.put(inputString);
String result = null;
int pkt = ml.waitForAnswer();
if (pkt == MathLink.RETURNTEXTPKT) {
// Wolfram言語での計算でNullではない結果が返されたので,RETURNTEXTPKTが
// 生成された. その内容(文字列)を読んでみる.
result = ml.getString();
// ここで再びwaitForAnswer()を呼び出す.これは常に後続する
// InputNamePacketを開いた後に戻る.この状況では,これは単に
// nextPacket()を呼び出すことである.
ml.waitForAnswer();
}
// この時点で,waitForAnswer()を呼び出すことによりMathLink.INPUTNAMEPKTが
// 戻った.次の入力プロンプトである,その内容を読み出す.
String nextPrompt = ml.getString();
ユーザに,メッセージとPrint出力を含むWolfram言語から届く出力のストリームをすべて見せたければ,EnterTextPacketを使っているときにPacketListenerを使うとよい.PacketListenerの実装は入出力セッションウィンドウに入ってくるパケットを書くことができる.実際に,そのようなPacketListenerがあるなら,結果を含むReturnTextPacketや次のプロンプトを含むInputNamePacketをはじめとするすべての出力を扱わせるとよい.そのようにすると,主プログラムでdiscardAnswer()を呼び出すだけで,後はPacketListenerがすべてを扱うようになる.
MathLinkExceptionを扱う
WSTPエラーが起きると,ほとんどのMathLinkとKernelLinkメソッドはMathLinkExceptionを投げる.これは関数がエラーコードを返すWSTP C APIとは対照的である.MathLinkExceptionを投げないメソッドは,一般的にすでに投げられたMathLinkExceptionを扱うcatchブロック内で使わなければならないものである.もしこれらのメソッドが自身の例外を投げるなら,catchブロック内に別のtry/catchブロックをネストする必要がある.
正しい形式の J/Link プログラムは,カーネルが不意に終了した等の致命的なWSTPエラー以外では通常はMathLinkExceptionを投げない.「正しい形式」というのは式をputしたりgetしたりするときに,putFunction()の呼出しで引数を3つ指定したのに2つしか送らなかったり,現在のパケットの内容を読み終える前にnextPacket()を呼び出したりするというような明らかな間違いをしないということである.J/Link APIは,リンクとの低レベルのインタラクションを隠すwaitForAnswer()やevaluateToOutputForm()のような高レベルの関数を提供することにより,そのような間違いを避ける助けとなるが,最も些細な J/Link プログラム以外のすべてで,まだそのようなエラーが起る可能性がある.投げられたMathLinkExceptionオブジェクトの大部分は,ユーザのエラーやランタイムの異常ではなくプログラムコードのロジックエラーを表している.それらはプログラマーが直さなければならないバグに過ぎない.
正しい形式の小さい J/Link プログラムでは,1つのtry/catchブロック内に数多くの J/Link 呼出し,あるいはプログラム全体さえも置くことができるかもしれない.というのは,エラーが起きたときプログラムが何をしているのかを正確に知る必要がないからである.ただメッセージを出力して終了するだけでよい.「サンプルプログラム」のプログラム例題はこの構造を取っている.多くの J/Link プログラムはMathLinkExceptionオブジェクトの扱いについて,ただ終了するよりもう少し洗練されている必要がある.プログラムのタイプにかかわりなく,プログラム作成中はきめ細かくtry/catchブロックを使い(つまり,それぞれのtry/catchブロックで,意味をなす小さな単位のコードをラップするだけにする),メッセージを出力したり何らかの方法で警告したりするcatchブロックにいつもコードを置くようにすることを強くお勧めする.プログラマーがWSTPエラーが起きたことを知らなかったり,コードのどの領域で生じたかを誤って識別したりしたために,デバッグに何時間も浪費されている.
以下は,回復したい場合のMathLinkExceptionの扱い方の例である.エラーの状態がクリアになるまで他のWSTPの呼出しは失敗するので,まずclearError()を呼び出す.もしclearError()がfalseを返したらリンクを閉じるしかない.clearError()が直すことのできるエラーの例として,現在のパケットが完全に読み込まれないうちにnextPacket()を呼び出すというものがある.clearError()が呼び出された後,リンクはnextPacket()違反の前の状態にリセットされる.それから現在のパケットの残りを読むことも,newPacket()を呼び出してそれを捨てることもできる.clearError()が直せるもうひとつのエラーの例に,リンクで待っているデータ型に,誤った「get」メソッドを呼び出したというものがある.例えば,整数が待っているときにgetFunction()を呼び出すのがこれに当たる.clearError()を呼び出したらその整数が読めるようになる.
try {
...
} catch (MathLinkException e) {
System.err.println(e.toString());
if (ml.clearError() != true) {
System.err.println("MathLinkException was unrecoverable; closing link.");
ml.close();
return; // あるいは他の適切なクリーンアップ
}
// clearErrorにどう応答するかは自由である
}
clearError()を呼び出した後catchブロックですることは,例外が投げられたときに何をしていたかによる.便利で一般的なガイドラインとして,例外が投げられたときにリンクから読込みをしていれば,パケットの残りを捨てるためにnewPacket()を呼び出すということしかいえない.こうすれば,たとえ前のパケットの内容をなくしたとしても,少なくとも新しいパケットを読む準備ができていることになる.
MathLinkExceptionには例外の原因を知らせる便利なメソッドがいくつかある.getErrCode()メソッドは内部的なWSTPエラーコードを与えるが,これはWSTPドキュメントで調べることができる.getMessage()では,エラーと関連した内部メッセージが得られ,より便利である.toString()メソッドは上記の情報すべてを知らせてくれ,デバッグのためには最も役に立つ出力である.
// 便利なMathLinkExceptionメソッドの例である
public int getErrCode();
public String getMessage();
public String toString();
public Throwable getCause();
MathLinkException例外の中には「ネイティブな」WSTPエラーではなく,さまざまなリンクインターフェースの実装により投げられる特殊な例外もある.J/Link は標準的な「例外連鎖」イディオムに従って,リンクの実装が内部的にこれらの例外を捕え,それをMathLinkExceptionでラップして投げ返すようにしている.例として,Java Remote Method Invocation (RMI)の上に構築されたKernelLinkの実装を考えてみる.RMIで呼び出されたメソッドはRemoteExceptionを投げることができるので,そのようなリンクの実装はすべてのRemoteExceptionを内部的に受け取りMathLinkExceptionでラップする.もし,リンクの実装がこのように動作せず,メソッドすべてがMathLinkExceptionの他にRemoteExceptionも投げるとすると,それを使うすべてのクライアントコードが修正されなければならない.つまり,MathLinkExceptionを捕えたら,それは内部的なWSTPの問題を表わしているのではなく,別の例外を「ラップしている」可能性があることである.MathLinkExceptionインスタンスにgetCause()メソッドを使うと,問題の実際の原因となっている,ラップされた例外を取り出すことができる.getCause()メソッドは,MathLinkExceptionが別のタイプの例外をラップしていない場合にはnullを返す.
グラフィックスとタイプセット出力
序文
Wolfram言語を使うJavaプログラムを書いているデベロッパは,Wolfram言語のグラフィックスやタイプセット式を作成したいと思うことが多い.J/Link にはこれらの画像の取得や表示がとても間単にできるようデザインされた高レベルのメソッドがあるが,それでもこれは比較的複雑な問題である.JavaウィンドウでWolfram言語の画像を表示したいときは,次のセクションで説明するMathCanvasあるいはMathGraphicsJPanelコンポーネントを使うとよい.その過程をもっとコントロールしたい,あるいは表示だけでなく画像データを使って何かしたい(ファイルやストリームに書き込む等)場合は,「evaluateToImage()とevaluateToTypeset()」メソッドについてのセクションを読まなければならない.
MathCanvasとMathGraphicsJPanel
MathCanvasクラスとMathGraphicsJPanelクラスはWolfram言語プログラムでよく使われるので,「MathCanvasとMathGraphicsJPanelクラス」ですでに説明してある.これはJavaプログラムでも同様に便利なものである.どちらも単純なグラフックスのコンポーネント(実際はJavaBean)であり,Wolfram言語のグラフィックスやタイプセット式を表示することができる.MathCanvasはAWTCanvasクラスの単純なサブクラスであり,MathGraphicsJPanelはSwing JPanelクラスのサブクラスである.これらは概念的には同一で,Wolfram言語グラフィックスを扱うための同じ追加のメソッドを持っている.AWTコンポーネントが必要なときはMathCanvasを,Swingコンポーネントが必要なときはMathGraphicsJPanelを使う.
この2つのクラスがどのように動作するかは,ソースコードを見てみるとよい.これらのクラスの最も重要なメソッドは以下のものである.
public void setMathCommand(String cmd);
public void setImageType(int type);
public void setUsesFE(boolean useFE);
public void setUsesTraditionalForm(boolean useTradForm);
public void setImage(Image im);
public void recompute();
public void repaintNow();
簡潔にするために,以下の解説ではMathCanvasのみに言及するが,ここに述べてあることはすべてMathGraphicsJPanelにも同様に当てはまる.setMathCommand()を使って,評価し結果を表示させる任意のWolfram言語コードを指定する.MathCanvasでWolfram言語のグラフィックスを表示する場合,計算の結果はグラフィックスオブジェクト(つまり,Graphics,Graphics3D等の頭部を持つもの)でなければならない.コマンドでグラフィックスが生成されるだけでは十分ではない.グラフィックスが返されなければならないのである.従って,setMathCommand(“Plot[x,{x,0,1}]”)は使えても,setMathCommand(“Plot[x,{x,0,1}];”)は使えない.というのは,最後にセミコロンがあるため,式を評価するとNullとなるからである.MathCanvasを使ってタイプセット出力を表示する場合は,setMathCommand()に与えられるコードを実行した結果は何でも構わない.それをタイプセットしたものが表示される.setMathCommand()で指定したコード内では,Java文字列内で特殊な意味を持つクォーテーションマークやその他の文字は,setMathCommand("Plot[x,{x,0,1},PlotLabel->\"A Plot\"]")のようにバックスラッシュを前に置いてエスケープしなければならない.
setImageType()メソッドはグラフィックスの表示とタイプセット式の表示を切り換える.この2つのモードを切り換えるにはsetImageType(MathCanvas.GRAPHICS)かsetImageType(MathCanvas.TYPESET)を呼び出す.
J/Link は2つの方法でWolfram言語のグラフィックスの画像を作成することができる.その2つの方法とは,カーネルのみを使用する方法と,フロントエンドからの付加的サービスとカーネルを両方使用する方法である.フロントエンドはうまくタスクをこなすが,欠点もある.フロントエンドを使用する場合は,setUsesFE(true)を呼び出す.setUsesFE(true)を呼び出すと,フロントエンドが起動するかもしれないし,すでに起動中のものが使われるかもしれない.実際の動作は使用中のオペレーティングシステムやWolfram言語のバージョンによって異なる.Mathematica 6.0以降では,すべてのグラフィックス出力にフロントエンドが必要となっているため,setUsesFE()メソッドは効力がない.これは常にtrueである.
タイプセット出力では,デフォルトはStandardFormである.TraditionalFormに変えるにはsetUsesTraditionalForm(true)を呼び出す.タイプセット出力の生成中は(つまり,setImageType(MathCanvas.TYPESET)を呼び出したら),フロントエンドは常にタイプセット出力の生成に携わるので,「サービスとしてフロントエンドを使用する」で扱われている問題について理解する必要がある.
setMathCommand()を呼び出したら,コマンドは直ちに実行され,結果の画像がキャッシュされ,ウィンドウが再描画されるたびに使われる.mathコマンドのコードは変化する変数に依存している場合もある.コマンドが再計算され,新しい画像が表示されるようにするには,recompute()を呼び出す.
repaintNow()メソッドは,JComponentメソッドのpaintImmediately()の「洗練された」バージョンのようなもので,paintImmediately()と同様の状況で使う.repaintNow()メソッドには描画する画像が分かっており,すべてのピクセルが準備できるまでブロックする.プロットの依存する変数をコントロールするスライダーのドラッグのようなユーザの動作に反応して,画像を瞬時にアップデートしたいときに,このメソッドを使うと直ちに再描画することができる.標準的なrepaint()メソッドを使ったとすると,Javaはフレームがたくさん表示されるまで再描画できない.プロットは変数の値の変化に応じて再描画されるのではなく,1つの値から次の値へとジャンプしているように見える.
今まで,setMathCommand()にコードを与えることによりMathCanvasにWolfram言語出力を簡単に表示する方法を説明してきた.MathCanvasに画像を表示するもうひとつの方法はJavaのImageオブジェクトを生成しsetImage()メソッドを呼び出すというものである.画像がWolfram言語データで作成されたビットマップの場合,あるいはJavaグラフィックスAPIを使ってオフスクリーンの画像に描画した場合は,この方法がよい.setImage()メソッドは主にWolfram言語コードから使用するために作成されたものである.すでにコンポーネントに描画する方法があるため,この方法はJavaプログラマーにとってそれほど大切なものではないが,便利なこともある.これにより,コンポーネントに自分の内容を描画する便利なテクニックであるpaint()メソッドをオーバーライドするためだけに,AWTコンポーネントのサブクラスを書く手間が省けるからである.MathCanvasはsetImage()メソッドとともに使うと,便利なAWTコンポーネントである.Wolfram言語とは直接何の関係もない.
次では,MathCanvasを使ってグラフィックスとタイプセット出力を表示するプログラム例を示す.
グラフィックスとタイプセット結果を表示するサンプルプログラム
これはWolfram言語のグラフィックスとタイプセット出力を表示するウィンドウを示す簡単なプログラムのコードであり,MathCanvasクラスの使い方を示す例である.このプログラムのコードとコンパイルされたクラスファイルはJLink/Examples/Part2/GraphicsAppディレクトリにある.引数としてカーネル実行ファイルへのパス名を与えてプログラムを起動する (「“」と「‘」を使うことに注意).
(Windows)
java -classpath GraphicsApp.jar;..\..\..\JLink.jar GraphicsApp "c:\program files\wolfram research\mathematica\10.0\mathkernel"
(Linux)
java -classpath GraphicsApp.jar:../../../JLink.jar GraphicsApp 'math -mathlink'
(Mac OSX コマンドライン)
java -classpath GraphicsApp.jar:../../../JLink.jar GraphicsApp '"/Applications/Mathematica.app/Contents/MacOS/MathKernel" -mathlink'
import com.wolfram.jlink.*;
import java.awt.*;
import java.awt.event.*;
public class GraphicsApp extends Frame {
static GraphicsApp app;
static KernelLink ml;
MathCanvas mathCanvas;
TextArea inputTextArea;
Button evalButton;
Checkbox useFEButton;
Checkbox graphicsButton;
Checkbox typesetButton;
public static void main(String[] argv) {
try {
String[] mlArgs = {"-linkmode", "launch", "-linkname", argv[0]};
ml = MathLinkFactory.createKernelLink(mlArgs);
ml.discardAnswer();
} catch (MathLinkException e) {
System.out.println("An error occurred connecting to the kernel.");
if (ml != null)
ml.close();
return;
}
app = new GraphicsApp();
}
public GraphicsApp() {
setLayout(null);
setTitle("Graphics App");
mathCanvas = new MathCanvas(ml);
add(mathCanvas);
mathCanvas.setBackground(Color.white);
inputTextArea = new TextArea("", 2, 40, TextArea.SCROLLBARS_VERTICAL_ONLY);
add(inputTextArea);
evalButton = new Button("Evaluate");
add(evalButton);
evalButton.addActionListener(new BnAdptr());
useFEButton = new Checkbox("Use front end", false);
CheckboxGroup cg = new CheckboxGroup();
graphicsButton = new Checkbox("Show graphics output", true, cg);
typesetButton = new Checkbox("Show typeset result", false, cg);
add(useFEButton);
add(graphicsButton);
add(typesetButton);
setSize(300, 400);
setLocation(100,100);
mathCanvas.setBounds(10, 25, 280, 240);
inputTextArea.setBounds(10, 270, 210, 60);
evalButton.setBounds(230, 290, 60, 30);
graphicsButton.setBounds(20, 340, 160, 20);
typesetButton.setBounds(20, 365, 160, 20);
useFEButton.setBounds(180, 340, 100, 20);
addWindowListener(new WnAdptr());
setBackground(Color.lightGray);
setResizable(false);
// このコードはevaluateToImageあるいはevaluateToTypesetで
// 自動的に呼び出されるが,これではフロントエンドウィンドウが
// このJavaウィンドウの前面に来てしまう.
// 従って,はじめにフロントエンドウィンドウを邪魔にならないところに置いて,
// 前面に持ってきたいときにtoFrontを呼び出すようにする.
// KernelLink.PACKAGE_CONTEXTは単に「JLink`」のことであるが,
// パッケージの内容をハードコードしないでこのシンボル定数を
// 使った方がよい.
ml.evaluateToInputForm("Needs[\"" + KernelLink.PACKAGE_CONTEXT + "\"]", 0);
ml.evaluateToInputForm("ConnectToFrontEnd[]", 0);
setVisible(true);
toFront();
}
class BnAdptr implements ActionListener {
public void actionPerformed(ActionEvent e) {
mathCanvas.setImageType(
graphicsButton.getState() ? MathCanvas.GRAPHICS : MathCanvas.TYPESET);
mathCanvas.setUsesFE(useFEButton.getState());
mathCanvas.setMathCommand(inputTextArea.getText());
}
}
class WnAdptr extends WindowAdapter {
public void windowClosing(WindowEvent event) {
if (ml != null) {
// フロントエンドを使用したので,リンクを
// 閉じる前にCloseFrontEnd[]を呼び出すことが.
// 大切である.これは,フロントエンドの終了を
// 強制したいからではなく,起動した
// フロントエンドセッションをユーザが使い始めた場合に
// このような操作はしたくないからである.
// CloseFrontEndは,必要に応じてフロントエンドから
// 丁寧に解放される.
// Mathematicaの将来のバージョンでは,
// このようなことをする必要はなくなるであろう.
ml.evaluateToInputForm("CloseFrontEnd[]", 0);
ml.close();
}
dispose();
System.exit(0);
}
}
}
evaluateToImage()とevaluateToTypeset()
前述の通り,MathCanvasクラス,またはMathGraphicsJPanelがニーズに合わなければ,KernelLinkインターフェースでevaluateToImage()とevaluateToTypeset()を使って手動でWolfram言語のグラフィックスの画像とタイプセット式を作成することができる.
それぞれについて複数のシグネチャがある.evaluateToImage()については,1セットがより簡単な引数のリストを取り,あまり一般的でない引数にはデフォルトの値を使う.以下はKernelLinkインターフェースからのグラフィックスとタイプセットメソッドである.
evaluateToImage()メソッドは入力に文字列かExpr,結果の画像の幅と高さにピクセルを取る.拡張バージョンでは,1インチあたりのドット値やノートブックフロントエンドの使用をするか否か(後で説明する)について指定することができる.短いバージョンではdpiの値に0を,フロントエンドを使用するかどうかの値にfalseを使う.dpiを0と指定することで,Wolfram言語はそのデフォルト値を使う.画像は縦横比を変えることなく,ボックス幅で高さ 以内に収まるように大きさが調整される.つまり,画像は正確にはこの大きさではないかもしれないが,どちらの大きさも決してボックスの大きさより大きくなることはなく,きれいに収めるためにどちらかだけが引き伸ばされることもない.Automatic値を得るには幅と高さに0を渡す.もし入力を評価してもグラフィックス式にならなければ,nullが返される.演算でプロットが作成されるだけでは不十分である.演算の戻り値の頭部はGraphics(あるいはGraphics3D等)でなければならない.もしuseFrontEnd引数がtrueでノートブックフロントエンドが起動していなければ,evaluateToImage()がそれを起動する.Mathematica 5.1以降では,フロントエンドは常にグラフィックスで使用されるので,useFrontEnd引数は必要ない.
evaluateToTypeset()メソッドは入力に文字列あるいはExpr,タイプセットされる前の出力を折り返すページ幅,StandardFormとTraditionalFormを使うかどうかを指定するフラグを取る.ページ幅の単位はピクセル(無限大は0)である.evaluateToTypeset()メソッドはノートブックフロントエンドのサービスを要求する.まだフロントエンドが起動していなければそこで起動する.
この両方のメソッドの結果はGIFデータのbyte配列である.GIF形式はほとんどのWolfram言語のグラフィックスにうまく適合するが,3Dグラフィックスの中には色使いが理想的ではないものもある.JPEG形式を使うようにしたければ,カーネルの$DefaultImageFormatを"JPEG"に設定するとよい.
// evaluateToImage()とevaluateToTypeset()の後続の
// 呼出しにJPEG形式を指定する.
ml.evaluateToOutputForm("$DefaultImageFormat = \"JPEG\"", 0);
これらのメソッドは1ステップで演算して結果を返すという点でevaluateToInputForm()やevaluateToOutputForm()に似ている.このようなメソッドはすべて「evaluateTo」メソッドと呼ばれている.MathLinkExceptionが生じる等のありえないイベントではnullを返す.
MathCanvasクラスとMathGraphicsJPanelクラスは内部的にこれらのクラスを使うので,そのソースコードは呼出しの例を探す絶好の場所である.MathCanvasコードは,GIFあるいはJPEGデータのbyte配列を取り,それをJavaのImageに変換する方法を示す.
下のTypesetterプログラム例題はまた別の例である.これはコマンドラインに与えられたWolfram言語の式を取り,evaluateToTypeset()を呼び出し,GIFファイルに画像データを書き込む.以下のようなコマンドラインから呼び出す.
(Windows)
java Typesetter "c:\program files\wolfram research\mathematica\10.0\mathkernel" "Sqrt[z]" test.gif
(Linux)
java Typesetter 'math -mathlink' "Sqrt[z]" test.gif
(Mac OSXコマンドライン)
java Typesetter '"/Applications/Mathematica.app/Contents/MacOS/MathKernel" -mathlink' "Sqrt[z]" test.gif
1つ目の引数はWolfram言語カーネルを起動するコマンドライン,2つ目の引数はタイプセットする式,3つ目の引数は作成するファイル名である.このプログラムは便利さではなく,単に例示を意図したものである.
import com.wolfram.jlink.*;
import java.io.*;
public class Typesetter {
public static void main(String[] argv) throws MathLinkException {
KernelLink ml;
try {
String[] mlArgs = {"-linkmode", "launch", "-linkname", argv[0]};
ml = MathLinkFactory.createKernelLink(mlArgs);
ml.discardAnswer();
} catch (MathLinkException e) {
System.err.println("FATAL ERROR: link creation failed.");
return;
}
byte[] gifData = ml.evaluateToTypeset(argv[1], 0, false);
try {
FileOutputStream s = new FileOutputStream(new File(argv[2]));
s.write(gifData);
s.close();
} catch (IOException e) {}
// evaluateToTypeset()あるいはevaluateToImage()の
// useFEパラメータをtrueに設定して使った後は,
// 必ずカーネルをキルする前にCloseFrontEnd[]を実行するようにする.
ml.evaluateToOutputForm("CloseFrontEnd[]", 0);
ml.close();
}
}
カーネルのリンクを閉じる前にCloseFrontEnd[] を必ず実行する.これは特に,すでに起動中のものが使われユーザがドキュメントを開いている場合等,フロントエンドが終了するべきではないところで終了するのを防ぐのに大切なことである.
演算の放棄と中断
J/Link には演算の放棄および中断の方法が2つある.最初の方法は低レベルのputMessage()関数を使って望みのWSTPメッセージを送ることである.2つ目の方法は J/Link 2.0で導入された新しいKernelLinkメソッドを使うことで,お勧めするのはこちらの方法である.以下にこれらをリストする.
abortEvaluation()メソッドはWolfram言語に放棄の要求を送る.これは評価 ▶ 評価を放棄を選んだときにノートブックフロントエンドで起ることと全く同じである.Wolfram言語は現行の評価を終了しシンボル$Abortedを返すことでこのコマンドに応答する.カーネルは中断あるいは放棄にすぐに応答できない状況にある場合があるということにご注意いただきたい.
interruptEvaluation()メソッドはWolfram言語に中断の要求を送る.Wolfram言語は現行の評価を中止し,次に何をしたらよいかの選択肢を含む特殊なパケットを送り返すことで,このコマンドに応答する.この選択肢は,その時点でカーネルが何をしているかによるが,ほとんどの場合,放棄,継続,ダイアログの入力を含んでいる.この選択肢のリストを自分で扱いたいという人はあまりいないので,代りにabortEvaluation()を呼び出して計算を中止した方がよいかもしれない.しかし,インタラクティブなフロントエンドを開発している場合は,ユーザにノートブックフロントエンドが提供する同じタイプの選択肢を見てもらいたいと思うこともあるであろう.そのような場合は,新しいInterruptDialogクラスを使うことができる.InterruptDialogクラスは後のセクションで説明する.
abandonEvaluation()メソッドは,まさにその名が示す通りのことを行う.これにより,何かがリンクに到着するのを待っているコマンドがすぐに引き戻され,MathLinkExceptionを投げる.このMathLinkExceptionは回復可能なので(つまりclearError()がtrueを返すので),理論的にはwaitForAnswer()を後で再び呼び出してそれが到着したときに結果を得ることができる.しかし,実際には,リンクを閉じる予定がない場合にはこのメソッドを使うべきではない.abandonEvaluation()メソッドは,カーネルがどのような状態であるかにかかわらず,プログラムが結果を待っている状態から引き戻す「非常口」的な関数であると思っていただきたい.abandonEvaluation()はWolfram言語に放棄の要求を送るだけなので,カーネルからの協力が必要だということを思い出す必要がある.実行中の評価が適時に放棄されるという保証はない.abandonEvaluation()の直後にclose()を呼び出した場合,カーネルはまだ計算で忙しいので,通常終了することはない.カーネルが確実にシャットダウンするためにはclose()の前にterminateKernel()を呼び出さなければならない.下のコードでこれを例示する.
terminateKernel()メソッドは,低レベルのWSTPメッセージ,WSTERMINATEMESSAGEを送ることにより,Wolfram言語に終了要求を送る.これはオペレーティングシステムのコマンドでカーネルプロセスを終了させるようなものなので,カーネルにシャットダウンを指示する最強の方法である.カーネルの「通常の」操作では,リンクにclose()を呼び出すとカーネルは終了する.しかし,場合によっては,カーネルが計算で忙しいとき等に限って,終了しないこともある.このような場合,terminateKernel()を呼び出すと,通常即座にカーネルを強制終了させることができる.その後直ちにclose()を呼び出さなければならない.サーバ環境では,Wolfram言語カーネルを起動して終了するJavaプログラムが,できる限り高い信頼性で長時間に渡り,放置された状態で動作する必要がある.従って,カーネルがまだビジーの間にclose()を呼び出さなければならない場合は,常にclose()の前にterminateKernel()を呼び出すようにした方がよい.まれに(通常Wolfram言語の何かが異常な場合),terminateKernel()を呼び出してもカーネルが終了しないことがある.そのようなときはオペレーティングシステムの機能を使って(おそらくJavaのRuntime.exec()メソッドを呼び出すことで)カーネルプロセスを終了しなければならない.
演算を放棄,あるいは中断できるようにするには,プログラムに少なくとも2つのスレッドがなければならない.演算が呼び出されるスレッドはおそらく今まで見てきたサンプルプログラムとほぼ同じものである.「evaluateTo」メソッドの1つや,evaluate()の後にwaitForAnswer()を呼び出すとする.するとこのスレッドは結果を待ってブロックする.別のスレッド(おそらくユーザインターフェーススレッド)では,タイムアウト期間の経過のような,あるイベントを定期的にチェックすることができる.あるいはイベントリスナを使うと,Escキーが押されたときに通知を受けることもできる.どちらの方法で中止の要求を検出したいとしてもも,putMessage(MathLink.MLABORTMESSAGE)を呼び出しさえすればよいのである.もしカーネルが終了する前にメッセージを受け取っていて,中止できることを行っている最中であるとしたら,演算は終了しシンボル$Abortedが返される.演算スレッドでは何も特別なことをする必要はない.いつものように答を待つ.ただ,最終結果の代りに$Abortedが返ってくるか可能性がある.以下は,計算の放棄を示す典型的なコードの一部である.
// スレッド1で
ml.evaluate("Do[2+2, {20000000}]");
ml.waitForAnswer();
// ユーザが放棄した場合,結果は$Abortedというシンボルになる.
// スレッド2で
if (userPressedEscKey() || timeoutElapsed())
ml.abortEvaluation();
以下は演算を放棄し,カーネルを即座にシャットダウンさせる方法を示すコードである.
// スレッド1で
try {
ml.evaluate("While[True]");
ml.discardAnswer();
} catch (MathLinkException e) {
// abandonEvaluation()が別のスレッドで呼び出されたらこのようなことが起る.
System.err.println("MathLinkException occurred: " + e.toString());
if (!ml.clearError()) {
// abandonEvaluation()が呼び出されると,clearError()は常に失敗する.
ml.terminateKernel();
ml.close();
}
}
// スレッド2で
if (timeoutElapsedAndReallyNeedToShutdownKernel())
ml.abandonEvaluation();
ここまでの説明では,演算の中止と放棄のための高レベルのインターフェースに焦点を当てていた.この代替策として,低レベルのメソッド,putMessage()を使い,MathLink.MLINTERRUPTMESSAGE,MathLink.MLABORTMESSAGE,MathLink.MLTERMINATEMESSAGEの定数のうちの1つを渡すというものがある.しかしinterruptEvaluation(),abortEvaluation(),terminateKernel()が適切なメッセージをputするほんの1行のメソッドなので,この代替案を使う理由はない.MathLinkメソッドのputMessage()で言及されている「message」とは,見慣れたWolframシステムのエラー・警告メッセージと関連したものではない.それらは2つのWSTPプログラム間の特殊なタイプの通信なのである.この通信は式の通常の流れとは異なるチャンネルで行われるので,カーネルが計算の最中でリンクから読んでいないときでもputMessage()を呼び出すことができるのである.
MathLinkメソッドには,名前に「message」を含むものが,まだいくつかある.messageReady(),getMessage(),addMessageHandler(),removeMessageHandler()がそうである.これらは,カーネルが送ってくるメッセージを検出できるようにしたい場合にのみ有用である.J/Link プログラマーはあまりこれには関心がないと思われるので,これ以上詳しくは説明しない.messageReady()とgetMessage()は J/Link 2.0以降ではもう動作しないのでご注意いただきたい.Wolframシステムからメッセージが受け取れるようにしたい場合は,addMessageHandler()とremoveMessageHandler()を使わなければならない.これらのメソッドについてはJavaDocsに情報がある.
マークを使う
WSTPを使うとリンクに「マーク」を設定することができるので,よりたくさんのデータを読み,マークに戻りデータを読む前の状態にリンクを復元することができる.このように,マークによってリンクからデータを読み,データが消去されないようにすることができるので,後でまたデータを読むことも可能である.MathLinkインターフェースには3つのマーク関連のメソッドがある.
// MathLinkインターフェースで
long createMark() throws MathLinkException;
void seekMark(long mark);
void destroyMark(long mark);
マークを使うのは,着信の式を調べ,式の属性に従って異なるコードに分岐したい場合が一般的である.式全体を見るために実際に式を扱うコードが必要であるが,どのように扱われなければならないかを決めるために少なくとも式を部分的に読まなければならない(おそらくgetFunction()を呼び出して頭部を見るだけでよい).以下はこのテクニックを例示するコードの一部である.
String head = null;
long mark = ml.createMark();
try {
head = ml.getFunction().name;
ml.seekMark(mark);
} finally {
ml.destroyMark(mark);
}
if (head.equals("foo"))
handleFoo(ml);
else if (head.equals("bar"))
handleBar(ml);
getFunction()を呼び出した後マークに戻るので,リンクはhandleFoo()とhandleBar()メソッドが入力されたときに式の最初にリセットされる.マーク生成後にどんな例外が投げられたとしても,マークがいつも確実に破壊されるようにするためにtry/finallyブロックを使う.マークはいつもこのように使わなければならない.createMark()を呼び出した直後,finally節がdestroyMark()を呼び出すtryブロックを始める.createMark()とtryブロックの間には他のコード,特にWSTPの呼出し(これはMathLinkExceptionを投げる)が介入していないことが大切である.マークが生成されて破壊されなければ,着信データがリンクに山積みになり解放されないので,メモリリークが起る.
マークを使うと,式をある方法で読み,MathLinkExceptionが投げられたら戻って違う方法で読むこともできる.例えば,実数のリストがリンクで待っていると想定しているとする.マークをセットしgetDoubleArray1()を呼び出すことができる.リンクのデータが実数のリストに強制できなければ,getDoubleArray1()はMathLinkExceptionを投げる.するとマークに戻り,違う方法でデータを読むことができる.
double[] data = null;
long mark = ml.createMark();
ty {
data = ml.getDoubleArray1();
} catch (MathLinkException e) {
ml.clearError();
ml.seekMark(mark);
// ここで,データの異なる読み方を試す.
switch (ml.getNext()) {
...
}
} finally {
ml.destroyMark(mark);
}
マークの機能のほとんどは「Exprクラスの動機付け」で説明されるExprクラスによって包括されている.Exprオブジェクトにより,式を異なる方法で何度でも調べられるようになる.peekExpr()メソッドを使うと,近付いてくる式をリンクから消さないで簡単に前もって見ることができる.
ループバックリンクを使う
MathLinkとKernelLinkインターフェースに加え,LoopbackLinkインターフェースというものがある.ループバックリンクはプログラムがWolfram言語の式を保管することができるようにするWSTPの便利な機能である.リンクから式を読み,しばらくそれを保持し,同じあるいは異なるリンクにそれを書き戻したいとする.どのようにすればよいであろうか.標準的な読込み関数(getFunction(),getInteger()等)で読むと,式を小さなコンポーネントにまで砕き,その数も莫大なものになるかもしれない.それから一連の「put」メソッドに相当するもので式を再生しなければならなくなる.必要なのは,後で読んだり異なるリンクへまた移動させることができる,式を丸ごと移す一時的な場所である.ループバックリンクはこの目的を果たす.
ループバックリンクの検証に進む前に,J/Link のExprクラスがループバックリンクが使われるのと同じ目的のために使われるということに注目する.Exprオブジェクトは,内部的にループバックリンクを使うループバックリンクが提供する機能をずっと豊かに拡張したものである.プログラムでループバックリンクを使う代りにExprオブジェクトを使うことを考える必要がある.
MathLinkがパイプのようなものであるとすると,ループバックリンクはユーザを指すように折り曲がったパイプである.リンクの両側を管理し,FIFO順に「一方」から書き込みもう一方から読み出す.J/Link でループバックリンクを作るにはMathLinkFactoryメソッドのcreateLoopbackLink()を使う.
LoopbackLinkインターフェースはMathLinkインターフェースを拡張するので,すべてのMathLinkメソッドはループバックリンクで使用できる.LoopbackLinkはMathLinkインターフェースにこれ以上のメソッドを加えない.ではなぜ別のインターフェースがあるのか.それは,この種のリンクは通常の片側のWSTPリンクとは異なる動作をするので,このようなタイプがあると便利だからである.さらに,MathLinkインターフェースには引数としてループバックリンクを要求するメソッドが1つ (transferToEndOfLoopbackLink()) ある.このように,異なるLoopbackLinkタイプを持つことで,J/Link と自分のプログラム内のタイプ保全の方法が提供される.
ループバックリンクとともにおそらくMathLinkメソッドのtransferExpression()か,その変形のtransferToEndOfLoopbackLink()を使うことになる.transferExpression()は式を別のリンクからループバックリンクに動かすため,あるいは手動でループバックリンクに置いた式を別のリンクに動かすために必要である.これがその2つのメソッドの宣言である.
// MathLinkインターフェースで
void transferExpression(MathLink source) throws MathLinkException;
void transferToEndOfLoopbackLink(LoopbackLink source) throws MathLinkException;
ソースリンクが引数で,デスティネーションはthisリンクである.transferExpression()メソッドはソースリンクから1つの式を読み,それをデスティネーションリンクに置き,transferToEndOfLoopbackLink()メソッドはソースリンク(LoopbackLinkでなければならない)のすべての式をデスティネーションリンクに移す.
すでに述べたように,ループリンクは一般的に,式をあとで異なるリンクに書き込むための一時的な保管場所で使用すると便利である.しかし,これはExprオブジェクトを使うともっと簡単にできる(「Exprクラスの動機付け」).ループバックリンクを使うと,どのくらいの長さになるかが分かる前に式を送り始めることもできる.putFunction()メソッドは引数の数(長さ)の指定を要求する.しかし,式の長さが前もって分からないときもある.次のコードの一部を見てみる.乱数のリストをWolfram言語に送る必要があり,その長さはテストの結果によるが,それはコンパイル時には分からない.ループバックリンクを作成し,数が生成されたときに数えながらその上に押し出すことができる.ループが終了したときに,いくつ生成されたか分かるので,putFunction()を呼び出しループバックリンクの内容をデスティネーションリンクに「注ぎ込む」.この例では,ループバックリンクよりもJava配列やVectorに累算する数を保管する方が簡単である.しかし,もし複雑な式を送っているとしたら,ネイティブのJava構造に保管するのは簡単ではない.リンクに書き込むだけにして,保管の問題はWSTPの内部に任せる方が簡単である.
// 送信開始時に長さが分かっていない式(realsのリスト)を
// 送ってみる.
try {
...
LoopbackLink loop = MathLinkFactory.createLoopbackLink();
int count = 0;
while (someTest) {
loop.put(Math.random());
count++;
}
ml.putFunction("List", count);
ml.transferToEndOfLoopbackLink(loop);
loop.close();
...
} catch (MathLinkException e) {}
Exprオブジェクトを使う
Exprクラスの動機付け
ExprクラスはJavaにWolfram言語の式の直接表現を提供する.Wolfram言語のコンポーネントはすべて式であり,WSTPはプログラム間でWolfram言語の式を伝達し合うためのものなので,Exprクラスは便利だと思える.
WSTPプログラムでWolfram言語の式を扱うのにはいくつかの方法がある.まず,文字列としてそれらを送信および受信することができる.これは特にユーザによってタイプされた入力を取り込む場合やユーザに結果を表示する場合に便利である.KernelLinkメソッドの多くは入力に文字列を取り結果も文字列で返す.2つ目の方法はWolfram言語の式をリンクに置くか,一連の「put」呼出し,あるいは「get」呼出しで一度に1つずつリンクから読むことである.3つ目の方法はループバックリンクに保管し,リンク間でそれを動かすことである.これらの方法にはどれも長所と短所がある.
ループバックについては前のセクションで説明したが,Exprクラスの動機付けを理解する背景となるので,ここでもう一度要約しておく.基本的に,ループバックリンクはリンクから読んだりプロセスで小さなコンポーネントに分解せずにWolfram言語の式を保管する方法を提供する.従って,ループバックリンクにより,式を後で読んだり他のリンクに移したりするために保管しておくことができるのである.しかし,最終的に式を読んだり調べたりしたいときは正しい「get」呼出しの列で任意の式をリンクから分析するという難しいタスクを行わなければならない.ここでExprクラスが使える.ループバックリンクのように,Exprオブジェクトは任意のWolfram言語の式を保管する.しかしExprクラスはずっと先を行き,式の構造を調べたり,その一部を抽出したり,新しい式を構築したりするメソッドを提供する.このメソッドの名前と操作はWolfram言語プログラマーにはお馴染みであろう.これにはhead(),length(),dimensions(),part(),stringQ(),vectorQ(),matrixQ(),insert(),delete()等,多数ある.
Exprがループバックリンクより優れているのは,式を調べるのに低レベルのMathLinkインターフェースを使わなければならないという制限がないからである.Wolfram言語から任意の式を受け取って位置 [[2,3]](Wolfram言語表記)の要素がベクトル(サブリストのないリスト)かどうかを調べるタスクを考えてみる.これはExprを使って次のように行うことができる.
ml.evaluate("some code");
ml.waitForAnswer();
Expr e = ml.getExpr();
Expr part23 = e.part(new int[] {2, 3});
boolean isVector = part23.vectorQ();
このタスクはMathLinkインターフェースを使うともっと難しい.Exprクラスは式を調べたり分解したりするのに最小のWolfram言語のような機能的インターフェースを提供する.
Expr読込みと書込みのためのMathLinkインターフェースのメソッド
Exprオブジェクトを扱うのにMathLinkインターフェースには3つのメソッドがある.これはExprクラスの多数のメソッドの追加となるもので,Exprオブジェクトの構成や分解を扱う.getExpr()とpeekExpr()メソッドはリンクから式を読むが,peekExpr()は式の最初にリンクを再設定する.やがて現れる式を消さずに先に「覗き見」するのである.これはデバッグに非常に便利である.put()メソッドは対応するWolfram言語の式としてExprオブジェクトを送る.
// MathLinkインターフェースで:
Expr getExpr() throws MathLinkException;
Expr peekExpr() throws MathLinkException;
void put(Object obj) throws MathLinkException;
ループバックリンクの代替としてのExpr
Exprは簡単なループバックリンクの代替として使うことができる.MathLinkメソッドのgetExpr()を使うとリンクからどのような式も読むことができ,結果として生じるExprオブジェクトにそれを保管することができる.式をリンクに書き込むには,put()メソッドを使う.次の2つのコードを比べてみる.
// 従来の方法では,以下のようにループバックリンクを使う.
LoopbackLink loop = MathLinkFactory.createLoopbackLink();
// リンクからexprを読み,それをループバックに保存する.
loop.transferExpression(ml);
...
// 後でexprをリンクに書き戻す.
ml.transferExpression(loop);
loop.close();
// 新しい方法では,Exprを使う.
Expr e = ml.getExpr();
...
// 後で式をリンクに書き戻す.
ml.put(e);
e.dispose();
最後にdispose()を呼び出している.dispose()メソッドはExprオブジェクトに,内部的に使っている可能性のある任意のリソースを解放するよう指示する.通常,Exprが終了したら,Exprにdispose()を使う.dispose()メソッドは「Exprsの処理」で詳しく説明する.
式の文字列表現を取得する手段としてのExpr
Exprクラスで特に便利なメソッドはtoString()であり,これは式のInputFormタイプの文字列表現を(もちろんカーネルの関与なしで)作成する.これはリンクに何が届いているかをさっと見たいときに,デバックするのに特に便利である.「デバッグのためのPacketPrinterクラス」で述べたように,J/Link にはPacketListenerインターフェースを実装し,パケットが届いたときにプログラムを修正することなくその内容を簡単に出力するのに使われるPacketPrinterクラスがある.下がそのクラスのpacketArrived()メソッドで,任意の式の印刷可能なテキスト表現を得るのにExprオブジェクトとtoString()メソッドを使っている.
public boolean packetArrived(PacketArrivedEvent evt) throws MathLinkException {
KernelLink ml = (KernelLink) evt.getSource();
Expr e = ml.getExpr();
strm.println("Packet type was " + pktToName(evt.getPktType()) +
". Contents follows.");
strm.println(e.toString());
e.dispose();
return true;
}
PacketPrinterクラスを使っても使わなくても,この方法はどの式が渡されているかを見るのに便利である.これはよくリンクから式を読んだ後で,式が消されないようにリンクを再設定するMathLinkのpeekExpr()メソッドとともに使われる.このようにして,プログラムの他のリンク読込みコードを妨害することなくリンクに到着する式を見ることができる.上のPacketPrinterコードはpeekExpr()を使っていないが,リンクの再設定は他のところで扱われるので同じ効果がある.
KernelLinkメソッドの引数としてのExpr
KernelLinkメソッドのevaluate(),evaluateToInputForm(),evaluateToOutputForm(),evaluateToImage(),evaluateToTypeset()は,評価するWolfram言語の式を文字列あるいはExprとして取る.「evaluateToメソッド」では,文字列の代りに入力を与えるためになぜ,どのようにExprオブジェクトを使うことができるかを説明した.ここでは,引数としてまたExprとしてWolfram言語に2+2を送る方法を比較する簡単な例を見てみる.Exprの場合,ループバックリンクに式を構築し,そのリンクのExprを読む.非常に単純な式以外は,この方法の方がExprコンストラクタを使おうとするよりも,一般的に簡単である.
// 入力を文字列として送る.
String result = MathLink.evaluateToOutputForm("2+2", 0);
// 入力をExprとして送る.
LoopbackLink loop = MathLinkFactory.createLoopbackLink();
// ループバックリンクに2+2という式を構築する.
loop.putFunction("Plus", 2);
loop.put(2);
loop.put(2);
loop.endPacket();
// ここでループバックリンクからExprを読み出す.
Expr e = loop.getExpr();
// これでループバックリンクの終了である.
loop.close();
String result = ml.evaluateToOutputForm(e, 0);
e.dispose();
Exprの検査と操作
Wolfram言語の式と同様に,Exprオブジェクトは不変である.つまり,一旦作成されたら変更できない.insert()メソッドのようなExprを修正するように見える操作も,実は,オリジナルをコピーしてこのコピーを修正し,新しい不変のオブジェクトを返しているのである.不変であるために,Exprクラスはスレッドがあっても大丈夫である.複数のスレッドが,同期化を心配することなく同じExprを操作することができる.
Exprクラスは検査と操作のために,最低限のWolfram言語形式のAPIを提供する.その関数には通常,Wolfram言語で対応する関数の名前が付いており,操作方法も同じである.ここではExpr APIを簡単に見てみる.このメソッドについての詳細はJavaDocs(JLink/Documentation/JavaDocディレクトリにある)を参照のこと.
以下は,Exprの構造を知るためのメソッドである.
「Q」で終わる名前の付いたメソッドがたくさんあり,これはWolfram言語でtrueあるいはfalseを返す関数と同じ名前の付け方である.以下には,すべてがリストされているわけではない.
// 「Q」メソッドのサンプリング
public boolean atomQ();
public boolean stringQ();
public boolean integerQ();
public boolean numberQ();
public boolean trueQ();
public boolean listQ();
public boolean vectorQ();
public boolean matrixQ();
Exprを分解したり構築したりするメソッドもいくつかある.Wolfram言語のように部分の番号や指数は1がベースになっている.最後から遡って数える場合は負の数を与えることもできる.多くのExprメソッドは,例えば指数がExprの長さよりも大きい場合等,無効な入力で呼ばれたときにはIllegalArgumentExceptionを投げる.このような例外は,Wolfram言語コードで同様のエラーがあるときに出るWolframシステムのエラーメッセージに相当する.
public Expr part(int index);
public Expr part(int[] indices);
public Expr take(int n);
public Expr delete(int n);
public Expr insert(Expr e, int n);
以下はいくつかのExpr操作を示す,非常に簡単なコードである.
ml.evaluate("Expand[(x + y)^4]");
ml.waitForAnswer();
Expr e1 = ml.getExpr();
System.out.println("e1 is: " + e1.toString());
System.out.println("the length is: " + e1.length());
System.out.println("the head is: " + e1.head().toString());
System.out.println("part [[2]] is: " + e1.part(2));
System.out.println("part [[-1]] is: " + e1.part(-1));
System.out.println("part [[2, 2]] is: " + e1.part(new int[]{2, 2}));
System.out.println("drop the last element: " + e1.delete(-1).toString());
System.out.println("e1 is unmodified: " + e1.toString());
Expr e2 = e1.insert(new Expr(new double[] {1.0, 2.0, 3.0}), 1);
System.out.println("e2 is: " + e2.toString());
e1 is: Plus[Power[x,3],Times[3,Power[x,2],y],Times[3,x,Power[y,2]],Power[y,3]]
the length is: 4
the head is: Plus
part [[2]] is: Times[3,Power[x,2],y]
part [[-1]] is: Power[y,3]
part [[2, 2]] is: Power[x,2]
drop the last element: Plus[Power[x,3],Times[3,Power[x,2],y],Times[3,x,Power[y,2]]]
e1 is unmodified: Plus[Power[x,3],Times[3,Power[x,2],y],Times[3,x,Power[y,2]],Power[y,3]]
e2 is: Plus[{1.0,2.0,3.0},Power[x,3],Times[3,Power[x,2],y],Times[3,x,Power[y,2]],Power[y,3]]
Exprの処理
Exprクラスの説明の中で,dispose()メソッドを頻繁に使ってきた.Exprオブジェクトはループバックリンクを内部的に利用する可能性があるので,JavaクラスがこのようなJavaのリソースではないものを持つときは常に,そのリソースを解放させるdispose()メソッドをプログラマーに提供することが大切である.Exprクラスのファイナライザはdispose()を呼び出すが,ファイナライザが呼び出されることを当てにすることはできない.Exprを使い終えたときにそれに対してdispose()を呼び出すことは常にスタイルとしてはよいことであるが,Exprに対して行える操作の多くによってその内部ループバックリンクからExprがはずれ,リンクが閉じてしまう.このようなことになると,dispose()メソッドは必要ではない.toString()メソッドを呼び出すと,dispose()が必要なくなるのはこの例に当たる.実質的に,Exprの構造を問い合せたり,一部を抽出したりするような操作にはすべて同様の作用がある.これを知っておくと,以下のような短縮したコードが使える.
Exprオブジェクトに対して,明示的にdispose()を呼び出す習慣をつけてるとよい.指定された変数にExprを保管するのが不便でExprを捨てる必要がない場合は,呼び出さなくてもよい.
既存の式の一部を抽出することによりdispose()が必要なくなるので,他の式の一部として得たExprオブジェクトについてはdispose()を呼び出す必要はない.
Expr e = ml.getExpr();
// 以下でhead()あるいはpart()がeに対して呼び出されたとき,e,e2,e3の
// どれも捨てる必要がないことが分かる.
Expr e2 = e.head();
Expr e3 = e.part(1);
dispose()が呼び出された後ではExprオブジェクトを正確に使用することはできない.すでに説明したように,多くのExprオブジェクトはすでに内部的なループバックリンクを閉じているので,dispose()はほとんどの場合不必要である.そのようなExprでは,dispose()は全く効力を持たないので,dispose()が呼び出された後でExprを使い続けても問題ない.dispose()を呼び出した後にExprを使おうとするのは,ひどいスタイルだといわれている.dispose()の呼出しは,与えられたExprあるいはそのどの部分もそれ以後は使わないということを明らかに示すものでなければならない.
スレッド,ブロック,Yield
MathLinkとKernelLinkインターフェースを実装するクラスは,スレッドの安全を保障するものではない.つまり,1つのリンクオブジェクトが複数のスレッドで使用される J/Link プログラムを書く場合,並行処理問題に特に注意しなければならない.リンクの実装クラスで関係のあるメソッドは同期が取れているので,個々のメソッドレベルでは,2つのスレッドが同時にリンクを使おうとすることはない.しかし,リンクとのインタラクションは,通常結果の書込み読込みの複数に及ぶステップを含む全トランザクションがかかわっているので,これだけではスレッドの安全性は保障できない.この全トランザクションが保護されなければならないのである.このためには,スレッドがそれぞれのリンクの使用を妨げないようにするsynchronizedブロックを使う.
「evaluateTo」メソッドはsynchronizedで,全トランザクションを1度の呼出しにカプセル化するので,これらのメソッドのみを使っている限りは心配はない.一方,evaluate(),waitForAnswer(),あるいは複数のメソッド呼出しに1つのトランザクションを分けるその他の方法を使う場合は,次のようにトランザクションをsynchronizedブロックでラップしなければならない.
同期は同じリンクを使うスレッドが複数ある場合にのみ問題になる.
リンクから読む J/Link の関数はデータがそのリンクに届くまでブロックする.例えば,evaluateToOutputForm()を呼び出すと,Wolfram言語が計算して結果を返すまで返ってこない.evaluateToOutputForm()が呼び出されたスレッドがアクティブのままでいる必要がある場合,これは問題になるかもしれない.例えば,それがユーザインターフェースイベントを処理するAWTスレッドの場合である.
ブロックの取扱いは一般的なプログラミング問題で,解決方法はたくさんある.Java環境には複数のスレッドがあるので,明らかな解決策はシステムの他のイベントに継続的に反応する必要のないスレッドで J/Link を呼び出すことである.
WSTPには「yield関数」の概念がある.それはWSTPがもう一方からの入力を待ってブロックしている間に,WSTPの内部から呼び出されるよう指定できる関数である.yield関数はスレッドを持たないオペレーティングシステムでのブロック問題の解決や,ポータブルスレッドライブラリを持たないプログラミング言語のために主に使われる.シングルスレッドプログラムが,主のイベントループを起動するyield関数をインストールすることで,プログラムがWSTPデータを待っている間でもユーザインターフェースイベントを処理することができるようになる.
Javaを使うと,この動機付けはなくなる.ブロックしている間にプログラムの唯一のスレッドがイベントを扱えるようにするyield関数を使う代りに,ユーザインターフェーススレッドとは別のスレッドを単にスタートさせて J/Link 呼出しの内部でブロックさせる.Javaプログラムではyield関数の有用性が限られているにもかかわらず,J/Link はとにかくそれを使う能力を提供する.
MathLinkインターフェースのsetYieldFunction()メソッドは呼び出される関数を認知する3つの引数を取る.これらの引数はstaticメソッドでもstaticでないメソッドでもよいようにデザインされているので,3つの中の2つだけを指定すれば十分である.staticメソッドでは,メソッドのClassと名前を与え,2つ目の引数はnullにしておく.非staticメソッドでは,メソッドを呼び出したいオブジェクトとメソッド名を与え,Class引数をnullにしておく.関数はpublicであり,引数を取らず,booleanを返さなければならない.
指定した関数は J/Link がリンクから読み込もうとする呼出しでブロックをしている間に定期的に呼び出される.戻り値は J/Link が読込みの呼出しから戻り,すぐに返ってくるかどうかを示すのに使われる.読込みの呼出しから戻ることで,リンクから読み込んでいるメソッドがMathLinkExceptionを投げる.このMathLinkExceptionは修復可能(つまりclearError()がtrueを返すということ)なので,後で再びwaitForAnswer()を呼び出し,結果が到着したときにそれを得ることができる.yield関数からfalseが返ってくるとどんなアクションも取られないことを示し(このような理由で,yield関数の通常の戻り値はfalse),trueが返ると J/Link は読込み呼出しから戻らなければならないことを示す.yield関数をオフにするにはsetYieldFunction(null, null, null)を呼び出す.
yield関数を必要とする J/Link プログラマーはほとんどいない.この関数はある問題への解決策であるが,その問題は複数のスレッドを使用することにより,Javaでもっとうまく処理できるからである.yield関数を使う唯一の動機とは,時間がかかりすぎる計算で,それを放棄することができない場合,あるいはどちらにしてもリンクを閉じたいと思う場合に,その計算を止めることができるということであろう.これは別のスレッドでabandonEvaluation()を呼び出しても実行することができる.abandonEvaluation()メソッドは「評価の放棄と中止」で説明している.abandonEvaluation()は内部的にyield関数を使用するので,それを呼び出すと自分でインストールしたyield関数をすべて消してしまう.
Wolfram言語にオブジェクト参照を送る
ユーザガイドの前半ではWolfram言語コードがJavaランタイムを起動し,Javaクラスをロードし,Javaメソッドを直接実行することができるようにするための J/Link の使い方を説明した.Wolfram言語カーネルを起動して通信するプログラムを自分で書く場合,これはWolfram言語と非常に高レベルのインタラクションができるということを意味する.自分のオブジェクトをWolfram言語に送り,Wolfram言語コードでそれを使うことができる.この種のインタラクションには特殊なステップが必要である.
Wolfram言語カーネルのJavaフロントエンドを書いたとする.そのプログラムのユーザが J/Link の「インストール可能なJava」機能を使うWolfram言語の関数,つまりWolfram言語のInstallJavaを呼び出したらどうなるであろうか.InstallJavaは別のJavaランタイムを起動し,すべての J/Link のトラフィックをそのJavaランタイムに向け続ける.カーネルはそれをしているフロントエンドがノートブックフロントエンドかJavaプログラムなのか全く気にしない.どちらの場合でも同じように動作する.これでよいのであり,これが J/Link プログラマーが望んでいることである.Wolfram言語コードが J/Link の「インストール可能なJava」機能をたまたま起動したとしても,別のJavaランタイムが使われるので何も特別なことをする必要はない.
Javaオブジェクトとインタラクトするために J/Link がWolfram言語に与えた能力を利用したいとしたらどうであろうか.Javaオブジェクト参照をWolfram言語に送ってWolfram言語コードで操作したいかもしれない.Wolfram言語はJavaを呼び出すことによりJavaオブジェクトを「操作」するので,そのようなオブジェクトのどのコールバックも自分のJavaランタイムに向けられていなければならない.J/Link についてもっと詳しくいえば,J/Link はすべてのインストール可能なJavaの使用でアクティブなJavaランタイムを1つだけしかサポートしない.結局もしWolfram言語に自分のオブジェクトへの参照を渡したければ,InstallJavaを呼び出す関数が実行される前にInstallJavaを呼び出して,自分のJavaランタイムへのリンクを指定しなければならない.事実,J/Link のJava環境へのコールバックを可能にするために多くのステップが必要なので,J/Link にはKernelLinkインターフェースにすべて扱う特別なメソッドであるenableObjectReferences()があるのである.
public void enableObjectReferences() throws MathLinkException;
// オブジェクト参照を送るため
public void put(Object obj) throws MathLinkException;
public void putReference(Object obj) throws MathLinkException;
enableObjectReferences()を呼び出した後,KernelLinkインターフェースのput()あるいはputReference()メソッドを使ってJavaオブジェクトをWolfram言語に送ることができる.それらは「Wolfram言語からJavaを呼び出す」で説明したようにWolfram言語コードで使用できるJavaObject式として届く.put()とputReference()メソッドの違いは,put()はWolfram言語で意味をなす「値」表現を持つオブジェクト(配列や文字列等)は数値で,その他はすべて参照で送るが,putReference()メソッドはすべてを参照として送るという点である.enableObjectReferences()が使いたい場合は,プログラムの早いうち,putReference()を呼び出す前に呼び出す.JLink.mファイルはあるべき場所になければならない.つまり,J/Link はカーネルを起動しているマシンに標準的な方法でインストールしなければならない.
一旦enableObjectReferences()を呼び出すと,JavaオブジェクトをWolfram言語に送ることができるだけでなく,Wolfram言語がJavaに送り返してきたJavaオブジェクトの参照を読むこともできる.getObject()メソッドはこのために使用される.有効なJavaObject式がリンクで待っている場合,getObject()はそれが参照するオブジェクトを返す.
プログラムでenableObjectReferences()を呼び出す場合,自分でパケットループを書いてはならない.代りに,結果が受け取られるまで,パケットの読込みと扱いをカプセル化するKernelLinkメソッドを使う.これらのメソッドはwaitForAnswer(),discardAnswer(),evaluateToInputForm(),evaluateToOutputForm(),evaluateToImage(),evaluateToTypeset()である.入ってくるパケットが見たい場合は,これらのメソッドのどれかと一緒にPacketListenerオブジェクトを使う.この問題については「PacketListenerインターフェースの使用」で説明する.
なぜenableObjectReferences()を使った方がよいのかという問題はもっと詳細を見てみる価値がある.従来,WSTPプログラムはC APIと作動していたが,これはCとWolfram言語との間で渡されるデータのタイプをWolfram言語の式に制限する.Wolfram言語の式は通常Cプログラムでは意味をなさないので,これは数,文字列,あるいはその配列に変換される.CやC++プログラムで意味をなすネイティブの構造はWolfram言語では意味がないのである.その結果,プログラマーはWolfram言語と簡素な一方通行の伝達を使いがちになり,ネイティブデータ構造とオブジェクトを数や文字列のような単純なコンポーネントに分解してしまう.Wolfram言語はプログラムのロジックと完全にCあるいはC++でコード化され,数学的演算に使われるだけになる.
対照的に,J/Link ではJavaとWolfram言語コードが高度に協力し合うことができる.その方が簡単なら,Wolfram言語でアルゴリズムや他のプログラムの動作を簡単にコード化することができる.例として,何らかの方法でWolfram言語カーネルを使用しなければならないJavaサーブレットを書いているとする.サーブレットのdoGet()メソッドはHttpServletRequestとHttpServletResponseオブジェクトを引数として呼び出される.これらのオブジェクトから必要な情報を抽出し,Wolfram言語のためにパッケージ化し,望みの演算を評価に送るのがひとつのアプローチである.別のアプローチは単にHttpServletRequestとHttpServletResponseオブジェクト自身をWolfram言語に送ることである.これでJavaではなくWolfram言語のサーブレットの動作をコード化する,「Wolfram言語からJavaを呼び出す」で説明したシンタックスや機能を使うことができる.もちろん,これらは連続体の両極に過ぎない.一方ではサーブレット動作をコンパイルされたJavaクラスファイルにハードコード化し,細いパイプライン(数値,文字列,配列等,単純なものだけを渡すという意味で細い)を使って限られた方法でWolfram言語を利用する.別の一方ではWolfram言語にすべての仕事を送るだけの完全に汎用のサーブレットがある.サーブレットの動作は完全にWolfram言語で書かれている.Wolfram言語を数学エンジンとして必要としていなくてもこのアプローチを取ることができる.Wolfram言語言語でサーブレットロジックを開発したりデバッグする方が簡単かもしれない.この連続体のどこででもJavaとWolfram言語の境界線を引いてそれぞれの言語で好きなだけの仕事をすることができる.
そのような汎用サーブレットがどのようなものかを示すため,ここではdoGet()メソッドを使う.
// ml.enableObjectReferences()は,サーブレットのinitメソッド等で,
// 事前に呼び出されていなければならなかった.
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
try {
ml.putFunction("EvaluatePacket", 1);
ml.putFunction("DoGet", 2);
// これらのオブジェクトは参照によってputされるので,
// 通常の「put」を使うことも可能である.
ml.putReference(req);
ml.putReference(res);
ml.endPacket();
ml.discardAnswer();
} catch (MathLinkException e) {}
}
これは2つのJavaオブジェクト引数を取り,サーブレット動作を実装するWolfram言語の関数のDoGetが伴う.シンタックスは「Wolfram言語からJavaを呼び出す」で説明してある.
doGet[req_, resp_] :=
JavaBlock[
Module[{outStream},
outStream = resp@getOutputStream[];
outStream@print["<HTML> <BODY>"];
outStream@print["Hello World"];
outStream@print["</BODY> </HTML>"];
]
]
]
特殊なユーザインターフェースクラス
はじめに
J/Link には,Javaプログラムに非常に高レベルのユーザインターフェースコンポーネントを提供するクラスがいくつかある.そのクラスについて,以下でひとつずつ説明する.これらのクラスは新しいcom.wolfram.jlink.uiパッケージに入っているので,使いたい場合はそのパッケージを忘れずにインポートすること.
ConsoleWindow
ConsoleWindowクラスはSystem.outおよび/またはSystem.errストリームに出力された結果を表示するトップレベルのフレームウィンドウを提供する.これには入力機能はない.これは「Javaコンソールウィンドウ」で説明したWolfram言語関数のShowJavaConsoleを実装するために使用されるクラスである.このクラスはコンソール出力をするのに便利な場所がないJavaプログラムのデバッグに非常に便利である.その例として,サーブレットがある.実行するたびにサーブレットコンテナのログファイルを調べなくても,ConsoleWindowを表示するだけでデバッグの出力を見ることができる.
このクラスはシングルトンである.つまり,1つのインスタンスしか存在しない.パブリックコンストラクタは持たない.ConsoleWindowオブジェクトを得るためにはstaticなgetInstance()メソッドを呼び出す.以下はConsoleWindowの使い方を示すコードの一部である.JavaDocページにはこのクラスについての詳細が記載されている.
// 「com.wolfram.jlink.ui.ConsoleWindow」を忘れずにインポートすること.
// これは他のJ/Linkとは異なるパッケージである.
ConsoleWindow cw = ConsoleWindow.getInstance();
cw.setLocation(100, 100);
cw.setSize(450, 400);
cw.show();
// System.outとSystem.errを捕らえたいということを指定する.
cw.setCapture(ConsoleWindow.STDOUT | ConsoleWindow.STDERR);
System.out.println("hello world from stdout");
System.err.println("hello world from stderr");
MathSessionPane
MathSessionPaneクラスは,カット,コピー,ペースト,取消し,やり直し,グラフィックスのサポート,文法の色付け,カスタマイズ可能なフォントスタイルを含む,編集機能一式を持ったIn/OutのWolframシステムセッションウィンドウを提供する.これはWolfram言語カーネルの「ターミナル」インターフェースと少し似ているが,それよりもずっと洗練されたものである.Wolfram言語の完全なコマンドラインインターフェースを必要とするどのJavaプログラムにも簡単に組み込むことができる.クラスはJava BeanでGUIビルダ環境でもうまく使うことができる.これには外観と動作をカスタマイズできるようにする多数のプロパティがある.
MathSessionPaneの機能に慣れる最善の方法は,JLink/Examples/Part2/SimpleFrontEndディレクトリにあるSimpleFrontEnd例題プログラムを実行することである.SimpleFrontEndは,MathSessionPaneをホストするフレームとメニューバーに少し機能を付け足したようなものである.キーボードコマンドやオプションメニューで設定できるプロパティをはじめ,実質的にご覧になる機能はすべてMathSessionPaneに組み込まれている.この例題を実行するためには,SimpleFrontEndディレクトリで次のコマンドラインを実行する.
(Windows)
java -classpath SimpleFrontEnd.jar;..\..\..\JLink.jar SimpleFrontEnd
(Linux, Mac OS X):
java -classpath SimpleFrontEnd.jar:../../../JLink.jar SimpleFrontEnd
アプリケーションウィンドウが現れて起動するカーネルへのパスを入力するよう求められる.一旦Wolfram言語が起動したら,プロットを含むさまざまな演算を試してみるとよい.メニューにあるいろいろな設定やコマンドを実験してみるのである.SimpleFrontEndメニューバーには現れないMathSessionPaneの機能のひとつに,高度にカスタマイズ可能な文法の色付け機能がある.デフォルトの動作は,組込みのWolfram言語シンボルを色付けすることであるが,あるリストのシンボルは常に赤で,あるパッケージからのシンボルは常に青で表示されるように指定する等,どのようにすることもできる.
MathSessionPaneのメソッドとプロパティについては,JLink/Documentation/JavaDocディレクトリのJavaDocsに詳細が記載されている.
BracketMatcherとSyntaxTokenizer
補助的クラスのBracketMatcherとSyntaxTokenizerは,MathSessionPaneが使用するが,自分のプログラムでこのようなサービスを提供するために別々に使うこともできる.このクラスが便利であるプログラムの例として,Wolfram言語プログラマーのための特別機能を持つ必要があるテキストエディタコンポーネントがある.
これらのクラスについては,JavaDocのそれぞれのページに詳細が説明してある.J/Link のJavaDocsはJLink/Documentation/JavaDocディレクトリにある.MathSessionPaneクラスのソースコード(MathSessionPane.java)でどのようにこれらのクラスが使用されているかを見ることもできる.
BracketMatcherクラスは,Wolfram言語コードにおいて対応するカッコのペア((),{},[],(**)のどれでも)を見付ける.文字列とWolfram言語コメント内のカッコは無視する.ネストされたコメントに対応することができる.検索方法は一般的である.現在の選択範囲を左右に拡張し,最初に対応するカッコを見付ける.実際の動作を見たい場合は,MathSessionPaneについての前のセクションで説明したSimpleFrontEndの例題プログラムを実行し,そのカッコの対応機能を実験するとよい.
SyntaxTokenizerはWolfram言語コードを文字列,コメント,シンボル,通常(その他すべて)の4つの文法クラスに分けることのできるユーティリティクラスである.これを使うと,カラーシンタックス,あるいは,Wolfram言語コードのファイルからコメントやシンボルをすべて抽出することのできるコード解析ツールを実装することができる.
InterruptDialog
InterruptDialogクラスは,評価を中断ダイアログボックスを提供する.ダイアログボックスには,そのときにカーネルが何をしているかによって放棄,カーネルの終了等の選択肢が表示される.
InterruptDialogコンストラクタはダイアログボックスの親ウィンドウとなるDialogあるいはFrameインスタンスを取る.この引数として与えるのは,通常アプリケーションの主要なトップレベルのウィンドウである.InterruptDialogはPacketListenerインターフェースを実装するので,他のPacketListenerを使うようにそれを使う.
// 「com.wolfram.jlink.ui.InterruptDialog」を忘れずにインポートすること.
// これは他のJ/Linkとは異なるパッケージである.
ml.addPacketListener(new InterruptDialog(myParentFrame));
コードの上記のラインの実行後は,MLINTERRUPTMESSAGEを送って,あるいはより一般的な方法としてKernelLink interruptEvaluation()メソッドを呼び出して計算を中止する度に,どのように続行するかについての選択肢を持ったモーダルダイアログボックスが現れる.
「MathSessionPane」で説明したSimpleFrontEndの例題プログラムはInterruptDialogを利用している.実際の動作を見たい場合は,例題プログラムを起動して次のWolfram言語文を実行する.
次に評価メニューから評価を中断を選ぶ.カーネル一時停止中ダイアログボックスが現れる.その評価中のコマンドを放棄するというボタンをクリックすると計算を中止することができる.自分のプログラムでInterruptDialogを使うためには,ユーザインターフェースに中断ボタンや特殊なキーの組合せのような中止要求を送る手段がなければならない.この動作に反応して,プログラムはKernelLink interruptEvaluation()メソッドを呼び出す.
完全な評価を中止ダイアログボックスと同じくらい複雑な動作が,ほんの1行のコードでJavaプログラムに加えられるということは,「PacketListenerインターフェースの使用」で説明したPacketListenerインターフェースに多様性があるという証拠である.InterruptDialogクラスはカーネルから入ってくるパケットを監視し,中止要求の後にカーネルが送る特殊なタイプのMenuPacketを検出することによって動作する.Wolfram言語から到着するパケットについて知らなければならないアプリケーション理論がある場合は常に,それをPacketListenerとして実装しなければならない.
アプレットを書く
このユーザガイドでは J/Link を使ってJavaプログラム(アプリケーション,JavaBeans,サーブレット,アプレット,その他)でWSTP機能を使う方法についての情報を紹介してきた.ローカルのWolfram言語カーネルを利用するアプレットを書く場合,ブラウザがアプレットを起動するJavaセキュリティ「サンドボックス」をエスケープする必要があるので特別な配慮をしなければならない.
J/Link が特別なブラウザセキュリティ許可を必要とするのは J/Link ネイティブライブラリをロードするときのみである.ネイティブライブラリが必要とされる,あるいは存在する唯一の理由は,NativeLinkクラスのJavaの呼出しとWolfram Researchのプラットフォーム依存型のWSTPライブラリとの間の変換を行うためである.NativeLinkは,ネイティブメソッドについてMathLinkインターフェースを実装するクラスである.現在,MathLinkFactory.createMathLink()あるいはMathLinkFactory.createKernelLink()を呼び出すときはいつもNativeLinkクラスのインスタンスが生成されるので,J/Link ネイティブライブラリがロードされなければならない.つまり,J/Link でネイティブライブラリが必要なのはNativeLinkクラスだけということであるが,現在すべてのMathLinkあるいはKernelLinkオブジェクトがNativeLinkオブジェクトを使う.ネイティブライブラリのロードを要求しなければ J/Link で何をすることもできない.
アプレットがネイティブライブラリをロードすることができる条件はブラウザによって異なる.多くの場合,アプレットは「サイン」され,ブラウザは指定の設定が可能になっていなければならない.Wolfram言語は繊細なファイルを読んだり,ファイルを削除したりすることができるので,Javaアプレットがローカルのカーネルを起動できるようにすることはセキュリティ違反である.一般的に,ユーザがアプレットによりブラウザのセキュリティサンドボックスにそのような大きな穴をあけてしまうのはよい考えだとはいえない.それより,Javaアプレットにサーバにあるカーネルを使わせる方がよい.この場合,ブラウザのJavaランタイムはローカルのネイティブライブラリをロードする必要がないので,考慮すべきセキュリティ問題はない.これはクライアントとサーバ側に多大のサポートを要求する.このサポートは J/Link の一部ではなく,作成に J/Link を使うことのできる種類のプログラムのよい例といえる.