2018

JavaFXのPrintAPIを使ってはがきの印刷をしてみた。(文面)

JavaFX

このエントリーは じゃばえふえっくす Advent Calendar 2018 の12月22日分です。

JavaFX 8でPrintAPIが追加されて随分経ちます。

ネット上にもいろんな情報が共有されるようになりました。

そこではがき印刷をJavaFXのPrintAPIを使ってやってみようと思い立ちました。

宛名のほうは位置決めとか縦書きとかめんどくさいのでパスとします。(^_^;)

あっ・・・ めんどくさいじゃなくて解らないって言った方が近いもしれません。

縦書きなんかは一文字ずつ分解してFontMetricsを取得して構築するのかな?

さて、とりあえずはがき印刷のゴールは文面サイドだけにします。

シンプルにイメージファイルと賀詞を入れるだけのものとします。

まずJavaFXのPrintAPIについてざっくりと調べてみることにします。

 

javafx.graphicsモジュールにあるjavafx.printパッケージに九つのクラスと七つの列挙型があります。

クラス
JobSettings
PageLayout
PageRange
Paper
PaperSource
Printer
PrinterAttributes
PrinterJob
PrintResolution

列挙型
Collation
PageOrientation
PrintColor
Printer.MarginType
PrinterJob.JobStatus
PrintQuality
PrintSides

ぱっと見、馴染みやすそうな構成になっています。

まずPrinterクラスがどんなものなのか見てみます。

Printerクラス

このクラスにはデフォルトプリンター、全てのプリンター、プリンター名を取得するメソッドなどが用意されています。

それとプリンターのデフォルトのページ・レイアウトを取得するメソッドや指定されたパラメータを使って新しいPageLayoutインスタンスを取得するものがあります。

あとPrinterAttributesクラスを返すメソッドが一つあります。

public PrinterAttributes getPrinterAttributes() 

プリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。

このメソッドを利用してプリンターの各種属性値を取得可能となります。

それでは次に上記のメソッドで返されるプリンターの属性値をカプセルかするPrinterAttributesクラスを見てみます。

PrinterAttributesクラス

このクラスは、ジョブ印刷機能に関連するプリンタ属性とその他の属性をカプセル化します。

デフォルトプリンターのデフォルトの属性値の取得、サポートされている属性値を取得することができます。

丁合い設定、印刷部数、用紙の向き、用紙サイズ、給紙方法、色設定、品質設定、印刷解像度、両面印刷設定、最大部数、ページ範囲など。

属性値にはあらかじめ列挙型が次の7種類用意されています。

列挙型
Collation
PageOrientation
PrintColor
Printer.MarginType
PrinterJob.JobStatus
PrintQuality
PrintSides

それでは次にPaperクラスを見てみます。

Paperクラス

このクラスはプリンター用紙のサイズをカプセル化するクラスになります。

用紙のサイズを指定するのに20個のstaticフィールド値があらかじめ用意されています。

ただ、使用するプリンターでそれがサポートされているかの確認は必要でしょう。

あと、プリンター用紙のサイズ取得のメソッドと用紙の名前を取得するメソッドが用意されています。

サイズはポイント(1/72インチ)単位となっています。

DTPやってる人はご存じかもしれませんが一般人にはあまり馴染みのない単位です。

次はPaperSourceクラスを見ていきましょう。

PaperSourceクラス

プリンター用紙に使用される給紙トレイ、給紙方法を扱うクラスになります。

9個のstaticフィールド値があらかじめ用意されています。

あと給紙方法の名前を返すメソッドが一つ用意されています。

次はPrintResolutionクラスを見ます。

PrintResolutionクラス

プリンターのサポートされている解像度(送り方向と前後送り方向)を1インチ当たりのドット数(DPI)で表すクラスです。

用紙の前後送り方向の解像度(dpi単位)、用紙の送り方向の解像度(dpi単位)を返すメソッドが二つ用意されています。

次はPrinterJobクラスです。

PrinterJobクラス

PrinterJobは、JavaFXシーングラフ印刷のルートとなります。

これには、次のものが含まれます。

  • プリンタの検出
  • ジョブの作成
  • サポートされるプリンタの機能に基づいたジョブの構成
  • ページの設定
  • ノード階層のページへのレンダリング。

APIドキュメントには非常にシンプルに印刷がされるように記述されています。

注意事項としては印刷と並行してノードを更新するのは避けてください。

と記述されています。

これはそうだろうなと誰もが思うだろうけど、ちょっと解りづらいというか・・・結局どうなの?って記述が下記のようにされてます。

FXアプリケーション・スレッドで印刷を実行する際の必要条件はありません。 ノードの印刷準備やジョブの呼出しはどのスレッドでも行うことができます。 ただし、アプリケーションUIの応答性に影響が及ばないように、FXアプリケーション・スレッドで実行される処理の量を最小限に抑えるのが一般的に望ましいと言えます。 したがって、印刷は新しいスレッドで実行し、実装内部のスケジューリングによって、FXスレッドで実行する必要のあるすべてのタスクがそのスレッドで実行されるようにすることをお薦めします。

FXアプリケーション・スレッドで印刷をできなくもないけど印刷用の新しいスレッドを用意して実行してくれってことでいいのかな?

今回のはがき印刷のプログラムではお勧めの印刷用の新しいスレッドを用意させていただくとしよう。

さて、このPrinterJobクラスには14個のメソッドと印刷ジョブのステータスをレポートする際に使われるネストされたstaticクラス列挙型PrinterJob.JobStatusがあります。

印刷ジョブのステータスを確認するにあたり、この列挙型PrinterJob.JobStatusの列挙型定数はありがたいです。

CANCELED     ジョブはアプリケーションによって取り消されました。
DONE   ジョブが印刷を開始し、その後endJob()を呼び出したところ、成功とレポートされました。
ERROR    ジョブの実行中にエラーが発生しました。
NOT_STARTED  新しいジョブのステータス。
PRINTING   ジョブは1ページ以上の印刷をリクエストし、まだ印刷を終了していません。

ジョブの一般的なライフサイクルは次のとおりです:

  • ジョブは、ステータスNOT_STARTEDで作成され、ダイアログなどでの構成中、このステータスにとどまります。
  • ジョブは、最初のページが印刷されると、PRINTING状態になります。
  • ジョブは、取り消されたり、エラーが発生したりすることなく正常に完了すると、DONE状態になります。 これでジョブが完了しました。
  • エラーが発生したERRORのジョブやCANCELEDのジョブも完了したと見なされます。

ジョブがライフサイクル中に前のステータスに戻ることはできず、現在のジョブ状態は実行可能な操作に影響を及ぼします。

たとえば、すでに印刷状態を過ぎて終了状態のいずれかになったジョブが再度印刷を開始することはできません。

PrinterJobクラス14個のメソッドはジョブの作成、取り消しステータス取得などのメソッドがあります。

あとジョブの構成オプションを取得するものや、ジョブに使われるプリンターを取得するもの、ジョブに使うプリンターの変更やネイティブな印刷ダイアログ、ページ設定ダイアログを表示するものなどがあります。

それではJobSettingsクラスを見ていきましょう。

JobSettingsクラス

JobSettingsクラスは、印刷ジョブの構成のほとんどをカプセル化します。

アプリケーションでJobSettingsインスタンスのセットを直接作成したり設定したりすることはありません(できません)。

印刷ジョブが作成された時点で、すでにそのジョブに1つインストールされています。

このクラスは印刷ジョブを構成するためのメソッドが30個あります。

ジョブの名前の設定、取得ページレイアウト、印刷品質、部数などだいたい必要なものがそろっています。

このJobSettingsクラスでジョブ構成を設定してもネイティブな印刷ダイアログを使用すれば設定を変更できてしまうようです。

次はPageLayoutクラスを見てみます。

PageLayoutクラス

PageLayoutは、コンテンツのレイアウトに必要な情報をカプセル化します。

8個のメソッドが用意されています。

それらを使ってページ・レイアウトのマージン(ポイント単位)、用紙、ページの印刷可能領域のデータ、向きを取得できます。

最後にPageRangeクラスを見ます。

PageRangeクラス

PageRangeは、印刷するジョブ印刷ストリーム・ページを選択または制限するために使用されます。

ページ番号は1から始まり、ユーザーの指定内容に対応します。

開始ページはゼロより大きく、終了ページ以下である必要があります。

開始と終了が同一の場合、範囲は単一ページを表します。

ジョブのページ数を超える値は、印刷時に単に無視されます。

各クラスの関連は下図のような感じですね。

b1

 

APIドキュメントを読んでいるだけでは面白くないのでとりあえず超シンプルなものを組んで印刷してみよう。

テキストエリアにデフォルトプリンターの名前、プリンター一覧の表示、そして印刷ジョブに使用するプリンターの設定、プリンターの属性の一部を表示させます。

UIはこれから作成予定のはがき印刷のものを流用しているのでちょっと変なことなってますがそれはきにしないでくださいませ。

これらの処理をトグルボタンが押されたら開始するように次のようにプログラムを組んでみました。

 

プログラムの実行結果は次のようになります。

1

まず、デフォルトプリンターを取得するためにPrinterクラスのstaticメソッドgetDefaultPrinter()を使います。

そしてデフォルトプリンターの名前を取得するためにPrinterクラスのString getName()メソッドを使いました。

次にプリンター一覧を表示させるために全てのプリンターを取得します。

PrinterクラスのstaticメソッドObservableSet<Printer> getAllPrinters()を使います。

プリンターの名前を取得するために先ほどと同様にPrinterクラスのString getName()メソッドを使います。

そしてプリンターの選択をしてみます。

現在のシステムに組み込まれているプリンターは下図のようになっています。

printer

デフォルトプリンターは”EPSON EP-806A Series”で間違いないのでEP-808A Series(ネットワーク)を指定してみました。

ところが予期せぬ結果となりました。

なんと、プログラムのプリンター一覧の表示結果では確かにあるのに指定できません。

T orElseGet(Supplier<? extends T> supplier)でデフォルトプリンターが返されます。

これの原因は調べてないので解りませんが別のPCでも同じでしたのでJavaFX側の問題かもしれません。

この解決策はOS側のプリンター設定で”EP-808A”のようにリネームすれば指定できました。

次にデフォルトプリンターの属性を取得しています。

var attributes = defaultPrinter.getPrinterAttributes();

PrinterクラスのPrinterAttributes getPrinterAttributes()メソッドでプリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。

そしてそれを使い各種属性値を取得表示させています。

PrinterAttributesクラスのSet<PageOrientation> getSupportedPageOrientations()でサポートされる向きを取得、

Set<PaperSource> getSupportedPaperSources()でサポートされる給紙方法(給紙ビンや給紙トレイ)を取得しています。

解りやすくフレンドリーなAPIがそろってます。

それではこれを印刷するコードを次のように追加しました。

注意: ステータスを確認するのにスレッドを少しスリープさせています。

 

 

PrinterJobクラスのstatic final PrinterJob createPrinterJob(Printer printer)メソッドでデフォルトプリンターを引数にとり新しいPrinterJobを作成します。

印刷に必要ではないけどJobStatus確認用にリスナーつけたり、ラベル、標準出力への表示などをしています。

PrinterJobクラスのsynchronized JobSettings getJobSettings()メソッドでジョブ構成オプション(部数、丁合いオプション、両面オプションなど)をカプセル化します。

初期値はプリンターの現在の設定に基づいています。

次にJobSettingsクラスのvoid setJobName(String name)メソッドでジョブの名前を設定します。

このプログラムではOSネイティブの印刷ダイアログを使用しました。

PrinterJobクラスのsynchronized boolean showPrintDialog(Window owner)を使います。

私はJavaFX 8がリリースされた当時このPrintAPIを早く試してみたい(いじってみたい)あまりにとんでもない間違いを犯していました。

当時、私は印刷ダイアログを呼び出して使うのに次のようにコードを書いていました。

job.showPrintDialog(anchorPane.getScene().getWindow());

これでも動くことは動くけどキャンセルしても印刷が実行されてしまいます。

twitterでこのことをつぶやいて何人かの方にリツイートされてしまい悪いことをしていまいました。

よくよく考えたらなんのためにboolean型の戻り値があるのかってことですね。

ネット上では印刷ダイアログを呼び出し使う方法がよくありますがほとんどキャンセル処理をしていません。

よって正しく使うためには印刷ダイアログとキャンセル処理は抱き合わせて使用することをお勧めします。

// 印刷ダイアログ
var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow());
// 印刷ダイアログでキャンセルした場合の処理
if (!nativeJob) {
    System.out.println(“Job has been canceled.”);
    job.cancelJob();
}

 

そして最後に印刷を開始するためにPrinterJobクラスのsynchronized boolean printPage(Node node)メソッドを使います。

引数のNodeが印刷対象となっています。

印刷に成功すればtrueが返されます。

印刷に成功したことを確認したらPrinterJobクラスのsynchronized boolean endJob()メソッドでジョブをプリンタのキューに正常にスプールされたらtrueを返します。

falseを返されたらプリンタージョブが失敗なのでそれなりの対処をします。

 

2

 

 

3

実際に印刷されたものがこのようになります。

a6

簡単ですね!

これで基本的なPrinter APIを扱うことが可能となったのではがき印刷のプログラムを組むことにします。

下図のようなGUIのプログラムを組んでみました。

21

Printボタンをクリックすると印刷のためのワーカースレッドが起動して印刷をはじめます。

いちおうキャンセルボタンもつけました。

Printer Infoトグルボタンはシステムのプリンターの属性を表示させています。

Open Imageボタンは画像を変更するためのファイルチューザーを出します。

賀詞を入れるためのテキストフィールドとジョブステータス表示用のラベルも用意しました。

印刷に関するコードを下記に示します。

 

コードが長くなっていますが基本的なことは先ほどのテキストエリアの文字列を印刷するものと何ら変わりはありません。

ただ、APIドキュメントでお勧めされているように印刷用のスレッドを別に起動させて印刷をしています。

今回のプログラムでは印刷ダイアログは使用せずJavaFXがはじめから用意してあるものを使用してみます。

まず、PrinterJobクラスのsynchronized JobSettings getJobSettings()を使いをカプセル化されたジョブ構成オプションを取得します。

JobSettingsクラスのsetterメソッドを使い設定します。

void setJobName(String string)メソッドで名前をつけます。

void setPaperSource(PaperSource ps)メソッドで給紙方法を設定します。

void setPageLayout(PageLayout pl)メソッドで使用するPageLayoutを設定します。

void setPrintColor(PrintColor pc)メソッドで印刷カラーを設定します。

void setPrintQuality(PrintQuality pq)メソッドで印刷品質を設定します。

final void setCopies(int i)メソッド印刷部数を設定します。

void setCollation(Collation cltn)メソッドで部単位で印刷設定をします。

カプセル化されているのでgetter、setterメソッドでアクセスできるのは楽ですね

ページレイアウトの設定はPrinterクラスのPageLayout createPageLayout​(Paper paper, PageOrientation orient, Printer.MarginType mType)メソッドを使い設定します。

印刷に関するものはこの程度ですね。

ではこのプログラムを実行してみましょう。

プリンターの印刷される音が鳴り止んではがきをとりにいって唖然としました。

こんなふうに印刷されていました。

a1

左上から少しだけ画像が切り取られて印刷されただけです。

これでは印刷はできたけどこれをもらった人はなんだろう?と首をかしげるに違いない!

プログラムを修正することにします。

何故このようになったのか?

はがきの印刷領域より画像が大きすぎたからと思われる。

左上の隅はJavaの2Dでは原点(0, 0)になるからそこから印刷可能な範囲だけ画像が印刷されたのだろう。

では残りの部分を印刷可能領域分だけ画像を位置変更して必要なだけ印刷すれば画像全体が印刷できるんじゃないか。

では早速コードを修正してみよう。

 

さぁ、これで理論上は問題ないはず!

さっそくプログラムを実行して印刷してみよう。

a2

印刷結果はこうなりました。

無事に印刷できたけど9枚のはがきが消費されました。

これを年賀状でだしたら受け取った人はパズルをすることになります。(^_^;)

わたしはなんと愚かな発想をしてしまったのだろう・・・

印刷可能領域に合わせるように画像をスケーリングしてしまえば良いだけのことじゃないですか!

せっかくだからこの複数枚印刷の機能を残したまま、縮小印刷機能を実装することにします。

使う用紙の印刷可能領域に画像をあわせるだけだから簡単ですね。

 

複数枚印刷のコードに印刷領域にあわせて印刷対象のノードをスケーリングして印刷するだけのコードを使いするだけです。

どちらの印刷方法にするかはチェックボックスを使って分岐させています。

印刷が終わったら元の大きさにもどしています。もちろん成功、失敗、キャンセルでも。

この自動スケーリングで印刷した結果が下の画像になります。

31

これでクリスマスカード、年賀状もシンプルなものならJavaFXで印刷できるようになった。

完全に自己満足の世界にひたっている。

いちおう動いてるのとキャンセルしたときのGifアニメを貼っておきます。

postcarf1

下のGifアニメではJobStatusがNOT_STARTEDのときにキャンセルボタンによるキャンセルをかけています。

あともう一つ、キャンセルボタンではタイミング合わせるのが難しかったのでJobStatusがPRINTINGに変更されたら強制的キャンセルをするようにしてあります。

JobStatusがPRINTINGの時にキャンセルかけるとCANCELED -> ERROR となるんですね。

なんでCANCELEDで終了しないのだろう?これは印刷リクエストがされたあとだからERRORで終了という解釈でいいんだろうか?

postcard2

最後にこのプログラムのコードを載せておきます。

 

 

 

 

 

Hatena タグ:

Jigsaw のお勉強 その4 JavaFX (Spinner がほんの少しだけ良くなった)

Java JavaFX

このエントリーは じゃばえふえっくす Advent Calendar 2018 の初日(12月4日)です。

未だにJigsawのお勉強をしています。(^_^;)

今回はJavaFXを使ってみます。

そんなJavaFXですが正式リリース前からさらにリリースされてからもいろいろありました。

現在はJavaにバンドルされていません。

正確にはいろいろなところからJDKが提供されているのでひょっとしたらJavaFXをバンドルしたJavaがあるのかもしれません。

今回はOpenJDKとOpenJFXを使います。

JavaFX 11でjavafx.scene.control.Spinner<T>クラスにほんの少し優しい変更が追加されたのでそれを試してみました。

矢印ボタンをマウスにて押し続けたときの入力時間がJavaFX 8の750ms固定だったのが自由に変更できるようになりました。

InitialDelayとRepeatDelayが設定可能となります。

デフォルトの設定時間はそれぞれ300ms、60ms となっています。

まず、Spinnerについて少しだけ復習しておきましょう。

SpinnerのAPIドキュメントに下記のように書かれています。

順序付けられたシーケンスからユーザーが数値またはオブジェクト値を選択できるようにする単一行のテキスト・フィールド。

通常、スピナーはシーケンスの要素間を移動するための小さな矢印ボタンのペアを提供します。

キーボードの上/下矢印キーでも要素間を自由に移動できます。

ユーザーがスピナーに直接(有効な)値を入力することもできます。

コンボ・ボックスも同様の機能を提供しますが、スピナーの方が好まれる場合があるのは、重要なデータを不明瞭化する可能性があるドロップ・ダウン・リストが不要であり、また、他の多くのJavaFX UIコントロールのようにObservableListデータ・モデルを使用せずに、wrappingなどの機能や、より単純な’無限’データ・モデルの仕様(SpinnerValueFactory)を使用できるためです。

Spinnerのシーケンス値はSpinnerValueFactoryで定義します。

value factoryはコンストラクタ引数として指定し、value factory propertyを使用して変更できます。

JavaFXには、次に示す一般的なタイプのSpinnerValueFactoryクラスが用意されています。

•SpinnerValueFactory.IntegerSpinnerValueFactory

•SpinnerValueFactory.DoubleSpinnerValueFactory

•SpinnerValueFactory.ListSpinnerValueFactory

Spinnerには、Spinnerの現在のvalueの表示および変更を行う、editorと呼ばれるTextField子コンポーネントがあります。

Spinnerはデフォルトで編集不可能ですが、editable propertyをtrueに設定すると、入力を受け入れることができます。

Spinnerエディタは、値ファクトリのvalue propertyに対する変更をリスニングすることにより、値ファクトリとの同期を保ちます。 ユーザーがeditorに表示された値を変更した場合、Spinnerのvalueとeditorの値が異なってしまう可能性があります。

モデルの値をeditorの値と同じにするには、ユーザーが[Enter]キーを使用して編集をコミットする必要があります。

APIドキュメントによると一般的なタイプのSpinnerValueFactoryクラスと使ってSpinnerインスタンスを作成できると書かれていました。

Spinner​(double min, double max, double initialValue) // SpinnerValueFactory.DoubleSpinnerValueFactory

Spinner​(double min, double max, double initialValue, double amountToStepBy) // SpinnerValueFactory.DoubleSpinnerValueFactory

Spinner​(int min, int max, int initialValue) // SpinnerValueFactory.IntegerSpinnerValueFactory

Spinner​(int min, int max, int initialValue, int amountToStepBy) // SpinnerValueFactory.IntegerSpinnerValueFactory

Spinner​(ObservableList<T> items) // SpinnerValueFactory.ListSpinnerValueFactory

Spinner​(SpinnerValueFactory<T> valueFactory) // 指定されたvalue factoryを設定する場合

Spinnerインスタンスを作成するのは容易にできることが確認できたので次はSpinnerの定数フィールド値を使ってスタイルの設定方法を調べてみます。

public static final String STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL

水平方向(左向きと右向き)の矢印がSpinnerの左側に配置されます。

public static final String STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL

垂直方向(上向きと下向き)の矢印がSpinnerの左側に配置されます。

public static final String STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL

水平方向(左向きと右向き)の矢印がSpinnerの右側に配置されます。

public static final String STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL

Spinnerの左側に減分矢印、右側に増分矢印が配置されます。

public static final String STYLE_CLASS_SPLIT_ARROWS_VERTICAL

スピナーの幅全体にわたって上下に伸びた矢印が配置されます。

何も指定をしない場合はデフォルトの垂直方向(上向きと下向き)の矢印がSpinnerの右側に配置されます。

Spinnerインスタンスの作成、スタイルの設定方法が解ったので試してみます。

Spinnerのデザインをデフォルトの垂直方向(上向きと下向き)の矢印がSpinnerの右側に配置

1

Spinnerのデザインを水平方向(左向きと右向き)の矢印がSpinnerの左側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL);

2

Spinner のデザインを垂直方向(上向きと下向き)の矢印がSpinnerの左側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL);

3

Spinnerのデザインを水平方向(左向きと右向き)の矢印がSpinnerの右側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL);

4

Spinnerのデザインを左側に減分矢印、右側に増分矢印が配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);

5

Spinnerのデザインをスピナーの幅全体にわたって上下に伸びた矢印が配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL);

6

// 最小値 0、最大値 100、初期値 50、増分または減分するステップの量のSpinnerインスタンス作成
Spinner<Integer> spinner = new Spinner<>(0, 100, 50, 10);

javafx.​scene.​Nodeパッケージのpublic final ObservableList<String> getStyleClass()でノードを論理的にグループ化するために使用できる文字列識別子のリストを取得しava.​util.​Listパッケージのpublic abstract boolean add(E arg0)メソッドでSpinnerの定数フィールド値を追加して設定します。

Spinnerの増分、減分の矢印ボタンの配置デザインの設定はこれで可能となりました。

次に Spinnerに表示されている値のアライメントとカスタムフォントを設定してみます。

//  カスタムフォント、アライメントを設定
Font f = Font.loadFont(this.getClass().getResourceAsStream(“resources/AKUBIN1.34.ttf”), 28);
spinner.getEditor().setStyle(“-fx-text-fill: black; -fx-font: 18px ‘Akubin’; -fx-alignment: CENTER_RIGHT;”);

SpinnerExample

上の行でカスタムフォントを読み込んでその下の行でSpinnerにそのフォントを設定、テキストのサイズ、文字色、アライメントを設定しています。

そしてついでに

// Spinneのエディタをユーザー入力可能とする
spinner.setEditable(true);

としてユーザー入力可能としてます。

増分、減分のステップ量は変わらずユーザーによって入力された値からの増分値、減分値となります。

また、最大値、最小値を超えることはありません。

ここまでのプログラムのソースコードは次のようになります。

それではSpinnerにInitialDelayとRepeatDelayを設定するプログラムを組んでみます。

せっかくだからJDK 12 Early-Access Builds (build: 21)とJavaFX Early-Access Builds (openjfx-12-ea+2)を使ってみます。

さらに、モジュラjarを作ってモジュール化します。

そして、jlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成します。

NetBeans IDE 10.0-vc4を使ってお気楽に組もうとしたがこのバージョンには未対応なので古典的なツールを使いました。

ちなみにNetBeans IDE 10.0 Devでプロジェクトをビルドし、jlinkでカスタムJRE組み込みアプリケーションの作成はできました。

ただ、エラーバッジの嵐のエディタは心が痛みます。

それではお気に入りのエディタか心を痛めながらNetBeans IDE 10.0-vc4を使って下記ファイルを作成します。

 

 

 

 

プロジェクトの構成は下図のようにします。

11

カスタムフォントを使うのでお気に入りのフォントをご用意ください。

コンパイル時にはJDK 12の新機能を使えるように–release 12 –enable-preview VMオプションをつけます。

Java SE 11からJavaFXはバンドルされなくなったのでそれらライブラリをダウンロードしてコンパイル、実行時にモジュールパスを指定します。

JavaFXはこちらから入手できます。

https://gluonhq.com/products/javafx/

JavaFX Linux SDKとJavaFX Linux jmodsがあります。

JavaFX Linux SDKだけでいいのですがjlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成する場合はJavaFX Linux jmodsが必要となります。

-d オプションでbin/IncludeeJavaFXディレクトリにコンパイルによって作成されるクラスファイルが格納されるようにしています。

–module-pathオプションでJavaFX SDKのパスを指定します。

–add-modulesオプションでアプリケーションで必要とされるモジュールを指定します。

12

FXMLDocument.fxmlはコンパイルを実行しても出力ディレクトリbin/IncludeeJavaFXには移動、コピーされないので存在しません。

FXMLDocument.fxmlをコンパイルして作成されたクラスファイルのある場所にコピーします。

13

フォントを格納しているresourcesディレクトリもコピーします。

14

プロジェクトの構成は下図のようになります。

15

それでは実行してみましょう。

–enable-preview VMオプションを忘れずに。

–module-path、–add-modules、–moduleオプションも必要です。

16

ちゃんとコンパイルされて実行できました。

17

と、思いきや

18

何やら妙な警告がでました。(>_<。)

GTK libraries version 3を使うとだめそうなので-Djdk.gtk.verbose=true -Djdk.gtk.version=2とVM オプションをあたえます。

19

これってJavaFX 12のリリースまでには修正されるのかな?

20

それではこのプログラムをモジュラjar化してみます。

modsディレクトリを作成してそこにモジュラjarを格納します。

–module-versionでこのモジュールのバージョンを設定します。

実行時メインクラスの指定をしなくていいように–main-classオプションを使ってメインクラスを指定してアプリケーションエントリポイントを設定します。

21

モジュラjarがちゃんとできたか確認します。

22

問題無さそうなのでプログラムを走らせてみます。

–module-pathにはこのアプリケーションを格納しているmodsディレクトリとJavaFX SDKのパスを指定します。

–moduleオプションはメインクラスの指定を省力できるようにしたのでモジュール名だけで実行できます。

モジュラjar作成時にメインクラスの指定をしなかった場合は–module モジュール名/モジュールのメインクラス名とアプリケーションエントリポイント指定しなければいけません。

今回の場合だと、–module IncludedJavaFX/jp.yucchi.includedjavafx.IncludedJavaFXとします。

23

アプリケーションのモジュール化に成功し、ちゃんと動くことが確認できました。

24

最後にjlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成します。

includedjavafx-imageディレクトリを作成しそこに成果物を格納します。

–module-pathオプションでJDKとJavaFXのモジュールを指定します。JavaFX Linux SDKのパスじゃなくてJavaFX Linux jmodsのパスを指定することに注意してください。

–add-modulesオプションでこのアプリケーション自体を指定します。

–launcherオプションを次のように設定します。

–launcher <コマンド名>=<モジュール名><メインクラス>

–outputオプションで出力先を設定します。

25

jlinkによって作成されたランチャーファイルにVMオプションを記述します。

26

27

これを忘れると少し焦ります。(^_^;)

28

動きましたね。

JavaがインストールされてないPCでも動くことを確認しました。(Linux)

ただしWindowsでは動きません。

Windowsで動く配布用アプリはWindowsで作成しなくてはいけないようです。

Write once, run anywhere ってのは遠い過去の夢になってしまったのでしょうか・・・

Spinner

肝心のことを忘れていました。

上のGifアニメで増分、減分ボタンを押しっぱなしにしている時のSpinnerの更新時間に注目してください。

// タイマーの時間を設定
versionSpinner.setInitialDelay(Duration.millis(INTIAL_DELAY_TIME));
versionSpinner.setRepeatDelay(Duration.millis(REPEAT_DELAY_TIME));

setInitialDelay​(Duration value) メソッドで押し続けて最初に更新される時間を設定しています。

このプログラムでは解りやすくするために1500ミリ秒に設定しました。

そのまま押し続けた時の更新時間(リピート時間)は

setRepeatDelay​(Duration value) メソッドで 500ミリ秒と設定しました。

Java SE 8 u40 では750ミリ秒に固定されていて変更することができなかったので少しうれしいアップデートです。

Swingも悪くはないけど近い将来にはJavaの標準 GUI はJavaFXになってほしい。

 

Jigsawにもほんの少しだけ慣れてきたけど難しいですね。

モジュールの依存関係を調べるのがお手軽にできるツールがあればいいのだけど・・・

jdepsは万能ではないので落とし穴に落ちます。(>_<。)

今回のプログラムではFXMLを利用しています。

JavaFXでGUIの構築にはとても便利です。

で、それを使うためにmodule-info.javaにrequires javafx.fxml;とrequiresモジュールディレクティブ設定します。

ところがそれだけではjava.lang.reflect.InvocationTargetExceptionが投げられます。

FXMLを利用したJavaFXアプリケーションではjavafx.fxmlモジュール側からこのプログラムのFXMLのコントローラークラスに対してリフレクションによるアクセスがあります。

モジュールのカプセル化はリフレクションにも適用されるからです。

JDK 9より前はリフレクションを使えばパッケージ内の全てのクラスやクラスの全てのメンバーにアクセスできました。

つまりリフレクションは開発者の本意と関係なくカプセル化を破壊することが可能です。

Jigsawでは強力で繊細なカプセル化が提供されています。

デフォルトでは外部モジュールからモジュール内のクラスにアクセスすることはできません。

外部モジュールからアクセスできるのはexportsされたパッケージの公開クラス、公開メンバだけなのです。

非公開なメンバへのアクセスは外部モジュールから禁止されています。

こういった所でもモジュール化のメリットである依存性、公開範囲の制限が絡んでくるんですね。

なのでこのリフレクションによるjavafx.fxmlモジュール側からのアクセスを許可する必要があります。

それにはopens…toモジュール・ディレクティブを設定します。

module-info.javaにopens jp.yucchi.includedjavafx to javafx.fxml;とopensモジュールディレクティブを付け加えるだけです。

module-info.javaにこのモジュールディレクティブを加えることによって実行時にのみ特定のモジュールに対してパッケージへのアクセスを許可することが可能です。

つまり、特定のパッケージ内のpublic型(およびネストされているpublic型とprotected型)に対して実行時にのみアクセスできることを指定可能となります。

指定したパッケージのすべての型(およびその型のすべてのメンバ)にリフレクションを使ってアクセスすることもできます。

Jigsawの強力で繊細なカプセル化はJDK 9より前のものより開発者には喜ばれるはずですね。

そうなるとjdepsのような依存性を解決する強力なツールの必要性が求められます。

そのうち統合開発環境なんかに乗っかってくることを期待します。

 

Jigsaw のお勉強 その1

Jigsaw のお勉強 その2

Jigsaw のお勉強 その3

Hatena タグ: ,


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Jigsaw のお勉強 その3

Java

Jigsaw のお勉強の続きです。

超スローペースですがゆっくり慌てず三歩進んで二歩下がるような感じでもう暫く進めていこうと思っています。

今回は、最新Java情報局 「Java SE 9を先取り、Project Jigsawでモジュールを作成する」の写経をおこなってみました。

プログラムは同じではないのですがProject Jigsawで実現される機能の確認は同様にしました。

元記事で解説されているようにモジュールの定義は次のようになっています。

モジュールは複数のパッケージやリソースなどを含む、自己記述可能なコードの集合

今までの JARファイルにモジュール名が付いて依存性や公開範囲を記述できるようになったようです。

詳しくは元記事を参照してくださいませ。

では、モジュールを作っていきます。

プログラムを起動したら挨拶(文字列)を表示させるだけのプログラムです。

挨拶を表示させるためのインタフェースとしてGreetingインタフェース、その実装クラスとしてMultilingualGreetingクラスを作成します。

実装クラスのMultilingualGreetingクラスは日本語の挨拶か英語の挨拶をランダムに返します。

MultilingualGreetingクラスは外部からアクセスされることを避けるようにします。(Java SE 9 からインターフェイスにprivateメソッドが記述できるようになったのは今回は忘れてください。)

このアプリケーションをモジュールとするためにmodule-info.javaをトップディレクトリに作成します。

exports jp.yucchi.greeting;とmodule-info.javaに記述されています。

これは公開範囲を定義しています。

jp.yucchi.greeting.internalパッケージは公開されてないので外部からアクセスすることはできません。

それではjp.yucchi.greetingモジュールを使用するモジュールを作成します。

続いてこちらもモジュールとするためにmodule-info.javaを作成します。

先程作成したjp.yucchi.greetingモジュールを使用するためにrequires jp.yucchi.greeting;と記述しています。

これは依存性の定義となります。

面白いのはexports jp.yucchi.helloと自分自身を公開すると定義しています。

Helloクラスのmain()メソッドをコールすることも外部アクセスとなるからです。

これでシンプルなお試しプログラムが完成しました。

ディレクトリ構造は前回までと少し変更しています。

1

それぞれコンパイルしてみましょう。

binディレクトリを作成してコンパイルされたファイルを配置しました。

2

それでは確認のためにプログラムを実行してみます。

3

ちゃんとうごきましたね!(^_^)

それではモジュラーJARとしてパッケージ化してみましょう。

modsディレクトリを作成してモジュラーJARとしてパッケージングし配置しました。

4

一応できているみたいですが正しくできているか念の為に確認します。

5

モジュラーJARの依存性、公開範囲、メインクラスなどの情報に間違いが無いことを確認することができました。

依存性の定義にjava.baseモジュールがありますがこれはjava.lang.Objectクラスなどが含まれた必要不可欠なもので全てのモジュールに含まれることになります。

greeting@1.0.jarでは非公開パッケージがcontains jp.yucchi.greeting.internalと表示されています。

ここでJava SE 9からjarコマンドが少し変更されているのでヘルプをみておきましょう。

yucchi@ubuntu:~/Documents/MyJigsawProjects/HelloJigsaw$ jar –help
使用方法: jar [OPTION…] [ [–release VERSION] [-C dir] files] …
jarはクラスおよびリソースのアーカイブを作成し、アーカイブから個々のクラスまたは
リソースを操作または復元できます。

例:
# 2つのクラス・ファイルを含むclasses.jarというアーカイブを作成する:
jar –create –file classes.jar Foo.class Bar.class
# foo/のすべてのファイルを含む、既存のマニフェストを使用したアーカイブを作成する:
jar –create –file classes.jar –manifest mymanifest -C foo/ .
# モジュラjarアーカイブを作成する。モジュール・ディスクリプタはclasses/module-info.classに
# ある:
jar –create –file foo.jar –main-class com.foo.Main –module-version 1.0
     -C foo/ classes resources
# 既存の非モジュラjarをモジュラjarに更新する:
jar –update –file foo.jar –main-class com.foo.Main –module-version 1.0
     -C foo/ module-info.class
# 複数リリースjarを作成し、一部のファイルをMETA-INF/versions/9ディレクトリに配置する:
jar –create –file mr.jar -C foo classes –release 9 -C foo9 classes

jarコマンドを短縮または簡略化するには、個別のテキスト・ファイルで引数を指定し、
記号(@)を接頭辞として使用してjarコマンドに渡します。

例:
# 追加オプションおよびクラス・ファイルのリストをファイルclasses.listから読込みます
jar –create –file my.jar @classes.list

メイン操作モード:

  -c、–create               アーカイブを作成します
  -i,、–generate-index=FILE  指定のjarアーカイブの索引情報を
                             生成します
  -t、–list                 アーカイブの内容を一覧表示します
  -u、–update               既存のjarアーカイブを更新します
  -x、–extract              指定の(またはすべての)ファイルをアーカイブから抽出します
  -d, –describe-module      モジュール・ディスクリプタまたは自動モジュール名を出力します

どのモードでも有効な操作修飾子:

  -C DIR                     指定のディレクトリに変更し、次のファイルを
                             取り込みます
  -f、–file=FILE            アーカイブ・ファイル名。省略した場合、stdinまたは
                             stdoutのいずれかが操作に基づいて使用されます
      –release VERSION      次のすべてのファイルをjarのバージョニングされたディレクトリ
                             (つまり、META-INF/versions/VERSION/)に配置します
  -v、–verbose              標準出力に詳細な出力を生成します

作成または更新モードでのみ有効な操作修飾子:

  -e、–main-class=CLASSNAME モジュラまたは実行可能なjarアーカイブに
                             バンドルされたスタンドアロン・アプリケーションの
                             アプリケーション・エントリ・ポイント
  -m、–manifest=FILE        指定のマニフェスト・ファイルからマニフェスト情報を
                             取り込みます
  -M、–no-manifest          エントリのマニフェスト・ファイルを作成しません
      –module-version=VERSION    モジュラjarの作成時または非モジュラjarの更新時の
                             モジュール・バージョン
      –hash-modules=PATTERN モジュラjarの作成時または非モジュラjarの更新時に
                             指定のパターンに一致し、直接または間接的に
                             依存しているモジュールのハッシュを
                             計算および記録します
  -p、–module-path          ハッシュを生成するモジュール依存性
                             の場所

作成、更新および索引生成モードでのみ有効な操作修飾子:

  -0, –no-compress          格納のみ。ZIP圧縮を使用しません

その他のオプション:

  -h、–help[:compat]        これ(オプションで互換性)をhelpに指定します
      –help-extra           追加オプションのヘルプを提供します
      –version              プログラム・バージョンを出力します

モジュール・ディスクリプタ’module-info.class’が指定のディレクトリのルートまたは
jarアーカイブ自体のルートにある場合、アーカイブはモジュラjarです。
次の操作は、モジュラjarの作成時または既存の非モジュラjarの更新時に
のみ有効です:  ‘–module-version’、
‘–hash-modules’および’–module-path’。

ロング・オプションへの必須またはオプションの引数は、対応するショート・オプション
に対しても必須またはオプションになります。

Project Jigsawの影響でコマンドが増えていますね。(当たり前か・・・)

最後にこのプログラムを実行してみます。


java --module-path mods --module jp.yucchi.hello

6

アプリケーション・エントリ・ポイントを設定したモジュールのモジュールパスとモジュール名を指定してjavaコマンドによって実行します。

今回はモジュラーJAR作成時にアプリケーション・エントリ・ポイントを指定してますのでこのように実行できます。

モジュラーJAR作成時にアプリケーション・エントリ・ポイントを設定してない場合は次のように実行します。

モジュールのモジュールパスの指定はおなじです。

–module モジュール名/モジュールのメイン・クラス名とアプリケーション・エントリ・ポイント指定しなければいけません。

7

ついでだから javaコマンドのヘルプも確認しておきます。

 

yucchi@ubuntu:~/Documents/MyJigsawProjects/HelloJigsaw$ java –help
使用方法: java [options] <mainclass> [args…]
           (クラスを実行する場合)
   または  java [options] -jar <jarfile> [args…]
           (jarファイルを実行する場合)
   または  java [options] -m <module>[/<mainclass>] [args…]
       java [options] –module <module>[/<mainclass>] [args…]
           (モジュールのメイン・クラスを実行する場合)

メイン・クラス-jar <jarfile>、-mまたは–module
<module>/<mainclass>に続く引数は、メイン・クラスへの引数として渡されます。

オプションは次のとおりです:

    -d32      推奨されていません。今後のリリースで削除される予定です
    -d64      推奨されていません。今後のリリースで削除される予定です
    -cp <ディレクトリおよびzip/jarファイルのクラス検索パス>
    -classpath <ディレクトリおよびzip/jarファイルのクラス検索パス>
    –class-path <ディレクトリおよびzip/jarファイルのクラス検索パス>
                  :区切りリスト(ディレクトリ、JARアーカイブ、
                  ZIPアーカイブ)で、クラス・ファイルの検索用。
    -p <module path>
    –module-path <module path>…
                  ディレクトリの:区切りリスト、各ディレクトリ
                  はモジュールのディレクトリです。
    –upgrade-module-path <module path>…
                  ディレクトリの:区切りリスト、各ディレクトリ
                  は、ランタイム・イメージ内のアップグレード可能な
                  モジュールを置換するモジュールのディレクトリです
    –add-modules <module name>[,<module name>…]
                  初期モジュールに加えて解決するルート・モジュール。
                  <module name>には次も指定できます: ALL-DEFAULT、ALL-SYSTEM、
                  ALL-MODULE-PATH.
    –list-modules
                  参照可能なモジュールをリストし終了します
    -d <module name>
    –describe-module <module name>
                  モジュールを説明し終了します
    –dry-run     VMを作成しメイン・クラスをロードしますが、メイン・メソッドは実行しません。
                  –dry-runオプションは、次の検証に役立つ場合があります:
                  モジュール・システム構成などのコマンド行オプション。
    –validate-modules
                  すべてのモジュールを検証し終了します
                  –validate-modulesオプションは、次の検索に役立つ場合があります:
                  モジュール・パス上のモジュールでの競合およびその他のエラー。
    -D<name>=<value>
                  システム・プロパティを設定します
    -verbose:[class|module|gc|jni]
                  詳細出力を有効にします
    -version      製品バージョンをエラー・ストリームに出力して終了します
    –version     製品バージョンを出力ストリームに出力して終了します
    -showversion  製品バージョンをエラー・ストリームに出力して続行します
    –show-version
                  製品バージョンを出力ストリームに出力して続行します
    –show-module-resolution
                  起動時にモジュール解決出力を表示します
    -? -h -help
                  このヘルプ・メッセージをエラー・ストリームに出力します
    –help        このヘルプ・メッセージを出力ストリームに出力します
    -X            追加オプションのヘルプをエラー・ストリームに出力します
    –help-extra  追加オプションのヘルプを出力ストリームに出力します
    -ea[:<packagename>…|:<classname>]
    -enableassertions[:<packagename>…|:<classname>]
                  指定した粒度でアサーションを有効にします
    -da[:<packagename>…|:<classname>]
    -disableassertions[:<packagename>…|:<classname>]
                  指定した粒度でアサーションを無効にします
    -esa | -enablesystemassertions
                  システム・アサーションを有効にします
    -dsa | -disablesystemassertions
                  システム・アサーションを無効にします
    -agentlib:<libname>[=<options>]
                  ネイティブ・エージェント・ライブラリ<libname>をロードします。例: -agentlib:jdwp
                  -agentlib:jdwp=helpも参照してください
    -agentpath:<pathname>[=<options>]
                  フルパス名を使用して、ネイティブ・エージェント・ライブラリをロードします
    -javaagent:<jarpath>[=<options>]
                  Javaプログラミング言語エージェントをロードします。java.lang.instrumentを参照してください
    -splash:<imagepath>
                  指定されたイメージを含むスプラッシュ画面を表示します
                  HiDPIスケールのイメージが自動的にサポートされて使用されます
                  (可能な場合)。スケーリングされないイメージのファイル名(image.extなど)を
                  引数として-splashオプションに必ず渡す必要があります。
                  指定された最も適切なスケーリング済イメージが選択されます
                  (自動的)。
                  詳細は、SplashScreen APIのドキュメントを参照してください
    @argumentファイル
                  オプションを含む1つ以上の引数ファイル
    -disable-@files
                  さらなる引数ファイル拡張を無効にします
長いオプションの引数を指定する場合、–<name>=<value>または
–<name> <value>を使用できます。

まとめ

今回は何も考えずにほぼ写経しただけなので随分楽に進みました。

しかし、モジュール化のメリットの依存性、公開範囲の制限を定義することを学ぶことができました。(^_^)

module-info.javaにrequiresとexportsを記述して依存と公開の定義をする。

requires モジュール名;とすることで指定したモジュールに依存することを定義します。

今回は使っていませんがrequires transient モジュール名;とすることで推移的依存(依存したモジュールの依存先も依存可能)を定義できます。

また、requires static モジュール名;とすることでコンパイル時のみ依存可能と定義できます。

ちょっと複雑に感じますが慣れてくると便利に依存を定義することができそうですね。

次に公開するパッケージを定義するのにexports パッケージ名;と記述します。

exports パッケージ名 to モジュール名;とすることで特定のモジュールに公開することが可能です。

 

ゆっくり少しずつだけどお勉強を進めていこう。

Jigsaw のお勉強 その1

Jigsaw のお勉強 その2

Hatena タグ:

Jigsaw のお勉強 その2

Java

Jigsaw のお勉強の続きです。

前回はクイックスタートガイドを機械翻訳にかけて斜め読みしただけなので今回は実際にプログラムを組んで動かしてみたいと思います。

とりあえず最小構成で試してみます。

次のような標準出力に文字列を表示させるだけのプログラムです。

s1

プログラムのディレクトリ構造は次のようにしてみました。

s2

module-info.java をパッケージのトップディレクトリに作成します。

これだけで Java Platform Module System アプリケーションができてしまいます。

モジュール名に関しては通常のパッケージ名と同じようにユニークになるようにドメイン名ベースとするのが好ましいという意見とそこまでする必要は無いという意見があり、今後どのような命名が一般的になるか気になるところです。

今回はお試しプログラムなので firstjigsaw とシンプルなモジュール名としました。

それではコンパイルしてみましょう。

1

ちゃんとコンパイルされて out/firstjigsaw ディレクトリに二つのクラスファイルが作成されました。(^_^)

実行してみます。

4

おおっ! はじめての Jigsaw 動いた!

–module-path でモジュールパスを指定しています。

-m オプションはメインモジュールを指定しています。

スラッシュの後の値は、モジュール内のメインクラスのクラス名です。

–module-path オプションは -p 短縮オプションが使えます。

また、–module オプションは -m 短縮オプションが使えます。

5

次は作成したクラスファイルを使って jar ファイルを mods ディレクトリに作成します。

3

これも問題なく jar ファイルが作成されました。(^_^)

それでは実行してみましょう。

6

jar ファイルも問題なく実行できました。(^_^)

これも短縮オプションが使えるようですね。

モジュールを使ったプロジェクトの不具合の解決の助けになるコマンドオプションも用意されています。

7

カスタムJRE を作成するためのコマンドもあります。

8

今の私にはまだ必要のないものですがちょっと触ってみました。(^_^;

とりあえず、今日はここまで。

初めて Jigsaw のコード書いて動かしただけだけどこれはわたしにとって大きな一歩です!

 

Jigsaw のお勉強 その1

Hatena タグ:

Jigsaw のお勉強 その1

Java

Java SE 9 がリリースされて随分経ちますが Jigsaw は積極的に使ってないのでさっぱり解らないのが現状です。

リリースされる前から注目されていてネット上には記事がちらほら見受けられていました。

それを読んではいたのですが未だに自分でコードを書いたことは無いのでそろそろなんとかしなければいけないと思い、

冷やし中華はじめました!

じゃなくて、Jigsaw はじめました!

とりあえず何から手を付けて良いものか解らないので Project Jigsaw: Module System Quick-Start Guide を読んでみることにしました。

 

Project Jigsaw: Module System Quick-Start Guide

このドキュメントでは、開発者がモジュールを使い始めるための簡単な例をいくつか紹介します。

サンプルコードのファイルパスはスラッシュを使用し、パス区切り子はコロンです。
Microsoft Windowsの開発者は、ファイルパスにバックスラッシュとセミコロンをパス区切り文字として使用する必要があります

  • Greetings
  • Greetings world
  • Multi-module compilation
  • Packaging
  • Missing requires or missing exports
  • Services
  • The linker
  • –patch-module

Greetings

この最初の例はcom.greetingsという名前のモジュールで、単に “Greetings!”を表示します。
モジュールは、モジュール宣言(module-info.java)とメインクラスの2つのソースファイルで構成されています。

慣例により、モジュールのソースコードは、モジュールの名前であるディレクトリにあります。

ソースコードは、次のコマンドでディレクトリmods / com.greetingsにコンパイルされます。

これで、次のコマンドでこの例を実行します。

–module-pathはモジュールパスです。その値はモジュールを含む1つ以上のディレクトリです。 -mオプションは、メインモジュールを指定します。スラッシュの後の値は、モジュール内のメインクラスのクラス名です。

Greetings world

この2番目の例は、モジュール宣言を更新して、org.astroモジュールへの依存関係を宣言します。
モジュールorg.astroはAPIパッケージorg.astroをエクスポートします。

モジュールは一度に1つずつコンパイルされます。
モジュールcom.greetingsをコンパイルするjavacコマンドは、モジュールorg.astroへの参照と、エクスポートされたパッケージの型を解決できるように、モジュールパスを指定します。

この例は、最初の例とまったく同じ方法で実行されます。

Multi-module compilation

前の例では、モジュールcom.greetingsとモジュールorg.astroが別々にコンパイルされていました。 1つのjavacコマンドで複数のモジュールをコンパイルすることもできます

Packaging

これまでの例では、コンパイルされたモジュールの内容がファイルシステム上で展開されています。
移植と配備の目的では、通常、モジュールをモジュラーJARとしてパッケージ化する方が便利です。
モジュラーJARは、トップレベルのディレクトリにmodule-info.classを持つ通常のJARファイルです。
次の例では、org.astro@1.0.jarおよびcom.greetings.jarをmlibディレクトリに作成します。

この例では、モジュールorg.astroは、そのバージョンが1.0であることを示すようにパッケージ化されています。 モジュールcom.greetingsは、メインクラスがcom.greetings.Mainであることを示すようにパッケージ化されています。 メインクラスを指定することなく、モジュールcom.greetingsを実行できるようになりました。

–module-pathの代わりに-pを使用することによって、コマンドラインも短縮されます。

jarツールには多くの新しいオプションがあります(jar -helpを参照)。その1つはモジュラーJARとしてパッケージ化されたモジュールのモジュール宣言を出力することです。

Missing requires or missing exports

ここで、前の例で、com.greetingsモジュール宣言からの要求を誤って省略した場合の動作を見てみましょう。

このモジュール宣言を修正しましたが、別の間違いを導入しました。
今回はorg.astroモジュール宣言からのエクスポートを省略します。

Services

サービスは、サービスコンシューマモジュールとサービスプロバイダモジュールとの間の疎結合を可能にする。

この例には、サービスコンシューマモジュールとサービスプロバイダモジュールがあります。

モジュールcom.socketは、ネットワークソケット用のAPIをエクスポートします。
このパッケージがエクスポートされるように、APIはcom.socketパッケージ内にあります。
APIは、代替実装を可能にするためにプラガブルです。
サービスタイプは同じモジュール内のクラスcom.socket.spi.NetworkSocketProviderであるため、パッケージcom.socket.spiもエクスポートされます。

モジュールorg.fastsocketはサービスプロバイダモジュールです。
com.socket.spi.NetworkSocketProviderの実装を提供します。
パッケージをエクスポートしません。

以下はcom.socketモジュールのソースコードです。

以下はorg.fastsocketモジュールのソースコードです。

簡単にするために、両方のモジュールをまとめてコンパイルします。 実際には、サービスコンシューマモジュールとサービスプロバイダモジュールは、ほぼ常に別々にコンパイルされます。

最後に、モジュールcom.greetingsを変更してAPIを使用します。

最後に実行します。

出力は、サービスプロバイダが見つかったこと、およびNetworkSocketのファクトリとして使用されたことを確認します。

The linker

jlinkはリンカーツールであり、モジュールのセットとそれらの推移的依存とをリンクしてカスタムモジュラーランタイムイメージを作成するために使用することができます(JEP 220を参照)。

このツールでは、現在、モジュールパス上のモジュールをモジュラーJARまたはJMOD形式でパッケージ化する必要があります。
JDKビルドでは、標準およびJDK固有のモジュールをJMOD形式でパッケージ化します。

次の例では、モジュールcom.greetingsとその推移依存を含むランタイムイメージを作成します。

–module-pathの値は、パッケージ化されたモジュールを含むディレクトリのPATHです。Microsoft Windowsでは パスセパレータ ‘:’を ‘;’に置き換えます。

$ JAVA_HOME / jmodsは、java.base.jmodとその他の標準モジュールとJDKモジュールを含むディレクトリです。

モジュールパス上のディレクトリmlibには、モジュールcom.greetingsの成果物が含まれています。

jlinkツールは、生成されたイメージをカスタマイズするための多くの高度なオプションをサポートしています。詳細はjlink –helpを参照してください。

--patch-module

Doug LeaのCVSからjava.util.concurrentクラスをチェックアウトする開発者は、ソースファイルをコンパイルし、それらのクラスを-Xbootclasspath / pでデプロイするために使用されます。

-Xbootclasspath / pが削除された場合、そのモジュールの置き換えは、モジュール内のクラスを上書きするオプション–patch-moduleです。 また、モジュールの内容を増やすために使用することもできます。 –patch-moduleオプションはjavacでもサポートされており、モジュールのようにコードをコンパイルします。

以下は、新しいバージョンのjava.util.concurrent.ConcurrentHashMapをコンパイルし、それを実行時に使用する例です。

More information

The State of the Module System

JEP 261: Module System

Project Jigsaw

Feedback

Please send usage questions and experience reports to the jigsaw-dev list. Specific suggestions about the design of the module system should be sent to the JSR 376 Expert Group’s comments list.

機械翻訳によって日本語化してみました。

この資料が古くてカビが生えてなければいいのですが・・・ とりあえず読んでみました。

Hatena タグ:

« 古い記事