- 投稿日:2020-08-11T23:54:20+09:00
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.うまくいく場合
最終的にうまくいった設定。上の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
- 投稿日:2020-08-11T21:40:32+09:00
【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) { }
- 投稿日:2020-08-11T20:58:58+09:00
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)); } }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/LICENSEApache 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
テスト結果を表示するHTMLファイルについては、私が手を付ける前から対応されてました。 ↩
- 投稿日:2020-08-11T20:08:15+09:00
AtCoder Beginner Contest 165 A問題「We Love Golf 」解説(Python3,C++,Java)
Ruteです。
AtCoder Beginner Contest 165 A問題「We Love Golf」の解説を行います。
問題概要
(問題文を簡潔に表すと以下の通りです)
$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.pyK = 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.javaimport 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"); } } }
- 投稿日:2020-08-11T17:36:25+09:00
AtCoder Beginner Contest 166 A問題「A?C」解説(Python3,C++,Java)
Ruteです。
AtCoder Beginner Contest 166 A問題「A?C」の解説を行います。問題概要
コンテストの開催ルールは以下のようである。(この問題における)
・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.pyS = 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.javaimport 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と同じかを正しく判定することが出来ます。
- 投稿日:2020-08-11T16:37:08+09:00
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.6JCICSX概要
「JavaからCICS機能を使用するためのクラスライブラリー≒EXEC CICS APIのJava版」という位置づけはJCICSもJCICSXも同様です。
何が違うのかというと、JCICSXはローカルPC上(開発環境上)でのテストをしやすくするために拡張されたAPIである、という点です。
また、JCICSの場合割と広範囲にEXEC CICS相当の機能をサポートしていますが、JCICSXはまだ限定的で、Channel&ContainerおよびLink関連の機能のみしか提供されていません。
参考:
JCICS Javadoc
JCICSX Javadoc"ローカルPC上(開発環境上)でテストしやすい"というのは具体的には以下の2つのポイントがあります。
ローカルPC上には当然CICSランタイムが無いので、CICS依存のコード(JCICSX部分)はそのままだと実行できません。DBアクセスなど環境依存のコードを含むモジュールの単体テストを行う場合、外部リソースアクセス部分をMockとして取り扱うようなフレームワーク(Mockitoなど)がありますが、それをCICSアクセス部分にも応用するという話になります。(これはMockitoなどのフレームワークで提供される機能の恩恵が主だと思います。)
(2) PC上のLibertyサーバー上で稼働確認が可能
こちらがCICSの拡張機能としては主要なものになりますが、ローカルPC上(開発環境上)のLibertyでJCICSXの稼働確認が行える仕組みを提供しています。イメージとしては以下の通りです。
この仕組みは"リモート開発"と呼ばれています。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=YESSIT
上のプロパティーファイルを配置したディレクトリを、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を開き、フィーチャーリストの右の追加をクリック
Install additional featuresをクリック
フィルターに「JCICS」を指定すると、JCICSX development feature for Javaがリストされるので、インストールをクリックして次へ
フィーチャーがインストールされたので、jcicsxでフィルターして表示された user:jcicsxClient-1.0を選択してOK
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をデザインビューで見ると以下のように表示されます。
これで一通り"リモート開発"の構成は完了です。
JCICSXを使用したローカルPC上でのアプリケーションの開発
JCICSX開発環境整備
JCICSXのクラスライブラリーの提供のされかたはいくつかありますが、CICS ExplorerのCICS SDK for Javaにも含まれています。CICS Explorer機能をEclipseに組み込めば管理もしやすいので、Eclipse環境にCICS Explorerを入れることにします。
参考: Downloading and starting CICS Explorer
Eclipseのメニューから ヘルプ-新規ソフトウェアのインストールを選択
追加を押して、以下のリポジトリを追加します。
「https://public.dhe.ibm.com/ibmdl/export/pub/software/htp/zos/tools/aqua3.2/」
インストールに結構時間がかかります。途中こういう確認が出たりしますので適宜対応。
これで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プロジェクトを選択
クローンしたディレクトリ下の「char-link-program-sample」を選択
中身の確認
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.javapackage 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
CharLinkServlet.javaを右クリック - 実行 - サーバーで実行
jcicsxの構成を行ったLiberty Serverを選択して次へ
指定したLibertyが起動してアプリがデプロイされた後、ブラウザが開いてServletが実行されます。
正常に実行されました! "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を選択
コンソールビューにてmvn installの実行結果が確認できます。
BUILD SUCCESSが出力されていればOKです。
念のため、CICS Explorerでバンドル、バンドルパーツがインストールされていることも確認しておきます。
実行
以下のURLでCICS-Libertyアプリにアクセス
http://etp1:56441/cics004-char-link-program-sample-1.0.0/SampleServlet
実CICS-Liberty環境でも同一のコードで稼働確認がとれました!
- 投稿日:2020-08-11T16:28:23+09:00
AtCoder Beginner Contest 167 B問題「Easy Linear Programming」解説(Python3,C++,Java)
AtCoder Beginner Contest 167 B問題「Easy Linear Programming」の解説を行います。
問題概要
$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.pyA,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.javaimport 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)); } } }
- 投稿日:2020-08-11T15:58:22+09:00
paizaスキルチェックを実施してみる
Javaのプログラム問題を実施するのに問題数も多く、個人的に利用頻度の高いpaizaを利用することにしました。
paizaにはスキルチェック問題があり、それらについては外部公開が許されているので「教材などにお使いください」と丁寧に書いてくれています。
またJava以外の言語も豊富なので、別言語を勉強したいときにも便利かと思います。
スキルチェック問題
ランクはBランクまで(外部公開なしの問題はSランクまで)が挑戦出来ます。問題の出され方や条件がすぐに理解できない部分があったので、まずは地道に見本問題をやってみて感を掴んでいこうと思います。
レベルアップ問題なら時間制限がないので、忘れてしまったところも反復しやすいです。
- 投稿日:2020-08-11T15:56:51+09:00
【MQTT/Java】JavaでMQTTのPub/Subをするクラスを実装した
はじめに
MQTTをPython/Javaで使ってみたので投稿しています。
本記事は以下の3にあたります。まだの方は1をまずご覧ください。
- 【MQTT】コマンドベースでMQTTの導入(前々回)
- 【Python】PythonでMQTTのPub/Subをするクラスを実装した(前回)
- 【Java】JavaでMQTTのPub/Subをするクラスを実装した(本記事)
- 【ROS】MQTT通信するノードを実装した(次回)
ライブラリ、ブローカーのインストール
これは前々回の記事にあるのでまだインストールしていない人のために書いておきます。
ライブラリ、ブローカーのインストール
1. windowsの場合
以下のサイトの、「Binary Installation」⇒「Windows」のところから自身の環境に合わせてインストーラーをダウンロードしてください。
https://mosquitto.org/download/
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の記事と同じように、ROSやOpenRTMなど、別で動作しているシステムから呼び出したいです。
PythonではSubscriberを立ち上げるさいにloop_start()
とすると別スレッドの処理が始まるようだったのですがJavaではそうではないようなので(ここは不明点です。どなたか簡単に別スレッドで処理ができるようでしたら教えてください。)別スレッドで処理をするクラスも作っています。よって、本記事のプログラムは以下のプログラムから成り立ちます。
- Publisher(別システムから呼び出す)
- Subscriber(別スレッドとして呼び出される)
- Thread(Subscriberを呼び出す)
こちらの記事をとても参考にしました。
Publisher
MqttPublisher.javapackage 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.javapackage 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.javapackage 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単体での動作ではなく、実際にほかのシステムから動かす際に使えるようにしてみました。
- 投稿日:2020-08-11T14:52:04+09:00
jar_アーカイブの操作_メモ_20200811
jar-アーカイブの操作
事前基礎知識
・アーカイブとは
複数のファイルやフォルダを一つのファイルにまとめたもの
1.jarコマンドとは
Windowsの実行プログラムは、通常はexeの拡張子を持った単一のファイルです。
一方、Javaで開発された実行可能プログラムの姿は、複数のクラスファイル(.class)の集合です。このように「複数のクラスファイルの集合」という形式では、
別の開発者に渡すときにファイルの受け渡し漏れなどの事故が発生する可能性もあります。
筆者も開発現場で実際に細かくクラスファイルを分けてもらってということもありました。そこで、それらのファイル一式を1つにまとめるアーカイブ形式(事前基礎知識を参照)として、
JAR(Java Archive)が定められています。JDKに標準添付されているjarコマンドを使えば、複数のクラスファイルやプロパティファイルなどを
まとめて1つのJARファイルにしたり、逆にJARファイルの中身をもとのクラスファイルなどに展開したり
できます。(図1)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ファイルは署名を施すことができるため、セキュリティ上の制限がある程度まで和らげることができる
間違っている知識などございましたら、ご教示いただけると幸いです。
- 投稿日:2020-08-11T14:40:04+09:00
AtCoder Beginner Contest 167 A問題「Registration」解説(Python3,C++,Java)
AtCoder Beginner Contest 167 A問題「Registration」の解説を行います。
問題概要
文字列$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.pyS = 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.javaimport 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.javaimport 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"); } } }
- 投稿日:2020-08-11T14:27:17+09:00
Java学習について調べたこと
Javaの実務経験は1年未満。
それ以外の言語の経験がそこそこあると、他の言語を勉強したくてもif文などをやり直しても・・・という気持ちになります。
今回は練習問題を使ってただコードを書く量を増やしていきたいと思い、練習問題サイト探してみました。プログラミング練習サイト
「xx(言語) 練習問題」で検索するとまとめられたサイトはたくさんあるのですが、私の目的「プログラミングする練習問題があるサイト」に絞り掲載させていただきます。
1.北ソフト工房
http://kitako.tokyo/lib/JavaExercise.aspx
以前から何度かお世話になったことがあるサイトです。
基本問題が多く応用問題は少ないように思うのですが、「C、Java練習
プログラム集」にはゲームを作りながら学ぶ問題が少しあります。2.paiza
https://paiza.jp/
練習問題も多く常に追加されており、ランク分けされているので難易度も分かりやすいです。
一部から有料になりますが、動画を見ながら学習もできるので初めて言語を学ぶの方もやりやすいと思います。3.JavaRU - Java練習問題
https://it-level-up.club/category/java-practice/
使用したことはありませんが、現在も更新中のサイトです。
練習問題も多く、細かく分類化されているので学びたい部分がわかりやすく学習できるのではと思います。今回使用するサイト
今回はpaizaを選びました。
理由は
1、問題がランク分けされているので難易度がわかりやすい
2、環境を作る必要がないので、すぐに取り掛かれる
3、以前から定期的に使用している
4、対応している言語が多く、今後を考えても利用しやすい
です。
また転職サイトと連携しているのでコードの外部公開はしてないかと思ったのですが、一部問題に関しては外部公開を許可されています。
https://paiza.jp/works/mondai
- 投稿日:2020-08-11T13:59:11+09:00
AtCoder Beginner Contest 173 B問題「Judge Status Summary」解説(Python3,C++,Java)
AtCoder Beginner Contest 173 B問題「Judge Status Summary」の解説を行います。
問題概要
テストケースのジャッジ結果を表す文字列$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.pyN = 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.javaimport 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と等しいかを判定することが出来ます。
- 投稿日:2020-08-11T12:46:29+09:00
【備忘録】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です
と表示されていれば成功です!エラーになってしまう場合
可能性としては";"が抜けている場合があると思いますので確認してみてください!
最後に
私も絶賛勉強中ではありますがプログラミングを勉強中の皆様、
根気強く、そしてコーディングが楽しくなるように一緒に頑張っていきましょう!
最後まで見てくださりありがとうございました!!!
- 投稿日:2020-08-11T08:53:55+09:00
【JUnit5対応】Spring boot 2.2, 2.3でJUnit5を使ったテストを書く
本記事は 『Spring boot2.2以上で、JUnit5に対応』 について記載してます。
下記の用途に参考になればー!
- Spring boot 2.1以下から、2.2以上にアップデート
- JUnit4からJUnit5への乗り換え
- 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
- 投稿日:2020-08-11T08:07:08+09:00
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 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.jspbuild.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 buildWAR ファイルが生成されていることを確認する。
$ file build/libs/mywebapp-1.0.war build/libs/mywebapp-1.0.war: Zip archive data, at least v1.0 to extractGlassFish 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.zipzip ファイルをダウンロードして展開するのみでインストールが完了する。
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.参考資料
- Eclipse GlassFish Documentation and User Guides
- Eclipse GlassFish Server Release Notes, Release 5.1
- Eclipse GlassFish Server Installation Guide, Release 5.1
- Eclipse GlassFish Server Quick Start Guide, Release 5.1
- Eclipse GlassFish Server Reference Manual, Release 5.1
- Eclipse GlassFish と IntelliJ IDEAで Webアプリケーションの開発環境を構築するメモ - Qiita
- 投稿日:2020-08-11T04:53:56+09:00
[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
のサブクラスです。RotatedPillarBlock
はBlock
に回転に関するものを追加したものです。そして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.jsonblockstates\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" } ] } ] }いつも通り。
もう一つ原木ブロックに関してやっておくことがあります。
ブロックの追加自体はここまでで完了していますが、あとで木を構成していく段階で必要になるので、ここで行っておきましょう。今追加した原木ブロックを
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" ] }
replace
にfalse
を与えることにより、同名のminecraft:logs
にこのファイルでの記述が統合されます。values
の中でブロックを指定しましょう。
デバッグモードでブロックにカーソルを合わせた際、赤下線を引いた部分のように#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.jsonblockstates\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.javapackage 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.javapackage 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.javaBlockExampleSapling.javapackage 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.jsonblockstates\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" } ] } ] }ゲームを起動して確認してみましょう。
苗が追加され、骨粉で木が育つことが確認できると思います。
また、木の幹を除去すると、葉ブロックが自然消滅をはじめ、消える際に確率で苗をドロップすることが確認できると思います。木の生成
ここまで来たらあと少しです。ここまでで実装した木をワールド生成時に自動で生成されるようにしましょう。
これは8. 鉱石の追加と生成とほぼ同じことをするのでそちらも参考にしてください。\src\main\java\jp\koteko\example_mod\ ├ blocks ├ items ├ lists ├ world │ ├ WorldGenOres.java │ └ WorldGenTrees.java └ ExampleMod.java
WorldGenTrees.java
を配置します。WorldGenOres.javapackage 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(); } //... }ゲームを起動して新たにワールドを生成します。
なんとなく光らせておいたので夜だと一層目立ちます。木の追加と生成ができました!
参考
1.14.3 Tags help - Modder Support - Forge Forums
バイオーム - Minecraft Wiki
ルートテーブル - Minecraft Wiki
地図アイテムフォーマット - Minecraft Wiki次の記事