torutkのブログ

ソフトウェア・エンジニアのブログ

Java読書会BOF「Effective Java第3版」を読む会(第5回)を開催して

Java読書会開催のデータ

昨日4月13日(土)は、Java読書会BOF主催の「Effective Java第3版」を読む会(第5回)を開催しました。

1998年12月にJava読書会が始まってから通算241回目、36冊目の書籍となります。
Effective Javaについては、2002年に第1版を、2008年に第2版をJava読書会で読んでいます。

今回の読書範囲からのメモ

オーバーロード

同じパラメータ数の二つのオーバーロードされたメソッドを提供しないこと

これに尽きますね。オートボクシングがあるので、引数の型がObjectとintでオーバーロードしていると落とし穴に陥りがちです。既存APIにあるので注意が必要です。

可変長引数

引数の個数が0個のことも考慮して実装する必要があります。引数を最低1個必須とする場合は、必須の引数と可変長引数と2つの引数を要求するようメソッドを定義します。

nullをメソッドの戻り値として返さない

nullを返すメソッドは呼び出し側にnullに対応する余分なコードが必要となる上、利用者がそのコードを書き忘れてプログラムのエラーを招く原因となります。

配列またはコレクションを戻り値型とする場合は、nullではなく空コレクションか空配列を返すようにします。

Java SE 8から導入されたOptionalを戻り値型とすると、メソッドが値を返さなかったときにデフォルト値を使う(orElse)、例外をスローする(orElseThrow)、別な処理を呼び出す(orElseGet)を簡潔に実装できます。isPresentはOptionalが提供する他のメソッドでは対応できないときの「安全弁」として使うという解説があり、なるほど~と思いました。

Optionalの使い方は読書会で議論となり、Serializableではないのでフィールドには使うべきではないねとなりました。

ドキュメントコメント

いわゆるJavadocコメントですが、Java SE 8以降も進化していました。

  • @implSpec
  • @index
  • @summary
  • 検査有効(-Xdoclintがデフォルトで有効)
  • HTML5を生成させる-html5オプション

@throwsに、可能性のある非チェック例外を書くのはよいが、メソッド内で外部ライブラリを呼び出しているとすべての非チェック例外を把握しているわけではないのでどうしよう?と議論に。

ライブラリを知り、ライブラリを使う

車輪を再発明しないでください

で、どのようにライブラリを探して使うかについて議論となりました。

  • 検索スキルが必要
  • こういうライブラリがあるはず(必要)、という意識がないとライブラリを探せない
  • ライブラリの更新頻度、ダウンロード数を参考にする
  • 大きなライブラリは避ける
  • 複数のライブラリを使うと、それらのライブラリがたまたま同じライブりに依存するがバージョンが違うときにはまる
  • google検索エンジンでトップに出てくる、内容が薄いQiita記事は何だろうね
  • 類似ライブラリを横並べて比較する、APIの違いを見る
Randomクラスについて

Java 7の時点で、Randomをもはや使うべきではありません。今日ではほとんどの場合、選択すべき乱数生成器はThreadLocalRandomです。それは高品質な乱数を生成し、きわめて速いです。

InputStreamにtransferToメソッドが追加

InputStreamに、long transferTo(OutputStream out) メソッドが追加され、入力ストリームをそのまま出力ストリームに流し込むことが簡単にできるようになりました。

    public static void main(String[] args) throws IOException {
        try (InputStream in = new URL(args[0]).openStream()) {
            in.transferTo(System.out);
        }
    }

おおー、これは便利。java.io.Readerとjava.nio.channels.FileChannelにもtransferToメソッドが追加されています。

toArrayのパフォーマンス

Effective Javaで次の記事が参照されていました。
Arrays of Wisdom of the Ancients

これは、ListインタフェースのtoArrayメソッドを呼ぶ際に指定する引数の値を、長さ0の配列を生成するか、Listの要素数に相当するサイズの配列を生成するかでどちらが速いかを調査した内容です。

List<Cheese> cheeseInStock = ...
return cheeseInStock.toArray(new Cheese[0]);  // (1)
return cheeseInStock.toArray(new Cheese[cheeseInStock.size()]); // (2)

Effective Javaでは、参照記事に基づいて(1)を用い、(2)はパフォーマンスに悪影響があるので使わないことを推奨しています。

(1)はコレクションcheeseInStockの要素数が1以上のときは新しい配列を生成します。(2)は渡した配列が使われ、新しい配列の生成はありません。そこで、(2)の方が性能がよいように見えます。

参照記事ではパフォーマンスを計測した結果、(1)の方がArrayListでは顕著に早く、HashSetではほぼ同じとのデータが得られ、それを考察しています。パフォーマンス計測にはJMHを使っているのでそこは信頼に足ります。

記事をざっと読んだところ次のとおり

  • 配列の生成にかかるコストよりも要素を配列に詰めるコストが支配的
  • ArrayListの場合は、内部の配列の要素をtoArrayで返却する配列にSystem.arraycopyでコピー
  • (1)ではネイティブコードのcheckcast_arraycopy_uninit関数が実行されている
  • (2)ではネイティブコードのcheckcast_arraycopy関数が実行されている。(1)のコードにはないrepz stosb命令(memset to zero)に時間を要している

ちなみに、引数なしのtoArray()では、AVX命令を使ったさらに高速なコピーが行われています。

Liberica JDK 12でJavaFXアプリケーションをIntelliJ IDEA上で作成

Java SE 12がリリース

先月3月20日Java SE 12(Oracle JDK 12およびOracleがビルドしたOpenJDK 12)がリリースされました。Java SE 12は半年毎のフィーチャーリリース(機能のアップデート)となります。

Oracle JDKは、Java SE 11から運用に使用する場合は有償ライセンスが必要となりました。開発用途ではOTNライセンスの下で無償提供されています。
OracleがビルドしたOpenJDKは用途によらず無償提供されています。

また、OpenJDKはオープンソース化されており、Oracle以外のいくつかの組織がそれぞれビルドしたOpenJDK 12が幾つかリリースされています。これらは無償提供され、また有償の技術サポートが提供されています。

JavaFXとOpenJDK

Oracleは、Java SE 11以降Oracle JDKOracleがビルドするOpenJDKからJavaFXを外してリリースしています。ただし、JavaFXはOpenJDKプロジェクトの一部として開発が継続しており、独立したライブラリとしてリリースされているほか、Oracle以外の組織が提供する一部のOpenJDKバイナリに組み込まれています。現時点では、次のOpenJDKにJavaFXが組み込まれています。

4月11日時点では、Liberica JDK 12がリリースされていますが、ZuluFX 12は未だリリースされていません。

Liberica JDK 12の入手とインストール(Windows 10)

Liberica JDKは、BellSoft ltd社(米国およびロシアにオフィスを持っている)がビルドするOpenJDKで、Windows向けには32bit版、64bit版が提供され、またJavaFX同梱版とJavaFX非同梱版が提供されています。

OpenJDKのWindows版では、MSI形式のインストーラーとZIP形式の2種が用意されています。MSI形式は環境変数PATHも設定されるので、複数JDKを使い分ける開発環境ではZIP形式の方がよいと思います。

今回は、Windows 64bit版 JavaFX同梱のZIP形式を入手します。

任意の場所に展開します。
例えば、C:\Program Files\Java の下に展開すると、jdk-12のフォルダが生成されるので、適宜liberica-jdk-12 のように分かりやすい名前に変更しておきます。

IntelliJ IDEA Community版で使用

IntelliJ IDEAで使用するJDKを定義するには、[File]メニュー > [Project Structure]をクリックし、「Project Structure]画面の左側ペインで[SDKs]を選択、中央ペインの上部にある[+]をクリック、ポップアップされるメニューから[JDK]をクリック、「Select Home Directory for JDK」ダイアログでLiberica JDK 12をインストールしたディレクトリを指定します。

JavaFXアプリケーションの作成

IntelliJ IDEAでJavaFXアプリケーションを作成します。
[File]メニュー > [New] > [Project] で「New Project」ダイアログを開き、左側ペインで[Java]を選択、[Project SDK]欄にliberica-jdk-12を選びます。

これで、JavaFXのクラスを利用したアプリケーションを記述できるようになります。

IntelliJ IDEAでJavaFXアプリケーションのProject Template作成

動機

IntelliJ IDEAでJavaFXアプリケーションを作成する際に、FXMLファイルで画面レイアウト定義、CSSファイルで見栄え定義、プロパティファイルでリソース定義をする場合に用意するファイルと毎度のコードを生成するプロジェクトテンプレートを作成します。

作成環境は次です。

OS Windows 10 Home 64bit
IntelliJ IDEA Community 2019.1
SDK Liberica 12

ここで、JavaFXアプリケーションのビルド・実行に使用するSDKJDK)は、BellSoft社が無償提供するOpenJDK 12にJavaFXを同梱したLiberica JDK 12です。
https://bell-sw.com/pages/java-12/

プロジェクトテンプレートの作成

IntellIJ IDEAには、作ったプロジェクトをテンプレートして保存する機能があります。
[Tools]メニュー > [Save Project as Template]

そこで、FXMLファイル、CSSファイル、プロパティファイルを使用するJavaFXアプリケーションの最小構成をプロジェクトとして作成し、この機能でテンプレート化してみます。

最小構成のプロジェクトを作成しテンプレート保存

[Files]メニュー > [New] > [Project] で「New Project」画面を開き、左側ペインで[Java]を選択、右側ペイン上の[Project SDK]欄ではJavaFXを同梱するJDK(今回はLiberica JDK 12)を指定します。
[Project name]には、samplefx と入れてみました。

続いて、パッケージを1つ作成します。パッケージ名は、テンプレートからプロジェクトを作る際に指定したものに置き換えられるので、ここでは適当な名前(com.torutk.samplefx)を付けておきます。

そのパッケージの下に、javafx.application.Applicationを継承したメインクラス、コントローラークラス、FXMLファイル、CSSファイル、プロパティファイルを作っていきます。

f:id:torutk:20190409231332p:plain:w240
IntelliJ のプロジェクトテンプレート用JavaFXアプリケーションプロジェクトのファイル構成

ビルド・実行ができることを確認し、プロジェクトテンプレートを作成します。
[Tools]メニュー > [Save Project as Template]を実行し、「Save Project As Template」画面でテンプレートの名前と説明を入力します。

f:id:torutk:20190409235316p:plain:w480
Save Project As Templateダイアログに名前と説明を記載

作成されたプロジェクトテンプレートはzipアーカイブファイルで、ユーザーディレクトリの下に生成されます。

C:\Users\<ユーザー名>\.IdeaIC<バージョン>\config\projectTemplates\JavaFX Sample Application.zip

テンプレートからプロジェクトを作成

先に作成したプロジェクトテンプレートからプロジェクトを生成します。

[File]メニュー > [New] > [Project]で「New Project」画面を開きます。左側ペインにある[User-defined]を選択すると右側ペインにユーザー定義のプロジェクトテンプレートがリストされます。この中に先ほど作成した[JavaFX Sample Application]があるのでこれを選択します。

f:id:torutk:20190409235945p:plain:w480
作成したテンプレートをNew Projectで指定

プロジェクト名とパッケージ名を指定します。

f:id:torutk:20190410075044p:plain
New Projectでプロジェクト名とパッケージ名指定

指定したプロジェクト名に合わせてJavaモジュール名を変更するか問うダイアログが表示されます。チェックを付けます。

f:id:torutk:20190410080107p:plain
New Projectでモジュール名変更

次のファイル構成で新しいプロジェクトが生成されます。

f:id:torutk:20190410080347p:plain
生成されたファイル群

注意点

Project Templateの雛形とするプロジェクトを作成するときに、プロジェクト名とパッケージ名を同じにすると、うまく動かないコードが生成されてしまいます。

例)プロジェクト名をsamplefxとし、パッケージ名をsampleとしたプロジェクトテンプレートを作成し、これを雛形とするアプリケーションプロジェクトを作成、パッケージ名にcom.torutk.helloと指定したところ、プロジェクトが正常に作られませんでした。

IntelliJ IDEAのモジュール定義ファイル samplefx.iml に対し、IntelliJ IDEAのプロジェクトファイルmodules.xmlの中で参照するパス名がsamplefx.imlではなく、com.torutk.hellofx.imlと"sample"の部分が置き換えられてしまいました。

Windows 7プリインストールマシンの購入はもう‥

お題

Windows 7は、新機能の追加およびセキュリティ以外の修正を行うメインストリームサポートが2015年1月に終了しています。そして、セキュリティ修正を行う延長サポートは2020年1月に終了の予定です。

何らかの理由によりWindows 7でないと動作しない機能を使うために、これから新たにWindows 7を載せたPCを調達しなくてはならないとします。その理由とは、例えば、独自のPCIPCI Express)カードを開発してPCに組み込んで使用している場合で、そのカードのドライバーソフトウェアがWindows 10に対応しておらず、直ちに対応することができない等です。

Windows 7 の販売終了日

Microsoftのサイトによると、Windows 7(Professional版)の販売終了は、製品版ソフトウェアが2013年10月、Windowsプレインストール済みPCの販売終了が2016年10月とあります。

https://support.microsoft.com/ja-jp/help/13853/windows-lifecycle-fact-sheet

各PCメーカーのWindowsプレインストールマシン販売状況

日本HP

日本HPの「Windows 7 Professionalプリインストールモデル販売継続予定のご案内(2017年12月版)」によると

マイクロソフトは、「Windows 10 Proからのダウングレード権を行使したWindows 7 ProfessionalもしくはWindows 8.1 Proプリインストールモデル」のPCメーカーからの出荷期限を2018年10月31日までとしています。

とあり、受注期限が、HP ProDesk/HP EliteDeskは2018年9月末まで、ZシリーズはZ240が2018年7月末までと既に終了しています。

http://jp.ext.hp.com/partners/reseller/partnernews/pdfs/ws7preinstall201712.pdf

その他

Dell富士通PanasonicNECエプソンとざっとWebで見た限りでは、Windows 7をプレインストールしたマシンを販売してはいませんでした。

ダウングレード権の行使によるWindows 7の利用

Windows 10 Pro搭載マシンを購入し、ダウングレード権の行使によってWindows 7 Professionalを入れる場合は、次を入手し、自身でWindows 7を新規にインストールする必要があります。

  • Windows 7 Professionalのインストールメディアの入手
  • Windows 7用ドライバの入手

ただし、現時点でWindows 7 Professionalのインストールメディアを入手することには制約があります。Windows 7が既に販売終了しているので、Microsoftボリュームライセンス(少量ならOpen License?)でWindows 10 Proのライセンスを購入し、Windows 7のメディアをダウンロードするといった手段が必要になりそうです。この場合、購入したPCのOEMライセンスのWindows 10 Proとダブってしまい無駄な費用が発生してしまいます。

また、最新のパーツはWindows 7が対応できないものがあるので、購入する機種はWindows 7に対応可能なパーツで構成されている必要があります。

  • Intel CPUは、第7世代(Kaby Lake)以降はWindows 10にのみ対応(動作はするが、Windows Updateがブロックされる模様)
  • AMD CPUは、Ryzen以降は対応せず

ダウングレード権によるWindows 7の利用期限

Microsoftのサイトによると、

7.ダウングレード権。お客様は、製造業者またはインストール業者から Windows の Professional バージョンがプレインストールされているデバイスを取得した場合、Windows 8.1 Pro または Windows 7 Professional バージョンを使用できますが、マイクロソフトが、(aka.ms/windowslifecycle) に規定されているとおり、かかる旧バージョンのサポートを提供している期間に限ります。

とあります。Windows 7のサポート期間は2020年1月に終了するので、この規定によるとWindows 7 ProfessionalがプレインストールされたPCを購入した場合、Windows 7 Professionalに使用は2020年1月までとなるように解釈できます。

https://www.microsoft.com/en-us/Useterms/OEM/Windows/10/UseTerms_OEM_Windows_10_Japanese.htm

NECのサイトでは、

Windows 10 Pro のダウングレード権により、Windows 7 Professional または Windows 8.1 Pro がプレインストールされたデバイスを利用の場合、Windows 7 Professional, Windows 8.1 Pro のサポートを提供している期間に限り利用が認められます。サポート期間が終了後は、デバイスにライセンスされている Windows 10 Pro に置き換えてご利用いただくことが必要となります。サポート期間終了後に引き続き Windows 7 Professional, Windows 8.1 Pro をご利用希望される際には、Microsoft ボリュームライセンスを通じて、ライセンスを別途取得いただく必要がございます。

との記載があり、ライセンスを別途取得(どのライセンスなのかは明記なし)する必要があるようです。

Windows 10 Proダウングレード権のご案内 :サポート情報: ビジネスPC | NEC

Windows Embedded Standard 7の寿命

Windows Embedded Standard 7は、メインストリームサポートが2015年10月に終了しており、延長サポートは2020年10月に終了の予定です。

Windows OSまわりのあれこれ

Windows 10 October 2018 Update(1809)を適用する

自宅PCの1台(デスクトップPC)はWindows 10 OSですが、なかなか バージョン1809のアップデートが来ません。設定から更新とセキュリティでWindows Updateを手動で実行しても1809は出てきませんでした。

このPCにブロックとなるハードウェアやソフトウェアはないはずなのですが・・・。

 

そこで、手動でアップデートを実行すべく、Windows 10 のダウンロード ページから[今すぐアップデート]でアップデートツールをダウンロードし実行してみました。

 

すると、1809のダウンロードが終わり、再起動された後も1803のままで、1809になりませんでした。再起動は通常何回か繰り返されるのですが、今回は1回だけで、OSの更新が走っていませんでした。

 

類似現象がないか調べていくと、次のブログに類似現象が書かれていました。

win.just4fun.bizこのPCもVisual Studioをインストールしているので、このブログの処置に倣ってWindows開発者モードを外して再度アップデートツールを実行したところ、今度は無事に更新が開始されました。

 

次は、再起動後の更新が82%でちっとも進まなくなりました。1時間経過しても、2時間経過しても82%のままです。ディスクアクセスランプは時折ちかっと光ります。しかし、過去にWindows 8.1から10にアップデートしたときも確か数時間かかっていたことを思い出し、ここは我慢で待つことにします。買い物に出かけて戻ってきたら、更新が終わっていました。4時間ほどかかったことになります。

 

ということで、

 

  • Visual Studioをインストールし、Windows開発者モードが有効になっていると、October 2018 Update(1809)に更新ができない。
  • 1809への更新時、途中でずっと止まったような状況になっても、2時間程度であきらめずに待つ。

が肝要でした。

 

しかし、うちにあるノートPC(Visual Studioは入っていない)も、October 2018 Updateが未だに来ていません。

こっちは、Intel CPUでCPU内蔵のグラフィックスの構成です。この場合、IntelのHD Graphicsドライバーが適用されるのですが、これがブロックに該当している可能性が高いようです。

ただ、最新のHD Graphicsドライバーに更新しようとしても、ノートPCメーカーからドライバーを入手せよとエラーメッセージが表示されて更新できません。ノートPCメーカーのサイトには現在使用中のドライバより新しいものは用意されていません。

 

こちらは引き続き調査していきます。

 

ネットワークドライブ上のファイルをUAC昇格ありの場所へコピーしようとしたらエラー

ファイルエクスプローラーで、ネットワークドライブ上のファイルを、UAC昇格が発生するC:\Program Files下のディレクトリへコピーしようとしたら、

「H:\ は利用できません。・・・(中略)。ネットワーク上の場所を指している場合は、ネットワークやインターネットに接続されているかどうかを確認してから・・・(後略)」

とエラーメッセージが表示されます。ネットワークドライブ上のコピー対象ファイルは見える状態です。

これは、UAC昇格前のユーザーではネットワークドライブがマップされていますが、UACで昇格後の管理者ではネットワークドライブがマップされていないために発生しているようです。

 

具体的に理解するため、コマンドプロンプトをふつうに(管理者権限ではなく)立ち上げ、net useコマンドでネットワークドライブの接続状況を表示させます。

 

C:\Users\torutk> net use
新しい接続は記憶されます。
ステータス    ローカル名    リモート名                  ネットワーク名
-------------------------------------------------------------------------------
OK            H:            \\fileserver\sharefolder    Microsoft Windows Network

コマンドは正常に終了しました。

 

次に、コマンドプロンプトを管理者権限で立ち上げ、net useコマンドでネットワークドライブの接続状況を表示させます。

 

C:\WINDOWS\system32>net use
新しい接続は記憶されます。

一覧にエントリが存在しません。

 

このため、ファイルエクスプローラーでネットワークドライブにあるファイルをローカルの管理者権限が必要なフォルダにコピーしようとし、UAC昇格が行われると、ネットワークドライブが見えなくなってしまい、そのファイルが参照できなくなってしまいます。

 

対処の1つは、ファイルコピー時はネットワークドライブをドライブレター付きのパスでアクセスするのではなく、UNC形式でアクセスします。

別な方法の1つは、昇格した管理者アカウントでも同じドライブ名で同じネットワークドライブに接続するよう設定しておきます。

 

 

JavaFXアプリケーションのJDK11対応(Windowsインストーラー作成編)

はじめに

JDK 10までは、JavaFXアプリケーションを配布する際に、ネイティブ・インストーラー形式にするjavapackager機能を使ってOS固有のインストーラーを作成することができました。Windowsネイティブなインストーラーを作成する際には、外部ツールとしてWiX Toolsetを使ってMSI形式のインストーラーを作成するか、同じく外部ツールとしてInnoSetupを使ってEXE形式のインストーラーを作成することができました。

しかし、JDK 11ではJavaFXとともにjavapackager機能が分離したため、JDK11上でのJavaFXアプリケーションは独自にインストーラーを作成することになります。

今回は、次のJavaFXアプリケーションをWindows OS上へインストールするMSI形式のインストーラーを作成していきます。gitのブランチは、jdk11 となります。masterは、JDK 8版のままです。

GitHub - torutk/AnalogClockGadget: JavaFX analog clock program to alternate for a desktop analog clock gadget

環境

項目 内容
OS Windows 10 64bit
JDK OpenJDK 11.0.1
インストーラー作成ツール WiX Toolset 3.10

WiX Toolsetの概要とインストール方法は、次のWikiページに記載しています。

http://www.torutk.com/projects/swe/wiki/WiX

JavaFXアプリケーションの実行イメージ作成

インストーラーを作成する前に、JavaFXアプリケーションの実行イメージをjlinkコマンドを使って作成しておきます。

D:\work\AnalogClockGadget> jlink ^
 --module-path "C:\Program Files\Java\JavaFX\javafx-jmods-11.0.1";build\modules ^
 --add-modules com.torutk.gadget.analogclock ^
 --launcher analogclock=com.torutk.gadget.analogclock/com.torutk.gadget.analogclock.AnalogClockApp ^
 --compress=2 --no-header-files --output runtime

D:\work\AnalogClockGadget> dir /w runtime
[.]       [..]      [bin]     [conf]    [legal]   [lib]     release

D:\work\AnalogClockGadget> 
  • JavaFXライブラリ(JMODモジュールファイル)と、ビルドしたアプリケーション(モジュール)を--module-pathオプションで指定します。
  • モジュール依存の基点となるJavaFXアプリケーション(モジュール)を--add-modulesオプションで指定します。
  • JavaFXアプリケーション実行用スクリプト/バッチファイルを生成するため、--launcherオプションでモジュールとエントリポイントクラス名を指定します。ここではスクリプト/バッチファイルの名称をanalogclockとし、モジュール名/エントリポイントクラス名を指定します。
  • 生成する実行イメージの大きさを小さくするため、--compressオプションでZIP圧縮(=2)を指定します。
  • 実行イメージにはJNI用のC/C++ヘッダーファイルを含まないよう--no-header-filesオプションを指定します。

生成されたruntimeディレクトリのbin\analogclock.bat を実行し、アプリケーションが起動することを確認します。

インストーラーの作成(第一歩)

WiX Toolsetを使って、JavaFXアプリケーションのインストーラーを作成します。まずは、最低限の機能だけを持つインストーラーを作成します。

インストーラー作成では、WiXXML文書を記述します。今回は、インストールする内容、インストール先ディレクトリを定義するAnalogClock.wxsファイルと、インストールするファイル群をheatコマンドで生成したruntime.wxsファイルを用意します。

WiXXMLドキュメント(AnalogClock.wxs)

AnalogClock.wsx(左端の▼印をクリックすると内容を表示/非表示)

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
  <Product Id="*" Name="Analog Clock" 
           Language="1041" Codepage="932" Version="0.4.1"
           Manufacturer="High Bridge" 
           UpgradeCode="fe287b25-e03d-4744-90f4-96b05dc36bf0">
    <Package Description="Analog Clock Gadget"
             Comments="(c) 2019 High Bridge"
             InstallerVersion="200" Compressed="yes"
             InstallScope="perMachine" />
    <MajorUpgrade DowngradeErrorMessage="既に新しい [ProductName]がインストールされています。"/>
    <MediaTemplate EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFiles64Folder">
    <Directory Id="CompanyFolder" Name="High Bridge">
      <Directory Id="ApplicationFolder" Name="AnalogClock" />
    </Directory>
      </Directory>
    </Directory>
    
    <Feature Id="Product" Title="Analog Clock" Level="1">
      <ComponentGroupRef Id="RuntimeGroup" />
    </Feature>
  </Product>
</Wix>

Wix要素

WiXXML文書構造は、最上位の要素がWixで、Wix要素の属性で名前空間を指定しています。

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    :
</Wix>
Product要素

Wixの子要素にProduct要素を記述し、アプリケーション名などを定義します。

  <Product Id="*" Name="Analog Clock" 
           Language="1041" Codepage="932" Version="0.4.1"
           Manufacturer="High Bridge" 
           UpgradeCode="fe287b25-e03d-4744-90f4-96b05dc36bf0">
    :
    </Product>
  • Id属性にはGUIDを指定しますが、"*"を指定するとコンパイル時にGUIDが自動生成されます。インストーラーはこのGUIDが等しいと同じプログラムと認識します。新しいバージョンなどインストーラーを作り直した際はこのGUIDを変更する必要があります。
  • Name属性にはアプリケーション名を定義します。
  • Version属性にはアプリケーションのバージョン番号を定義します。通常ピリオドで区切った4つの数値を指定します。ただしMSIインストーラーはバージョンアップ判定時に4つ目の数字を無視することに留意が必要です。
  • Language属性とCodepage属性には、インストーラーが使用するロケール文字コードを定義します。日本語の場合は、Language属性に1041、Copdepage属性に932を指定します。
  • Manufacturer属性には開発元の組織名または開発者名を定義します。
  • UpgradeCode属性には同じアプリケーションで新しいバージョンを更新インストールする際の識別に使うGUIDを定義します。このGUIDは後々新しいバージョンのインストーラーを作成するときに継続して使用します。GUIDを生成する方法については、Windowsデスクトップ環境でUUID(GUID)を生成する方法 - torutkのブログ や、 GUIDの生成 に記載しています。
Package要素

Productの子要素にPackage要素を記述します。

        <Package Description="Analog Clock Gadget"
                 Comments="(c) 2019 High Bridge"
                 InstallerVersion="200" Compressed="yes"
                 InstallScope="perMachine"/>
  • Description属性とComments属性は、プログラムの説明、コメントを記載します。インストーラーファイルのプロパティ(詳細)で説明に表示されます。
  • InstallerVersion属性はMSIインストーラー(msiexec.exe)のバージョンを指定します。MSIのバージョンに2.0を指定する場合、"200"とします。現時点では4.5を指定("405")してもいいかと思います。MSI 4.5は、Windows Vista SP2、Windows Server 2008 SP2に標準搭載されています。
  • InstallScope属性は、全てのユーザーが使用できるperMachineか、インストールしたユーザーだけが使用できるperUserかを指定します。
  • Platform属性は、インストールするパッケージが64bit用であればx64を指定します。この属性でx64を指定すると、Component要素全てにWin64属性の指定が必要になります。そこで、64bitプログラムをインストールする場合は、この属性ではなく、candleコマンドの-archオプションでx64を指定します。
MajorUpgrade要素

新しいバージョンへ更新する仕組みを入れます。

<MajorUpgrade DowngradeErrorMessage="既に新しい [ProductName]がインストールされています。"/>

DowngradeErrorMessage属性には、既にインストールされたバージョンより古いバージョンをインストールしようとしたときにエラーメッセージとして表示する内容を定義します。

MediaTemplate要素

MSIファイルの中にCABファイルを含める場合に指定します(ふつう含めるので)。

        <MediaTemplate EmbedCab="yes" />
Directory要素

インストーラーでインストールする先のディレクトリ構造を定義します。 今回は、次のディレクトリにインストールするものとします。

C:\
  +-- Program Filse
        +-- High Bridge
              +-- AnalogClock

このディレクトリ構造に対応するDirectory要素の定義は次となります。

      <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFiles64Folder">
                <Directory Id="CompanyFolder" Name="High Bridge">
                    <Directory Id="ApplicationFolder" Name="AnalogClock" />
                </Directory>
            </Directory>
      </Directory>
  • ルートディレクトリ(ドライブ)は、Id属性にTARGETDIRを指定します。
  • 64bitアプリがインストールされるシステム共通のディレクトリは C:\Program Files ですが、これは事前定義名ProgramFiles64FolderをId属性に指定します。
  • C:\Program Filesの下に作成するディレクトリを指定します。
Feature要素
    <Feature Id="Product" Title="Analog Clock" Level="1">
      <ComponentGroupRef Id="RuntimeGroup" />
    </Feature>

Feature要素ではインストールする内容を定義します。今回は、インストールするファイル群をWiX Toolsetのheatコマンドで自動生成させるので、heatコマンドで作成するComponentGroupのIdを、ComponentGroupRefで参照する記述とします。なお、ComponentGroupのIdはheatコマンドのオプションで指定します。

WiX Toolsetコマンドの実行

heatコマンドでruntimeディレクトリ以下のファイル群から定義作成
D:\work\AnalogClockGadget> heat dir runtime ^
 -srd -dr ApplicationFolder ^
 -cg RuntimeGroup ^
 -gg -g1 ^
 -sfrag -sreg ^
 -var "var.runtimeFolder" -o runtime.wxs
  • 第1引数で収集対象をディレクトリ(dir)と指定します。
  • -srdオプションでルートディレクトリ(ここではdirオプションに続けて指定したruntimeディレクトリ)に対応するディレクトリの生成を抑制します。このオプションを指定しないと、インストール先ディレクトリ(C:\Program Files\High Bridge\AnalogClock)の下にruntimeディレクトリが作成され、その下にbinディレクトリ等が配置されます。 -drオプションで、インストール先ディレクトリのIdを指定します。今回はAnalogClock.wxs でAnalogClockプログラムをインストールするディレクトリとして定義したDirectory要素のIdに指定したApplicationFolderを指定しています。
  • -cgオプションで、生成するComonentGroupのIdを指定します。このIdは、AnalogClock.wxsのFeature要素の子要素ComponentGroupRefで参照されるので一致させる必要があります。
  • -ggオプションで各ComponentのGUIDを生成するよう指定します。
  • -ggオプションでGUIDを生成する際、波括弧を省略するよう指定します。
  • -sfragオプションでFragment要素をComponent要素毎ではなく、ComponentGroup要素に1つ生成するよう指定します。
  • -sregオプションで、DLLファイルに対するレジストリ情報収集(COMのDLLで必要)を抑制するよう指定します。64bit DLLのときにHEAT5150警告が出るのを抑制します。
  • -varオプションで、File要素のSource属性で指定するパスの基点となるフォルダを、変数として生成するよう指定します。このオプションを指定しないと、SourceDirという固定文字列がパスの基点となり、存在しないので後のlightコマンド実行時にエラーとなります。
  • -oオプションでディレクトリ以下のファイル群を収集した情報を吐き出すWiXファイル名を指定します。
candleコマンド

WiX文書ファイルをcandleコマンドでコンパイルし、wixobjファイル(中間ファイル)を生成します。

D:\work\AnalogClockGadget> candle -arch x64 package\windows\AnalogClock.wxs

D:\work\AnalogClockGadget> candle -arch x64 -druntimeFolder=runtime runtime.wxs
  • 64bitバイナリをインストールする場合は、-archオプションでx64を指定します。
  • -dオプションで、heatコマンドで、File要素のSource属性でファイルパスの基点を変数として生成した箇所に実際のパスを設定します。

実行結果、AnalogClock.wixobj と runtime.wixobjファイルが生成されます。

lightコマンド

Wix中間ファイルであるwixobjファイルから、lightコマンドでインストーラーファイルに生成します。

D:\work\AnalogClockGadget> light AnalogClock.wixobj runtime.wixobj -o analogclock.msi
  • 引数に、candleコマンドで生成したwixobjファイル(中間ファイル)をすべて指定します。
  • -oオプションで作成するMSI形式インストーラーのファイル名を指定します。

実行結果、analogclock.msiファイルが生成されます。ちなみに、ファイルサイズは38MBでした。

インストール

この第一歩のインストーラーは、必要最小限の機能しか設定していないので、インストーラーを実行すると対話(ダイアログ)画面もなく、所定のディレクトリにインストールするだけとなります。スタートメニューやデスクトップへのショートカット登録もありません。

analogclock.msiを実行すると、次のように経過を表示する画面が出てすぐに終了します。

f:id:torutk:20190103133822p:plain:w400
インストーラー(最初の一歩)実行画面(1)

f:id:torutk:20190103133853p:plain:w400
インストーラー(最初の一歩)実行画面(2)

プログラムを実行するには、インストール先のC:\Program Files\High Bridge\AnalogClock\bin\analogclock.batを実行します。

インストーラーの作成(第二歩)

第一歩で作成したインストーラーは、所定のディレクトリへプログラムをインストールするだけのものでした。 次は、スタートメニューのプログラム一覧にインストールしたJavaFXアプリケーションを起動するショートカットを追加する機能を盛り込みます。

Directory要素にスタートメニュー(プログラムメニュー)とサブフォルダ定義を追加

          <Directory Id="ApplicationFolder" Name="AnalogClock" />
        </Directory>
       </Directory>
+      <Directory Id="ProgramMenuFolder">
+          <Directory Id="ApplicationProgramsFolder" Name="High Bridge" />
+      </Directory>
     </Directory>

Directoryのルート要素の子要素に、Id属性でProgramMenuFolderを指定するDirectory要素を追加します。ProgramMenuFolderは事前定義名で、全ユーザー用のスタートメニューのプログラムディレクトリを示します。(C:\ProgramData\Microsoft\Windows\Start Menu\Programs)

そして、その下にアプリケーションのショートカットを収容するサブフォルダのDirectory要素を追加します。任意のId属性と作成するフォルダ名をName属性に指定します。

ショートカット要素を定義

プログラムを実行するショートカットを定義します。 DirectoryRef要素でショートカットを作成するフォルダを指定し、その子要素にComponent要素で囲ってShortcut要素を定義します。また、アンインストール用のRemoveFolder要素、Shortcut要素のKeyPath代替としてRegistryValue要素を定義します。

    <DirectoryRef Id="ApplicationProgramsFolder">
      <Component Id="ApplicationShortcut" Guid="*">
        <Shortcut Id="ApplicationStartMenuShortcut"
                  Icon="java.exe"
                  Name="Analog Clock Gadget"
                  Show="minimized"
                  Target="[ApplicationFolder]\bin\analogclock.bat"
                  WorkingDirectory="ApplicationFolder"/>
        <RemoveFolder Id="CleanUpShortcut"
                      Directory="ApplicationProgramsFolder"
                      On="uninstall" />
        <RegistryValue Root="HKCU"
                       Key="Software\[Manufacturer]\[ProductName]"
                       Name="installed" Type="integer" Value="1"
                       KeyPath="yes"/>
      </Component>
    </DirectoryRef>

    <Icon Id="java.exe" SourceFile="runtime\bin\java.exe" />
  • DirectoryRef要素のId属性は、先に定義したショートカットを収容するフォルダのId属性を参照
  • Comonent要素のId属性は、後でFeature要素の子要素に追加するComponentRefで参照する名前を指定
  • Shortcut要素
    • Icon属性でショートカットのアイコンを指定(アイコンをリソースに持つexeファイル等も指定可能)、icon要素のId属性を参照します。
    • Name属性はスタートメニューに表示されるショートカット名となるのでわかりやすい名前を指定
    • Show属性は、バッチファイルのショートカットを作成した場合に実行時にコマンドプロンプトが表示されるのを抑制するためminimizedを指定
    • Target属性はショートカットの対象ファイルを指定。Directory要素のIdで定義した名前を利用することができる
    • WorkingDirectory属性でショートカット実行時のカレントディレクトリを指定
  • RemoveFolder要素は、アンインストール時にショートカットフォルダを削除するための定義をしています。
  • RegistryValue要素は、Shortcut要素がKeyPath属性を持てない制約を解決するために、レジストリを使用します。
  • Icon要素は、Shortcut要素のIcon属性から参照され、ショートカットのアイコンを規定します。Idは対象ファイルと同じ拡張子を持つ必要があります。

Feature要素に追加

     <Feature Id="Product" Title="Analog Clock" Level="1">
       <ComponentGroupRef Id="RuntimeGroup" />
+      <ComponentRef Id="ApplicationShortcut" />
     </Feature>
  • Feature要素の子要素に、先ほどショートカット定義で作成したComonent要素のIdを参照するComponentRef要素を追加します。

インストールするとスタートメニューにショートカットが生成

f:id:torutk:20190104171628p:plain:w400
スタートメニューにショートカット

インストーラーの作成(第三歩)

第一歩、第二歩で作成したインストーラーは、インストールを実行すると一瞬ダイアログが表示されますがすぐに消えてインストールが完了してしまいます。

そこで、普通のインストーラーのように、インストール画面を表示し、ユーザーが操作するとインストールが進むようにします。

WiXでは、お仕着せの対話ダイアログがいくつか用意されているので、その中からニーズに合うものを選択することもできますし、カスタムの対話ダイアログを作ることもできます。今回第三歩では、お仕着せの対話ダイアログから最小限の対話を行うWixUI_Minimalを使用します。このWixUI_Minimalは、インストール時にライセンス表示を行い、ユーザーが確認をするとインストールが開始されます。

WiX定義にUIを追加

     </Feature>
+
+    <UIRef Id="WixUI_Minimal" />
+    <WixVariable Id="WixUILicenseRtf" Value="License.rtf" />
+
   </Product>

Product要素の子要素にUIRef要素を追加し、Id属性に事前定義済みWixUI_Minimalを指定します。

同じく、Product要素の子要素にWixVariable要素を追加し、Id属性に事前定義済みWixUILicenseRtfを指定、Value属性にRTF(Rich Text Format)形式で用意したライセンス許諾ファイルを指定します。アプリケーションのライセンスを記述したファイルをRTF形式で保存します。RTF形式は、Windows標準ツールのWordpadで作成可能です。

lightコマンドのオプション追加

- light AnalogClock.wixobj runtime.wixobj -o analogclock.msi
+ light AnalogClock.wixobj runtime.wixobj -o analogclock.msi -ext WixUIExtension

インストール実施

MSI形式のインストーラーファイル(第三歩版)を実行すると、ライセンス画面が表示されます。

f:id:torutk:20190104185259p:plain:w400
第3歩のインストーラー画面(1)

ライセンスを承諾すると、インストールが開始します。

f:id:torutk:20190104185329p:plain:w400
第3歩のインストーラー画面(2)

インストールが完了すると、インストール完了画面が表示されます。

f:id:torutk:20190104185359p:plain:w400
第3歩のインストーラー画面(3)

インストーラーの作成(第四歩)

こ個までの段階では、インストール先が固定となっています。今回は、インストール先を変更するダイアログを追加します。

WiX定義を変更(問題あり)

第三歩で追加した対話ダイアログ種類は、ライセンス表示とインストール結果表示を持つWixUI_Minimalでした。これを、インストール先変更表示を持つWixUI_InstallDirに変更します。 このWixUI_InstallDirを使うときは、アプリケーションをインストールするディレクトリを定義したDirectory要素のIdを、プロパティWIXUI_INSTALLDIRに指定する必要があります。

     </Feature>

-    <UIRef Id="WixUI_Minimal" />
+    <Property Id="WIXUI_INSTALLDIR" Value="ApplicationFolder" />
+    <UIRef Id="WixUI_InstallDir" />
     <WixVariable Id="WixUILicenseRtf" Value="License.rtf" />

インストール実施(問題あり)

インストールを実施すると次の画面が表示されます。

f:id:torutk:20190104213442p:plain:w400
第4歩のインストーラー画面(1)

ライセンス表示画面が表示されます。

f:id:torutk:20190104213515p:plain:w400
第4歩のインストーラー画面(2)

デフォルトのインストール先が表示されます。

f:id:torutk:20190104213547p:plain:w400
第4歩のインストーラー画面(3)

[Change]ボタンを押すと、ディレクトリ選択画面が表示されます。

f:id:torutk:20190104213610p:plain:w400
第4歩のインストーラー画面(4)

インストール準備完了の画面が表示されます。

f:id:torutk:20190104213632p:plain:w400
第4歩のインストーラー画面(5)

インストールの経過を示す画面が表示されます。

f:id:torutk:20190104213651p:plain:w400
第4歩のインストーラー画面(6)

インストールが完了した画面が表示されます。

f:id:torutk:20190104213712p:plain:w400
第4歩のインストーラー画面(7)

問題とは?

インストーラーでインストール先を変更したにも関わらず、インストール先はデフォルトのディレクトリとなってしまいました。

いろいろ調べてみたところ、WIXUI_INSTALLDIRプロパティで指定するディレクトリのIdの名前はすべて大文字とする必要があるということが判明しました。

WiX定義を変更(問題解決)

AnalogClock.wxsの変更箇所
     <Directory Id="TARGETDIR" Name="SourceDir">
       <Directory Id="ProgramFiles64Folder">
        <Directory Id="CompanyFolder" Name="High Bridge">
-         <Directory Id="ApplicationFolder" Name="AnalogClock" />
+         <Directory Id="APPLICATIONFOLDER" Name="AnalogClock" />
        </Directory>
       </Directory>
                   Show="minimized"
-                  Target="[ApplicationFolder]\bin\analogclock.bat"
-                  WorkingDirectory="ApplicationFolder"/>
+                  Target="[APPLICATIONFOLDER]\bin\analogclock.bat"
+                  WorkingDirectory="APPLICATIONFOLDER"/>
         <RemoveFolder Id="CleanUpShortcut"
     <UIRef Id="WixUI_InstallDir" />
+   <Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONFOLDER" />
     <WixVariable Id="WixUILicenseRtf" Value="License.rtf" />
heatコマンド実行オプションの変更
 D:\work\AnalogClockGadget> heat dir runtime ^
- -srd -dr ApplicationFolder ^
+ -srd -dr APPLICATIONFOLDER ^
  -cg RuntimeGroup ^
  -gg -g1 ^
  -sfrag -sreg ^
  -var "var.runtimeFolder" -o runtime.wxs

インストール実施(問題解決)

インストーラーの画面に変更はありませんが、インストール先が無事変更できるようになりました。

jlinkで生成するランチャー(バッチファイル)の修正

jlink で--launcherオプションを指定すると、次のバッチファイルが生成されます。

@echo off
set JLINK_VM_OPTIONS=
set DIR=%~dp0
"%DIR%\java" %JLINK_VM_OPTIONS% -m com.torutk.gadget.analogclock/com.torutk.gadget.calendar.AnalogClockApp %*

このバッチファイルに2つの修正を加えます。

  1. 実行時にコマンドプロンプトが表示されたままとならないよう起動コマンドをjava.exeからjavaw.exeに変更
  2. CPU・メモリにやさしいプロセスとするべくコマンドラインオプションを追加

起動コマンドをjava.exeからjavaw.exeに変更

javaコマンドはコンソール(コマンドプロンプト)を伴うので、このランチャー(バッチファイル)を実行すると画面にコマンドプロンプトが表示されたままとなります。ショートカットでコマンドプロンプトを最小化する設定をしていますが、タスクバーにはコマンドプロンプトが残ったままとなります。

そこで、バッチファイルの中でjavaコマンドをjavawコマンドに修正し、コマンドプロンプトが残らないようにします。

  @echo off
  set JLINK_VM_OPTIONS=
  set DIR=%~dp0
- "%DIR%\java" %JLINK_VM_OPTIONS% -m com.torutk.gadget.analogclock/com.torutk.gadget.calendar.AnalogClockApp %*
+ start "" /b "%DIR%\javaw" %JLINK_VM_OPTIONS% -m com.torutk.gadget.analogclock/com.torutk.gadget.calendar.AnalogClockApp %*
  • startコマンドでjavawコマンドを起動します。最初の""がないと、startコマンドが"%DIR%javaw" をウィンドウタイトルとして扱い、エラーとなります。

CPU・メモリにやさしいコマンドラインオプションを追加

64bit版のjava(javaw)コマンドはデフォルトではCPU、メモリをがんがん使う設定となっています。 そこで、必要最低限のCPUとメモリ使用にすべく、JavaVMオプションを追加指定します。

  @echo off
-  set JLINK_VM_OPTIONS=
+ set JLINK_VM_OPTIONS=-Xms32m -Xmx64m -Xss256k ^
+ -XX:TieredStopAtLevel=1 -XX:CICompilerCount=2 -XX:CompileThreshold=1500 ^
+ -XX:InitialCodeCacheSize=160k -XX:ReservedCodeCacheSize=32m ^
+ -XX:MetaspaceSize=12m -XX:+UseSerialGC
  set DIR=%~dp0
  start "" /b "%DIR%\javaw" %JLINK_VM_OPTIONS% ^
  -m com.torutk.gadget.analogclock/com.torutk.gadget.analogclock.AnalogClockApp

オプションの意味・値などは次に簡単なまとめ記載 JDK 9 JavaVMオプション - ソフトウェアエンジニアリング - Torutk

まとめ

JavaFXアプリケーションをJDK11&モジュール(JPMS)対応した後に、Windowsインストーラーを作ってJava実行環境を含めて配布できるようにしました。

  • 作業環境は、Windows 10、OpenJDK 11.0.1、OpenJFX 11.0.1、WiX Toolset 3.10
  • jlink コマンドで、JavaFXアプリケーションとその実行に必要なモジュールを抽出して実行環境のイメージを作成
  • WiX Toolset で実行環境のイメージからMSI形式インストーラーを作成
    • AnalogClock.wxs ファイル(XMLドキュメント)を記述し、アプリケーションの情報、インストール先ディレクトリ、スタートメニューのショートカット、インストーラーのGUI種類などを定義
    • heatコマンドでjlinkが生成した実行環境のディレクトリ・ファイル情報を収集し runtime.wxsファイル(XMLドキュメント)を生成
    • candleコマンドで、2つのWiX XMLドキュメント(AnalogClock.wxs、runtime.wxs)をコンパイル
    • lightコマンドで、MSI形式インストーラーを生成
  • ランチャー(バッチファイル)にオプション追記

MSIインストーラーのサイズは約38MB、インストール後のサイズは約52MBとなりました。

Java読書会BOF 満20年

今月12月22日(土)に、Java読書会BOF主催の「Effective Java 第3版」を読む会(第1回)を開催しました。

Effective Java 第3版

Effective Java 第3版

Java読書会は、1998年12月にJava互助会の有志でJavaの技術書の読書会を実施したのに端を発し、その後Java読書会BOFとして読書会を毎月1回開催する団体として現在に至るまで活動を続けてきました。

そして、この2018年12月で満20年を迎えました。Java読書会の12月時点での開催データは次の通りです。

項目 データ
通算回数 237回
書籍数 37冊
総ページ数 14,089ページ
平均ページ数 59.4ページ/回
平均参加人数 11.5人/回
最大参加人数 30人
最小参加人数 2人

一つのプログラミング言語で20年間もよく読書会が継続できたものだなぁと思います。オブジェクト指向プログラミングから、言語仕様、仮想マシン、並列処理、ネットワーク、セキュリティ、ユニットテスト、Webアプリケーション、ビッグデータ機械学習Android、リアクティブプログラミング、関数型プログラミングJVM言語のScalaとKotlinなど、Javaが登場してからこの20年間でソフトウェアの世界にもいろいろな技術が登場してきましたが、JavaというOSをまたいだ汎用プログラミング言語がそれら技術と関わりを持ってきたことになります。Javaを積極的に使い勉強してきたことで、幅広くいろいろな技術を学ぶことができたのだと感じています。

2018年は、Javaに大きな変化が訪れた年でもありますが、未来に向けた変化であると思います。来年も、Java読書会BOFは継続して読書会を実施していきますので、よろしくお願いいたします。

Java読書会BOF代表 高橋 徹