20200811のJavaに関する記事は17件です。

CentOS8でTomcat8の自動起動設定で2度ハマった話

概要

掲題の件で1度解決したが、再度別の環境で同じ問題に遭遇し、記録をとっていなかったがために、2時間格闘してしまい、今後3度目の過ちを繰り返さないようメモ。

環境

  • CentOS8.2
  • OpenJDK8
  • Tomcat8.0

うまくいかない場合

(https://weblabo.oscasierra.net/installing-tomcat8-centos7-1/) にある例の通りに/etc/systemd/system/tomcat.serviceを作成した場合、うまくいかない。

File Edit Options Buffers Tools Help
[Unit]
Description=Apache Tomcat 8
After=network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
PIDFile=/usr/java/tomcat8/temp/tomcat.pid
RemainAfterExit=yes

ExecStart=/usr/java/tomcat8/bin/startup.sh
ExecStop=/usr/java/tomcat8/bin/shutdown.sh
ExecReStart=/usr/java/tomcat8/bin/shutdown.sh;/usr/java/tomcat8/bin/startup.sh

[Install]
WantedBy=multi-user.target

うまくいかない場合に出るエラー

ちなみに/usr/java/tomcat8/bin/startup.sh を手動で実行すると問題なく動作する。

[root@localhost ~]# systemctl start tomcat
Job for tomcat.service failed because the control process exited with error code.
See "systemctl status tomcat.service" and "journalctl -xe" for details.

8月 11 23:47:39 localhost.localdomain systemd[1]: Starting Apache Tomcat 8...
-- Subject: Unit tomcat.service has begun start-up
-- Defined-By: systemd
-- Support: https://access.redhat.com/support
--
-- Unit tomcat.service has begun starting up.
 8月 11 23:47:39 localhost.localdomain systemd[2079]: tomcat.service: Failed to execute command: Permission denied
 8月 11 23:47:39 localhost.localdomain systemd[2079]: tomcat.service: Failed at step EXEC spawning /usr/java/tomcat8/bin/startup.sh: Permission denied
-- Subject: Process /usr/java/tomcat8/bin/startup.sh could not be executed
-- Defined-By: systemd
-- Support: https://access.redhat.com/support
--
-- The process /usr/java/tomcat8/bin/startup.sh could not be executed and failed.
--
-- The error number returned by this process is 13.
 8月 11 23:47:39 localhost.localdomain systemd[1]: tomcat.service: Main process exited, code=exited, status=203/EXEC
 8月 11 23:47:39 localhost.localdomain systemd[1]: tomcat.service: Failed with result 'exit-code'.
 8月 11 23:47:39 localhost.localdomain systemd[1]: Failed to start Apache Tomcat 8.
-- Subject: Unit tomcat.service has failed
-- Defined-By: systemd
-- Support: https://access.redhat.com/support
--
-- Unit tomcat.service has failed.
--
-- The result is failed.

うまくいく場合

https://stackoverflow.com/questions/53994521/tomcat-8-on-centos-7-does-not-start-as-service-but-it-starts-manually を参考にするとよい。

最終的にうまくいった設定。上のURLにも記載されているが、結局WorkingDirectoryが怪しい?

/etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat 8
After=network.target

[Service]
Type=forking
WorkingDirectory=/usr/java/tomcat8/bin

ExecStart=/bin/bash /usr/java/tomcat8/bin/catalina.sh start
ExecStop=/bin/bash /usr/java/tomcat8/bin/catalina.sh stop

User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java練習問題 - 中級】ファイル周りの処理

はじめに

テスト勉強で自分用の問題作ろうと思ったのですが、せっかく作ったのに1人で使うのは勿体無いなと思ったのでこちらで投稿することにしました。

間違った点や改善点などございましたらご指摘宜しくお願いします?

try-with-resources構文

クローズ処理はどのタイミングでされますか?

問題
try (リソースの型 res = リソース) {  // java7
  // resの処理
} catch(例外の型 e) {
  // 例外処理
} finally {
  // 何かしらの処理
}
答え
tryブロックの後で`res.close()`される。
catch, finallyブロックはリソース解放後に実行されるので注意。

抑制された例外の取得

空欄の行のコードを埋めてください

問題
// 強制的に例外をthrowするようにInputStreamをoverride
InputStream inst = new InputStream() {
  @Override
  public int read() throws IOException {
    throw new IOException("最初のIOException");
  }

  @Override
  public void close() throws IOException {
    throw new IOException("抑制された例外");
  }
}

try (inst) { // java9
  inst.read();  // read()呼び出しによる例外発生
} catch(IOException e) {  // close()呼び出しによる例外発生
  System.out.println(e);
  // 空欄
  System.out.println(Arrays.toString(supressedExs));
}
答え
Throwable[] supressedExs = e.getSuppressed();   

try-with-resources構文によって、close()が内部で呼び出されます。close()の処理で例外が発生した場合、その例外をIOExceptionでキャッチしてしまうはずです。

ですが、もしclose()で発生した例外をキャッチしてしまった場合、read()で発生したはずの例外はキャッチすることができません。そのため、Javaは本来のread()で発生した例外へ内包します(例外の抑制)。こうすることで、本来の例外と抑制された例外どちらも取得することができます。

この抑制された例外を取得するためのメソッドがgetSuppressed()メソッドです。

パス(Pathクラス)

パスの生成

問題
`D:\tmp\test.txt`のパスを生成してください
答え
// パターン1(Java7以降)
Path path1 = Paths.get("D:\\tmp\\test.txt");

// パターン2(Java11以降)
Path path2 = Path.of("D:\\tmp\\test.txt");

パスの解決

出力される値は?

問題
Path path1 = Path.of("D:\\");
Path path2 = Path.of("D:\\tmp\\text.txt");
Path path3 = Path.of("tmp\\text.txt");

System.out.println(path1.resolve(path2));
System.out.println(path1.resolve(path3));
答え
D:\tmp\test.txt
D:\tmp\test.txt

引数が絶対パスであれば、引数に渡したパスが返却、
引数が相対パスであれば、連結して返却される

ファイル操作(Filesクラス)

文字列として読み込み

空欄に、文字列をパスD:\tmp\test.txtから読み込む処理を書いてください

問題
try {
  String in = // 空欄
  System.out.println(in);
} catch (IOException e) { }
答え
// パターン1(Java7)
new String(Files.readAllBytes(Path.of("D:\\tmp\\test.txt")), Charset.forName("MS932"));

// パターン2(Java11)
Files.readString(Path.of("D:\\tmp\\testAfter.txt"), Charset.forName("MS932"));

java6以前のtry文だけどclose処理書かなくても良いのかと思うかもしれませんが、readAllBytes()は内部でtry-with-resourcesを使ってるので大丈夫みたいです。

readString()は内部でreadAllBytes()を使用しているので同様にclose処理は必要ありません。

複数行をリストとして読み込み

空欄に、パスD:\tmp\test.txtから行ごとにリストとして読み込む処理を書いてください

問題
try {
  List<String> lines = // kuurann

  System.out.println(lines.get(lines.size() - 1));              
} catch (IOException e) { }
答え
Files.readAllLines(Path.of("D:\\tmp\\test.txt"), 
Charset.forName("MS932"));

readAllLines()も自動クローズされます。

ファイルへの書き込み

パスD:\\tmp\\test.txtから文字列を読み込み、"a""b"へ変換してD:\\tmp\\test2.txtへ書き込む処理です。空欄を埋めてください

問題
try {
  String in = new String(Files.readAllBytes(Path.of("D:\\tmp\\test.txt")), Charset.forName("MS932"));
  in = in.replaceAll("a", "b");
  // 空欄
} catch (IOException e) { }
答え
// パターン1(Java7)
Files.write(Path.of("D:\\tmp\\test2.txt", in.getBytes(Charset.forName("MS932")));;

// パターン2(Java11)
Files.writeString(Path.of("D:\\tmp\\testAfter.txt"), txt1, Charset.forName("MS932"));

パターン1の第2引数にはバイトを書き込むためgetBytes()でバイトの配列にしています。

第3引数にStandardOpenOption.APPENDなどを指定することでどんどん追加していくように書き込むこともできます。その他のオプションはこちらから(Java-SE13 Enum StandardOpenOption

ファイル・ディレクトリの作成

覚える必要はないと思うので問題は割愛

既にファイルが存在していたり、親のパスが存在していない場合は例外をスローする。

try {
  Files.createDirectory(Path.of("D:\\tmp\\demo"));
} catch (IOException e) { } 

ファイルの存在確認

覚える必要はないと思うので問題は割愛

ファイル作成の前に親パスの存在を確認して、自動生成するなどに使えます。

if (Files.exists(Path.of("D:\\tmp\\test.txt"))) {
  System.out.println("えいや");
}

ファイルのコピー

覚える必要はないと思うので問題は割愛

try {
  Files.copy(Path.of("D:\\tmp\\test.txt"), Path.of("D:\\tmp\\demo\\test.txt"));
} catch (IOException e) { }

オプションは色々あります(Java-SE13 Enum StandardCopyOption

ファイルの移動

こちらも割愛

try {
  Files.move(Path.of("D:\\tmp\\test.txt"), Path.of("D:\\tmp\\demo\\test.txt"));
} catch (IOException e) { }

ファイルの削除

こちらも割愛

try {
  Files.delete(Path.of("D:\\tmp\\move.txt"));
} catch (IOException e) { }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gradleに貢献してリリースノートに名前が載りました

概要

このたび、Gradleに貢献してリリースノートに名前が載りました。めっちゃうれしいです!
といっても、実はあまり胸を張れるような話ではないです。修正内容は地味ですし、詳細は後述しますが色々やらかしてだいぶご迷惑をおかけしてしまいました…
そんな体たらくなのに、一応コントリビュータとして扱ってくれるGradle開発元の皆様の懐の深さには感謝しかありません。

まあ、形はどうあれ私自身にとっては貴重な経験だったので、きっかけからマージされるまでの経過について記録を残すということで投稿します。

なお、該当のリリースノートはこちらです。
https://docs.gradle.org/6.6/release-notes.html

何をしたか

JUnit5で書かれたテストをGradleで実行した際に出力されるテスト結果のXMLの内容をちょっといじりました。具体的には @DisplayName アノテーションが付与されている場合はその値がXMLに出力されるようにしました。
@DisplayName とは、テストクラスやテストメソッドについて、テストの実行結果に表示される名前を設定する、JUnit5で導入されたアノテーションです。こんな感じで使います。

@DisplayName("計算クラスのテスト")
class CalcTest {
    @Test
    @DisplayName("addメソッドは2と3を渡すと5を返す")
    void testAdd() {
        var calc = new Calc();
        assertEquals(5, calc.add(2, 3));
    }
}

Screen Shot 2020-07-25 at 20.35.44.png

JUnit4まではそういった機能がなかったので、テストの実行結果ではテストメソッド名がテストの名前として出力されていました。テストの名前をわかりやすくするために、テストメソッド名を日本語で書く人もいたりしました。
@DisplayName を使えばテストメソッド名とは別にテストの名前を設定でき、テストの実行結果ではメソッド名ではなく @DisplayName で指定した名前が表示されるわけです。これにより、メソッド名を日本語で書くという、人によっては抵抗がある行為をしなくて済みます。

……と言いたいところなのですが、テスト結果を出力するツールが @DisplayName に対応していない場合があり、その場合は @DisplayName で設定した値は無視されてメソッド名が表示されてしまいます。
Gradleも @DisplayName に完全には対応しておらず1、テスト結果のXMLファイルについては @DisplayName の値を無視してテストのクラス名やメソッド名が出力される実装となっていたため、今回の修正に至ったわけです。

経過

きっかけ

(けっこう前の話なので記憶が曖昧な部分もあり、細部が実際と違うかもしれません)

うちのチームでは長らくJUnit4を使っていたのですが、JUnit5への移行を提案したところあっさり認められました。ただ、そのためには当然チームのみんながJUnit5を使えるようになる必要があるということで、チーム内で簡単にJUnit5勉強会を開催しました。
勉強会の中で件の @DisplayName についても言及しまして、 @DisplayName はできるだけ設定しましょうと偉そうに述べました。
そのすぐ後だったと思うのですが、チームのメンバーの一人から、Jenkinsのテスト結果の表示画面に @DisplayName の設定値が反映されないと指摘を受けました。確認してみると、たしかにその通りでした。 @DisplayName を使おうと呼び掛けた直後にこれだったので、少々ばつが悪い感じになりました…

原因調査

Jenkinsはテスト結果のXMLファイルを読み取ってテスト結果画面を表示するのですが、そのことはもともと知っていたので、まずそのXMLファイルの中身を覗いてみました。結果、XMLには @DisplayName の値ではなくクラス名やメソッド名が出力されていました。原因はJenkinsではなく、そのXMLファイルを生成したツールにありそうです。
どうやって突き止めたのか覚えてないですが、そのXMLを生成しているのはGradleであるとわかりました。つまりGradleが @DisplayName に対応していないことが原因でした。

なんとなくcloneしてみる

@DisplayName の値が無視されたところで大した影響はないのですが、なんとなく興味が湧いたのでGradleのソースをcloneしてみました。
XMLの要素名とか属性名で検索してみたところ、一箇所のみがヒットしました。つまりそこがまさにXMLの出力処理を行うコードでした。ざっと読んでみて、この程度の修正だったら俺でもできるんじゃね?と思い、挑戦してみることにしました。
以前、非常に軽微なドキュメント修正だけですがJenkinsに貢献したことがあったので、精神的なハードルは多少下がっていたのかもしれません。
OSS Gate東京ワークショップ2019-12-14に参加してJenkinsにコントリビュートした話

貢献方法を調べる

OSSかどうか調べる

OSSに貢献するには、まずはその対象が本当にOSSなのかどうかを確かめる必要があります。OSSじゃなかったら貢献もなにもないですからね…
OSSかどうかはライセンスを調べることでわかります。上の記事にも書きましたが(というか上の記事を見ながらこれを書いてますが)、ライセンスが下記の一覧の中にあればOSSだと言えるそうです。
https://opensource.org/licenses/alphabetical
そして、ライセンスは一般的には公式サイトかGitHubに書かれています。

GradleのライセンスはApache License 2.0ですね。
https://github.com/gradle/gradle/blob/master/LICENSE

Apache License 2.0はバッチリ一覧にありますので、GradleはOSSであると言えます。

Gradleへの貢献方法調査〜最初のプルリク

OSSに貢献する方法は、そのOSSごとに異なります。大抵は貢献方法が書かれたドキュメントがあるので、それを探します。
Gradleについては下記のようなドキュメントがありましたので、それに従って作業を進めました。
https://github.com/gradle/gradle/blob/master/CONTRIBUTING.md

当然全て英語なので、読むのにけっこう時間がかかりました…
通常はまずIssueを開くのですが、本件のIssueは他の方が既に開いていました。
https://github.com/gradle/gradle/issues/11445

それならこれに対する修正という形でプルリクを投げればいいだろうと思いました。修正は簡単で修正量も少ないように思えたので、いきなりプルリクを投げても大丈夫だろうと思いました。Jenkinsの時もそうだったし。
(まあ、その認識は甘過ぎたということを後ほど思い知ることになるのですが…)

修正を施し、UTを追加し、動作確認もして、翻訳ツールに頼りながら辿々しい英文を書き、ドキドキしながら最初のプルリクを投げました。

反応あり

プルリクを投げてから少し間が空きましたが、反応がありました。曰く、修正の影響でテストが失敗しているとのこと…
修正したソースが含まれるプロジェクトのテストについては成功することを確認していたのですが、別のプロジェクトのテストが失敗しているということでした。全く気づいてませんでした…大きな反省点の一つです。
私が行った修正では @DisplayName が付与されていない場合の結果が修正前と変わってしまっていました。具体的には、修正前はクラス名がFQNだったのですが、修正後はパッケージ名を含まないクラス名となってしまっていました。動作確認は行ったのですが、デフォルトパッケージのクラスで確認したため、修正前後で差分がなくて気付きませんでした。大変お恥ずかしい話です…

再修正

指摘を受けて、修正方法を検討しました。修正するには @DisplayName が付与されているかどうかを判定する必要があります。それくらい大したことないだろうと最初は思っていたのですが、実際にはけっこう大変だとわかりました。
どこかに @DisplayName の値を取得しているコードがあるはずなんですが想像以上に範囲が広くて、そのコードがどこにあるのか特定するのにめちゃくちゃ時間がかかりました。また、該当コードから最初に修正した箇所まで値を受け渡していく必要がありますが、その過程でけっこうな数のファイルを修正することになり、その影響でUTもあちこちで失敗して、なかなか苦労しました。それ以外にも様々な種類の実行時エラーを踏み抜きました。思った以上に大変な問題に首を突っ込んでしまったらしいと、プルリクを投げたことを若干後悔していましたが、最終的にはなんとか動くようになりました。

プルリクを投げた後に再修正をした場合、どういう操作を行えばいいのかわからなかったのですが、ググったらそれらしい情報がトップに出てきたので助かりました。
https://teratail.com/questions/122843

プルリクを修正したら、すぐに反応がありました。一部指摘を受けましたが、修正の方向性は間違ってないみたいな主旨のコメント(たぶん)をもらってうれしかったです。
とりあえず、指摘の箇所は単純に削除すればよさそうだったので、そのようにしました。

再レビュー依頼をかける

上記の修正後は非常に素早くレビューしてもらえたのですが、今回はなかなかレビューのコメントをもらえませんでした。なんでだろうと思っていたのですが、どうやら私がレビュー依頼をしていなかったのが理由だったようです…
修正したという主旨のコメントは書いていたのでそれでレビュー依頼をした気になっていたのですが、どうやらGitHubの仕組みに則ってレビュー依頼を出さないといけないようでした。
レビュアーの名前の右にある点をクリックするとレビュー依頼が出せるみたいなので、そうしました。

レビュアーによる修正〜マージへ

レビュー依頼をかけた後はわりとすぐに反応をもらえたのですが、私が投げたプルリクについてはクローズされてしまい、新たなプルリクが作られて、レビュアーが私の修正したコードをガッツリ再修正した後にマージされました。
レビュアーが直々にレビュイーのコードを修正するって一般的なことなんでしょうか?まあ、普通に考えれば少なくとも喜ばしいことではないですけど。コイツには任せておけんみたいな感じだったのかなあ…
修正されるということはつまり問題があるということなので、私はまだまだ実力が足りないのだなあと思いました。

まあ、修正されちゃったとはいえ一応私が書いたコードがマージされたというのは非常にうれしかったです。
また、一時はプルリクを投げたことをちょっと後悔していたくらいですから、なんとか最後まで終わったということで、肩の荷が下りた、解放されたという思いが強かったですw

反省点まとめ

  • 修正後の動作確認が不十分だった
    • その結果として修正難易度を見誤った(そのおかげで貢献できたとも言えますが…w)
  • テストが失敗しているのに気づかなかった
  • レビュー依頼をせずにレビュアーを無駄に待たせてしまった(仕組みを知らなかった。調査不足)
  • 上には書いてなかったが、よく考えたら修正用ブランチを作ってなかった

あと反省点ではないですが、力不足感が半端じゃないので修正差分を見て勉強させてもらおうと思います。(早くやれという感じですが…)

終わりに

本当は「OSSへの貢献は難しくないよ!みんなもやろう!」みたいな記事を書きたかったところですが、実際やってみたらけっこう難しかったです…(実力不足のせいでもありますし、そもそもピンキリだから簡単な場合もあるでしょうけど)

とりあえず、今回「OSSへの貢献」という経験を積むことができたので、今後また同様のことがあった時に多少は自信を持って取り組めるかなと思いました。修正されちゃったけど!

今後は、積極的に貢献のネタを探し回ったりはしないと思いますが、普段使っているOSSで何か問題を見つけたら、自分で直せる程度の問題なのかどうか確認するくらいのことはしようかなと思います。

おまけ

色々とお恥ずかしい内容ですが、該当のプルリクを晒しておきます。
https://github.com/gradle/gradle/pull/12541


  1. テスト結果を表示するHTMLファイルについては、私が手を付ける前から対応されてました。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 165 A問題「We Love Golf 」解説(Python3,C++,Java)

Ruteです。

AtCoder Beginner Contest 165 A問題「We Love Golf」の解説を行います。

問題URL:
https://atcoder.jp/contests/abc165/tasks/abc165_a

問題概要

(問題文を簡潔に表すと以下の通りです)
$A$以上$B$以下の整数の中に$K$の倍数が存在するかを判定せよ。
存在する場合は'OK'
存在しない場合は'NG'と出力せよ。

制約

・入力は全て整数
・$1 \leq A \leq B \leq 1000$
・$1 \leq K \leq 1000$

解法1(ループ)

この問題においてまず1つ目に思いつく解法は$(A,B)$の範囲の中に$K$の倍数が含まれているかをfor文のループで全通り調べるというものです。計算量は、$O(B)$になります。

解法2(B以下の最大のKの倍数を求める)

問題文は、$B$以下の最大の$K$の倍数が、$A$以上であるか というように言い換えることも出来ます。よってこれを満たしているかを条件分岐で判定することでACすることも可能です。計算量は判定だけなので$O(1)$ですが制約が小さいのでこのようなことをする必要はありません。

解き方が分かりやすいのは解法1の方なので、解法1の方での解答例をこの後示します。

以下、Python3,C++,Java それぞれの解答例です。

各言語解答例

Python3での解答例
ABC165A.py
K = int(input())
A,B = map(int,input().split())
flag = 0
#flagはフラグ関数(Kの倍数が存在したかどうか)
for i in range(A,B+1):
#(A,B)だとA以上B-1以下までを調べてしまうので注意!!
  if i%K == 0:
    flag += 1
    print("OK")
    break
if flag == 0:
  print("NG")

C++での解答例
ABC165A.cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
  int k,a,b;
  cin >> k;
  cin >> a >> b;
  int flag = 0;
  for (int i = a; i <= b; i++){
    if (i%k==0){
      flag++;
      cout << "OK" << endl;
      break;
    }
  }
  if (flag == 0){
    cout << "NG" << endl;
  }
}

Javaでの解答例
ABC165A.java
import java.util.Scanner;
public class Main{
  public static void main(String[] args){
    Scanner scan = new Scanner(System.in);
    int k = scan.nextInt();
    int a = scan.nextInt();
    int b = scan.nextInt();
    int flag = 0;
    for (int i = a; i <=b; i++){
      if (i%k==0){
        flag++;
        System.out.println("OK");
        break;
      }
    }
    if (flag == 0){
      System.out.println("NG");
    }
  }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 166 A問題「A?C」解説(Python3,C++,Java)

Ruteです。
AtCoder Beginner Contest 166 A問題「A?C」の解説を行います。

問題URL:
https://atcoder.jp/contests/abc166/tasks/abc166_a

問題概要

コンテストの開催ルールは以下のようである。(この問題における)
・ABCが開催された次の週にはARCが開催される
・ARCが開催された次の週にはABCが開催される

先週開催されたコンテストを表す文字列$S$が与えられる。
今週開催されるコンテストを表す文字列を出力せよ。

制約

・$S$は'ABC'または'ARC'である

解説

・ABCが開催された次の週にはARCが開催される
・ARCが開催された次の週にはABCが開催される

というルールから、
・$S$が'ABC'なら今週開催されるのはARC
・$S$が'ARC'なら今週開催されるのはABC

ということが分かります。
よって、文字列$S$を読み取って上の条件分岐通りの出力をすれば良いです。
$S$の2文字目を読みとってその条件分岐を行うという方法でもACすることは可能ですが、実装が楽なのは前述した方法です。

以下、Python3,C++,Javaでの解答例を示します。

各言語解答例

Python3での解答例
ABC166.py
S = input()
if S == "ABC":
  print("ARC")
else:
  print("ABC")

C++での解答例
ABC166A.cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
  string S;
  cin >> S;
  if (S == "ABC"){
    cout << "ARC" << endl;
  }else{
    cout << "ABC" << endl;
  }
}

Javaでの解答例
ABC166A.java
import java.util.Scanner;
public class Main{
  public static void main(String[] args)
  {
    Scanner scan =new Scanner (System.in);
    String S =  scan.nextLine();
    if (S.equals("ABC")){
      System.out.println("ARC");
    }else{
      System.out.println("ABC");
    }
  }
}

※ Javaでは文字列比較の時に==ではなく"文字列A".equals("文字列B")とコーディングしましょう。==という条件式の場合コンパイルエラーが発生する場合があります。後者の方だと文字列Aが文字列Bと同じかを正しく判定することが出来ます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CICS-Javaアプリケーションを動かす - (5)JCICSXの利用

はじめに

CICS Transaction ServerではJavaからCICS機能を利用するためのJCICSというクラスライブラリーが提供されています。これはいわゆるEXEC CICS APIのJava版とも言えるもので、LINKで他のCICSプログラムを呼び出したりTSQ/TDQなどのCICS管理リソースにアクセスできたりします。これを使用することでJavaでCICSのアプリケーションが書けるということになります。
2020/06/12に出荷されたCICS TS V5.6では、JCICSを拡張したJCICSXという新しいJava用のCICS APIが提供されるようになりました。ここではそのJCICSXを動かしてみた時の一連の流れを記載します。

関連記事

CICS-Javaアプリケーションを動かす - (1)単純なサンプルアプリの稼働
CICS-Javaアプリケーションを動かす - (2)Mavenによるビルド管理
CICS-Javaアプリケーションを動かす - (3)Gradleによるビルド管理
CICS-Javaアプリケーションを動かす - (4)Spring Bootアプリ
CICS-Javaアプリケーションを動かす - (5)JCICSXの利用

環境情報

開発環境
Windows10

実行環境
z/OS V2.4
CICS Transaction Server for z/OS V5.6

JCICSX概要

「JavaからCICS機能を使用するためのクラスライブラリー≒EXEC CICS APIのJava版」という位置づけはJCICSもJCICSXも同様です。
何が違うのかというと、JCICSXはローカルPC上(開発環境上)でのテストをしやすくするために拡張されたAPIである、という点です。
また、JCICSの場合割と広範囲にEXEC CICS相当の機能をサポートしていますが、JCICSXはまだ限定的で、Channel&ContainerおよびLink関連の機能のみしか提供されていません。
参考:
JCICS Javadoc
JCICSX Javadoc

"ローカルPC上(開発環境上)でテストしやすい"というのは具体的には以下の2つのポイントがあります。

(1) テスト用フレームワークを使用した単体テストが可能
image.png

ローカルPC上には当然CICSランタイムが無いので、CICS依存のコード(JCICSX部分)はそのままだと実行できません。DBアクセスなど環境依存のコードを含むモジュールの単体テストを行う場合、外部リソースアクセス部分をMockとして取り扱うようなフレームワーク(Mockitoなど)がありますが、それをCICSアクセス部分にも応用するという話になります。(これはMockitoなどのフレームワークで提供される機能の恩恵が主だと思います。)

(2) PC上のLibertyサーバー上で稼働確認が可能
こちらがCICSの拡張機能としては主要なものになりますが、ローカルPC上(開発環境上)のLibertyでJCICSXの稼働確認が行える仕組みを提供しています。イメージとしては以下の通りです。
image.png

この仕組みは"リモート開発"と呼ばれています。PC上のLibertyと、開発環境用にCICS-Libertyを用意しておき、それぞれJCICSXリモート開発用フィーチャーの構成をしておきます。このような構成をしておくことで、ローカルPC上のLibertyがあたかもCICS上で稼働しているかのように動作してくれます。そのため、トライ&エラーやテストを行う際に毎回ホスト側にアプリをデプロイして動作確認を行う必要は無く、ローカルPC上で動作確認、修正の細かいサイクルを回すことができます。
最終的にコードが固まったら、それをそのままテスト環境/本番環用の実CICS-Liberty環境にデプロイして稼働させることが可能です。

つまり、Channel&Containerでデータを受け渡してLinkで既存アプリを呼び出すLibertyアプリを作る場合にはJCICSXを使うと便利ですよ、ということになります(COMMAREA渡しのLINKは不可)。

以降、PC上のLibertyサーバーでJCICSXのテストを行うための構成手順を示します。

CICS-Libertyリモート開発環境構成

参考: Configuring the environment for JCICSX

CICS-Liberty側(サーバー側)の構成

JVMプロファイル

USS上にJVMプロファイルを準備します。(JVMServerに関するプロパティーは実質このUSS上のファイルに指定します。JVMSERVER資源定義ではこのJVMプロファイルのファイル名をポイントすることになります。)
サンプルが提供されているのでそれをコピーして使用します。

/var/cicsts/cicsts56/CT56B4A1/JVMProfiles というディレクトリを作成し、そこに/usr/lpp/cicsts/cicsts56/JVMProfiles/DFHWLP.jvmprofile(CICS導入ディレクトリ下に配置されているLiberty用のサンプル定義)を、DFHWLPX.jvmprofileという名前でコピーします。
環境に合わせて適宜カスタマイズします。

DFHWLPX.jvmprofile抜粋
JAVA_HOME=/usr/lpp/java/J8.0_64/
WORK_DIR=/var/cicsts/cicsts56/CT56B4A1/work
WLP_INSTALL_DIR=/usr/lpp/cicsts/cicsts56/wlp
-Dcom.ibm.cics.jvmserver.wlp.autoconfigure=true
-Dcom.ibm.cics.jvmserver.wlp.server.host=*
-Dcom.ibm.cics.jvmserver.wlp.server.http.port=56461
-Dcom.ibm.cics.jvmserver.wlp.server.https.port=56471
-Xms128M
-Xmx256M
-Xmso1M
-Xgcpolicy:gencon
-Xscmx256M
-Xshareclasses:name=cicsts%g,groupAccess,nonfatal
-Xtune:virtualized
-Dcom.ibm.tools.attach.enable=no
-Dfile.encoding=ISO-8859-1
_BPXK_DISABLE_SHLIB=YES

SIT

上のプロパティーファイルを配置したディレクトリを、SITパラメーター"JVMPROFILEDIR"に指定します。

JVMPROFILEDIR=/var/cicsts/cicsts56/CT56B4A1/JVMProfiles 

変更反映のためにリージョンを再起動します。

JVMServer定義

JVMSERVER資源定義を準備します。
製品提供のDFH$WLPというグループにあるJVMSERVER定義"DFHWLP"を適当なグループに"DFHWLPX"という名前にコピーしてカスタマイズします。

OBJECT CHARACTERISTICS                                    CICS RELEASE = 0730 
 CEDA  View JVmserver( DFHWLPX  )                                             
  JVmserver      : DFHWLPX                                                    
  Group          : TAGGRP                                                     
  DEScription    : CICS JVM server to run WLP samples                         
  Status         : Enabled            Enabled | Disabled                      
  Jvmprofile     : DFHWLPX                                        (Mixed Case)
  Lerunopts      : DFHAXRO                                                    
  Threadlimit    : 015                1-256                                   
 DEFINITION SIGNATURE                                                         
  DEFinetime     : 08/07/20 17:01:19                                          
  CHANGETime     : 08/07/20 17:01:36                                          
  CHANGEUsrid    : CICSUSER                                                   
  CHANGEAGEnt    : CSDApi             CSDApi | CSDBatch                       
  CHANGEAGRel    : 0730                                                                                                                    

※Jvmprofile: DFHWLPXとなっていますが、これはSITのJVMPROFILEDIRに指定されたディレクトリ下のDFHWLPX.jvmprofileというファイルがJVMプロパティーファイルとして使用されることを意味します。

一旦このJVMSERVERをインストールします。

CEMT I JVMSERVERで見てEnableになっていればOK。

I JVMS                                                
STATUS:  RESULTS - OVERTYPE TO MODIFY                 
 Jvm(DFHWLPX ) Ena     Prf(DFHWLPX ) Ler(DFHAXRO )    
    Threadc(012) Threadl( 015 ) Cur(65873224)              

JCICSXフィーチャー / サーバー側構成

JVMProfileで「com.ibm.cics.jvmserver.wlp.autoconfigure=true」を指定しているので、JVMServerインストール時に自動でLibertyが構成されます。

<WORK_DIR>/CT56B4A1/DFHWLPX/wlp/usr/servers/defaultServer/server.xmlが作成されているので、それを編集します。
以下のように、cicsts:jcicsxServer-1.0というフィーチャーを追加します。

server.xml
...
    <featureManager>
        <feature>cicsts:core-1.0</feature>
        <feature>cicsts:defaultApp-1.0</feature>
        <feature>jsp-2.3</feature>
        <feature>wab-1.0</feature>
        <feature>transportSecurity-1.0</feature>
        <feature>cicsts:jcicsxServer-1.0</feature>
    </featureManager>
...

JVMServerを再起動すると、Liberty起動時メッセージとして以下のようなログが確認できます。
(<WORK_DIR>/CT56B4A1/DFHWLPX/wlp/usr/servers/defaultServer/logs/messages.log)

messages.log
...
[8/7/20 8:13:40:387 GMT] 00000050 com.ibm.ws.webcontainer.osgi.webapp.WebGroup                 I SRVE0169I: Loading Web Module: com.ibm.cics.wlp.jcicsxserver.
[8/7/20 8:13:40:387 GMT] 00000050 com.ibm.ws.webcontainer                                      I SRVE0250I: Web Module com.ibm.cics.wlp.jcicsxserver has been bound to default_host.
[8/7/20 8:13:40:387 GMT] 00000050 com.ibm.ws.http.internal.VirtualHostImpl                     A CWWKT0016I: Web application available (default_host): http://xx.xx.xx:56461/jcicsxServer/
...

ここで示されているURLはPC側のLiberty構成で使用します。

ローカルPC上のLiberty(クライアント側)の構成

Eclipse/Liberty導入

Windows10上にEclipse環境をセットアップしてLibertyを導入します。
手順は以下の記事のものをそのまま使用します。
WebSphere Application Server Liberty Base - JCA接続環境構築メモ - 開発環境セットアップ(Windows)

JCICSX関連フィーチャー追加

Eclipseのサーバービューで対象のLibertyのフィーチャーマネージャーをダブルクリックしてserver.xmlを開き、フィーチャーリストの右の追加をクリック
image.png

Install additional featuresをクリック
image.png

フィルターに「JCICS」を指定すると、JCICSX development feature for Javaがリストされるので、インストールをクリックして次へ
image.png

ライセンスを確認して完了
image.png

image.png

フィーチャーがインストールされたので、jcicsxでフィルターして表示された user:jcicsxClient-1.0を選択してOK
image.png

フィーチャーとして追加されました。
image.png

server.xmlのソースをみるとこんな感じになっています。

server.xml
    <featureManager>
        <feature>jsp-2.3</feature>
        <feature>localConnector-1.0</feature>
        <feature>servlet-3.1</feature>
        <feature>jca-1.7</feature>
        <feature>jndi-1.0</feature>
        <feature>ejb-3.2</feature>
        <feature>usr:jcicsxClient-1.0</feature>
    </featureManager>
...

JCICSXフィーチャー / クライアント側構成

server.xmlを編集して以下の設定を追加します。

server.xml
...
    <!-- JCICSX Client Config -->
    <usr_jcicsxClient serverUri="http://etp1:56461"/>
...

serverUriのアドレスとポートは、JCICSXサーバー用の構成をしたCICS-Libertyのエンドポイントです。
server.xmlをデザインビューで見ると以下のように表示されます。
image.png

これで一通り"リモート開発"の構成は完了です。

JCICSXを使用したローカルPC上でのアプリケーションの開発

JCICSX開発環境整備

JCICSXのクラスライブラリーの提供のされかたはいくつかありますが、CICS ExplorerのCICS SDK for Javaにも含まれています。CICS Explorer機能をEclipseに組み込めば管理もしやすいので、Eclipse環境にCICS Explorerを入れることにします。

参考: Downloading and starting CICS Explorer

Eclipseのメニューから ヘルプ-新規ソフトウェアのインストールを選択
image.png

追加を押して、以下のリポジトリを追加します。
https://public.dhe.ibm.com/ibmdl/export/pub/software/htp/zos/tools/aqua3.2/」
image.png

CICS Explorerを選択して次へ
image.png

次へ
image.png

ライセンスを確認して完了
image.png

インストールに結構時間がかかります。途中こういう確認が出たりしますので適宜対応。
image.png

完了すると以下のポップアップが出るのでEclipse再起動
image.png

これでCICS Explorer機能がEclipseに組み込まれました。

サンプルアプリの取り込み

ここではGitHubに提供されているサンプルを動かしてみることにします。
GitHub: cics-java-jcicsx-samples
ローカルにクローンを作成します。

c:\y\workspace\cicsts56>git clone https://github.com/cicsdev/cics-java-jcicsx-samples.git
Cloning into 'cics-java-jcicsx-samples'...
remote: Enumerating objects: 361, done.
remote: Counting objects: 100% (361/361), done.
remote: Compressing objects: 100% (157/157), done.
Receiving objects:  76% (275/361)   ed 350 (delta 137), pack-reused 0
Receiving objects: 100% (361/361), 97.91 KiB | 331.00 KiB/s, done.
Resolving deltas: 100% (145/145), done.

いくつかあるサンプルのうち、「char-link-program-sample」をEclipseに取り込みます。

JavaEEパースペクティブのEnterprise Explorerビューを右クリック - インポート
Maven - 既存Mavenプロジェクトを選択
image.png

クローンしたディレクトリ下の「char-link-program-sample」を選択
image.png

プロジェクトが作成されました。
image.png

中身の確認

pom.xmlを見てみます。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.ibm.cics</groupId>
  <artifactId>char-link-program-sample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>jsr250-api</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>

    <!-- JCICSX dependency, used in CharLinkServlet.java -->
    <dependency>
      <groupId>com.ibm.cics</groupId>
      <artifactId>com.ibm.cics.jcicsx</artifactId>
      <version>1.000.0-5.6</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>

      <!-- The below bundles the application as a WAR in a CICS bundle and deploys this to CICS using the CICS bundle deployment API.
           This is optional and can be removed if you don't wish to deploy the application this way -->
      <plugin>
        <groupId>com.ibm.cics</groupId>
        <artifactId>cics-bundle-maven-plugin</artifactId>
        <version>1.0.0</version>
        <executions>
          <execution>

            <!-- These goals will firstly run the war packaging on the project, and then will run the deploy goal, which will happen during the verify phase of the lifecycle by default-->
            <goals>
              <goal>bundle-war</goal>
              <goal>deploy</goal>
            </goals>

            <configuration>
              <!-- The bundle classifier indicates that the war should be packaged into a CICS bundle -->
              <classifier>cics-bundle</classifier>

              <!-- Update the default JVM server that the application will be installed into by default, This is used when creating the bundle, and goes into the CICS bundle's manifest -->
              <jvmserver>DFHWLP</jvmserver>

              <!-- Set the URL of the deploy target -->
              <url>http://yourcicsurl.com:9080</url>

              <!-- We'd recommend that you use Maven's password encryption, or supply your credentials using environment variables or properties, as shown here. -->
              <username>${cics-user-id}</username>
              <password>${cics-password}</password>

              <!-- Identify which bundle definition you're going to use from the CSD and which region and CICSPlex you want to deploy to -->
              <bunddef>DEMOBUNDLE</bunddef>
              <csdgroup>BAR</csdgroup>
              <cicsplex>CICSEX56</cicsplex>
              <region>IYCWEMW2</region>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

JCICSXの依存関係が指定されています。
BUNDLE定義用の設定も含まれていますが、ここでは一旦そのまま置いておきます。

次にServletのソースを見てみます。

CharLinkServlet.java
package sample;

/* Licensed Materials - Property of IBM                                   */
/*                                                                        */
/* SAMPLE                                                                 */
/*                                                                        */
/* (c) Copyright IBM Corp. 2020 All Rights Reserved                       */
/*                                                                        */
/* US Government Users Restricted Rights - Use, duplication or disclosure */
/* restricted by GSA ADP Schedule Contract with IBM Corp                  */
/*                                                                        */

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ibm.cics.jcicsx.CICSConditionException;
import com.ibm.cics.jcicsx.CICSContext;

/**
 * A sample servlet to demonstrate how to use JCICSX to LINK to a CICS Program
 * with CHAR data
 */
@WebServlet("/SampleServlet")
public class CharLinkServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    /**
     * Name of the program to invoke.
     */
    private static final String PROG_NAME = "EDUCHAN";

    /**
     * Name of the channel to use.
     */
    private static final String CHANNEL = "MYCHANNEL";

    /**
     * Name of the container used to send data to the target program.
     */
    private static final String INPUT_CONTAINER = "INPUTDATA";

    /**
     * Name of the container which will contain the response from the target
     * program.
     */
    private static final String OUTPUT_CONTAINER = "OUTPUTDATA";

    /**
     * Data to place in the container to be sent to the target program.
     */
    private static final String INPUTSTRING = "Hello from Java";

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");

        response.getWriter().print("Hello world! ");

        // Message to emit as the response
        String resultStr = null;

        // Gets the current CICS Context for the environment we're running in
        CICSContext task = CICSContext.getCICSContext();

        try {
            // Create a reference to the Program we will invoke and specify the channel
            // Don't syncpoint between remote links, this is the default
            // Link to the program with an input container, containing the input string of
            // "Hello from Java"
            task.createProgramLinkerWithChannel(PROG_NAME, CHANNEL).setSyncOnReturn(false)
                    .setStringInput(INPUT_CONTAINER, INPUTSTRING).link();

            // Get the data from the output container as a string
            // You could remove task.getChannel(CHANNEL) and do this as one chained command
            // above, but this demonstrates how you could call this part later on in your
            // program
            resultStr = task.getChannel(CHANNEL).getCHARContainer(OUTPUT_CONTAINER).get();

            if (resultStr == null) {
                // Missing response container
                resultStr = "<missing>";
            }

            // Format the final message and print it
            String msg = "Returned from link to \'" + PROG_NAME + "\' with a text response of \'" + resultStr + "\'";
            response.getWriter().println(msg);

        } catch (CICSConditionException e) {
            response.getWriter().println("An exception has occured" + "\nRESP: " + e.getRespCode() + "\nRESP2: "
                    + e.getResp2() + "\nMessage: " + e.getMessage());
        }
    }

}

ここではJCICSXのクラスを使って、Channel&Containerに対する操作、および、プログラムのLINKを実施しています。
具体的には以下のような操作をしています。

  • "MYCHANNEL"というチャネルに"INPUTDATA"というコンテナを作って、"Hello from Java"という文字列を投入
  • "MYCHANNEL"を指定して"EDUCHAN"というCOBOLプログラムを呼び出す
  • "MYCHANNEL"の"OUTPUTDATA"というコンテナからデータを取得して結果を画面に表示

COBOLのソースも提供されているのでそちらも確認しておきます。

EDUCHAN
      *----------------------------------------------------------------*
      *  Licensed Materials - Property of IBM                          *
      *  SAMPLE                                                        *
      *  (c) Copyright IBM Corp. 2016 All Rights Reserved              *
      *  US Government Users Restricted Rights - Use, duplication or   *
      *  disclosure restricted by GSA ADP Schedule Contract with       *
      *  IBM Corp                                                      *
      *----------------------------------------------------------------*

      ******************************************************************
      *                                                                *
      * Module Name        EDUCHAN.CBL                                 *
      * Version            1.0                                         *
      * Date               22/10/2016                                  *
      *                                                                *
      * CICS back-end channel/container sample                         *
      *                                                                *
      * This program expects to be invoked with a CHAR container named *
      * INPUTDATA and returns the following containers:                *
      * A CHAR container containing the reversed input string          *
      * A CHAR container containing the time                           *
      * A BIT container containing the CICS return code from reading   *
      * the input container                                            *
      ******************************************************************

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EDUCHAN.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       DATA DIVISION.
       WORKING-STORAGE SECTION.

      *  Container name declarations
      *  Channel and container names are case sensitive
       01 DATE-CONT          PIC X(16) VALUE 'CICSTIME'.
       01 INPUT-CONT         PIC X(16) VALUE 'INPUTDATA'.
       01 OUTPUT-CONT        PIC X(16) VALUE 'OUTPUTDATA'.
       01 LENGTH-CONT        PIC X(16) VALUE 'INPUTDATALENGTH'.
       01 ERROR-CONT         PIC X(16) VALUE 'ERRORDATA'.
       01 RESP-CONT          PIC X(16) VALUE 'CICSRC'.


      *  Data fields used by the program
       01 INPUTLENGTH        PIC S9(8) COMP-4.
       01 DATALENGTH         PIC S9(8) COMP-4.
       01 CURRENTTIME        PIC S9(15) COMP-3.
       01 ABENDCODE          PIC X(4) VALUE SPACES.
       01 CHANNELNAME        PIC X(16) VALUE SPACES.
       01 INPUTSTRING        PIC X(72) VALUE SPACES.
       01 OUTPUTSTRING       PIC X(72) VALUE SPACES.
       01 RESPCODE           PIC S9(8) COMP-4 VALUE 0.
       01 RESPCODE2          PIC S9(8) COMP-4 VALUE 0.
       01 DATE-TIME.
         03 DATESTRING         PIC X(10) VALUE SPACES.
         03 TIME-SEP           PIC X(1) VALUE SPACES.
         03 TIMESTRING         PIC X(8) VALUE SPACES.
       01 RC-RECORD          PIC S9(8) COMP-4 VALUE 0.
       01 ERR-RECORD.
         03 ERRORCMD           PIC X(16) VALUE SPACES.
         03 ERRORSTRING        PIC X(32) VALUE SPACES.


       PROCEDURE DIVISION.
      *  -----------------------------------------------------------
       MAIN-PROCESSING SECTION.
      *  -----------------------------------------------------------

      *  Get name of channel
           EXEC CICS ASSIGN CHANNEL(CHANNELNAME)
                            END-EXEC.

      *  If no channel passed in, terminate with abend code NOCH
           IF CHANNELNAME = SPACES THEN
               MOVE 'NOCH' TO ABENDCODE
               PERFORM ABEND-ROUTINE
           END-IF.


      *  Read content and length of input container
           MOVE LENGTH OF INPUTSTRING TO INPUTLENGTH.
           EXEC CICS GET CONTAINER(INPUT-CONT)
                            CHANNEL(CHANNELNAME)
                            FLENGTH(INPUTLENGTH)
                            INTO(INPUTSTRING)
                            RESP(RESPCODE)
                            RESP2(RESPCODE2)
                            END-EXEC.

      *  Place RC in binary container for return to caller
           MOVE RESPCODE TO RC-RECORD.
           EXEC CICS PUT CONTAINER(RESP-CONT)
                            FROM(RC-RECORD)
                            FLENGTH(LENGTH OF RC-RECORD)
                            BIT
                            RESP(RESPCODE)
                            END-EXEC.

           IF RESPCODE NOT = DFHRESP(NORMAL)
             PERFORM RESP-ERROR
           END-IF.

      *  Place reversed string in output container
           MOVE FUNCTION REVERSE(INPUTSTRING) TO OUTPUTSTRING.

           EXEC CICS PUT CONTAINER(OUTPUT-CONT)
                            FROM(OUTPUTSTRING)
                            FLENGTH(LENGTH OF OUTPUTSTRING)
                            CHAR
                            RESP(RESPCODE)
                            END-EXEC.

           IF RESPCODE NOT = DFHRESP(NORMAL)
             PERFORM RESP-ERROR
           END-IF.

      *  Get the current time
           EXEC CICS ASKTIME ABSTIME(CURRENTTIME)
                            END-EXEC.

      *  Format date and time
           EXEC CICS FORMATTIME
                     ABSTIME(CURRENTTIME)
                     DDMMYYYY(DATESTRING)
                     DATESEP('/')
                     TIME(TIMESTRING)
                     TIMESEP(':')
                     RESP(RESPCODE)
                     END-EXEC.

      *  Check return code
           IF RESPCODE NOT = DFHRESP(NORMAL)
               STRING 'Failed' DELIMITED BY SIZE
                            INTO DATESTRING END-STRING
           END-IF.

      *  Place current date in container CICSTIME
           EXEC CICS PUT CONTAINER(DATE-CONT)
                            FROM(DATE-TIME)
                            FLENGTH(LENGTH OF DATE-TIME)
                            CHAR
                            RESP(RESPCODE)
                            END-EXEC.
      *  Check return code
           IF RESPCODE NOT = DFHRESP(NORMAL)
             PERFORM RESP-ERROR
           END-IF.



      *  Return back to caller
           PERFORM END-PGM.

      *  -----------------------------------------------------------
       RESP-ERROR.
             MOVE 'EDUC' TO ABENDCODE
             PERFORM ABEND-ROUTINE.

           PERFORM END-PGM.

      *  -----------------------------------------------------------
      *  Abnormal end
      *  -----------------------------------------------------------
       ABEND-ROUTINE.
           EXEC CICS ABEND ABCODE(ABENDCODE) END-EXEC.

      *  -----------------------------------------------------------
      *  Finish
      *  -----------------------------------------------------------
       END-PGM.
           EXEC CICS RETURN END-EXEC.


COBOLプログラムでは、INPUTDATAコンテナで受け取った文字列を逆順にしたものをOUTPUTDATAコンテナにセットして返しています。その他日時やRESP Codeを入れるコンテナを追加したりしています。

特に修正はせずにそのままソースは使用します。

COBOLアプリ準備

このサンプルは既存のCOBOLプログラムを呼び出すJavaプログラムを作成する、というシナリオなので、COBOLアプリは開発環境に準備しておく必要があります。COBOLソース(EDUCHAN)をz/OSに転送して、コンパイル/リンクし、リモート開発用CICS-Liberty構成を行ったCICSリージョンにプログラム定義を登録しておきます。これは従来のCICS-COBOLアプリケーションの手順と変わらないので、ここでは割愛します。

Javaアプリケーションのビルド/デプロイ/テスト

プロジェクトを右クリック - Maven - プロジェクトの更新を選択
プロジェクトを選択してOK
image.png

CharLinkServlet.javaを右クリック - 実行 - サーバーで実行
jcicsxの構成を行ったLiberty Serverを選択して次へ
image.png

完了
image.png

指定したLibertyが起動してアプリがデプロイされた後、ブラウザが開いてServletが実行されます。
image.png

正常に実行されました! "Hello from Java"という文字列が逆順になって返されています。すなわち、ローカルのLibertyで動かしたにも関わらず、CICS上のCOBOLプログラムが呼び出されたことが分かります。つまり、事前にJCICSXのリモート開発用構成を行っておけば、PC側の操作だけでLiberty上のテストまでできることが確認できました!
通常の開発プロセスとしてPC上で修正、デプロイ、テストが完結して行えることになります。

実CICS-Liberty環境へのデプロイ/テスト

CICS側準備

実際にCICS-Libertyアプリケーションを動かすためには、Liberty用のJVMSERVERの構成が必要になります。ここではJCICSXのリモート開発用のフィーチャーは必要ありませんので、通常通りのJVMSERVER構成をすればOKです。また、ここではMavenプロジェクトとしてDeployまで自動化する想定なので、ターゲットのCICSはCICSplexに属していて、WUIにはDeployment APIの構成を行っておく必要があります。その辺りは以下の"実行環境の準備"がそのまま該当します。
参考: CICS-Javaアプリケーションを動かす - (2)Mavenによるビルド管理

pom.xmlの編集

pom.xmlには、CICS Bundleプロジェクト用の構成も含まれています。実際のCICS環境にアプリをデプロイする場合その辺りの構成が必要になりますので上で設定した実行環境に合わせて編集します。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.ibm.cics</groupId>
  <artifactId>cics004-char-link-program-sample</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>jsr250-api</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>

    <!-- JCICSX dependency, used in CharLinkServlet.java -->
    <dependency>
      <groupId>com.ibm.cics</groupId>
      <artifactId>com.ibm.cics.jcicsx</artifactId>
      <version>1.000.0-5.6</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>

      <!-- The below bundles the application as a WAR in a CICS bundle and deploys this to CICS using the CICS bundle deployment API.
           This is optional and can be removed if you don't wish to deploy the application this way -->
      <plugin>
        <groupId>com.ibm.cics</groupId>
        <artifactId>cics-bundle-maven-plugin</artifactId>
        <version>1.0.0</version>
        <executions>
          <execution>

            <!-- These goals will firstly run the war packaging on the project, and then will run the deploy goal, which will happen during the verify phase of the lifecycle by default-->
            <goals>
              <goal>bundle-war</goal>
              <goal>deploy</goal>
            </goals>

            <configuration>
              <!-- The bundle classifier indicates that the war should be packaged into a CICS bundle -->
              <classifier>cics-bundle</classifier>

              <!-- Update the default JVM server that the application will be installed into by default, This is used when creating the bundle, and goes into the CICS bundle's manifest -->
              <jvmserver>DFHWLP</jvmserver>

              <!-- Set the URL of the deploy target -->
              <url>http://etp1:56002</url>

              <!-- We'd recommend that you use Maven's password encryption, or supply your credentials using environment variables or properties, as shown here. -->
              <username>TAG</username>
              <password>********</password>

              <!-- Identify which bundle definition you're going to use from the CSD and which region and CICSPlex you want to deploy to -->
              <bunddef>CHARLINK</bunddef>
              <csdgroup>TAGGRP</csdgroup>
              <cicsplex>C73PLX</cicsplex>
              <region>CT56B4A1</region>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

先頭のartifactId, versionも同一CICSplex環境でユニークになるよう修正しておきます。

BUNDLE定義の準備

上のpom.xmlの定義に合わせてCICS上にBUNDLE定義を作成します。

OBJECT CHARACTERISTICS                                    CICS RELEASE = 0730 
 CEDA  View Bundle( CHARLINK )                                                
  Bundle         : CHARLINK                                                   
  Group          : TAGGRP                                                     
  DEScription    : CICS-BUNDLE-DEMO                                           
  Status         : Enabled            Enabled | Disabled                      
  BUndledir      : /var/cicsts/cicsts56/bundles/cics004-char-link-program-sam 
  (Mixed Case)   : ple_1.0.0                                                  
                 :                                                            
                 :                                                            
                 :                                                            
  BAsescope      :                                                            
  (Mixed Case)   :                                                            
                 :                                                            
                 :                                                            
                 :                                                            
 DEFINITION SIGNATURE                                                         
  DEFinetime     : 08/11/20 16:05:22                                          
  CHANGETime     : 08/11/20 16:07:52                                          

ここではインストールは行いません。

JVMSERVERの準備

今回、開発環境用と本番環境用のリージョンは同じものを使いますが、JVMServerは分けています。開発環境用としてはDFHWLPXというJVMSERVER定義を使用していましたがこれはDiscardして、本番環境用のJVMServer: DFHWLP(JCICSX用の構成がされていないもの)のみをEnableにしておきます。

COBOLアプリケーションの準備

JavaアプリケーションからはEDUCHANというCOBOLプログラムをLINKしていますのでそのプログラムを呼び出せる状態にしておく必要があります。今回開発環境用と本番環境用は同一リージョンを使うので、COBOlプログラムも既にセットアップ済みの想定です。
リージョンを分けている場合は当然実環境にプログラム定義追加しておく必要があります。

Javaアプリケーションのビルド/デプロイ

Eclipseでpom.xmlを右クリック - 実行 - Maven installを選択
image.png

コンソールビューにてmvn installの実行結果が確認できます。
image.png

BUILD SUCCESSが出力されていればOKです。

念のため、CICS Explorerでバンドル、バンドルパーツがインストールされていることも確認しておきます。
image.png

実行

以下のURLでCICS-Libertyアプリにアクセス
http://etp1:56441/cics004-char-link-program-sample-1.0.0/SampleServlet
image.png

実CICS-Liberty環境でも同一のコードで稼働確認がとれました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 167 B問題「Easy Linear Programming」解説(Python3,C++,Java)

AtCoder Beginner Contest 167 B問題「Easy Linear Programming」の解説を行います。

問題URL:
https://atcoder.jp/contests/abc167/tasks/abc167_b

問題概要

$1$が書かれたカードが$A$枚、$0$が書かれたカードが$B$枚、$-1$が書かれたカードが$C$枚存在する。
これらのカードからちょうど$K$枚を選んで取るとき取ったカードに書かれた数の和として、ありうる最大値はいくつか答えよ。

制約

・入力は全て整数
・$0 \leq A,B,C$
・$1 \leq K \leq A+B+C \leq 2×10^9$

解説

最大値を可能な限り大きくするため$K$の値によってカードの取り方を以下のようにします。

1.$K \leq A$の場合
$K$枚の$1$が書かれたカードを選んで取れば良いです。

2.$A < K \leq A+B$の場合
1.で示した取り方を行った後に、残りの$(K-A)$枚の$0$が書かれたカードを選んで取れば良いです。

3.$A+B < K \leq A+B+C$の場合
2.で示した取り方を行った後に、残りの$(K-A-B)$枚の$-1$が書かれたカードを選んで取れば良いです。

1.の場合答えは$K$
2.の場合答えは$A$
3.の場合答えは$A-(K-A-B)$
となります。

$K$の値によって答えの出力を変えれば良いです。

なお、制約が
$A+B+C \leq 2×10^9$
となっていますが、
一般的なint型(C++やJava)で表せる数値の範囲は
$-2^{31}$~$2^{31}-1$まであり、
$2^{31} = 21474883647$であることから、
制約内では、$K$はint型で定義しても問題はないでしょう。

以下、Python3,C++,Javaでの解答例を示します。

各言語解答例

Python3での解答例
ABC167B.py
A,B,C,K = map(int,input().split())
if (K <= A):
  print(K)
elif (A < K <= A+B):
  print(A)
else:
  print(A-(K-A-B))

C++での解答例
ABC167B.cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
  int a,b,c,k;
  cin >> a >> b >> c >> k;
  if (k <= a){
    cout << k << endl;
  }else if (k <= a+b){
    cout << a << endl;
  }else{
    cout << a-(k-a-b) << endl;
  }
}

Javaでの解答例
ABC167B.java
import java.util.Scanner;
public class Main{
  public static void main(String[] args){
    Scanner scan = new Scanner(System.in);
    int a = scan.nextInt();
    int b = scan.nextInt();
    int c = scan.nextInt();
    int k = scan.nextInt();
    if (k <= a){
      System.out.println(k);
    }else if (k <= a+b){
      System.out.println(a);
    }else{
      System.out.println(a-(k-a-b));
    }
  }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

paizaスキルチェックを実施してみる

Javaのプログラム問題を実施するのに問題数も多く、個人的に利用頻度の高いpaizaを利用することにしました。
paizaにはスキルチェック問題があり、それらについては外部公開が許されているので「教材などにお使いください」と丁寧に書いてくれています。
またJava以外の言語も豊富なので、別言語を勉強したいときにも便利かと思います。
スクリーンショット 2020-08-11 14.22.07.png

スキルチェック問題

スクリーンショット 2020-08-11 15.49.26.png
ランクはBランクまで(外部公開なしの問題はSランクまで)が挑戦出来ます。

問題の出され方や条件がすぐに理解できない部分があったので、まずは地道に見本問題をやってみて感を掴んでいこうと思います。
レベルアップ問題なら時間制限がないので、忘れてしまったところも反復しやすいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【MQTT/Java】JavaでMQTTのPub/Subをするクラスを実装した

はじめに

MQTTをPython/Javaで使ってみたので投稿しています。
本記事は以下の3にあたります。まだの方は1をまずご覧ください。

  1. 【MQTT】コマンドベースでMQTTの導入(前々回)
  2. 【Python】PythonでMQTTのPub/Subをするクラスを実装した(前回)
  3. 【Java】JavaでMQTTのPub/Subをするクラスを実装した(本記事)
  4. 【ROS】MQTT通信するノードを実装した(次回)

ライブラリ、ブローカーのインストール

これは前々回の記事にあるのでまだインストールしていない人のために書いておきます。


ライブラリ、ブローカーのインストール

1. windowsの場合

以下のサイトの、「Binary Installation」⇒「Windows」のところから自身の環境に合わせてインストーラーをダウンロードしてください。
https://mosquitto.org/download/
image.png

2. Linuxの場合

以下の2つのコマンドを実行してください。

# Mosquitto(Broker)をインストール
$ sudo apt-get install mosquitto

# Mosquittoクライアントをインストール
$ sudo apt-get install mosquitto-clients

クライアントライブラリのインストール

Javaのクライアントライブラリのインストール方法は以下になります。
こちらのリンクから
org.eclipse.paho.client.mqttv3_1.2.3.jar
をダウンロードしてください。


PublisherとSubscriberと別スレッド処理のコード

Pythonの記事と同じように、ROSOpenRTMなど、別で動作しているシステムから呼び出したいです。
PythonではSubscriberを立ち上げるさいにloop_start()とすると別スレッドの処理が始まるようだったのですがJavaではそうではないようなので(ここは不明点です。どなたか簡単に別スレッドで処理ができるようでしたら教えてください。)別スレッドで処理をするクラスも作っています。

よって、本記事のプログラムは以下のプログラムから成り立ちます。

  • Publisher(別システムから呼び出す)
  • Subscriber(別スレッドとして呼び出される)
  • Thread(Subscriberを呼び出す)

こちらの記事をとても参考にしました。

Publisher

MqttPublisher.java
package mqtt;

import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class MqttPublisher {
    String broker = "";
    String topic  = "";

    /**
     * コンストラクタ 
     * @param brokerHostName
     * @param publishTopic
     */
    public MqttPublisher(String brokerHostName,String publishTopic) {
        broker = "tcp://"+brokerHostName+":1883";
        topic  = publishTopic;
    }

    /**
     * 引数をpublishする.
     * @param publishMessage
     */
    public void publish(String publishMessage) {
        final int qos             = 2;
        final String clientId     = "Publisher";
        try {
            MqttClient mqttClient = new MqttClient(broker, clientId, new MemoryPersistence());
            MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setCleanSession(false);

            mqttClient.connect(connOpts);

            MqttMessage message = new MqttMessage(publishMessage.getBytes());
            message.setQos(qos);
//            System.out.println("publish message");
//            System.out.println("Topic : "+topic+", Message : "+message);
            mqttClient.publish(topic, message);

            mqttClient.disconnect();
            mqttClient.close();
        } catch(MqttException me) {
            System.out.println("reason: "   + me.getReasonCode());
            System.out.println("message: "  + me.getMessage());
            System.out.println("localize: " + me.getLocalizedMessage());
            System.out.println("cause: "    + me.getCause());
            System.out.println("exception: "+ me);
        }
    }

    public static void main(String[] args) {
        MqttPublisher publisher = new MqttPublisher("localhost","testTopic2");
        publisher.publish("test");
    }   
}

main関数にあるように、インスタンス生成時にホスト名、トピック名を指定してpublishメソッドでメッセージをpublishします。

Subscriber

MqttSubscriber.java
package mqtt;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Timestamp;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class MqttSubscriber  implements MqttCallback {
    Timestamp recieveTime;
    Timestamp lastTime;
    String broker = "";
    String topic  = "";

    /**
     * コンストラクタ
     * @param brokerHostName
     * @param subscribeTopic
     */
    public MqttSubscriber(String brokerHostName,String subscribeTopic) {
        broker = "tcp://"+brokerHostName+":1883";
        topic  = subscribeTopic;
    }

    /**
     * MQTTブローカーとの接続を失った時に呼び出される.
     */
    @Override
    public void connectionLost(Throwable cause) {
        System.out.println("Connection lost");
        System.exit(1);
    }

    /**
     * メッセージを受信したときに呼び出される.
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) throws MqttException {
//        System.out.println("Message arrived");
//        System.out.println("Topic:"+ topic);
//        System.out.println("Message: " + new String(message.getPayload()));

        recieveTime = new Timestamp(System.currentTimeMillis());
        MqttThread.recieveData = new String(message.getPayload());
    }

    /**
     * Subscribeしたか否かを判断する.
     * @return isNewフラグ
     */
    public boolean isNew() {
        boolean flag = false;
        if(recieveTime==lastTime) flag = false;
        else flag = true;
        lastTime=recieveTime;
        return flag;
    }

    public static void main(String[] args) throws InterruptedException {
        try {
            MqttSubscriber subscriber = new MqttSubscriber("localhost","testTopic1");
            subscriber.subscribe();
        } catch(MqttException me) {
            System.out.println("reason: "   + me.getReasonCode());
            System.out.println("message: "  + me.getMessage());
            System.out.println("localize: " + me.getLocalizedMessage());
            System.out.println("cause: "    + me.getCause());
            System.out.println("exception: "+ me);
        }
    }

    /**
     * メッセージを受信する.
     * 標準入力があるまで接続し続ける.
     * 
     * @throws MqttException
     * @throws InterruptedException 
     */
    public void subscribe() throws MqttException, InterruptedException {
        //Subscribe設定
        final int qos             = 2;
        final String clientId     = "Subscribe";

        MqttClient client = new MqttClient(broker, clientId, new MemoryPersistence());
        client.setCallback(this);
        MqttConnectOptions connOpts = new MqttConnectOptions();
        connOpts.setCleanSession(false);

        System.out.println("Connecting to broker:"+broker);
        client.connect(connOpts);

        client.subscribe(topic, qos);

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try{
            //標準入力を受け取るまで待ち続ける
            br.readLine();
        }catch(IOException e){
            System.exit(1);
        }
        client.disconnect();
        client.close();
        System.out.println("Disconnected");
    }

    /**
     * MqttCallbackに必要,subscribeからは呼び出されなさそう.
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }
}

こちらもpublisherと同様にインスタンス生成時にホスト名とトピック名を指定してsubscribeメソッドでsubscribeを開始しします。

スレッド処理

Threadを継承して、Subscribeの処理をスレッド処理で行うクラスです。

MqttThread.java
package mqtt;

import org.eclipse.paho.client.mqttv3.MqttException;

public class MqttThread extends Thread{
    String broker = "";
    String topic  = "";
    static MqttSubscriber subscriber;
    public static String recieveData = "";

    //コンストラクタ
    public MqttThread(String brokerHostName,String subscribeTopic) {
        broker = brokerHostName;
        topic  = subscribeTopic;
        subscriber = new MqttSubscriber(broker, topic);
    }

    public void run() {
        try {
            subscriber.subscribe();
        } catch(MqttException me) {
            System.out.println("reason: "   + me.getReasonCode());
            System.out.println("message: "  + me.getMessage());
            System.out.println("localize: " + me.getLocalizedMessage());
            System.out.println("cause: "    + me.getCause());
            System.out.println("exception: "+ me);
        } catch (InterruptedException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }   
    }

    public boolean isNew() {
        boolean flag = false;
        flag = subscriber.isNew();
        return flag;
    }
}

使用例

Subscribeの処理は別スレッド、Pythonと同様にsubscribe時に呼び出されるcallback関数内(messageArrived関数)で処理を行います。
Publishの処理は都度関数を呼び出してpublishします。

package main;

import mqtt.MqttThread;
import mqtt.MqttPublisher;

public class testMQTTClient {

    public static void main(String[] args) {
            MqttThread mthread = new MqttThread("ホスト名","トピック名");
            mthread.start();
            // 1回だけpublish
            MqttPublisher publisher = new MqttPublisher("ホスト名","トピック名");
            publisher.publish("メッセージ内容");
            if(mthread.isNew()){
                System.out.println(mthread.recieveData);
            }
    }
}

コンストラクタ呼び出し時(インスタンス生成時)にホスト名とトピック名を指定します。

mthread.start()でsubscribeの処理が別スレッドで動きます。
publisher.publish("メッセージ")で1回だけpublishされます。
subscribeされたデータ内容にはmthread.receiveDataでアクセスできます。Pythonのものと同様にisNew()関数とセットで使ってください。

前々回の記事のPub/Subの起動の部分を参考に、PublisherとSubsrriberを起動して動作確認できると思います。

おわりに

今回はJavaでの実装例を紹介しました。
Pub/Sub単体での動作ではなく、実際にほかのシステムから動かす際に使えるようにしてみました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jar_アーカイブの操作_メモ_20200811

jar-アーカイブの操作

事前基礎知識

    ・アーカイブとは

      複数のファイルやフォルダを一つのファイルにまとめたもの

1.jarコマンドとは

  Windowsの実行プログラムは、通常はexeの拡張子を持った単一のファイルです。
  一方、Javaで開発された実行可能プログラムの姿は、複数のクラスファイル(.class)の集合です。

  このように「複数のクラスファイルの集合」という形式では、
  別の開発者に渡すときにファイルの受け渡し漏れなどの事故が発生する可能性もあります。
  筆者も開発現場で実際に細かくクラスファイルを分けてもらってということもありました。

  そこで、それらのファイル一式を1つにまとめるアーカイブ形式(事前基礎知識を参照)として、
  JAR(Java Archive)が定められています。

  JDKに標準添付されているjarコマンドを使えば、複数のクラスファイルやプロパティファイルなどを
  まとめて1つのJARファイルにしたり、逆にJARファイルの中身をもとのクラスファイルなどに展開したり
  できます。(図1)

図1
コメント 2020-08-11 114326.png

1.jarコマンドの基本的な使い方

  jarコマンドで通常行うのは
  「アーカイブの作成」「アーカイブの展開」「アーカイブ内容の閲覧」です。
  それぞれ下記のコマンドで実行します

  ・アーカイブの作成

     >jar -cvf JARファイル名 ファイルおよびフォルダ・・・

  ・アーカイブの展開
    
    >jar -xvf JARファイル名

  ・アーカイブ内容の閲覧

    >jar -tvf JARファイル名

  たとえば、Test1.classとTest2.classをまとめてTest.jarを作りたい場合、次のようなコマンドを実行します。

  >jar -cvf Test.jar Test1.class Test2.class

2.マニフェストファイル

  zipやlhaといったアーカイブ形式の圧縮ファイルを使ったことがある人はもちろんいるでしょう。
  実はJAR形式はzip形式とほとんど同じようなものです。
  (JARファイルの拡張子をzipに書き換えると、zipファイルを展開するソフトで問題なく展開することができます)

  ただしJARファイルには、アーカイブ内に含まれる様々なファイルに関する追加情報を記述した
  マニフェストファイルを含めることが一般的です。

  マニフェストはテキストファイル形式であり、アーカイブ内のMETA-INFフォルダ中の
  MANIFEST.MFという名前で格納する決まりになっています。

3.JARファイル利用のメリット

  ・ファイル領域の効率の良さ

    圧縮すれば必要なファイル領域が減って効率が上がる

  ・セキュリティ

    JARファイルは署名を施すことができるため、セキュリティ上の制限がある程度まで和らげることができる

間違っている知識などございましたら、ご教示いただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 167 A問題「Registration」解説(Python3,C++,Java)

AtCoder Beginner Contest 167 A問題「Registration」の解説を行います。

問題URL:
https://atcoder.jp/contests/abc167/tasks/abc167_a

問題概要

文字列$S$,$T$が与えられる。
$T$が$S$の末尾に1文字追加した文字列であるか判定せよ。

$T$が条件を満たすならばYes
条件を満たさなければNoと出力せよ。

制約

・$S,T$は英小文字列
・$1 \leq |S| \leq 10$
・$|T| = |S| + 1$

解説

文字列$T$について、$|S|$文字目までのスライスを取得し、それが$S$と等しいかどうかを判定すればよいです。
ここで、スライスとは、文字列の一部のことを指します。

スライスの取得方法

Python3では T[i:j+1]で$T$の$i$文字目から$j$文字目までのスライスを取得することができます。
(ここで、0-indexであることに注意して下さい)

[コラム]0-index,1-indexとは?

コンピューター上では、数字のカウンタは1ではなく0から始めます。ですので、私たちが処理する文字列は、私たちにとっては1文字目から数えますが、コンピューターにとっては0文字目なのです。
コンピューター的な配列(文字列含む)の数え方を0-indexといいます。
それに対して、私たちが普段している配列の数え方を1-indexといいます。
今後も解説でよく使用する言葉ですので覚えておきましょう。

本題に戻ります。
C++では、T.substr(i,j)で$T$の$i$番目から$j$文字分のスライスを取得することが出来ます。(iは0-indexです)
Javaでは、T.substring(i,j+1)で$T$の$i$番目から$j$番目までのスライスを取得することができます。(i,jは0-indexです)

これらの関数を用いて$T$のスライスを取得し、$S$と同じかどうか判定すれば良いでしょう。

以下、Python3,C++,Javaでの解答例を示します。

各言語解答例

Python3での解答例
ABC167A.py
S = input()
T = input()
if T[0:len(S)] == S:
  print("Yes")
else:
  print("No")

C++での解答例
ABC167A.cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
  string S,T;
  cin >> S >> T;
  string Judge = T.substr(0,S.size());
  if (Judge == S){
    cout << "Yes" << endl;
  }else{
    cout << "No" << endl;
  }
}

Javaでの解答例
ABC167A.java
import java.util.Scanner;
public class Main{
  public static void main(String[] args){
    Scanner scan = new Scanner(System.in);
    String S = scan.nextLine();
    String T = scan.nextLine();
    String judge = T.substring(0,S.length());
    if (judge == S){
      System.out.println("Yes");
    }else{
      System.out.println("No");
    }
  }
}

実は、このコードを実行すると、WAになります。(分かる方は)理由は分かりますか?

理由を知りたい方は調べてみて下さい。

正解コードは以下の通りです。

ABC167A_AC.java
import java.util.Scanner;
public class Main{
  public static void main(String[] args){
    Scanner scan = new Scanner(System.in);
    String S = scan.next();
    String T = scan.next();
    String judge = T.substring(0,S.length());
    if (S.equals(judge)){
      System.out.println("Yes");
    }else{
      System.out.println("No");
    }
  }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java学習について調べたこと

Javaの実務経験は1年未満。
それ以外の言語の経験がそこそこあると、他の言語を勉強したくてもif文などをやり直しても・・・という気持ちになります。
今回は練習問題を使ってただコードを書く量を増やしていきたいと思い、練習問題サイト探してみました。

プログラミング練習サイト

「xx(言語) 練習問題」で検索するとまとめられたサイトはたくさんあるのですが、私の目的「プログラミングする練習問題があるサイト」に絞り掲載させていただきます。

1.北ソフト工房

http://kitako.tokyo/lib/JavaExercise.aspx
スクリーンショット 2020-08-11 13.38.32.png
以前から何度かお世話になったことがあるサイトです。
基本問題が多く応用問題は少ないように思うのですが、「C、Java練習
プログラム集」にはゲームを作りながら学ぶ問題が少しあります。

2.paiza

https://paiza.jp/
スクリーンショット 2020-08-11 13.47.12.png
練習問題も多く常に追加されており、ランク分けされているので難易度も分かりやすいです。
一部から有料になりますが、動画を見ながら学習もできるので初めて言語を学ぶの方もやりやすいと思います。

3.JavaRU - Java練習問題

https://it-level-up.club/category/java-practice/
スクリーンショット 2020-08-11 14.00.48.png
使用したことはありませんが、現在も更新中のサイトです。
練習問題も多く、細かく分類化されているので学びたい部分がわかりやすく学習できるのではと思います。

今回使用するサイト

今回はpaizaを選びました。
理由は
1、問題がランク分けされているので難易度がわかりやすい
2、環境を作る必要がないので、すぐに取り掛かれる
3、以前から定期的に使用している
4、対応している言語が多く、今後を考えても利用しやすい
です。
また転職サイトと連携しているのでコードの外部公開はしてないかと思ったのですが、一部問題に関しては外部公開を許可されています。
https://paiza.jp/works/mondai
スクリーンショット 2020-08-11 14.22.07.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 173 B問題「Judge Status Summary」解説(Python3,C++,Java)

AtCoder Beginner Contest 173 B問題「Judge Status Summary」の解説を行います。

問題URL : https://atcoder.jp/contests/abc173/tasks/abc173_b

問題概要

テストケースのジャッジ結果を表す文字列$S_i$が$N$個与えられる。
ジャッジ結果が'AC','WA','TLE','RE'であったものの個数をそれぞれ求めよ。

制約

・$1 \leq N \leq 10^5$
・$S_i$は'AC','WA','TLE','RE'のいずれか

解説

問題文の通りに実装を行えばよいです。
例えば、
・'AC'である個数を$C_0$,
・'WA'である個数を$C_1$,
・'TLE'である個数を$C_2$,
・'RE'である個数を$C_3$,
という4つの変数を作成し、
それぞれを0で初期化し、
$N$個の$S_i$について条件分岐を行って変数を管理します。
その後、出力例のように出力すればACすることが出来ます。

以下、Python3,C++,Javaでの解答例を示します。

各言語解答例

Python3での解答例
B.py
N = int(input())
c0 = 0
c1 = 0
c2 = 0
c3 = 0
for i in range(N):
  S = input()
  if S == "AC":
    c0 += 1
  elif S == "WA":
    c1 += 1
  elif S == "TLE":
    c2 += 1
  elif S == "RE":
    c3 += 1
print("AC", "x",c0)
print("WA", "x",c1)
print("TLE", "x",c2)
print("RE", "x",x3)

C++での解答例
B.cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
  int n;
  cin >> n;
  int c0 = 0;
  int c1 = 0;  
  int c2 = 0;
  int c3 = 0;
  for (int i = 0; i < n; i++){
    string S;
    cin >> S;
    if (S == "AC"){
      c0 += 1;
    }else if (S == "WA"){
      c1 += 1;
    }else if (S == "TLE"){
      c2 += 1;
    }else if (S == "RE"){
      c3 += 1;
    }
  }
  cout << "AC" << " " << "x" << " " << c0 << endl;
  cout << "WA" << " " << "x" << " " << c1 << endl;
  cout << "TLE" << " " << "x" << " " << c2 << endl;
  cout << "RE" << " " << "x" << " " << c3 << endl;
}

Javaでの解答例
B.java
import java.util.Scanner;
public class Main{
  public static void main(String[] args){
    Scanner scan = new Scanner(System.in);
    int n = scan.nextInt();
    int c0 = 0;
    int c1 = 0;
    int c2 = 0;
    int c3 = 0;
    for (int i = 0; i < n; i++){
      String S = scan.next();
      if (S.equals("AC")){
        c0 += 1;
      }else if (S.equals("WA")){
        c1 += 1;
      }else if (S.equals("TLE")){
        c2 += 1;
      }else if (S.equals("RE")){
        c3 += 1;
      }
    }
    System.out.println("AC"+" "+"x"+" "+c0);
    System.out.println("WA"+" "+"x"+" "+c1);
    System.out.println("TLE"+" "+"x"+" "+c2);
    System.out.println("RE"+" "+"x"+" "+c3);
  }
}

※S.equals("文字列A")でSが文字列Aと等しいかを判定することが出来ます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Java 奇数の要素と偶数の要素の和の出力

はじめに

 今年の2月からプログラミングスクールへ通い、晴れて7月にIT企業に就職した者です。
学習した言語のアウトプットをしたくて記事の投稿を始めてみようと思いました!
 今回はプログラミングスクールで学習しなかったJavaに関する記事を投稿します。

開発環境

今回は"Eclipse"という総合開発環境でやってみました。
こちらの記事を参考に開発環境を構築してみてください。

EclipseでJava開発環境を作る(Mac編)
※今回はMacでの作業となります。

実装

class Main {
  public static void main(String[] args) {

    int[] numbers = {1, 5, 8, 10, 13, 18};
    int oddSum = 0;
    int evenSum = 0;

    for (int number : numbers) {
      if (number % 2 == 0) {
        evenSum += number;
      } else {
        oddSum += number;
      }
    }

    System.out.println("奇数の和は" + oddSum + "です");
    System.out.println("偶数の和は" + evenSum + "です");
  }
}

上記コードを上から順に解説します!

 Mainクラスの中にmainメソッドを定義します。

class Main {
public static void main(String[] args) {

 int型の要素を持つ配列に変数numberに好きな整数をいくつか書きます。

int[] numbers = {1, 5, 8, 10, 13, 18};

 int型に変数oddSumとevenSumを定義し両変数の中には0を入れます。
 ※oddSumは奇数の和、evenSumは偶数の和という意味です。

int oddSum = 0;
int evenSum = 0;

 for文で繰り返し処理をさせif文で条件分岐を作ります。
 今回は"拡張for文"というものを使用します。

for(データ型 変数 : 配列名) {
 繰り返し処理;
 }

 文の中にはint型の変数numberと配列numbersを記入します。
 if文の中には"numberが偶数の場合"と条件をつけます。
 ※+=とは例えばa += bの場合 a = a + b と同じということです。

for (int number : numbers) {
if (number % 2 == 0) {
evenSum += number;
} else {
oddSum += number;
}
}

 そして最後に出力をしてあげます!

System.out.println("奇数の和は" + oddSum + "です");
System.out.println("偶数の和は" + evenSum + "です");

 Mainクラスとmainメソッドの閉じかっこも忘れずに!

}
}

実行

奇数の和は19です
偶数の和は36です
と表示されていれば成功です!

エラーになってしまう場合

可能性としては";"が抜けている場合があると思いますので確認してみてください!

最後に

私も絶賛勉強中ではありますがプログラミングを勉強中の皆様、
根気強く、そしてコーディングが楽しくなるように一緒に頑張っていきましょう!
最後まで見てくださりありがとうございました!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JUnit5対応】Spring boot 2.2, 2.3でJUnit5を使ったテストを書く

本記事は 『Spring boot2.2以上で、JUnit5に対応』 について記載してます。
下記の用途に参考になればー!

  1. Spring boot 2.1以下から、2.2以上にアップデート
  2. JUnit4からJUnit5への乗り換え
  3. JUnit5を記述する際、事前にJUnit4を除外したい

0. 前提

環境

  • Java 8〜(Spring boot2.2以上では、Java8, 11にサポートしています)
  • Spring boot 2.2〜

知っておくといい知識

  • Spring boot 2.2以上からは、JUnit5がデフォルト
  • JUnit4と、JUnit5は基本的に互換はない
  • 現状JUnit4と5が共存できるようになっている(いずれなくなる)

1. JUnit5を使うための依存関係

  • やること
    • junit-vintage-engineを除外する(JUnit4が動くようにする依存のため)

例1. Mavenの場合

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

例2. Gradleの場合

testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}

2. JUnit4関係の記述を修正

  • @RunWith が廃止された
    • 対応 ... 削除 or @ExtendWithに変更
  • @Test のimport元が変わった
    • 対応前 ... import org.junit.Test;
    • 対応後 ... import org.junit.jupiter.api.Test;
  • @Before が廃止された
    • 対応 ... @BeforeEach or @BeforeAllに変更

(参考)

↓ 参考にしたサイトです
- Spring Boot 2.2 Release Notes · spring-projects/spring-boot Wiki · GitHub
- JUnit 5 User Guide

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GlassFish 5.1 + Servlet + JSP で Hello World

概要

  • Java Servlet と JSP を含む WAR ファイルを Gradle で作成する
  • GlassFish 5.1.0 をインストールする
  • WAR ファイルを GlassFish 5.1.0 にデプロイする
  • 動作確認環境: Java 8 (AdoptOpenJDK 1.8.0_265) + GlassFish 5.1.0 (Java Servlet 4.0 + JavaServer Pages 2.3) + macOS Catalina + Gradle 6.5.1

GlassFish 5.1 とは

GlassFish 5.1 は Jakarta EE 8 と Java EE 8 に準拠した Web アプリケーションサーバ。Java Servlet 4.0 や JavaServer Pages 2.3 に対応している。

Eclipse GlassFish

Eclipse GlassFish is a Jakarta EE compatible implementation sponsored by the Eclipse Foundation. Eclipse GlassFish 5.1 is also Java EE 8 Compatible.

Java 8 をインストールする

Java 8 をインストールしていない場合は、Homebrew などでインストールしておき、 環境変数 JAVA_HOME と PATH を設定する。

$ brew tap AdoptOpenJDK/openjdk

$ brew cask install adoptopenjdk8

$ /usr/libexec/java_home -v 1.8
/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home

$ export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home

$ export PATH=${JAVA_HOME}/bin:${PATH}

Java 11 や Java 14 では以下のようなエラーが発生し Glassfish 5.1.0 が起動しないので注意。

$ ./glassfish5/bin/asadmin start-domain
Exception in thread "main" java.lang.NullPointerException
    at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.initializeServiceLocator(AbstractModulesRegistryImpl.java:128)
    at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.newServiceLocator(AbstractModulesRegistryImpl.java:120)
    at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.createServiceLocator(AbstractModulesRegistryImpl.java:194)
    at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.createServiceLocator(AbstractModulesRegistryImpl.java:200)
    at com.sun.enterprise.module.single.StaticModulesRegistry.createServiceLocator(StaticModulesRegistry.java:64)
    at com.sun.enterprise.admin.cli.CLIContainer.getServiceLocator(CLIContainer.java:193)
    at com.sun.enterprise.admin.cli.CLIContainer.getLocalCommand(CLIContainer.java:231)
    at com.sun.enterprise.admin.cli.CLICommand.getCommand(CLICommand.java:207)
    at com.sun.enterprise.admin.cli.AdminMain.executeCommand(AdminMain.java:347)
    at com.sun.enterprise.admin.cli.AdminMain.doMain(AdminMain.java:282)
    at org.glassfish.admin.cli.AsadminMain.main(AsadminMain.java:33)

Java Servlet と JSP を含む WAR ファイルを Gradle で作成する

ファイル一覧

├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── MyServlet.java
        └── webapp
            ├── WEB-INF
            │   └── web.xml
            └── myjsp.jsp

build.gradle

plugins {
  id 'war'
}

repositories {
  mavenCentral()
}

dependencies {
  // Java Servlet 4.0 API
  // https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
  providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
}

// Java 8
sourceCompatibility = 1.8

// Application
version = '1.0'

src/main/java/com/example/MyServlet.java

package com.example;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/myservlet")
public class MyServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
    res.setContentType("text/html; charset=utf-8");
    try (PrintWriter out = res.getWriter()) {
      out.println("<html><body>");
      out.println("サーブレット: Hello Servlet World!<br>");
      out.println(getServletContext().getServerInfo());
      out.println("</body></html>");
    }
  }
}

src/main/webapp/myjsp.jsp

<%@ page contentType="text/html; charset=utf-8" %><html><body>
ジェイエスピー: Hello JSP World!<br>
<%= pageContext.getServletContext().getServerInfo() %><br>
java.vm.name: <%= System.getProperty("java.vm.name") %><br>
java.vm.vendor: <%= System.getProperty("java.vm.vendor") %><br>
java.vm.version: <%= System.getProperty("java.vm.version") %><br>
</body></html>

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- Web Application Deployment Descriptor (Java Servlet 4.0) -->
<web-app
  xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">

  <servlet>
    <servlet-name>myjsp</servlet-name>
    <jsp-file>/myjsp.jsp</jsp-file>
  </servlet>

  <servlet-mapping>
    <servlet-name>myjsp</servlet-name>
    <url-pattern>/myjsp</url-pattern>
  </servlet-mapping>

</web-app>

WAR ファイルを作成する

Gradle の build タスクで WAR ファイルを作成する。

$ gradle build

WAR ファイルが生成されていることを確認する。

$ file build/libs/mywebapp-1.0.war 
build/libs/mywebapp-1.0.war: Zip archive data, at least v1.0 to extract

GlassFish 5.1.0 をインストールする

公式のマニュアル Installing Eclipse GlassFish Server 5.1 に従ってインストールする。

Eclipse GlassFish | projects.eclipse.org から glassfish-5.1.0.zip (Eclipse GlassFish 5.1.0, Full Profile) をダウンロードする。

インストール先のディレクトリにダウンロードした glassfish-5.1.0.zip を展開する。

$ unzip glassfish-5.1.0.zip

zip ファイルをダウンロードして展開するのみでインストールが完了する。

GlassFish を起動する

Quick Start for Basic Features - Starting and Stopping the Default Domain に従って起動する。

$ ./glassfish5/bin/asadmin start-domain
Waiting for domain1 to start ......
Successfully started the domain : domain1
domain  Location: /Users/foo/glassfish5/glassfish/domains/domain1
Log File: /Users/foo/glassfish5/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.

起動すると Administration Console http://localhost:4848/ で GlassFish の状態を管理できる。

WAR ファイルを GlassFish 5.1.0 にデプロイする

asadmin deploy コマンドでデプロイできる。コマンドの詳細はリファレンスマニュアル deploy に載っている。

$ ./glassfish5/bin/asadmin deploy --contextroot=mywebappcr --name=mywebappname /Users/foo/mywebapp/build/libs/mywebapp-1.0.war
Application deployed with name mywebappname.
Command deploy executed successfully.

デプロイされた WAR ファイルはインストール先のディレクトリ以下にコピーされて展開される。

$ find . | grep mywebapp
./glassfish5/glassfish/domains/domain1/generated/xml/mywebappname
./glassfish5/glassfish/domains/domain1/generated/ejb/mywebappname
./glassfish5/glassfish/domains/domain1/generated/policy/mywebappname
./glassfish5/glassfish/domains/domain1/generated/policy/mywebappname/mywebappname
./glassfish5/glassfish/domains/domain1/generated/policy/mywebappname/mywebappname/granted.policy
./glassfish5/glassfish/domains/domain1/generated/jsp/mywebappname
./glassfish5/glassfish/domains/domain1/applications/mywebappname
./glassfish5/glassfish/domains/domain1/applications/mywebappname/myjsp.jsp
./glassfish5/glassfish/domains/domain1/applications/mywebappname/META-INF
./glassfish5/glassfish/domains/domain1/applications/mywebappname/META-INF/MANIFEST.MF
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes/com
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes/com/example
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes/com/example/MyServlet.class
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/web.xml
./glassfish5/glassfish/domains/domain1/applications/__internal/mywebappname
./glassfish5/glassfish/domains/domain1/applications/__internal/mywebappname/mywebapp-1.0.war

動作確認

デプロイした Web アプリケーションの動作を確認する。

(Servlet 4.0 に対応しているはずなのに Servlet/3.1 と出力されるのはちょっと気になる)

$ curl --include http://localhost:8080/mywebappcr/myservlet
HTTP/1.1 200 OK
Server: GlassFish Server Open Source Edition  5.1.0 
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  5.1.0  Java/AdoptOpenJDK/1.8)
Content-Type: text/html;charset=utf-8
Content-Length: 118

<html><body>
サーブレット: Hello Servlet World!<br>
GlassFish Server Open Source Edition  5.1.0 
</body></html>
$ curl --include http://localhost:8080/mywebappcr/myjsp
HTTP/1.1 200 OK
Server: GlassFish Server Open Source Edition  5.1.0 
X-Powered-By: JSP/2.3
Set-Cookie: JSESSIONID=0123456789abcdef0123456789ab; Path=/mywebappcr; HttpOnly
Content-Type: text/html;charset=utf-8
Content-Length: 230

<html><body>
ジェイエスピー: Hello JSP World!<br>
GlassFish Server Open Source Edition  5.1.0 <br>
java.vm.name: OpenJDK 64-Bit Server VM<br>
java.vm.vendor: AdoptOpenJDK<br>
java.vm.version: 25.265-b01<br>
</body></html>

Web アプリケーションをアンデプロイする

asadmin undeploy コマンドでデプロイした Web アプリケーションを取り除くことができる。

$ ./glassfish5/bin/asadmin undeploy mywebappname
Command undeploy executed successfully.

GlassFish を停止する

asadmin stop-domain コマンドで停止できる。

$ ./glassfish5/bin/asadmin stop-domain
Waiting for the domain to stop .
Command stop-domain executed successfully.

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java]MinecraftのModを作成しよう 1.14.4【9. 木の追加と生成】

(この記事は一連の解説記事の一つになります)

先頭記事:入門編
前の記事:8. 鉱石の追加と生成
次の記事:

木の追加

前記事では鉱石の追加について学びました。次は木を追加し、ワールドに生成させてみましょう。だいぶ複雑になってきますので、一緒に頑張っていきましょう。

原木の追加

まず気を構成する原木ブロックを追加します。2. ブロックの追加を参考にブロックを追加していきますが、ところどころ異なる点があるので順にみていきましょう。

BlockList.java
//...
public class BlockList {
    public static Block ExampleLog = new LogBlock(
            MaterialColor.CYAN,
            Block.Properties.create(Material.WOOD, MaterialColor.BLUE)
                    .hardnessAndResistance(2.0F)
                    .sound(SoundType.WOOD)
                    .lightValue(6))
            .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_log"));

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().registerAll(
                ExampleLog
        );
    }

    @SubscribeEvent
    public static void registerBlockItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().registerAll(
                new BlockItem(ExampleLog, new Item.Properties().group(ExampleItemGroup.DEFAULT))
                        .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_log"))
        );
    }
}

基本的には同じですが、単なるBlockクラスではなく、LogBlockクラスでつくります。
これはBlockクラスのサブクラスRotatedPillarBlockのサブクラスです。RotatedPillarBlockBlockに回転に関するものを追加したものです。そしてLogBlockではさらにマップ上での表示に関するものを追加しているようです。
コンストラクタの引数は1番目に一つ増えて、MaterialColorクラスでマップ上で表示する色を渡します。Block.Properties.create()の方でも引数が増えて色を渡していますが、これは非垂直設置時のマップ上の色で、先程のほうが垂直設置時の色になるようです。これらは適当に選びましょう。


例のごとくresourcesの設定をしていきますが、原木ブロックは面によってテクスチャが異なるため、この設定を以下に示します。これらはminecraftの原木ブロックを参考にしているので特別なことをしない限りこれでよいです。

\src\main\resources
   ├ assets
   │  └ example_mod
   │     ├ blockstates
   │     │  └ example_log.json
   │     ├ lang
   │     │  └ en_us.json
   │     │  └ ja_jp.json
   │     ├ models
   │     │  ├ block
   │     │  │  └ example_log.json
   │     │  └ item
   │     │     └ example_log.json
   │     └ textures
   │        ├ blocks
   │        │  ├ example_log.png
   │        │  └ example_log_top.png
   │        └ items
   └ data
      └ example_mod
         └ loot_tables
            └ blocks
               └ example_log.json
blockstates\example_log.json
{
  "variants": {
    "axis=y": { "model": "example_mod:block/example_log" },
    "axis=z": { "model": "example_mod:block/example_log", "x": 90 },
    "axis=x": { "model": "example_mod:block/example_log", "x": 90, "y": 90 }
  }
}

このように書くことで、おそらくLogBlock(厳密にはRotatedPillarBlock)クラスが持つメンバ変数axis(面の法線方向)がx,y,zのいずれかによって条件分岐するようになっています。(公式ドキュメントにもそれっぽいことが書いてありますね。)
"x":90"y":90などの記述は回転角です。要するに、垂直設置状態を基準に原木を倒した表示になるよう回転させています。

models\block\example_log.json
{
  "parent": "block/cube_column",
  "textures": {
    "end": "example_mod:blocks/example_log_top",
    "side": "example_mod:blocks/example_log"
  }
}

parentにはblock/cube_columnを指定します。これによって立方体型で上下面と側面で区別したテクスチャの適用ができます。それぞれのテクスチャファイルへのパスを指定しましょう。

models\item\example_log.json
{
  "parent": "example_mod:block/example_log"
}

これは今まで通りblockのモデルファイルを引き継げばよいです。

en_us.jp
{
  "block.example_mod.example_log": "Example Log"
}
ja_jp.json
{
  "block.example_mod.example_log": "例原木"
}

言語ファイルも今まで通りです。

\loot_table\blocks\example_log.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "example_mod:example_log"
        }
      ]
    }
  ]
}

いつも通り。

キャプチャ.PNG
このように方向を持つブロックが追加できました。


もう一つ原木ブロックに関してやっておくことがあります。
ブロックの追加自体はここまでで完了していますが、あとで木を構成していく段階で必要になるので、ここで行っておきましょう。

今追加した原木ブロックをminecraft:logsのタグに追加します。
6. レシピの追加でも軽く触れましたが、「タグ」とはすなわち共通因子を持つオブジェクトたちをまとめたグループです。minecraftには原木に相当するものをまとめたタグがあり、木に関する処理をする際にこれを使います。そのため、この原木ブロックもこのタグに含めておくと無駄な実装をしなくて済みます。

\src\main\resources
   ├ assets
   └ data
      ├ example_mod
      └ minecraft
         └ tags
            └ blocks
               └ logs.json

自分のプロジェクトフォルダ内に\src\main\resources\data\minecraft\tags\blocksフォルダを作り、そこにlogs.jsonを配置します。この名前は必ず同じにしてください。

logs.json
{
  "replace": false,
  "values": [
    "example_mod:example_log"
  ]
}

replacefalseを与えることにより、同名のminecraft:logsにこのファイルでの記述が統合されます。valuesの中でブロックを指定しましょう。

キャプチャ.PNG
デバッグモードでブロックにカーソルを合わせた際、赤下線を引いた部分のように#minecraft:logsと表示されていれば完了です。

葉の追加

次に木を構成する葉ブロックを追加します。重ねてになりますが、ベースは2. ブロックの追加を参考に、順にみていきましょう。

BlockList.java
//...
public class BlockList {
    public static Block ExampleLeaves = new LeavesBlock(
            Block.Properties.create(Material.LEAVES)
                    .hardnessAndResistance(0.2F)
                    .tickRandomly()
                    .sound(SoundType.PLANT)
                    .lightValue(8))
            .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_leaves"));

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().registerAll(
                ExampleLeaves
        );
    }

    @SubscribeEvent
    public static void registerBlockItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().registerAll(
                new BlockItem(ExampleLeaves, new Item.Properties().group(ExampleItemGroup.DEFAULT))
                        .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_leaves"))
        );
    }
}

葉ブロックはLeavesBlockでつくります。その他はいつも通りです。


resourcesの設定をしていきます。

\src\main\resources
   ├ assets
   │  └ example_mod
   │     ├ blockstates
   │     │  └ example_leaves.json
   │     ├ lang
   │     │  └ en_us.json
   │     │  └ ja_jp.json
   │     ├ models
   │     │  ├ block
   │     │  │  └ example_leaves.json
   │     │  └ item
   │     │     └ example_leaves.json
   │     └ textures
   │        ├ blocks
   │        │  └ example_leaves.png
   │        └ items
   └ data
      └ example_mod
         └ loot_tables
            └ blocks
               └ example_leaves.json
blockstates\example_leaves.json
{
  "variants": {
    "": { "model": "example_mod:block/example_leaves" }
  }
}
models\block\example_leaves.json
{
  "parent": "block/leaves",
  "textures": {
    "all": "example_mod:blocks/example_leaves"
  }
}

parentにはblock/leavesを指定しましょう。(グラフィック設定にもよりますが)半透明なテクスチャが適用できます。

models\item\example_leaves.json
{
  "parent": "example_mod:block/example_leaves"
}
en_us.jp
{
  "block.example_mod.example_leaves": "Example Leaves"
}
ja_jp.json
{
  "block.example_mod.example_leaves": "例の葉"
}

これらはいつも通り。

\loot_table\blocks\example_log.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "condition": "minecraft:alternative",
                  "terms": [
                    {
                      "condition": "minecraft:match_tool",
                      "predicate": {
                        "item": "minecraft:shears"
                      }
                    },
                    {
                      "condition": "minecraft:match_tool",
                      "predicate": {
                        "enchantments": [
                          {
                            "enchantment": "minecraft:silk_touch",
                            "levels": {
                              "min": 1
                            }
                          }
                        ]
                      }
                    }
                  ]
                }
              ],
              "name": "example_mod:example_leaves"
            },
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "condition": "minecraft:survives_explosion"
                },
                {
                  "condition": "minecraft:table_bonus",
                  "enchantment": "minecraft:fortune",
                  "chances": [
                    0.05,
                    0.0625,
                    0.083333336,
                    0.1
                  ]
                }
              ],
              "name": "example_mod:example_sapling"
            }
          ]
        }
      ]
    }
  ]
}

loot_tableファイルは今まで結構異なります。(今まではさぼってきましたが)葉ブロックは複数のドロップをもちエンチャントやツールにも依存するのできちんと実装するとこのようになります。
詳しくは参考ページに説明を譲りますが、簡単に説明します。
これは「ブロックからのドロップであり、数は1つ、childrenのどれかを返す。childrenは2つあって、まずハサミあるいはシルクタッチⅠのツールで壊した場合に葉ブロックを返すもの。次にそれ以外の場合のランダム確率(幸運エンチャで確率アップ)で苗を返すもの。」という風に書かれています。ここで先に出てしまい順番が前後しますが、次の項で苗の追加を行います。
これはminecraftのOrkの葉を参考にしています。各自色々と見て好きなように書きましょう。


最後に、MaterialColorの設定をします。これは葉や草がバイオームに応じて色が変わる効果をもたらすものです。ここは面倒な場合無視してもよいです。無視する場合全バイオームで同じ色が表示されるのでその色でテクスチャを用意しましょう。

BlockList.java
//...
public class BlockList {
    //...
    @SubscribeEvent
    public static void registerBlockColors(ColorHandlerEvent.Block event) {
        event.getBlockColors().register((p_210229_0_, p_210229_1_, p_210229_2_, p_210229_3_) -> {
            return p_210229_1_ != null && p_210229_2_ != null ? BiomeColors.getFoliageColor(p_210229_1_, p_210229_2_) : FoliageColors.getDefault();
        }, ExampleLeaves);
    }

    @SubscribeEvent
    public static void registerBlockItemColors(ColorHandlerEvent.Item event) {
        event.getItemColors().register((p_210235_1_, p_210235_2_) -> {
            BlockState blockstate = ((BlockItem)p_210235_1_.getItem()).getBlock().getDefaultState();
            return event.getBlockColors().getColor(blockstate, (IEnviromentBlockReader)null, (BlockPos)null, p_210235_2_);
        }, ExampleLeaves);
    }
}

宣言と登録をしているBlockList.javaで同時にこれも登録してしまいます。ColorHandlerEventというものがあるので、これをつかってregister()することで反映されます。ブロックとアイテムそれぞれについて行います。変数などが意味不明な文字の羅列になっていますが、これは難読化されたminecraftのコードからそのまま引っ張ってきただけなので変えてもよいです。
ブロックについては、BiomeColors.getFoliageColor()によってそのブロックが存在するバイオームに応じた色が取得され、登録されます。
アイテムについては、デフォルトの色が取得され、登録されます。
両者ともに、register()の第一引数に色(ラムダ式で取得してます)、第二引数に色を設定するオブジェクトを渡します。
この辺りは難しいことをやろうとしない限り深く理解しなくてよいです。

実際の色については、参考ページに詳しく書いてあります。基本的にはグレースケールの葉ブロックテクスチャを用意して色を載せているようですが、今回色味がかったファイルを用意してみた場合実際のブロックもその色味になったので、おそらく内部では加算してるのかな、と思っています。

TreeFeatureクラスとTreeクラスの追加

これらは違いを言葉で説明するのが難しいですが、ともに木を管理するクラスです。Treeクラスは木自体を管理するクラスで、この後で追加する苗に関連して必要になります。一方で、TreeFeatureクラスは木の生成に関することのみを管理しており、Treeクラスから該当するTreeFeatureを取得することもできます。

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   │   └ trees
   │      └ ExampleTree.java
   ├ items
   ├ lists
   ├ world
   │   └ features
   │      └ ExampleTreeFeature.java
   └ ExampleMod.java

(ファイル配置はいろいろ参考にして合わせているつもりですが、悩んだら割と適当です。)

ExampleTreeFeature.java
package jp.koteko.example_mod.world.features;

import jp.koteko.example_mod.lists.BlockList;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.feature.TreeFeature;
import net.minecraftforge.common.IPlantable;

public class ExampleTreeFeature extends TreeFeature {
    public ExampleTreeFeature() {
        super(NoFeatureConfig::deserialize, false, 4, BlockList.ExampleLog.getDefaultState(), BlockList.ExampleLeaves.getDefaultState(), false);
        setSapling((IPlantable) BlockList.ExampleSapling);
    }
}

TreeFeatureクラスを継承してExampleTreeFeatureクラスを作ります。継承元のコンストラクタに渡す引数は順に以下の通りです。
第一引数はNoFeatureConfigのインスタンスで多分特に有効に使われません。
第二引数は何らかの通知の可否を決定するboolean値で、他の木々でfalseだったため真似しました。何かわかったら追記します。
第三引数は木の最小の高さを決めるint値です。
第四引数は幹となるブロックのBlockStateです。
第五引数は葉となるブロックのBlockStateです。
第六引数は木にツタを伸ばすかどうかを決めるboolean値です。
また、setSapling()は苗の設定をしており、ここに苗のインスタンスを渡します。苗は次の項で実装します。

ExampleTree.java
package jp.koteko.example_mod.blocks.trees;

import jp.koteko.example_mod.world.features.ExampleTreeFeature;
import net.minecraft.block.trees.Tree;
import net.minecraft.world.gen.feature.AbstractTreeFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;

import javax.annotation.Nullable;
import java.util.Random;

public class ExampleTree extends Tree {
    @Nullable
    protected AbstractTreeFeature<NoFeatureConfig> getTreeFeature(Random random) {
        return new ExampleTreeFeature();
    }
}

抽象クラスTreeを継承してExampleTreeクラスを作ります。getTreeFeatureというメソッドを定義します。先ほど定義したExampleTreeFeatureのインスタンスを返すようにしましょう。
引数で乱数を受け取って使っていませんが、これはオークの木などが確率で通常種と巨大種に分かれる場合などに使われています。

苗の追加

次に苗を追加していきます。
まず苗のクラスを用意します。

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   │   ├ trees
   │   └ ExampleSapling.java
   ├ items
   ├ lists
   ├ world
   └ ExampleMod.java
BlockExampleSapling.java
package jp.koteko.example_mod.blocks;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.BushBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.trees.Tree;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;

import java.util.Random;

public class BlockExampleSapling extends BushBlock implements IGrowable {
    public static final IntegerProperty STAGE = BlockStateProperties.STAGE_0_1;
    protected static final VoxelShape SHAPE = Block.makeCuboidShape(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
    private final Tree tree;

    public BlockExampleSapling(Tree p_i48337_1_, Block.Properties properties) {
        super(properties);
        this.tree = p_i48337_1_;
        this.setDefaultState(this.stateContainer.getBaseState().with(STAGE, Integer.valueOf(0)));
    }

    public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) {
        return SHAPE;
    }

    public void tick(BlockState state, World worldIn, BlockPos pos, Random random) {
        super.tick(state, worldIn, pos, random);
        if (!worldIn.isAreaLoaded(pos, 1)) return; // Forge: prevent loading unloaded chunks when checking neighbor's light
        if (worldIn.getLight(pos.up()) >= 9 && random.nextInt(7) == 0) {
            this.grow(worldIn, pos, state, random);
        }

    }

    public void grow(IWorld worldIn, BlockPos pos, BlockState state, Random rand) {
        if (state.get(STAGE) == 0) {
            worldIn.setBlockState(pos, state.cycle(STAGE), 4);
        } else {
            if (!net.minecraftforge.event.ForgeEventFactory.saplingGrowTree(worldIn, rand, pos)) return;
            this.tree.spawn(worldIn, pos, state, rand);
        }

    }

    /**
     * Whether this IGrowable can grow
     */
    public boolean canGrow(IBlockReader worldIn, BlockPos pos, BlockState state, boolean isClient) {
        return true;
    }

    public boolean canUseBonemeal(World worldIn, Random rand, BlockPos pos, BlockState state) {
        return (double)worldIn.rand.nextFloat() < 0.45D;
    }

    public void grow(World worldIn, Random rand, BlockPos pos, BlockState state) {
        this.grow(worldIn, pos, state, rand);
    }

    protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) {
        builder.add(STAGE);
    }
}

このコードについてですが、(ほぼ)すべてminecraftの苗のコードと同じです。じゃあ何故わざわざ作ったか、というと、minecraftのSaplingクラスのコンストラクタがprotectedで、modのコード内から直接使えなかったためです。minecraft側がこれをどう扱っているかは理解する時間が不足していたためここでは考えないことにしますが、おそらくそちらに合わせた形でのもう少しうまい実装の仕方があると思います。
パッケージ名、クラス名、コンストラクタの3つのみ変更して使います。

BlockList.java
//...
public class BlockList {
    public static Block ExampleSapling = new BlockExampleSapling(
            new ExampleTree(),
            Block.Properties.create(Material.PLANTS)
                    .doesNotBlockMovement()
                    .tickRandomly()
                    .hardnessAndResistance(0.0F)
                    .sound(SoundType.PLANT))
            .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_sapling"));

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().registerAll(
                ExampleSapling
        );

    }

    @SubscribeEvent
    public static void registerBlockItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().registerAll(
                new BlockItem(ExampleSapling, new Item.Properties().group(ExampleItemGroup.DEFAULT))
                        .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_sapling"))
        );
    }
}

先程用意した苗のクラスでブロックを追加します。
第一引数は対応する木を渡すので、先ほど作ったExampleTreeクラスのインスタンスを渡します。

いつも通りresourcesの設定をしていきます。特に変わらないものは解説を省略します。

\src\main\resources
   ├ assets
   │  └ example_mod
   │     ├ blockstates
   │     │  └ example_sapling.json
   │     ├ lang
   │     │  └ en_us.json
   │     │  └ ja_jp.json
   │     ├ models
   │     │  ├ block
   │     │  │  └ example_sapling.json
   │     │  └ item
   │     │     └ example_sapling.json
   │     └ textures
   │        ├ blocks
   │        │  └ example_sapling.png
   │        └ items
   └ data
      └ example_mod
         └ loot_tables
            └ blocks
               └ example_sapling.json
blockstates\example_sapling.json
{
  "variants": {
    "": { "model": "example_mod:block/example_sapling" }
  }
}
models\block\example_sapling.json
{
    "parent": "block/cross",
    "textures": {
        "cross": "example_mod:blocks/example_sapling"
    }
}

parentにはblock/crossを指定します。これによって平面が交差したような形でテクスチャの適用ができます(実際に形を見れば言ってる意味が分かると思います)。

models\item\example_sapling.json
{
    "parent": "item/generated",
    "textures": {
        "layer0": "example_mod:blocks/example_sapling"
    }
}

blockのモデルファイルを引き継ぐのではなく、item/generatedで指定します。

en_us.jp
{
  "block.example_mod.example_sapling": "Example Sapling"
}
ja_jp.json
{
  "block.example_mod.example_sapling": "例の苗"
}
\loot_table\blocks\example_sapling.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "example_mod:example_sapling"
        }
      ]
    }
  ]
}

ゲームを起動して確認してみましょう。
キャプチャ.PNG
苗が追加され、骨粉で木が育つことが確認できると思います。
キャプチャ.PNG
また、木の幹を除去すると、葉ブロックが自然消滅をはじめ、消える際に確率で苗をドロップすることが確認できると思います。

木の生成

ここまで来たらあと少しです。ここまでで実装した木をワールド生成時に自動で生成されるようにしましょう。
これは8. 鉱石の追加と生成とほぼ同じことをするのでそちらも参考にしてください。

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   ├ items
   ├ lists
   ├ world
   │   ├ WorldGenOres.java
   │   └ WorldGenTrees.java
   └ ExampleMod.java

WorldGenTrees.javaを配置します。

WorldGenOres.java
package jp.koteko.example_mod.world;

import jp.koteko.example_mod.world.features.ExampleTreeFeature;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.gen.GenerationStage;
import net.minecraft.world.gen.feature.Feature;
import net.minecraft.world.gen.feature.IFeatureConfig;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.placement.AtSurfaceWithExtraConfig;
import net.minecraft.world.gen.placement.Placement;
import net.minecraftforge.registries.ForgeRegistries;

public class WorldGenTrees {
    public static void setup() {
        addTreeToOverworld(new ExampleTreeFeature());
    }

    private static void addTreeToOverworld(Feature<NoFeatureConfig> featureIn) {
        for(Biome biome : ForgeRegistries.BIOMES) {
            if (!biome.getCategory().equals(Biome.Category.NETHER) && !biome.getCategory().equals(Biome.Category.THEEND)) {
                biome.addFeature(
                        GenerationStage.Decoration.VEGETAL_DECORATION,
                        Biome.createDecoratedFeature(
                                featureIn,
                                IFeatureConfig.NO_FEATURE_CONFIG,
                                Placement.COUNT_EXTRA_HEIGHTMAP,
                                new AtSurfaceWithExtraConfig(2, 0.1F, 1)
                        )
                );
            }
        }
    }
}

AtSurfaceWithExtraConfigの引数は、1チャンク当たりの抽選回数、追加抽選を行う可能性、追加抽選を行う場合の追加回数です。この例の場合、「1チャンク当たり2か所抽選を行い、10%の確率でさらに1回抽選を行う」です。

最後に、今定義したWorldGenOres.setup()をメインファイル内のsetup中で呼びます。

ExampleMod.java
//...
public class ExampleMod
{
    //...
    private void setup(final FMLCommonSetupEvent event)
    {
        WorldGenTrees.setup();
    }
    //...
}

ゲームを起動して新たにワールドを生成します。
2020-08-09_17.46.30.png
なんとなく光らせておいたので夜だと一層目立ちます。

木の追加と生成ができました!

参考

1.14.3 Tags help - Modder Support - Forge Forums
バイオーム - Minecraft Wiki
ルートテーブル - Minecraft Wiki
地図アイテムフォーマット - Minecraft Wiki

次の記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む