Quantcast
Channel: torutkのブログ
Viewing all 442 articles
Browse latest View live

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

$
0
0

はじめに

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

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

今回は、次のJavaFXアプリケーションをWindows OS上へインストールするMSI形式のインストーラーを作成していきます。

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 ^
 -sdr -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
インストーラー(最初の一歩)実行画面(1)

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

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


Windows OSまわりのあれこれ

$
0
0

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    MicrosoftWindows Network

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

 

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

 

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

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

 

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

 

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

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

 

 

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

$
0
0

お題

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月に終了の予定です。

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

$
0
0

動機

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"の部分が置き換えられてしまいました。

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

$
0
0

Java SE 12がリリース

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

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

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

JavaFXとOpenJDK

Oracleは、Java SE 11以降OracleJDKOracleがビルドする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のクラスを利用したアプリケーションを記述できるようになります。

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

$
0
0

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) メソッドが追加され、入力ストリームをそのまま出力ストリームに流し込むことが簡単にできるようになりました。

publicstaticvoid 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命令を使ったさらに高速なコピーが行われています。

JDKのアップデート(2019年4月)

$
0
0

Java SE Development (JDK) の2019年4月アップデート

JDKは、現在年に2回のバージョンアップと年に4回のセキュリティとバグ修正のアップデートが行われています。バージョンアップの時期は3月と9月、アップデートは1月、4月、7月、および10月です。

そして、この4月のアップデートでは和暦対応(来月から元号が「令和」となる対応)も含まれています。

JDKのバイナリは現在、Oracleから提供されるOracleJDKとOpenJDKの他、AdoptOpenJDK、Zulu、Liberica、Red HatAmazon Correttoなどがあります。

OracleJDK

OracleJDKは、次のアップデートが公開されています。

OracleJDKJava SE 8は、2019年1月で無償提供するアップデートは終了となっていますが、今回は OracleJava SE OTNライセンス(Oracle Technology License Agreement for Java SE)の範囲で無償提供されました。(OTNライセンスでは、個人利用・開発用途で無償利用可)

OracleJDKJava SE 11 はLTS(長期サポート版)なのでJava SE 12がリリースされた後も更新版が提供されています。一方、非LTSのJava SE 9、Java SE 10はアップデートは提供されていません。Java SE 11は商用ライセンス(Java SE Subscription)か若しくはOTNライセンスでの提供です。

OTNのダウンロードサイトからOracleJDKJava SE 11.0.3をダウンロードしようとすると、「Oracleプロファイルへのサインイン」が要求されました。Java SE 12.0.1のダウンロードでは要求されませんでした。無償で登録できますが、OTNアカウントと同じ模様です。

OracleJDKJava SE 12 は非LTSで、次のJava SE 13がリリースされるまでの期間アップデートが提供される予定です。

Oracle OpenJDK

Oracleが無償提供するOpenJDKは、最新バージョンのJava SE 12のアップデートが提供されています。

AdoptOpenJDK

AdoptOpenJDKファウンデーションが無償提供するOpenJDKは、4月20日時点で次のアップデートが提供されています。

  • OpenJDK 8u212
  • OpenJDK 11.0.3

Zulu JDK

Azul Systemsが提供するOpenJDKは4月20日時点で次のアップデートが提供されています。

  • Zulu OpenJDK 7u222
  • Zulu OpenJDK 8u212
  • Zulu OpenJDK 11.0.3
  • Zulu OpenJDK 12.0.1

ZuluFX JDK

Azul Systemsが提供するOpenJDK(JavaFX同梱版)は、4月20日時点ではまだアップデートが提供されていません。前回(2019年1月のアップデート)も少し遅くリリースされたので、推測では1か月以内にはリリースされるかと思われます。

Liberica JDK

BellSoft社が提供するOpenJDK(JavaFX同梱版)は、4月20日時点で次のアップデートが提供されています。

  • Liberica JDK 8u212
  • Liberica JDK 11.0.3
  • Liberica JDK 12.0.1

AWS Corretto

Amazonが提供するOpenJDKは、4月20日時点で次のアップデートが提供されています。

JDK 8のアップデートも近日リリースされると推測します。

OpenJDKのダウンロード先等の情報

次のWikiに、各種OpenJDKの情報をまとめています。
www.torutk.com

GPSの高度と標高

$
0
0

GPSの高度と標高

GPSを使った測位では、地球を回転楕円体(WGS84回転楕円体)としたときの緯度・経度および高度を取得します。

ここで、GPS測位で得られる高度は回転楕円体面からの距離として算出されるので、実際の標高(海面からの距離)とは異なります。回転楕円体の表面と海面(ジオイド面)とは場所によりおよそプラスマイナス100mの範囲で差異があります。

東京付近では、回転楕円体表面から40mほど高い位置にジオイド面があります。したがって、GPSの高度から40mを減算すると標高(日本の標高は、東京湾の平均海面を0mとしたときの高さ)となります。

ジオイド高は、国土地理院ジオイド高計算サイトで計算させることができます。
https://vldb.gsi.go.jp/sokuchi/surveycalc/geoid/calcgh/calcframe.html


さくらVPSのOS更新

$
0
0

さくらVPSのOSをCentOS 6からCentOS 7へ更新

さくらVPSのサーバーは利用開始時から長らくCentOS 6でしたが、このGWで時間が取れたのでやっとCentOS 7へ更新してみました。

OSの再インストールをするので、データのバックアップ、設定のバックアップを取ってから作業に着手します。Let's EncryptのSSL証明書(無料)を利用していたので、再インストール時には、スタートアップスクリプトとして用意されているLetsEncryptを追加しています。

とくに問題なくCentOS 7になりました。IPv6が無効なので有効にして、root以外の作業用アカウントを作成し、SSHポートを標準から変更し、rootのsshログインを無効にしました。

Redmineの稼働にむけて

Ruby 2.4のインストール

RedmineはOS更新前はバージョン3.4系で稼働していましたが、そろそろ4.0系も動かしたいところです。Redmine 4.0は、Ruby on Rails 5.2を用いているため、rubyのバージョンは2.2.2以降となります。しかし、CentOS 7の標準rubyは2.0のため、rubyを別途更新する必要があります。

rubyenvは開発者個人の環境で使うのはまだしも本番環境で使うには気持ち悪いので、以前は自前でRPMビルドしていました。

今回は手元にCentOS 7環境がないので、他の手段を探してみたところ、Red Hat Software Collections というyumリポジトリRed Hatから提供されており、これを利用するのがよさそうです。

$ sudo yum install centos-release-scl-rh 

rubyについては、rh-ruby22、rh-ruby23、rh-ruby24、rh-ruby25とrubyの新しバージョンが用意されています。今回は、Redmine 3.4が対応する最新のrh-ruby24を入れます。

$ sudo yum install rh-ruby24

Red Hat Software Collectionsパッケージは、/opt/rh以下にインストールされます。結構パスが深いのですが、パスを通すenableというファイルが提供されているので、これを/etc/profile.d下にリンクします。

$ sudo ln -s /opt/rh/rh-ruby24/enable /etc/profile.d/rh-ruby24.sh
ruby develのインストール
$ sudo yum --enablerepo=centos-sclo-rh install rh-ruby24-ruby-devel

rubygem bundlerのインストール
$ sudo yum --enablerepo=centos-sclo-rh install rh-ruby24-rubygem-bundler
rubygem rakeのインストール
$ sudo yum --enablerepo=centos-sclo-rh install rh-ruby24-rubygem-rake
  • メモ)rakeは事前インストール不要

Development Toolsのインストール

Redmineインストール手順では、yum groupinstallでDevelopment Toolsをインストールしますが、これが次の結果となりました。

$ sudo yum groupinstall "Development Tools"
Loaded plugins: fastestmirror, langpacks
There is no installed groups file.
Maybe run: yum groups mark convert (see man yum)
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * epel: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * updates: ftp.iij.ad.jp
Warning: Group development does not have any packages to install.
Maybe run: yum groups mark install (see man yum)
No packages in any requested group available to install or update

これは最初エラーかなと思いましたが、実は必要なパッケージはインストール済みであるときに出るようです。

MariaDBのインストール

最新のRedmineインストール手順(redmine.jp)は、データベースにPostgreSQLを使うものになっています。今回は、MySQLMariaDB)のダンプを使うので、MariaDBをインストールします。

$ sudo yum install mariadb-server mariadb-devel
  :

MySQL/MariaDBでutf8を指定した場合、UTF-8のうち4バイト文字を扱えません。例えば絵文字が該当します。そこで、UTF-8の4バイト文字を扱えるようにするにはutf8mb4を指定します。

/etc/my.cnf.d/server.cnf
[mysqld]
character-set-server = utf8mb4
/etc/my.cnf.d/mysql-clients.cnf
[mysql]
default-character-set = utf8mb4
show-warnings
MariaDB自動起動設定
$ sudo systemctl enable mariadb.service
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.
mysql_secure_installation実行

セキュアな初期設定を行うツール mysql_secure_installationを実施します。ツールを実行すると次の有無を尋ねられ、基本Yesを設定していきます。

  • MariaDBのrootユーザーのパスワードを設定
  • 匿名ユーザーを削除
  • MariaDBのrootユーザーのリモート接続禁止
  • testデータベースとそのアクセス設定を削除
redmineのユーザーアカウントとデータベース作成
$ mysql -uroot -p
MariaDB [(none)]> CREATE DATABASE redmine CHARACTER SET utf8mb4;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> CREATE USER 'redmine'@'localhost' IDENTIFIED BY '********';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';
Query OK, 0 rows affected (0.00 sec)

ImageMagickのインストール

$ sudo yum install ImageMagick ImageMagick-devel ipa-pgothic-fonts
  :

Redmine実行ユーザー作成

Redmineを実行する際、root権限ではなくredmine実行ユーザー権限とします。

$ sudo groupadd -g 1000 redmine
$ sudo useradd -u 1000 -g redmine redmine
$ sudo passwd redmine
  :

Redmineのダウンロード

$ sudo mkdir /var/lib/redmine-3.4-stable
$ sudo chwon redmine:redmine /var/lib/redmine-3.4-stable
$ su redmine
$ cd /var/lib
$ svn co http://svn.redmine.org/redmine/branches/3.4-stable redmine-3.4-stable
  : 

database.ymlの作成

production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: redmine
  password: "********"
  encoding: utf8mb4

configuration.ymlの作成

production:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: "localhost"
      port: 25


imagemagick_convert_command: /usr/bin/convert
rmagick_font_path: /usr/share/fonts/ipa-pgothic/ipagp.ttf

Redmineが依存するgemパッケージのインストール

$ bundle install --without development test --path vendor/bundle
  :

セッション秘密鍵生成

$ bundle exec rake generate_secret_token

バックアップしたデータベースを復元

$ mysql -uredmine -p redmine < /tmp/redmine_mysql_www.torutk.com-20190430T110540.dump
Enter password:********
データベースのマイグレート
$ RAILS_ENV=production bundle exec rake db:migrate

unicornのインストール

Gemfile.local新規作成
  • /var/lib/redmine-3.4-stable/Gemfile.local
gem "unicorn"
gemパッケージのunicornインストール
$ bundle update
unicorn.rb

動作確認
$ bundle exec unicorn_rails -c config/unicorn.rb -E production

エラー発生

I, [2019-04-30T21:28:36.880592 #27919]  INFO -- : Refreshing Gem list
ArgumentError: wrong number of arguments (given 0, expected 2)
  /var/lib/redmine-3.4-stable/vendor/bundle/ruby/2.4.0/gems/unicorn-5.5.0/lib/unicorn.rb:49:in `block in builder'

unicorn 5.5.0だと発生するとのこと。
stackoverflow.com

unicorn 5.4.1で止めおくのが回避策とあるので、

redmine-unicorn.service
[Unit]
Description=Redmine Unicorn Server
After=mariadb.service

[Service]
WorkingDirectory=/var/lib/redmine
Environment=RAILS_ENV=production
SyslogIdentifier=redmine-unicorn
PIDFile=/var/lib/redmine/tmp/pids/unicorn.pid

ExecStart=/opt/rh/rh-ruby24/root/bin/bundle exec "unicorn_rails -c config/unicorn.rb -E production"
ExecStop=/usr/bin/kill -QUIT $MAINPID
ExecReload=/bin/kill -USR2 $MAINPID

User=redmine
Group=redmine

[Install]
WantedBy=multi-user.target

実行するとエラー

$ sudo systemctl start redmine-unicorn.service

原因を調べるため、suコマンドでユーザーを切り替えてunicornを実行するとエラーとなりました。
エラー内容は

/opt/rh/rh-ruby24/root/usr/bin/ruby: error while loading shared libraries: libruby.so.2.4: cannot open shared object file: No such file or directory

suコマンドで(-なしに)ユーザーを切り替える場合は、profile.dの設定を読み込まないので、rubyのパスが参照できないためです。
これは、systemdで実行ユーザーを指定して実行する場合と同じと考えられます。

systemdのunitファイル記述では、EnvironmentFile指定が可能ですが、このファイル内では変数展開ができないため、先に設置した/etc/profile.d/rh-ruby24.shを指定することができません。

回避策として、実行コマンドをsclコマンドで指定します。

ExecStart=/usr/bin/scl enable rh-ruby24 -- bash -c 'bundle exec unicorn_rails -c config/unicorn.rb -E production'

Red Hat Software Collectionsのコマンドsclを使ってコマンドを実行すると、必要な環境設定が反映されます。

nginxとの連携

  • バックアップしていたredmine.confを/etc/nginx/conf.d/に配置
  • /etc/nginx/conf.d/default.confを削除、空のdefault.confに置き換え
  • /etc/nginx/conf.d/https.confを削除、空のhttpd.confに置き換え

nginxのアップデート時にdefault.confとhttps.confが生成されないよう、削除だけではなく空のファイルを置いておきます。

テーマのインストール

gitmike
$ cd /var/lib/redmine/public/themes
$ git clone git://github.com/makotokw/redmine-theme-gitmike.git gitmike

プラグインのインストール

$ cd /var/lib/redmine/plugins
Issue Template

現時点でmasterブランチはredmine 4.0以降のみ対応となっています。redmine 3.xで使うときは、ブランチ名にv0.2.x-support-Redmine3 を指定します。

$ git clone -b v0.2.x-support-Redmine3 https://github.com/akiko-pusu/redmine_issue_templates.git
Banner

現時点でmasterブランチはredmine 4.0以降のみ対応となっています。redmine 3.xで使うときは、ブランチ名にv0.1.x-support-Redmine3 を指定します。

$ git clone -b v0.1.x-support-Redmine3 https://github.com/akiko-pusu/redmine_banner.git
Wiki Extensions

現時点で存在するブランチdevelopおよびmasterはともにredmine 4.0以降のみ対応となっています。redmine 3.xで使うときは、タグ名に0.8.2を指定します。

$ git clone https://github.com/haru/redmine_wiki_extensions
XLS Export
$ git clone https://github.com/two-pack/redmine_xls_export.git
$ bundle install

gemパッケージのspreadsheet を必要とするので、bundle installを実行します。

Clipboard Image Paste
$ git clone https://github.com/peclik/clipboard_image_paste.git
Wiki Lists
$ git clone https://github.com/tkusukawa/redmine_wiki_lists.git
LaTeX MathJax Macro
$ git clone https://github.com/mboratko/redmine_latex_mathjax.git
$ bundle install
  • gemパッケージのrender_parentを必要とするので、bundle installを実行します。
Sidebar Hide
$ git clone https://gitlab.com/bdemirkir/sidebar_hide.git
Glossary
$ git clone https://github.com/torutk/redmine_glossary.git
Github hook
$ git clone https://github.com/koppen/redmine_github_hook.git

設定の復旧は後日実施。

View Customize
$ git clone https://github.com/onozaty/redmine-view-customize.git view_customize
$ bundle install
  • ディレクトリ名はview_customizeでないといけない
  • gemパッケージのactiverecord-compatible_legacy_migrationを必要とするので、bundle installを実行します
Startpage
$ git clone https://github.com/gatATAC/redmine_startpage.git

添付ファイルの復元

バックアップを取っていたマシンからrsyncで転送します。

リポジトリSVN、Git)の復元

SVNは、ダンプファイルをインポートします。
Gitは、git clone --mirror でクローンしたリポジトリから配置したい場所へpushします。

NginxからUnicorn使用

Nginxの設定ファイルを戻します。

およそ復元完了

4月30日の昼頃から作業を開始して、5月1日の昼頃までかかってしまいました。
平成のうちには更新が終わらなかったです。

次はRedmine 4.0へのアップデートをする予定です。

Java読書会6月からの本の投票

$
0
0

Java読書会BOFの書籍選定

Java読書会BOFでは、月1回の読書会を開催しており、5月で「Effective Java第3版」をほぼ読了し、6月からは新しい本の読書会を開始します。現在読書会のWebサイトでWeb投票実施中(5月26日〆切)です。

Javaの技術動向は変化が大きくなってきている

Javaは、2017年9月に Java SE 9がリリースされ、そこではModule Systemが導入されています。また、Java SE 9以降はこれまでの2~3年に1回のメジャーバージョンアップをリリースする形態から、半年毎にメジャーバージョンアップをリリースする形態に変更され、新機能が早い段階でリリースされ利用可能になっています。2018年3月にJava SE 10、2018年9月にJava SE 11、2019年3月にJava SE 12と半年毎にバージョンアップされています。

一方で安定的に利用したいユーザー向けには3年に1回、長期サポート版(LTS)を提供するようになりました。最初のLTS版は、2018年9月のJava SE 11です。これは少なくとも次のLTS版まで(則ち最低3年間)はセキュリティパッチを含む修正版が提供されます。LTS版以外は、次のバージョンがリリースされるまでの間(則ち半年間)だけは修正版が提供されます。

余談、有償化騒ぎについて

このLTS版については、Oracleが提供するOracleJDKについては商用利用(個人利用および商用利用でも開発・試験・デモは除く)が有償となっているので、すわJavaが有償化だと一部で騒ぎになっていました。しかし、Javaはずいぶん前からオープンソース開発(OpenJDK)となっており、特に最初のLTS版であるJava SE 11ではOracleJDKに搭載されていた商用機能(Flight Recorder等)がOpenJDKに搭載され、Oracle以外にもいくつかの組織からLTS版が無償提供されています。

読書会で読みたい本が・・・

このようにJavaの進化は短い時間で次々行われてきていますが、日本語での書籍でJava SE 9以降の新しい機能を詳しく解説しているものはほとんど出版されていません。

となると、Javaの新しい技術を読みたいとすると洋書を選択することになります。過去読書会でも幾度か洋書を読んできたので、それほど敷居が高いということはありませんが、洋書ウォッチャーが少ないのでこれぞという本を選ぶのが大変です。

Java Platform Module System(JPMS)の本

Java SE 9で導入され、なかなか骨のある(移行が大変、理解が大変)割にプログラミングの改善にはあまり寄与していない(APIの実装隠蔽や、ソフトウェアのライフサイクル上はとても重要な)技術がJPMSではないでしょうか。

JPMSを扱った書籍を調べてみると、次が見つかりました。

最初の本はまだ未完です。2番目以降はJava SE 9リリース時期(2年前)に出版されているので、ちょっと古いなというのと、ページ数がやや多いが難です。
(洋書の読書会の場合、事前に粗く訳してくるので、ページ数が多いとそれだけ大変)

ということで、JPMS本は今回は推薦せずにおこうかと思います。

Java読書会BOFは6月から「Java 11 and 12 - New Features」を読み始めます

$
0
0

Java読書会BOFは、6月から新しい書籍「Java 11 and 12 - New Features」(洋書)を読み始めます。

Java SE 9が2017年9月にリリース後、この2019年5月末時点までに、Java SE 10(2018年3月)、Java SE 11(2018年9月)、Java SE 12(2019年3月)とリリースされ、Java SE 11については、長期間サポート版(LTS: Long Term Support)となっています。

ところが、この2年内のJavaのアップデートについてしっかり書かれた日本語書籍はほとんどなく、洋書で勉強するとなった次第です。

Java読書会BOFでは、過去洋書を扱ったときは事前に分担を決め日本語訳をして、それを当日配布して日本語訳を朗読するという方法を取ってきました。
これは、精読できるのですが事前の準備負担が大きいので対応し難くなっています。今回は、事前訳はせずにその場で粗い日本語で朗読を進めようかと思っています。

さて、今回の書籍の章題を列挙すると次になります。

  • 型推論(Type Inference)
  • アプリケーション・クラス・データ共有(Application Class-Data Sharing: AppCDC)
  • ガーベージコレクター最適化(Garbage Collector Optimizations)
  • JDK 10の細々な改善(Miscellaneous Improvements in JDK 10)
  • ラムダ式の引数のローカル変数文法(Local Variable Syntax for Lambda Parameters)
  • イプシロンGC(Epsilon GC
  • HTTPクライアントAPI(The HTTP Client API
  • ZGC
  • フライトレコーダーとミッションコントロール(Flight Recorder and Mission Control)
  • JDK 11の細々な改善(Miscellaneous Improvements in JDK 11)
  • switch式(Switch Expressions)
  • JDK 12の細々な改善(Miscellaneous Improvements in JDK 12)
  • プロジェクトAmberにおける列挙型の改善(Enhanced Enums in Project Amber)
  • データクラスとその使用(Data Classes and Their Usage)
  • 生文字列リテラル(Raw String Literals)
  • ラムダの食べ残し(Lambda Leftovers)
  • パターンマッチング(Pattern Matching)

JDKの新しい機能としてちらっと見聞きしたことのある、でも良くわからない機能が並んでいます。
今回の読書会を通じて、これらの新機能について一定の理解をしておくことができるのはなかなかに期待度が高いです。

今月のJava読書会は、6月15日(土)に川崎駅近くで開催します。

Javaプログラムへの初期設定の与え方を悩む

$
0
0

Javaプログラムに初期設定を外部から渡す方法として、プログラム起動時のコマンドラインオプション、設定ファイル、システムプロパティ、リソースバンドル、プリファレンスAPIjava.util.pref)辺りが候補に挙がります。
今回、PCからシリアル通信でセンサー機器と通信し、センサー機器の状態をGUIで表示するスタンドアロンプログラムを作成しています。プログラムの構造は、GUIに纏わる部分、シリアル通信に纏わる部分とを疎結合しています。ここで、疎結合ゆえにプログラムの初期設定をどのように渡すかを悩むこととなりました。

候補となる方法

コマンドライン引数

GUIJavaFXで作成しています。JavaFXコマンドラインを解析し、JavaFXのParametersクラスにキー・バリュー形式で保持する仕組みを持っています。しかし、このParametersクラスをシリアル通信部分に渡してしまうと、シリアル通信部分がJavaFXのライブラリへ依存してしまい、結合を疎にする観点では後退してしまいます。

設定ファイル

プログラムの起動時に所定のパスにあるProperties形式の設定ファイルを読み込み、これを参照する方法であれば、Java SEの標準APIjava.util.Properties)への依存で済むので、疎結合をそれなりには保てます。しかし、設定ファイルから生成したPropertiesインスタンスGUIの各所とシリアル通信の各所に持ち回す必要が生じます。
JavaFXでは、通常上位のApplication派生クラスからコントローラクラスへは直接参照を持たないので、プロパティを渡すためにコントローラーの参照を取得するコードをApplication派生クラスに追加し、またコントローラーにはプロパティを渡すメソッドを追加し、コントローラーからさらに各クラスに渡すといった持ちまわしが生じます。
シリアル通信においても、そのプロパティを各クラスに渡す持ちまわしが生じます。
この持ちまわしを避けるとしたら、プロパティをシングルトンで実装するといったプログラム全体のグローバル参照が発生します。
今回は小さなプログラムなので、1つのプロパティでもそう煩雑にはなりませんが、大きなプログラムになると、1つのプロパティに数百個以上のキー・バリュー定義が格納されることになり、あまり望ましい姿ではありません。

システムプロパティ

Java起動時のコマンドラインJVMオプション)でシステムプロパティを定義することができます。
システムプロパティは、Java SE標準のAPIjava.lang.SystemクラスのgetPropertyメソッド)で取得できるので、疎結合の点で問題はなく、持ちまわしも不要ですが、多数の項目があるとJava起動時のコマンドラインが膨大となってしまいます。

リソースバンドル

Javaには、主に国際化対応の仕組みとしてロケールに応じて切り替える文字列等のリソースをプロパティファイルまたはクラスに切り出し、実行時にロケールに応じたファイルを読み込むリソースバンドルの仕組みがあります。このファイルに初期設定を書く方法がありますが、リソースファイルは通常JARファイルの中に収められるので、インストール後に設定を変更する場合、JARから取り出し修正したファイルを再度JARに戻すという手順が必要になり、これも好ましくありません。また、リソースバンドルは主にGUIの表示で使うので、シリアル通信などGUIから切り離した内容を記述するのは不適切と思われます。

プリファレンスAPI

Javaには、プリファレンスAPIjava.util.prefs)があり、ユーザー毎に異なる設定を保存・読み出して使用することと、ユーザー共通の設定を保存・読み出して使用することができます。
これはJava SE標準APIなのでシリアル通信からJavaFXへの依存はなくて済みます。
プリファレンスAPIを通して設定する内容は、Windows OSの場合はレジストリに格納され、UNIX系OSの場合はユーザー毎の設定はユーザーのホームディレクトリ下のファイルに、ユーザー共通の設定はシステムで一意の場所にファイルで置かれます。
レジストリやファイルは、プログラムをインストールして最初にプリファレンスを保存するまで生成されないので、インストール直後から初期設定を置くにはプリファレンスAPIを使って保存操作を最初にする必要があります。また、実行するユーザーの権限ではユーザー共通の設定を書き込むことができないことがあります(通常このケースが多いかと)。プリファレンス設定ツールが必要になるかもしれません。

結局どれにしよう?

用途によりトレードオフすることとなりそうです。

JavaFXの入力部品でエラー時に色を変える

$
0
0

GUIでユーザーが入力した値が範囲外などのエラー時に、エラーとなった箇所が分かるよう色を変える方法を模索しました。今回はTextFieldを題材とします。

CSSの疑似クラスでエラーを定義

JavaFXで、CSSファイルを使って見栄えを定義している場合、CSSファイルにTextFieldの疑似クラスerrorを次のように定義します。

.text-field:error {
    -fx-text-box-border: red;
    -fx-focus-color: red;
}
  • "-fx-text-box-border"は、TextFieldにフォーカスが当たっていないときの枠の色を指定
  • "-fx-focus-color"は、TextFieldにフォーカスが当たっているときの枠の色を指定

Javaのコードで、テキストフィールドの個々のインスタンスに疑似クラスの状態を指定

@FXMLprivate TextField myTextField;
    private PseudoClass errorClass = PseudoClass.getPseudoClass("error");
    :
    void handleSubmit(ActionEvent event) {
        if (! validateInput()) {
            myTextField.pseudoClassStateChanged(errorClass, true);
        } else {
            myTextField.pseudoClassStateChanged(errorClass, false);
        }
        :
    }

疑似クラスの名前でPseudoClassインスタンスを取得し、対象となるTextFieldインスタンスのpseudoClassStateChangedメソッドで引数に疑似クラスのインスタンスと有効・無効のフラグを指定すると見栄えを変更することができます。

この実装では、submitボタンが押された段階でTextFieldの内容をチェックし疑似クラスerrorの適不適を切り替えています。
より本格的に、TextFieldに文字を入力する度にチェックし色を変えることもできますが、本日は割愛します。

Java読書会BOF「Java 11 and 12 New Features」(洋書)を読む会を開催して

$
0
0

Java 11 and 12 New Features」(洋書)を読む会を開催

昨日6月15日(土)に、Java読書会BOFは書籍「Java 11 and 12 - New Features」を読む会(第1回)を開催しました。

あいにくの雨模様でしたが、10名を超える参加者が集まりました。Javaの最新技術、といっても2年前の2017年にリリースされたJava SE9からの技術を解説した日本語の書籍が皆無で、洋書の選択となりました。

Java読書会BOFが開催する読書会では、書籍を本文、脚注、ソースコードとすべてを朗読して進めています。今回は洋書なので、どう進めようか?と思いましたが、英語のままで朗読して進めてみることとしました。

書籍の入手に関して

Amazonのサイトには、紙の書籍とKindle電子書籍版があり、紙版も2日程度で入手可能となっています。また、洋書の出版社Packt Publishingのサイトには、ePubおよびPDFの電子書籍版があります。Kindle版は2840円ですが、ePubおよびPDF版は10ドル(約1100円)と半額以下です。

英語の朗読について

英語の朗読なので、英語の得意な人は問題なく把握できますが、そうでないと分からない単語が多く読み取ることがなかなかむずかしいというところがありました。今回の本は割とやさしい英文でしたが、それでもちょっと苦労はあったようです。

少し時間がかかりますが、段落ごとに内容を日本語で簡単にまとめるといったフェーズをおくのがよかったかもしれません。

内容について

型推論

Java 10で導入された型推論(var)について、3時間はじっくりとかけて読みました。
ローカル変数のみ型をvarとして宣言可能で、未初期化、null代入、配列初期化子、配列の括弧とは一緒に定義できないという制約があります。また、varは予約語(キーワード)ではないので、メソッド名やインスタンス変数、static変数の識別子にvarの名前を使用可能です。

議論はありましたが、ローカル変数の宣言行でvarによる型推論を使うことでその宣言行を読んだだけでは型が分からない場合(例を次に示す)はvarによる型推論を使わない、というのが必須と思います。

var i = getData();

これは、getDataのメソッド名の宣言行(シグニチャ)を調べないと、ローカル変数iの型が分かりません。varを導入したことによりコードの読み手に対する情報が欠落してしまっています。

左辺の変数名、あるいは右辺のメソッド名から型が容易に推測できる場合はvarによる型推論を使ってもよいという意見もあります。しかし、個人的には、ローカル変数の宣言行で、型名を2回記述しなければならないときに型指定をvarとしてもよい、それ以外の場合は、型を書くというのをベストプラクティスにしたいです。

CDS(クラス・データ・シェアリング)

CDSは実はJava SE 5.0 から搭載されていた機能でしたが、まったく気にしたことがなく、使い方の記述を日本語では殆ど見かけたことがありませんでした。今回読書会では、JDKの標準クラスが格納されたclasses.jsaと、アプリケーションの起動時に必要なクラスをシェアリング用ファイルとして生成し使用するAppCDSとについて、じっくり読んで知識を得られました。

自宅のPC(Windows OS 64bit)にインストールしているJDKの各ディストリビューションとバージョンについてclasses.jsaの有無を見てみると、

  • OracleJDK 8/9/10 ⇒ あり
  • Oracle OpenJDK 11/12 ⇒ あり
  • Liberica JDK 11 ⇒ なし
  • ZuluFX 11 ⇒ なし

となっていました。classes.jsaがない場合は、次のコマンドで生成することができます。

java -Xshare:dump 

Java SE 11以降では、CDSの使用可否を指定するJVMオプション -Xshare=auto がデフォルトとなりました。
[JDK-8197967] Make -Xshare:auto the default for server VM - Java Bug System

なので、OracleJDKまたは Oracle OpenJDK の11以降を使う場合は明示的に指定しなくてもCDSが有効となります。Oracle以外のOpenJDKでclasses.jsaファイルがない場合は、そのOpenJDKのインストール直後に java -Xshare:dump を一度実行しておくとよいでしょう。

AppCDS(アプリケーション・クラス・データ・シェアリング)

CDSJavaの標準クラスをシェアする機構に対して、AppCDSはアプリケーションのクラスを含んだクラスをシェアする機構です。

オプションの指定がJava SE 11以降とJava SE 10とで変わるので少々混乱してしまいます。
次のJVMオプションはJava SE 11以降では廃止となって指定する必要はありません。

-XX:+UseAppCDS 

Java SE 11以降では、次のステップでAppCDSのシェアドアーカイブファイルを適用します。

  • 次のJVMオプションを指定してアプリケーションを実行、起動時にロードするクラス一覧のリストファイルを生成
 -Xshare:off -XX:DumpLoadedClassList=<リストファイル名>
  • 次のJVMオプションを指定してアプリケーションを実行、アプリケーションのクラスを含む起動時にロードするシェアドアーカイブファイルを生成
-Xshare:dump -XX:SharedClassListFile=<リストファイル名> -XX:SharedArchiveFile=<シェアドアーカイブファイル名> 
  • 以後、アプリケーションを実行するときは次のJVMオプションを指定
-XX:SharedArchiveFile=<シェアドアーカイブファイル名>

最初のステップで-Xshare:offを指定するのは、リストを作成するときに標準CDSアーカイブファイルから読み込むことを抑制するためと思われます。

次回の範囲は

次回は、次の項目を読み進める予定です。

  • Parallel full GC for G1
  • Miscellaneous Improvements in JDK 10
  • Local Variable Syntax of Lambda Parameters
  • Epsilon GC
  • The HTTP Client API

gitのリリースタグをアプリケーションのバージョン表記に使う

$
0
0

はじめに

JavaJavaFXで小さなスタンドアロン・アプリケーション(ユーティリティ)を作成、使用するマシンに配置し、使い勝手を見ながら改善しています。

使用しているJavaの種類は、JavaFXを内蔵しているAzul Systems社のOpenJDKであるZuluFX 11で、モジュール(Java Platform Module System)として作成し、jlinkコマンドで実行イメージを作成してJavaのランタイム込みで配布しています。

アプリケーションの利用者から今使っているバージョンを認識し、フィードバックを得るようにするため、バージョン番号表記をどのように組み込もうかと試行錯誤してみました。

世の中よく見かけるバージョン表記は、アプリケーションのメニューバーに「ヘルプ」を置き、メニュー項目に「このアプリケーションについて」等を入れ、それをクリックするとアプリケーションの名前、バージョン、その他情報をポップアップ表示するといったものです。

ですが、小さなプログラムでメニューバーを設けていない場合は別な手段が必要になります。
今回は、ウィンドウのタイトルバーにバージョンを表記することとしました。

f:id:torutk:20190624185938p:plain

バージョン管理ツールとバージョン

gitを使ってソースコードのバージョン管理をしています。リリースの際にはタグでリリースバージョンを設けます。

D:\work> git tag v1.3.2

このタグをバージョン表記に使用することとし、ソースコードリポジトリにはバージョン番号を記載したファイルは設けないようにします。

ビルド時に、次のコマンドでバージョンを取得します。

D:\work> git describe --tag abbrev=0
v1.3.2

abbrev=0を付与しない場合、タグをつけたコミットからタグをつけないコミットを行った際、次の様にタグをつけてからのコミット数とハッシュが追加されて表示されます。

D:\work> git describe --tag
v1.3.2-2-g595ad04
gitのタグの種類

gitには、軽量タグ、注釈タグ、署名タグとタグの種類がいくつか存在します。
軽量タグを付与した場合、git describeでは--tagオプションを指定する必要があります。

注釈タグは、タグを付与するときにコミットログを記述します。注釈タグを付与した場合、git describeでは--tagオプションがなくても最新のタグを取り出すことができます。

バージョン番号の埋め込み方法

JavaFXでは、ウィンドウのタイトルへ表示する文字は次のようにStageクラスのsetTitleメソッドを呼んで設定します。

@Overridepublicvoid start(Stage primaryStage) throws IOException {
        :(略)
        primaryStage.setTitle("STC-110 Remote Viewer");
        :(略)
    }

ビルド時にgitのタグからバージョン番号を取り出してウィンドウタイトルに表示させるため、ここではリソースバンドルにバージョンをビルド時に埋め込むようにします。

@Overridepublicvoid start(Stage primaryStage) throws IOException {
        var bundle = ResourceBundle.getBundle("antenna.direction.AntennaDirectionView");
        :(略)
        primaryStage.setTitle("STC-110 Remote Viewer " + bundle.getString("antenna.direction.version"));
        :
Windowsバッチファイルで置換する場合

リソースバンドルには、ビルド時に文字列置換できるように置換キーワードを記述した状態でコミットしておきます。

antenna.direction.version = #VERSION_TO_BE_REPLACED#

そして、ビルド時に置換キーワードをgitのタグ情報に置き換えます。

for /f %%i in ('git describe --tag --abbrev^=0') do set VERSION=%%i

ren %PROPERTY_DIR%\%PROPERTY_FILE% %PROPERTY_FILE%_
setlocal enabledelayedexpansion
for /f "delims=" %%I in (%PROPERTY_DIR%\%PROPERTY_FILE%_) do (
    set line=%%I
    echo !line:#VERSION_TO_BE_REPLACED#=%VERSION%!>>%PROPERTY_DIR%\%PROPERTY_FILE%
)
endlocal

JavaFXアプリケーションでユーザーが編集可能なCSSファイルを置くには

$
0
0

問題

JavaFXアプリケーションを作成する際に、画面のレイアウトをFXMLで記述、色やフォントなどの見栄えをCSSファイルに記述し、FXMLのルートノードにCSSファイルを指定しました。

模式的なソースファイル構成は次のようになります。

myapp
  +-- src
        +-- mypackage
              +-- MyApp.java
              +-- MyApp.fxml
              +-- MyApp.css

MyApp.javaの中でFXMLファイルを読み込みます。

Parent root = FXMLLoader.load(getClass().getResource("MyApp.fxml"));
Scene scene = new Scene(root);

MyApp.fxml の中でCSSファイルを指定します。

<HBox stylesheets="@MyApp.css" xmlns=...>

このアプリケーションをビルドすると、CSSファイルは他のクラスファイル等と一緒にJARファイルまたはjlinkでモジュールファイルに含まれます。

このアプリケーションを実行するマシンに配布したあとに、画面の見栄えをカスタマイズしようとCSSファイルを取り出して編集、再組み込みすることは簡単ではありません。
JARファイルならzipアーカイブツールでCSSファイルを取り出し編集後再度JARファイルに戻すことは可能です(かなり面倒です)。
jlinkのモジュールファイルの場合、zipではないので取り出し編集は困難です。

そこで、ビルド時にJARファイルまたはモジュールファイルに組み込まれるデフォルトの設定を記述したCSSファイルとは別に、独立したCSSファイルをアプリケーション実行時に読み込み反映する方法を探ってみました。

sceneに別CSSファイルを設定するも反映されず

カレントディレクトリにMyCustom.cssファイルが存在すればそれをSceneのスタイルシートとして設定するコードを追加しました。

Path path = Paths.get("MyCustom.css");
if (Files.exists(path)) {
    scene.getStylesheets().add(path.toUri().toString());
}

しかし、これではMyCustom.cssに記述したカスタム設定が反映されませんでした。

このコードでは、SceneとそのrootノードのHBoxにそれぞれ別のスタイルシートが設定されます。

Scene         ---> MyCustom.css
  +-- HBox    ---> MyApp.css

試行錯誤と資料調査をしたところ、JavaFXのドキュメント「JavaFXCSS Reference Guide」に次の記述を見つけました。

Style sheets from a Parent instance are considered to be more specific than those styles from Scene style sheets.

つまり、Sceneのスタイルシートよりもその子ノード(rootノード)のスタイルシートの方が優先されてしまいます

sceneのrootノードにスタイルシートを設定する

そこで、Sceneのrootノードに対してスタイルシートを設定するようにしました。

Path path = Paths.get("MyCustom.css");
if (Files.exists(path)) {
    scene.getRoot().getStylesheets().add(path.toUri().toString());
}

これで、アプリケーションを実行するときのカレントディレクトリにMyCustom.cssファイルが存在すれば、その内容が反映されるという仕組みができました。

インターネットとつながらないソフトウェア開発環境で使うツール

$
0
0

前提

ソフトウェア開発環境がインターネットと隔離されていることは、昨今のセキュリティ事情からよくあることと思います*1

ソフトウェアエンジニアの場合、事務作業で使うPC環境とは別にソフトウェア開発用のPC環境があり、それは事務作業で使うネットワーク環境とは異なるソフトウェア開発のネットワーク環境につながっており、インターネットとはつながらない(あるいは強度な制約のもとインターネットにつながる)環境となります。

例えば、事務作業用のPCと開発用のPCが物理的に分かれていて、それぞれ別々なネットワークに接続されていることや、事務作業用PCの利用は仮想マシンを通して行い、開発用PCと仮想マシン間のデータ交換は不可となっていることなどです。

開発したソフトウェアを組み込む製品と開発環境をつないで、ソフトウェアのインストール・更新、ログの取得、デバッグ、各種設定をする際は、製品が開発用ネットワークとはつながっていないため、開発用PCを開発用ネットワークから切り離して使うこともあります。場合によっては開発用ネットワークなしに単独で最初から最後まで開発を行うこともあります。

このように開発環境がインターネットとは接続されていない場合は、開発に使うツールの選定時点からそのことを考慮に入れておくことが不可欠です。

開発環境で使うツールの準備

インターネットと隔離されているので、開発環境で使うツールは開発環境用のネットワークの中で展開できるようにする必要があります。

ここで困るのが、Webインストーラーのみ提供され、インターネットとつながっていないとインストールができないツールです*2

開発用PCをインターネットにつないでインストールしてから開発環境用ネットワークに持ち込めばいいのでは?との声があるかもしれませんが、開発データを抱えたPCをインターネットに接続することは原則不可でしょう。

インストールはできるもののライセンス認証にインターネット接続が必須となるツールも使うことができません。

ツール本体はオフラインでインストールできても、プラグインの追加がインターネット接続必須となると困ります。

インストールができたとしても、実行時に問題があるというケースも困ります*3

主要なJava開発ツール(統合開発環境)のNetBeansEclipseIntelliJ IDEAは、いずれもインストールイメージをオフラインで展開して使用できます。

ビルドツール

maven、gradleはJavaのビルドツールとして非常に流行っておりますが、インターネットに接続していることをほぼ前提としているツールなので、インターネットと隔離した開発環境での使用には不適です。

mavenは、自身の実行にもインターネットからモジュールのダウンロードが発生します。オフラインオプションもありますが、一度オンラインでダウンロードしたキャッシュを使うものなので、ゼロから使うには、mavenセントラルリポジトリのクローンをNexusArtifactoryなどで開発ネットワーク内に構築し、各開発PCからはそのクローンを参照する必要があります*4。ただし、開発環境から切り離しての作業には制限があります*5

gradleは、mavenに比べるとオフライン使用がまだ容易ですが、オフラインで使用できるようにするための手間はやはり発生します。以前、Android Studioで開発した際は、SDKのインストール場所、ライブラリの配置などを調整する必要がありました。その時の取り組みは次のブログに記載しています。

現時点では、引き続きAntを使うのがインターネット隔離環境では適すると言えます。

mavenセントラルリポジトリのライブラリ利用におけるセキュリティリスク

mavenリポジトリを利用する場合、利用するライブラリが依存するライブラリ群を逐一把握して揃えなくてもビルド時に自動で取得してくれる利便性があります。しかし、これは逆に意図しないライブラリが入り込むことになります。意図しないものに、知財的な問題からバックドア等のセキュリティ脆弱性問題までいろいろな問題が含まれます。

開発者が使用するPC個別に直接mavenリポジトリへアクセスしライブラリをダウンロードしてくるので、マルウェア等が入り込み別サイトに誘導されダウンロードしてしまうということも考えられます。

バージョン管理ツール

開発用PCが開発ネットワークに接続して使用、あるいは切り離して使用するので、リポジトリサーバーへの常時接続がなくても管理できる分散リポジトリ方式のGitやMercurialがよいでしょう。特に製品組み込み後にその場で修正をした場合は、開発ネットワークから切り離されている間にリポジトリへのコミットを重ねておき、もどってから中央リポジトリに反映することができるのは大きな利点です。

CIツール

計画的な(デイリーの)ビルド・成果物作成はJenkinsなどのサーバーを使いますが、製品と隣り合わせでビルド・インストールを繰り返すときには持ち出しできる(開発PCに一緒に乗せて使える)CIツールがあるといいですね。これは今後調査していきたいと思っています。

不具合管理・プロジェクト管理ツール

プロジェクトの不具合状況、進捗状況は、ソフトウェア開発メンバーだけでなく、組織の管理者層や品質保証部門からも参照したい情報です。ソフトウェア開発環境の中に閉じたツールを立てると、開発メンバーしか共有できません。
隔離したネットワークの双方から参照したい場合の実現方法は今後調査していきたいと思っています。

コミュニケーションツール

開発環境とは別に、事務作業用の環境で使うか、開発環境の中に独自にツールを用意するかの選択(あるいは併用)となります。
ビルド結果の通知、プロジェクト管理ツールからの通知などをコミュニケーションツールで受けるので開発環境の中に置きたいところですが、開発以外のメンバーとのコミュニケーションには事務作業用の環境で使えるツールが必要です。インターネット接続環境ではチャットツールの活用でコミュニケーションが良好になっています。隔離したネットワーク内およびその外側とでコミュニケーションをとる方法を今後調査したいところです。

現状と今後

前提で書いた開発環境は、かなり制約がつよい(利便性が犠牲になっている)環境となっています。プログラマー視点で普段目にする技術情報では、便利な技術の紹介がほとんどで、インターネット上のサービスをガンガン使うものばかりですが、所属する会社組織において要求されるセキュリティレベルによってはそうした便利技術が使えないということが私の経験では多いです。

前提のような環境を強いられなくてもセキュリティが保てるような開発環境をどうしたら構築できるかについて、少しずつ検討していきたいと考えています。

*1:開発用PCがインターネットとつながってメールやWebができると、開発環境にある情報が漏洩するリスクがあり、入口対策でファイアウォールやウィルス対策をしていても、標的型攻撃や開発ツール・ライブラリ等に仕込まれたサプライチェーン攻撃等による漏洩は防げません。開発環境に攻撃が及んでも社外に漏洩しないよう隔離をして対策を取るケースです。

*2:Visual Studio 2017以降はISOイメージが提供されなくなり、オフラインインストールが困難になりました。インターネットに接続したPCでいったんWebインストーラーを動かしオフラインイメージをダウンロード、それを検疫等必要な処置をして開発環境へ持ち込み、開発PCへインストールする手順は用意されていますが、コード署名の証明書が追加で必要なるとか、ダウンロードしたPCと構成が違うときにエラーになるといったトラブルが生じることがあります。また、インターネットに接続するPCでWebインストーラーを実行できるかは組織のセキュリティポリシーにより許されないこともあります。

*3:過去にあったのが、アセンブリ(DLLファイル)にコード署名が施されており、ツールを起動するたびにコード署名の検証をするがその際インターネット接続を試み、タイムアウトが発生してから起動するというもの。ツールの起動に数分待たされてしまいました。

*4:すごい大変

*5:キャッシュしたコマンドは実行可能だが、切り離してから始めて使うコマンドはキャッシュがないのでエラーになってしまう

JavaFXで折れ線グラフを出そうとしてみた

$
0
0

はじめに(お題)

電気信号を時間領域から周波数領域に変換し、周波数に対するエネルギー量を解析できる測定器にスペクトラム・アナライザがあります。今回、このスペクトラム・アナライザで解析した結果をファイルに保存し、別な場所にあるPC上でその結果を表示、分析したいとします。

計測器にCSVファイルで結果を保存する機能があればこれをExcelで読み込ませてグラフ化することは容易ですが、今回の計測器はバイナリデータのファイルに保存するものでした。そこで、JavaFXで保存したバイナリデータのファイルを読み込み折れ線グラフ(LineChart)で表示するプログラムを作成してみます。

LineChart

JavaFXのプロジェクトを作成し、折れ線グラフ(LineChart)を表示する画面をScene Builderで作成しました。 ファイル構成は次です。

ファイル名内容
SimpleSpectrumApp.java Application派生クラス、プログラムのエントリ(main)
SimpleSpectrumView.fxml 画面(ビュー)のレイアウトを定義するFXMLファイル
SimpleSpectrumView.css画面(ビュー)の見栄えを定義するCSSファイル
SimpleSpectrumViewController.java画面(ビュー)に対応するコントローラークラス
SimpleSpectrumViewModel.javaGUI内のモデルクラス

デフォルトのLineChart

Scene BuilderでLineChartを画面にポトペタすると次の表示となります。 f:id:torutk:20190706211210p:plain

縦軸は数値メモリとなっていますが、横軸は表示されていません。Scene Builderの左下ペインを見ると、LineChartの子ノードとしてNumberAxisとCategoryAxisが1つずつ置かれています。縦軸がNumberAxisで、横軸がCategoryAxisです。NumberAxisは名前の通り数値の軸を表し、CategoryAxisは何らかの分類(県名、年別、チーム別、など)の軸を表します。

Scene Builder上では、LineChartの子ノードの軸を変更する方法が見つからなかったので、FXMLファイルを直接エディタで開き修正しました。

<LineChart BorderPane.alignment="CENTER">
        <xAxis>
-         <CategoryAxis side="BOTTOM" />+         <NumberAxis side="BOTTOM" /></xAxis>
        <yAxis>
          <NumberAxis side="LEFT" />
        </yAxis>
      </LineChart>

横軸がNumberAxisに変更されました。 f:id:torutk:20190706212808p:plain

最初のコード

SimpleSpectrumApp.java
package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

publicclass SimpleSpectrumApp extends Application {
    @Overridepublicvoid start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("SimpleSpectrumView.fxml"));
        primaryStage.setTitle("Simple Spectrum");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    publicstaticvoid main(String[] args) {
        launch(args);
    }
}

JavaFXの典型的なApplication派生クラスの実装です。mainメソッドでプロセスのエントリを用意し、startメソッドでFXMLファイルを読み込みシーングラフを作成、Stageを表示させています。

SimpleSpectrumView.fxml
<?xml version="1.0" encoding="UTF-8"?><?import javafx.scene.chart.LineChart?><?import javafx.scene.chart.NumberAxis?><?import javafx.scene.layout.BorderPane?><BorderPane maxHeight="-Infinity"maxWidth="-Infinity"minHeight="-Infinity"minWidth="-Infinity"prefHeight="400.0"prefWidth="600.0"xmlns="http://javafx.com/javafx/11.0.1"xmlns:fx="http://javafx.com/fxml/1"fx:controller="sample.SimpleSpectrumViewController"><center><LineChart fx:id="chart"BorderPane.alignment="CENTER"><xAxis><NumberAxis side="BOTTOM"fx:id="xAxis" /></xAxis><yAxis><NumberAxis fx:id="yAxis"side="LEFT" /></yAxis></LineChart></center></BorderPane>

Scene Builderでポトペタ作成したFXMLファイルで、ビュー(画面)を定義しています。 コントローラークラスを指定しているので、このFXMLがロードされると自動でコントローラークラスのインスタンスが生成されます。

SimpleSpectrumViewController.java
package sample;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;

import java.net.URL;
import java.util.ResourceBundle;

publicclass SimpleSpectrumViewController implements Initializable {
    @FXML
    LineChart chart;
    @FXML
    NumberAxis xAxis;
    @FXML
    NumberAxis yAxis;

    SimpleSpectrumViewModel model = SimpleSpectrumViewModel.ofRandom();

    @Overridepublicvoid initialize(URL location, ResourceBundle resources) {
        chart.getData().setAll(model.getSeriesList());
    }
}

FXMLに対応するコントローラークラスです。 コントローラーから操作するビュー部品(コントロール)は、@FXMLアノテーションを指定し、FXML側で指定したfx:idの識別子と同じ名前の変数名とすることで、FXMLロード時にインスタンスがインジェクションされます。

データについては、今回はデータファイルの読み込み部分が未実装なので、ランダムな値を表示するモデルを使います。

SimpleSpectrumViewModel.java
package sample;

import javafx.scene.chart.XYChart;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

publicclass SimpleSpectrumViewModel {

    private List<XYChart.Series<Double, Double>> seriesList = new ArrayList<>();

    public List<XYChart.Series<Double, Double>> getSeriesList() {
        return Collections.unmodifiableList(seriesList);
    }

    publicstatic SimpleSpectrumViewModel ofRandom() {
        var model = new SimpleSpectrumViewModel();
        model.seriesList.add(createRandomSeries("Random"));
        return model;
    }

    privatestatic XYChart.Series<Double, Double> createRandomSeries(String name) {
        XYChart.Series<Double, Double> series = new XYChart.Series<>();
        series.setName(name);
        for (double x = 950d; x <= 1450d; x++) {
            series.getData().add(new XYChart.Data<Double, Double>(
                    x, ThreadLocalRandom.current().nextGaussian() * 5.0 - 60.0));
        }
        return series;
    }
    
}

JavaFXではモデルクラスについては特にフレームワークが用意されていませんが、コントローラーを軽くするために、ちょっとしたロジックであってもモデルクラスを設けるようにしています。

表示確認のために、データファイルを読み込む前にランダムなスペクトルを表示するモデルを作りました。

JavaFXの折れ線グラフ(LineChart)に表示する1本の連続する線は、データ系列(XYChart.Series)で定義し、複数のデータ系列を表示することができます。 データ系列は、複数の点データ(XYChart.Data)のリストとして作られています。 今回は、1つのデータ系列を用意しています。 用意するデータは、周波数軸(X軸)の950~1450までの間を1刻みで作成し、縦軸の電力は正規分布乱数で平均-60.0、分散5.0としています。

表示

プログラムを実行すると、次の画面が表示されました。

f:id:torutk:20190707043824p:plain

いろいろ改善したい点があります。

  • 縦軸、横軸に名称・単位を表示したい
  • 〇の表示(シンボル)は不要としたい
  • 折れ線を細くしたい
  • 縦軸、横軸の表示範囲を指定したい

LineChartの設定

縦軸、横軸に名称を表示

これは、Scene Builderで、軸のLabelプロパティに文字列を設定すれば表示できます。 f:id:torutk:20190707050535p:plain

縦軸、横軸の表示範囲を指定

縦軸は、-30.0~-80.0の範囲を表示し、横軸は950~1450の範囲を表示します。 表示すべきデータを読み込んだ際にデータに応じた値を設定するので、コードで制御します。

前回からのコード変更部分を次に記載します。

    @Override
    public void initialize(URL location, ResourceBundle resources) {
+       xAxis.setLowerBound(950d);+       xAxis.setUpperBound(1450d);+       yAxis.setLowerBound(-80d);+       yAxis.setUpperBound(-30d);
        chart.getData().setAll(model.getSeriesList());
    }

NumberAxisには、表示する軸の値の上限値・下限値を指定するlowerBound、upperBoundがあります。 ここに表示する軸の数値範囲を指定します。この上下限値設定は、LineChartにデータ(XYChart.Series)を登録する前に実行する必要があります。あとから実行したら表示には反映されません。

ただし、これだけでは意図したとおりの動作をしません。NumberAxisのAutoRangingプロパティが有効だと、登録したデータの値に応じた軸の数値範囲に自動調整が働いてしまいます。今回はScene Builder上でAutoRangingプロパティを無効にします(次の図参照)。

f:id:torutk:20190707101033p:plain

プログラムを実行すると、次の様に意図した数値範囲でグラフが表示されました。

f:id:torutk:20190707101215p:plain

  • グラフの罫線間隔については、NumberAxisのTickUnitプロパティで制御可能です。
〇の表示(シンボル)をなくす

デフォルトでは、折れ線グラフの点データに丸いシンボルが表示されます。 データ数が少ないときは、データとデータを補完する線とを明確に表現するこの表示が適していますが、データ数が多いときは上図のようにシンボルだらけで肝心の折れ線が見えなくなってしまいます。

シンボルをなくすのであれば、LineChartのCreateSymbolプロパティを無効にします(次の図参照)。

f:id:torutk:20190707103825p:plain

プログラムを実行すると、次のとおり丸いシンボルが表示されなくなりました。

  • シンボルを消すのではなく、シンボルの形状を変更したいときは、CSSセレクターに.chart-line-symbolを指定し見栄えを記述します。

f:id:torutk:20190707104015p:plain

折れ線を細く

デフォルトでは、折れ線の幅がちょっと太いので、これを細くします。形状の変更はCSSで記述します。 セレクターに.chart-series-lineを指定し見栄えを記述します。

.chart-series-line{
    -fx-stroke-width: 0.75px;
}

CSSファイルは、アプリケーション派生クラスでSceneに設定するか、FXMLで最上位要素に指定するかの方法があります。今回は、FXML(Scene Builder)上で最上位要素のBorderPaneにCSSファイルを設定します。CSSファイルはFXMLファイルと同じ場所に置き、BorderPaneのStylesheetsプロパティに指定します(次の図参照)。

f:id:torutk:20190707105138p:plain

プログラムを実行すると、次のとおり折れ線が細くなりました。

f:id:torutk:20190707105538p:plain

JavaFXのグラフの凡例部分を操作可能にする

$
0
0

はじめに

先週書いた次のブログの続編です。 torutk.hatenablog.jp

一つのグラフ表示領域に、複数のグラフ(Series)を重畳表示した際に、簡単な操作でそれぞれのグラフを非表示にしたり表示したりとできると使い勝手が向上します。

グラフに凡例を表示した場合、それぞれの凡例をクリックするとその凡例に対応するグラフを表示する、非表示すると切り替えられるようにします。

課題

凡例全体のNodeは、Legendクラス(com.sun.javafx.chart.Legend)で実装されています。しかし、このcom.sun.javafx.chartパッケージは、アプリケーション側のモジュールには公開されていません。

ぐぐって検索した情報は、子ノードを辿ってLegendクラスかどうか判別してLegendを見つけ、Legendにキャストしてそのitemを取得して利用するもので、Java SE 9以降では使えません(--add-exportsオプション指定でコンパイルすれば可能)。

各凡例を表示するNodeを取得する方法

それぞれの凡例は、CSSセレクタ .chart-legend-item が指定されています。JavaFXAPIには、セレクタを指定してNodeを取得する機能(lookupおよびlookupAll)が提供されているので、これを使って凡例のNodeを取得します。

@FXML
LineChart<Float, Float> spectrumChart;
:
Set<Node> legendItems = spectrumChart.lookupAll(".chart-legend-item");

それぞれの凡例にマウスハンドラーを設定するには例えば次のようにします。

legendItems.forEach(item -> item.setOnMouseClicked(event -> System.out.println(event + " clicked")));

各凡例をクリックしたら対応するグラフを表示/非表示する

クリックされた凡例のNodeにマウスハンドラーを設定することはできましたが、次はそこから対応するグラフを取得して、その表示を切り替える方法を考えなくてはなりません。

それぞれの凡例はLabelクラスのインスタンスで、ラベルに表示されるテキストはグラフ(Series)のnameプロパティとなっています。

そこで、取得したそれぞれの凡例をLabel型にキャストしてラベルに表示されるテキストを取得し(getTextメソッド)、次にそのテキストを名前として持つSeriesをLineChartから探して取得します。

Labelにキャストしてテキストを取り出す

legendItems.forEach(item -> item.setOnMouseClicked(event -> {
    if (item instanceof Label) {
        String name = ((Label) item).getText();
    }
}));

Label型へのキャストが可能な場合に、Labelへキャストし、getTextメソッドでラベルに表示されるテキストを取得します。

チャート(LineChart)に登録されているSeriesからテキストと同じ名前のものを取り出す

legendItems.forEach(item -> item.setOnMouseClicked(event -> {
    if (item instanceof Label) {
        String name = ((Label) item).getText();
        Optional<XYChart.Series<Float, Float>> any = spectrumChart.getData().stream()
                .filter(series -> series.getName().equals(name))
                .findAny();
    }
}));

chartのdataプロパティには表示するグラフ(Series)がリストで登録されています。これを順次アクセスしSeriesのnameプロパティが凡例ラベルのテキストと一致するものを取り出します。

クリックした凡例に一致するグラフ(Series)の表示/非表示を切り替える

legendItems.forEach(item -> item.setOnMouseClicked(event -> {
    if (item instanceof Label) {
        String name = ((Label) item).getText();
        Optional<XYChart.Series<Float, Float>> any = spectrumChart.getData().stream()
                .filter(series -> series.getName().equals(name))
                .findAny();
        any.ifPresent(series -> {
            boolean currentVisible = series.getNode().isVisible();
            series.getNode().setVisible(!currentVisible);
        });
    }
}));

取り出したグラフ(Series)のgetNodeメソッドで画面に表示されるグラフのNodeを取得し、その表示状態をisVisibleで取得します。そして、その真偽を反転させてsetVisibleに指定し、表示/非表示を反転させます。

グラフが非表示のときは凡例の表示を薄くしたい

実現方法を悩みましたが、CSSの疑似クラスを使って非表示の際に薄く(透過度を変えて)表示させることにしました。

まず、CSSで、.labelに疑似クラス haze を記述します。

.label:haze {
    -fx-opacity: 0.2;
}

次に、凡例ラベルのマウスハンドラー内でグラフ(Series)の表示/非表示切り替えに合わせて疑似クラスの有効・無効を凡例ラベルに設定します。

privatestaticfinal PseudoClass HAZE_PSEUDO_CLASS = PseudoClass.getPseudoClass("haze");
    :
legendItems.forEach(item -> item.setOnMouseClicked(event -> {
    if (item instanceof Label) {
        String name = ((Label) item).getText();
        Optional<XYChart.Series<Float, Float>> any = spectrumChart.getData().stream()
                .filter(series -> series.getName().equals(name))
                .findAny();
        any.ifPresent(series -> {
            boolean toBeInvisible = series.getNode().isVisible();
            series.getNode().setVisible(!toBeInvisible);
            item.pseudoClassStateChanged(HAZE_PSEUDO_CLASS, toBeInvisible);
        });
    }
}));

JavaFXでGUI全体のフォントをCSSで指定

$
0
0

はじめに

前回のブログの続きです。 torutk.hatenablog.jp

画面の国際化対応で日本語プロパティを追加してみました。その際使用される日本語のフォントはJavaのコードやCSSでコントロール毎に指定可能ですが、全体を変更することも可能です。

CSSのrootセレクタに-fx-font-familyでフォントを指定します。 ここで、どのようなフォントファミリ名を指定できるか、OpenJDK 11で試してみました。

CSSの記述例

JavaFXCSSファイルを作成、.rootセレクタに-fx-font-familyでフォント名を指定します。

.root{
    -fx-font-family: "Meiryo";
}

当初いろいろググってみたところ、「外部フォント」の指定には、@font-faceでフォントファイルのURLを記述、なおTTFファイルは有効でTTCファイルは指定できないといった記述をみかけました。 しかし、Windows OS上でOpenJDK 11をにおいては、@font-faceを指定しなくてもWindows OSのフォントとしてインストールされているフォントの指定が可能でした。

動作確認(画面キャプチャ)環境

項目 内容
OS Windows 10 64bit (1903)
JDK Scene Builder 11.0内蔵のOpenJDK 11

Scene Builderで画面を作成している場合、Scene Builderの[プレビュー]メニュー > [Sceneスタイルシート] > [スタイルシートの追加] でCSSファイルを指定します。 すると、Scene Builder上のデザイン画面、およびプレビュー画面は指定したCSSファイルの設定が反映されます。 また、CSSファイルを変更すると自動で変更を反映してくれます。ですので、CSSでの見栄え設定であれば、アプリケーションを再ビルド・実行することなくScene Builder上で確認できるのでとっても楽に確認ができました。

CSSファイルでのフォント指定とその画面

では、実際にCSSファイルのrootセレクタにフォントファミリを指定して表示を確認していきます。

まずはデフォルト(指定なし)の日本語フォント

まずは、何もフォントファミリを指定しなかった場合の日本語フォントです。最初は、OpenJDKのfontconfig.propertiesで定義されるMSゴシックが使われると思っていました。しかし実行してみるとアンチエイリアスが効いたフォントが表示されました。

f:id:torutk:20190728103937p:plain

デバッグプリントでコントロール(Label)のフォントを調べてみると、次のようになっていました。

Font[name=System Regular, family=System, style=Regular, size=12.0]

これは、javafx.scene.text.FontクラスのgetDefaultメソッドが返すフォントで、JavaFXAPIドキュメント(JavaFX 8 日本語APIドキュメント)には次の記述があります。

デフォルト・フォントのファミリはSystem、スタイルは通常はRegularとなり、サイズについては、判断可能な範囲においてユーザーのデスクトップ環境と一致するものが取得されます。

Windows 10 日本語版では、デスクトップのシステムフォントが「游ゴシック」となっているので、デフォルトで表示されるフォントが游ゴシックとなっています。 Windows 7日本語版であれば、おそらくMSゴシックではないかと思われます。

フォントファミリにMS Gothicを指定

CSSファイルに以下を記述します。

.root{
    -fx-font-family: "MS Gothic";
}

表示は次の様になります。お馴染みのギザギザフォントですね。

f:id:torutk:20190728104948p:plain

フォントファミリ名は、英語で記述しました。日本語で「MS ゴシック」等と記述した場合は設定が反映されず、デフォルトフォントで表示されました。

フォントファミリにMeiryoを指定

.root{
    -fx-font-family: "Meiryo";
}

f:id:torutk:20190728105456p:plain

フォントファミリにMeiryo UIを指定

.root{
    -fx-font-family: "Meiryo UI";
}

f:id:torutk:20190728105532p:plain

フォントファミリにYu Minchoを指定

.root{
    -fx-font-family: "Yu Mincho";
}

f:id:torutk:20190728105609p:plain

フォントファミリにYu Mincho Demiboldを指定

.root{
    -fx-font-family: "Yu Mincho Demibold";
}

f:id:torutk:20190728105653p:plain

フォントファミリにHGMinchoEを指定(HG明朝E)

.root{
    -fx-font-family: "HGMinchoE";
}

f:id:torutk:20190728105733p:plain

Viewing all 442 articles
Browse latest View live