- 投稿日:2020-12-06T23:34:15+09:00
「Factoryクラスでドメインオブジェクトを生成する」という規約をArchUnitでテストする
「Factoryメソッドでドメインオブジェクトを生成する」という規約をArchUnitでテストするの続き。
「ドメインオブジェクトの生成を責務とするFactoryクラスが存在するかどうか」テストしてみる。
テスト対象のイメージ
public class Hoge { Hoge() {} } public class HogeFactory { public Hoge create() { return new Hoge(); } }テストコード
@AnalyzeClasses(packages = "com.example") public class DomainObjectTest { @ArchTest static ArchRule domain_object_has_no_public_constructors = constructors() .that().areDeclaredInClassesThat().haveSimpleName("Hoge") .should().bePackagePrivate(); // FactoryクラスにHogeを返すcreateというメソッドが存在すること @ArchTest static ArchRule factory_class_exists = methods().that().areDeclaredInClassesThat().haveSimpleName("HogeFactory") .should().haveName("create") .andShould().haveRawReturnType("Hoge"); } FactoryクラスからHogeコンストラクタを呼ぶため、コンストラクタがパッケージプライベートであることをテストしている。 前回のFactoryメソッドのテストの場合は`bePrivate()`を使用していたが、今回は、同じくArchUnitに用意されている`bePackagePrivate()`を使用してテストした。 このあたりの基本的なアサーションは一通り利用できるほか、それらを組み合わせて新たな判定ロジックを定義することもArchUnitでは宣言的に実施できる。
- 投稿日:2020-12-06T20:25:43+09:00
jarファイル内の一部を差し替える方法
はじめに
jarファイル内の一部を、再ビルドすること無く差し替えたい場合の解決方法を記載します。
環境
OS:macOS Catalina 10.15.6
jar:14.0.2手順
- jarファイルを解凍する。
jar xvf hoge.jar- x:解凍
- v:冗長に解凍結果を表示する。
- f:ファイルの指定
- 古いjarファイルを削除する。
rm -rf app.jar- ファイルを差し替える。
- macOSの場合は、DS_Storeも不要なので削除する。
find . -name ".DS_Store" | xargs rm- 再アーカイブする。
jar cmf0 META-INF/MANIFEST.MF ../app.jar *- c:アーカイブの作成
- m:マニフェストの指定
- 0:圧縮なし
参考
- 投稿日:2020-12-06T20:15:09+09:00
AIによるテスト自動生成ツール「Diffblue」を使ってみた
自動テストのしんどい所
自動テストってありがたいですよね。コードが壊れてもすぐ気付ける、というのはコードに対する不安感を払拭してくれます。
ですが、これをプロジェクトに導入しようとするとあら大変。「カバレッジ100%しか認めん!!」なんて言われて、実装には直接関係ないテストコードを量産しなければなりません。
そうなると自動化をしたくなるのが人の性ですが、無償ツールだとテンプレートベースなので結局テンプレートは作る必要があったり、可読性に難がありメンテができない、ということで、これはこれでしんどいです。1
そんなめんどくささに対応するべく生み出されたのがDiffblueです。Diffblueとは?
DiffBlueは機械学習を使用して、Java用のユニットテストを自動生成するツールです。ゴールドマン・サックスやAWSでも利用されているようです。今までは有償ツールだったのですが、9/9についにCommunity Editionが公開されました。2
読みやすいテストが特徴だそうです。環境とか
用意するツールとバージョン
Community EditionだとIntelliJ IDEAのプラグインのみなので、IntelliJ IDEAを導入します。
IntelliJ IDEA:2019.33
Diffblue:diffblue-cover-ij-2020.11.04-eval-jdk8-2020.2導入方法
- DiffBlueコミュニティ版をダウンロード
- IntelliJ IDEAを起動し、ファイル>設定>プラグインを開く
- ⚙マークから「ディスクからプラグインをインストール」を選択
![]()
- 1.でダウンロードしたzipファイルを選択
- ライブラリが読み込まれたら再起動
![]()
実行方法
特性
分岐は必要最小限のパターンで網羅する
DiffBlueは最小限のパターン数で分岐を網羅しようとします。意図通りの挙動を試験するとは限りません。
例えば、数字をFizzBuzzに変換するコードであれば、最低限1,3,5,15を試験してほしい所ですが、以下のようなコードであれば、0,1しか試験しません。FizzBuzz.javapublic static String fizzBuzz(Integer i) { String result = ""; if(i % 3 == 0){ result += "Fizz"; } if(i % 5 == 0){ result += "Buzz"; } if(result.equals("")){ result = i.toString(); } return result; }FizzBuzzTest.java@Test public void testFizzBuzz() { // Arrange int i = 1; // Act String actualFizzBuzzResult = FizzBuzz.fizzBuzz(i); // Assert assertEquals("1", actualFizzBuzzResult); } @Test public void testFizzBuzz2() { // Arrange int i = 0; // Act String actualFizzBuzzResult = FizzBuzz.fizzBuzz(i); // Assert assertEquals("FizzBuzz", actualFizzBuzzResult); }ループのテストケースが不十分
ループ処理のテストケースは、「分岐を全て網羅できるように」しか生成されません。
例えば以下の「nからmまでの数字を足し合わせる」プログラムでは、テストケースは1ケースしか生成されませんでした。1回ループすれば全て網羅できるからです。SumFor.javapublic static Integer sumFor(Integer n,Integer m){ Integer sum = 0; for (Integer i = n; i <= m; i++){ sum += i; } return sum; }SumForTest.java@Test public void testSumFor() { assertEquals(1, SumFor.sumFor(1, 1).intValue()); }ジェネリクスを使用した変数が引数に取られていると正常に動作しない
個人的に一番驚いたのがこれです。以下のコードのテストが生成できません。
SumForList.javaimport java.util.List; public class SumForList { public static Integer sumForList(List<Integer> list){ Integer sum = 0; for(Integer num : list){ sum += num; } return sum; } }エラーログは以下の通り。
14:56 Creating tests for method: SumForList.sumForList
14:56 Unable to generate test inputs not throwing a trivial exception.: 例外: java.lang.NullPointerException. We failed to find inputs to the method under test which would NOT lead to throwing of a trivial exception, like NullPointerException or LinkageError.
14:56 Test creation complete: Created 0 tests in total for 0 methods from 0 source classes型変数に入れたクラスのインスタンスをどう生成すればいいかわからないようです。
引数をArrayにする、ラッパークラスを作りいい感じにイテレーションする、等すればテストを生成できます。sumForArray.javapublic static Integer sumForArray(Integer[] array){ //引数を配列に変更 List<Integer> list = Arrays.asList(array); //内部でListへ格納 Integer sum = 0; for(Integer num : list){ sum += num; } return sum; }状態を持つクラスのテストにはgetter/equals/hashcodeの実装が必須
List等のラッパークラスを作ろう、と思っても落とし穴があります。
以下のように、パラメータのgetter/equals/hashcodeがないクラスの場合、テストケースが生成できません。
IDEの機能でさくっと生成してあげましょう。ListWrap.javapublic class ListWrap { private List<Integer> numList; public ListWrap(){ numList = new ArrayList<Integer>(); } public void addNum(Integer num){ numList.add(num); } public Integer max(){ Integer max = null; for(Integer num : numList){ if(max == null || max < num){ max = num; } } return max; }エラーの内容は以下の通り。sizeやtoStringも実装するよう書かれていますが、試した限りでは不要でした。
16:04 Creating tests for method: NumStat.
16:04 Nothing to assert: the constructed class does not have observers (e.g. getters or public fields).: Class class com.company.NumStat does not have observers. Observer methods are parameterless, read-only methods that return the state of the class, for instance, getters, size(), toString().
なお、何らかの値を設定する方法があれば、setterは不要です。(今回でいうとaddNum)
privateメソッドのテストコードは生成されないが、呼び出し元メソッドの試験で検証される
DiffBlueはprivateメソッドのテストコードは生成しません。4その代わり、試験生成対象メソッドで呼び出し先のprivateメソッドまで考慮し、テストパターンを作るようです。
以下のコードでは、三角形の条件を「0以下の辺がないこと」「ある辺が他の辺の合計より長くないこと」と分け、それぞれ別メソッドで検証しています。
「0以下の辺がないこと」:テストケース2,4,6
「ある辺が他の辺の合計より長くないこと」:テストケース3,5,7
でそれぞれ検証していますね。AssertTriangle.javapublic class AssertTriangle { /** * 三角形の辺としての整合性チェック * @param a * @param b * @param c * @return */ public static boolean assertTriangle(int a,int b,int c){ if(hasNegativeSide(a,b,c)) return false; return sideIsValid(a,b,c); } /** * 三角形として成立しない辺がないかチェック * @param a * @param b * @param c * @return */ private static boolean sideIsValid(int a, int b, int c) { return firstSideIsShorterThenOthersSum(a,b,c) && firstSideIsShorterThenOthersSum(b,c,a) && firstSideIsShorterThenOthersSum(c,a,b); } /** * 辺の長さが0以下の値がないかチェック * @param a * @param b * @param c * @return */ private static boolean hasNegativeSide(int a, int b, int c){ return a <= 0 || b <= 0 || c <= 0; } /** * 辺firstが辺second+辺thirdより短いかチェック * @param * @return */ private static boolean firstSideIsShorterThenOthersSum(int first,int second,int third){ return first < (second + third); } }TriangleServiceTest.javapublic class AssertTriangleTest { @Test public void testAssertTriangle() { // Arrange int a = 1; int b = 1; int c = 1; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertTrue(actualAssertTriangleResult); } @Test public void testAssertTriangle2() { // Arrange int a = 0; int b = 1; int c = 1; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertFalse(actualAssertTriangleResult); } @Test public void testAssertTriangle3() { // Arrange int a = 2; int b = 1; int c = 1; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertFalse(actualAssertTriangleResult); } @Test public void testAssertTriangle4() { // Arrange int a = 1; int b = 0; int c = 1; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertFalse(actualAssertTriangleResult); } @Test public void testAssertTriangle5() { // Arrange int a = 1; int b = 2; int c = 1; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertFalse(actualAssertTriangleResult); } @Test public void testAssertTriangle6() { // Arrange int a = 1; int b = 1; int c = 0; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertFalse(actualAssertTriangleResult); } @Test public void testAssertTriangle7() { // Arrange int a = 1; int b = 1; int c = 2; // Act boolean actualAssertTriangleResult = AssertTriangle.assertTriangle(a, b, c); // Assert assertFalse(actualAssertTriangleResult); } }所感
正常系を拾うのは苦手そうだと感じました。FizzBuzzで3とか5とかチェックしないのは正直びっくりです。
ですが、逆に0等の予期しない値を突っ込んでくれるので、TDD等で正常系のテストコードが出来ている状態で実行するとお手軽にカバレッジが上がっていいかなと思いました。参考サイト
DiffBlue公式
Diffblueを使ってユニットテストを自動生成する | Developers.IO
2020.3verが最新ですが、日本語化がうまくできないため2019.3verを使っています ↩
もともとはprivateメソッドも、リフレクション等を駆使して生成していたようですが、やめたようです。Why does Diffblue not use reflection? ↩
- 投稿日:2020-12-06T19:24:23+09:00
Servlet + JasperReports で動的に PDF を作成して Web サイトに表示してみるメモ
先日、Servlet + Apache FOP ネタ とか その続編 とか投稿したのですが、知り合いから「Jasper 良さげだよ」との情報をいただきまして。ググったら情報多い!主流はコッチだったか… と。
既に日本語の情報は十分にある気もするので、備忘録として、簡単な実施メモにまとめてみました。
JasperReports 概要
まず Jasper 系の PDF (帳票) 生成に関して、以下が理解の助けになりました。
- JasperReportsとiReportで帳票の常識を理解しよう (1/4)
- JavaでPDFのテンプレートを作成し、帳票作成を行う
- Jasperレポート作り方 ~iReport(基本編)~
- Jaspersoft StudioプラグインをEclipseにインストールする
以下は公式もしくはダウンロードサイト。
LGPL のようですが、一応はライセンス周りを確認。
Jaspersoft StudioプラグインをEclipseにインストール
iReportはjdk1.8に対応していない らしいので、今回は Jaspersoft Studio の Eclipse 用プラグインを利用してみます。
私の今のテスト環境 Eclipse 2020-09 で「Help -> Eclipse Marketplace」から Jaspersoft 6.16.0 をインストール。
ここで同意するライセンスは Eclipse Foundation Software User Agreement のみの模様。
インストール後は Eclipse が再起動するので、Welcome 画面に以下のような表示があればok。
後は新規作成メニューに追加された「Jasper Report」を新規作成すれば
テンプレートの選択画面が出てきて、
そして、こんな感じの帳票のデザイン画面が開きます。
開発環境の準備
1) お馴染みのデータ入力用のシンプルな html ページ
2) 今回の主役、PDF を自動生成する Servlet
3) 先ほどインストールした Jaspersoft Studioプラグインで作成したテンプレート
4) 開発時に必要な JasperReports Library 用 jar ファイル
5) Servlet 実行時に必要な JasperReports Library 用 jar ファイル4) ですが Jaspersoft Studioプラグインをインストール時に Eclipse に登録されていますので、ビルドパスへライブラリを追加します。
5) もプラグインをインストールしてあればPC内にあるはずなので、探してWEB-INF/lib下にコピーもしくはリンクします。MVN リポジトリ JasperReports Library から入手してもかまいません。なおipaexm.jarに関してはこの後で説明します。フォント設定
こちら の情報をもとに 「IPAex明朝」フォントをダウンロードし設定します。
ここで大事なのはPDF Encoding欄でIdentity-H(Unicode with horizontal writing)を選択することです。これを忘れると日本語が出力されません。フォントの設定が終わったら、一覧メニューで「Export」を実行します。
jar ファイルの保存画面になりますのでWEB-INF/lib下にipaexm.jarというファイル名で保存します。これで開発環境で指定したフォント設定を、Servlet 実行環境に反映することができました。PDF テンプレートを作成する
Jaspersoft Studioプラグインで PDF テンプレートを作成します。今回は以下のようにシンプルなもので試しましょう。
1) は単純にタイトルを表示しているだけです。
Static Textを配置して、適当なタイトルを入力し、さきほど設定した日本語フォントを指定します。
2) は
Current Dateを配置しただけです。日本語ではありませんが、表示を統一するためフォントだけ指定します。
3) が動的に設定するテキストになり、最も大事な部分です。まず右の要素ツリーで
MY_MESSAGEという名称の独自パラメーターを追加します。
指定する名称と型(今回は文字列)は以下のような感じ。
次にText Field要素を追加します。表示内容を指定する Expression Editor では追加した独自パラメーターのMY_MESSAGEを選択します。あ、フォント指定もお忘れなく。
さて、これでテンプレートは完成しました。以下はプレビューを実行した様子です。
保存した
sample1.jrxmlファイルは以下のような感じ。sample1.jrxml<?xml version="1.0" encoding="UTF-8"?> <!-- Created with Jaspersoft Studio version 6.16.0.final using JasperReports Library version 6.16.0-48579d909b7943b64690c65c71e07e0b80981928 --> <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="sample1" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="3fe7289f-d8d2-4a65-9b55-c9aa20be3391"> <property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/> <parameter name="MY_MESSAGE" class="java.lang.String" isForPrompting="false"/> <queryString> <![CDATA[]]> </queryString> <background> <band splitType="Stretch"/> </background> <title> <band height="79" splitType="Stretch"> <staticText> <reportElement x="80" y="20" width="400" height="30" uuid="b771d8a0-efcc-4c72-a8eb-f66f1b7bc644"/> <textElement textAlignment="Center"> <font fontName="IPAex明朝" size="16"/> </textElement> <text><![CDATA[JasperReports PDF サンプル]]></text> </staticText> </band> </title> <pageHeader> <band height="49" splitType="Stretch"> <textField pattern="MMMMM dd, yyyy"> <reportElement x="370" y="10" width="180" height="30" uuid="cf6034bc-e2c5-4db9-806d-e40ed1bc65a5"/> <textElement textAlignment="Right"> <font fontName="IPAex明朝"/> </textElement> <textFieldExpression><![CDATA[new java.util.Date()]]></textFieldExpression> </textField> </band> </pageHeader> <columnHeader> <band height="61" splitType="Stretch"/> </columnHeader> <detail> <band height="125" splitType="Stretch"> <textField> <reportElement x="0" y="10" width="550" height="100" uuid="0298349c-4284-4697-b6ad-792637b8e76e"/> <textElement> <font fontName="IPAex明朝"/> </textElement> <textFieldExpression><![CDATA[$P{MY_MESSAGE}.toString()]]></textFieldExpression> </textField> </band> </detail> <columnFooter> <band height="45" splitType="Stretch"/> </columnFooter> <pageFooter> <band height="54" splitType="Stretch"/> </pageFooter> <summary> <band height="42" splitType="Stretch"/> </summary> </jasperReport>保存した
sample1.jrxmlファイルを右クリックメニューからコンパイルし、sample1.jasper ファイルを生成しておきます。
入力用ページ
入力用ページは これまでと同じ ですので、説明は省きますね。フォーム部分は以下のような感じ。
index.html<div class="container"> <h2>Simple JasperReports sample with Servlet</h2> <form method="POST" action="/test03/pdfServlet"> <div class="form-group"> <label for="i_fname">Document format</label> <input type="text" class="form-control" id="i_fname" name="i_fname" value="sample1"> <label for="i_body">Body mwssage</label> <input type="text" class="form-control" id="i_body" name="i_body" value="Sample body text..."> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </div>Servlet
さて、今回のコアとなる Servelt 部分ですが、実は過去の Apache FOP 版 とあまり変わらなかったりします。呼び出しているライブラリが異なるぐらい?実際のコードを見てもらったほうが理解し易いとおもいます。
pdfServlet.javaimport java.io.File; import java.io.IOException; import java.util.HashMap; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.jasperreports.engine.JREmptyDataSource; import net.sf.jasperreports.engine.JasperRunManager; @WebServlet("/pdfServlet") public class pdfServlet extends HttpServlet { private static final long serialVersionUID = 1L; public pdfServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { request.setCharacterEncoding("utf-8"); String i_fname = request.getParameter("i_fname"); String i_body = request.getParameter("i_body"); File jasperFile = new File(getServletConfig().getServletContext().getRealPath("/" + i_fname + ".jasper")); HashMap<String, Object> params = new HashMap<String, Object>(); params.put("MY_MESSAGE", i_body); byte[] bytes = JasperRunManager.runReportToPdf(jasperFile.getPath(), params, new JREmptyDataSource()); response.setContentType("application/pdf"); response.setContentLength(bytes.length); response.setHeader("Content-Disposition", "inline"); ServletOutputStream out = response.getOutputStream(); out.write(bytes); out.flush(); } catch (Exception ex) { throw new ServletException(ex); } } }ポイントは
params.put("MY_MESSAGE", i_body);ですね。PDF テンプレートを作成した際に設定した独自パラメーターに対し、表示用の値をここで渡してあげるわけです。今回はテンプレートをファイルから読み込んでいますが、DB 化については 続編のほう を参照してみてください。コンパイル後はバイナリ形式であることに留意すれば、ほぼ同じ考え方で対応できるとおもいます。
実行してみよう
さて、まずはいつものように index.html を開いてテキストを入力し、
「Submit」で入力したテキストが埋め込まれた PDF が表示されることを確認します。
うん、問題なさそうですね。日本語のタイトルも、今日の日付も表示されています。そして本文の部分には、フォームで入力した日本語を含んだテキストが、ちゃんと埋め込まれているのを確認できました。ライセンス
この投稿に含まれる私の作成した全てのコードは Creative Commons Zero ライセンス とします。自由にお使いください。
Enjoy!
以上、Servlet を用いて Web サイトで、JasperReports を用いた PDF の動的生成を試してみました。これをベースに、いろいろ機能を追加して遊んでみてください。
Apache FOP も JasperReports も Servlet から使うぶんには、それほど違いがない気がします。でもテンプレートを作成する部分、JasperReports 用プラグインによる GUI ベースの設定は圧倒的に楽ですね。なので、JasperReports のほうを強く推したいとオモイマス。
それではまた!
- 投稿日:2020-12-06T18:05:26+09:00
1から考えて作ってみた指スマゲーム
目次
- はじめに
- 指スマとは
- ルール説明
- 環境
- コード
- まとめ
- 参考文献
はじめに
どうもプログラミング学習初心者のものです。
自分は今Java言語を使ってプログラミング学習をしているのですが、何か自分でプログラムを作ってみたいと思い立ち、かといって大それたものは作れないので何か遊び的なものをプログラム上で動かしてみようと思い、ふと浮かんだ「指スマ」にしようとしました。指スマとは
指スマを知らない人に向けて説明してと言われても自分で簡単に説明ができないので、weblioサイトで引用した説明を下記に載せておきました。
「数人で行われるゲームの一つで、参加者が自身の手のひらを向かい合わせにして握りこんだ状態から、掛け声に合わせて一斉に親指を立てた数を「親」が当てるというもののこと。「親」の予想した数は掛け声と共に言われる。フジテレビの番組SMAP×SMAPで指スマと呼ばれたことから定着した名前であるが、実際は番組以前にも存在していたゲームである。地域によって呼び方や掛け声に差があり、代表的なものには「いっせーのせ」や「バリチッチ」などが存在する。ゲームの名称と掛け声は一致する傾向にあり、例えば「親」が3本の親指が立つと予想する場合、「指スマ3」という掛け声になることが多い。」(weblio辞書 指スマ解説分より抜粋)ルール説明
- 参加人数を入力させ、各参加者の名前を入力させる
- 一人目から順番に「親」となりランダムに指数を宣言し、ランダムに参加者も指をランダムに出力する。
- 「親」が宣言した指数と参加者が出力した指数の合計が同じであればその「親」は指を一つおろし、もう一回当たれば上がることができる
- 最後の参加者(ドベ)が決まるまで繰り返す ルール自体は通常の指スマと同じです。 これをプログラミング上で実装します。
環境構築
java version "13.0.2" 2020-01-14
Java(TM) SE Runtime Environment (build 13.0.2+8)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)Eclipse Version: 2020-09 (4.17.0)
コード実装
Yubisuma.javapackage java_2020_11; import java.util.ArrayList; import java.util.Random; import java.util.Scanner; /* 指スマのプログラム 操作手順: 0:指スマ開始するかの入力値を作る 1:人数を指定できるように入力値を作る 2:一人ひとりの名前を記録し、1番目の人から指の数を入力できるようにする 3:かく順番が回ってくるのでその都度入力値として受け取った指の数を出力する。 4:3と同時に他のメンバーの指の数もランダム値として出力させる。 5:3と4の出力値が一致していた場合はあたりの判定を出力、外れの判定を出力する。 6:一回当たると、指を一つへらし、計2回当たると上がれる 7:最後のひとりになるまでこの処理を繰り返す。 */ public class Yubisuma { public static void main(String[] args) { Input in = new Input(); Calculation ca = new Calculation(); System.out.print("指スマを開始しますか。「はい」か「いいえ」を入力してください:: "); String choice = in.letter(); switch (choice) { case "はい": System.out.println("それでは指スマを開始します。"); System.out.print("何人でやりますか?(半角数字で入力):: "); int peoples = in.numbers(); int f_numbers = ca.finger_index(peoples); //指の数が代入される System.out.println("指の合計数は" + f_numbers + "です"); ArrayList<String> NamesList = new ArrayList<String>(); int[] count = new int[peoples]; //2回当たると上がれることを記憶するカウント配列 for(int i=1; i<=peoples; i++) { System.out.println (i + "番目の人の名前を入力してください。"); String name = in.letter(); NamesList.add(name); count[i-1] = 2; System.out.println(i + "番目の人の名前が" + name + "で登録されました。"); } System.out.println(NamesList + "全員の名前が登録されたのでゲームを始めます"); while(peoples != 1) { for (int i=0; i < peoples; i++) { int sum = 0; int a_fn = ca.finger(f_numbers); //指定した指の数を返す System.out.println(NamesList.get(i) + "さんの番です。" + "(" + NamesList.get(i) + ")" +"いっせっせーの" + a_fn + "!"); for (int j=0; j < peoples; j++) { int a = ca.other_finger(count[j]); //全員分の指数を返す。 System.out.print(NamesList.get(j) + ":" + a + " "); sum += a; } System.out.println("合計指数:" + sum); if(a_fn == sum) { System.out.println(NamesList.get(i) + "さんあたりです!"); if (count[i] != 1) { count[i] -= 1; f_numbers -= 1; System.out.println(NamesList.get(i) + "さんあと一揃いで抜けれます。頑張ってください"); }else { System.out.println(NamesList.get(i) + "さんアガリです!"); f_numbers -= 1; peoples -= 1; NamesList.remove(i); } }else { System.out.println(NamesList.get(i) + "さん外れです"); } } } System.out.println(NamesList.get(0) + "さんドベです。残念でした。"); System.out.println("皆さんお疲れ様でした。"); break; case "いいえ": System.out.println("お疲れ様でした。"); break; } } public static class Input { //入力値クラス Scanner scanner = new Scanner(System.in); public String letter() { //文字列入力の場合はletter()メソッド String le = scanner.nextLine(); return le; } public int numbers() { //数字入力の場合はnumbers()メソッド int num = scanner.nextInt(); return num; } } public static class Calculation { //計算クラス int finger_numbers; Random r = new Random(); public int finger_index(int peoples) { finger_numbers = peoples * 2; return finger_numbers; } public int finger(int f_numbers) { //i番の指定した指の本数をreturnでランダム数で返す int answer_index = r.nextInt(f_numbers + 1); return answer_index; } public int other_finger(int count) { //一人が上げる親指の本数 int other_answer_index; if (count == 2) { other_answer_index = r.nextInt(3); }else { other_answer_index = r.nextInt(2); } return other_answer_index; } } }コードの補足
- 各参加者の名前を入力してもらった時に格納するのが配列だとうまくいかないのでArrayListを使いました。また、誰かが言い当てた時、または上がれた時はその人の指を減らし、上がれた場合にはArrayList内に格納した人を削除する必要があったのでArrayListが最適でした。
- 今回は指の数を当て合うのはRomdom関数で全て自動にしたため、勝敗も自動で決まってしまいます。
- 指スマは計2回言い当てなければいけなくそのカウント配列もあらかじめ用意しました。
- 1クラスだけでは面白くないと思い、処理ごとにクラスを分けてみました。
- なぜか最初の参加者の名前だけ入力できない(2人目以降はしっかり名前を入力できるのに…)。それでもちゃんと動作するから原因が分からずじまい(泣)。
まとめ
感想
基礎的な処理の連続なのでコードの内容自体はシンプルなはずです。(これぐらいしかまだ実力がない…)
今までは何かの参考書を買い、そこに載っているコードをただ写経するだけでしたのでどれほど自分が理解できているのかわからなく、かつ理解してる風な状態だなと感じ、それが嫌だったわけで今に至るわけですが、案の定たくさん行き詰まり、自分がプログラミングというものを全く理解していないことを痛感しました。
また、それと同時に何かを自らの手で作り上げる楽しさを感じれたのでこれからは簡単な遊びだけでなくもっと実用的なものを考え、自らの手で作り上げたいと思います。オブジェクト指向に関して
javaで実装しているからオブジェクト指向型のコードを作ろうと必死にクラスを生成したりしてやろうとしましたが、自分でも「これ、なんか違うくね?」と違和感を感じます。笑笑
なんか「無理やりオブジェクト指向型にしてみよう」感がだだ漏れのコードだなぁと正直感じました。(実際なっているかも分からない。)
次何か作るときにはもっとうまく、そして見易いコードにしたいと思います。
参考文献
weblio辞書 https://www.weblio.jp/content/指スマ
Markdown記法 チートシート https://qiita.com/Qiita/items/c686397e4a0f4f11683d
- 投稿日:2020-12-06T16:00:47+09:00
Java SE8 Silverを受験するなら知っておくべきこと
はじめに
Java SE8 Silverの資格を受験してきました。
試験を振り返って、これから受験する・受験を考えている方のために自分が書けることがあるのではないかと思い、筆を執った次第です。受験者のバックグラウンド
私は先月からJavaの研修を受けているプログラミング初学者です。
資格の取得をひとつの区切りとして、DBの学習に移る予定です。Java SE8 Silver 試験とは
この記事はJava Silver SE8を受験しようという方向けに書いていますので、資格そのものの説明はOracleのJavaSE8認定資格公式ページより概要を抜粋するに留めます。
Oracle Certified Java Programmer, Silver SE 8 認定資格は、Javaアプリケーション開発に必要とされる基本的なプログラミング知識を有し、上級者の指導のもとで開発作業を行うことができる開発初心者向け資格です。日常的なプログラミング・スキルだけでなく、さまざまなプロジェクトで発生する状況への対応能力も評価することを目的としています。
出展:Java SE 8 認定資格本題 試験時間の長さ
試験を受験するにあたって、申し込み方法や勉強に最適なテキスト等、必要な情報というのは大体検索すればでてきます。申し込み方法は私の独力ではちょっと怪しいくらい複雑ですが、調べながらであればできました。これは後述。
それらとは別に、私が気になっていたのは、その試験時間の長さです。
Java SE8 Silver 試験の試験時間は150分です。つまり二時間半。一般的な大学のひとコマが90分であることを踏まえても、長過ぎるというのが私の素直な感想です。所持品
調べればわかることですが、試験会場は私物の持ち込みが禁止されています。腕時計や財布・鍵などの貴重品も全てロッカーに預けるように指示されます。水分補給もできません。
試験前にメモ用紙とペンが渡される他は、会場に持ち込めるのは荷物を入れたロッカーの鍵だけです。尿意との戦い
Java SE8 Silverは試験途中の入退室は認められていません。
つまり、試験中トイレに行くことができないということです。
二時間半もトイレに行くことができないということです。
トイレへ行く頻度は人それぞれなので、二時間半くらいどうということはないという方もおられるかと思いますが、私は熱中症で救急搬送されて以来どうにもトイレが近く、二時間半という時間にはかなり危機感を覚えました。事前に備えておかなければ重大なインシデントが発生する恐れがあります。そこで、試験当日は朝から水分を控え、試験前にはトイレへ行き、念入りに用を足すなどの対策を講じました。
試験前に会場のスタッフの方に、途中でトイレに行くことはできないか直接確認しましたが、やはりできないとのことでした。とはいえ、二時間半経つまで退席できないわけではなく、いつでも自由に退室できるが、退室後は試験会場に戻ることはできないというルールのようです。
最悪の場合、途中で試験を投げて、トイレへ駆け込むことは可能ということです。とは言え、高額な試験料を支払って受験している以上、途中棄権のような真似は避けたいところ。なぜ私は肝心の試験内容よりもトイレのことばかり考えているのだろうかと悲しくなりつつ試験に臨みました。試験開始から一時間くらいが経過した時点で、私は尿意を覚えました。「どうして」「試験前にトイレに行ったのに」「余裕をもって到着したから、近くのカフェでココアを飲みながら勉強をと思ったのが敗因??」脳裏を様々な思いが過ります。
試験時間はまだ一時間半も残っています。何よりまだ75問ある問題が35問しか解けていません! このまま退室したのでは、合格ラインである正答率65%に届かないことは明白です。
私は、尿意を我慢しながら試験を続行することにしました。一問も誤答していないとしても、合格のためには51問は解答する必要があります。ここで投げ出すわけにはいきませんでした。尿意を自覚して以降の試験は、つらく苦しいものでした。尿意を我慢しながらでは、十分に集中することは困難です。思考が散漫になり、新しい設問に移った際に表示されるコードが20行近くあると、それだけで悲鳴を上げたくなりました。
尿意を我慢するために、貧乏ゆすりをしました。周囲の受験者に申し訳ないと思いつつも、自身も試験を諦めるわけにはいきません。一刻も早く終わらせて退室するしかありません。必死で問題を解き、なんとか全問の解答を終えました。残り時間は45分。試験開始から115分が経過していました。ほぼ二時間です。本来ならば、ここから見直しをして、解答を万全のものとしてから試験を終了すべきですが、残念なことに私の膀胱はそれを許さないでしょう。私は見直しを諦め、試験を終了、退室しました。
試験を終え、退室したのだからトイレへ行ける。私はそう考えていました。しかし、これは甘い考えでした。試験を終えたからといって、すぐにトイレへ行けるわけではありませんでした。そんな馬鹿な!
試験を終えて退室した受験者は、会場のスタッフにメモ用紙とペンを返却し、試験会場に戻らない旨の書類にサインしなければいけないらしいのです。それが済むまでは、トイレに行かせるわけにはいかないと言われました。泣きそうになりつつも、署名の順番を待ちます。(退室のタイミング次第では、何人かが退室のための順番待ちをしていることがあります。)
退室時に、メモ用紙・ペン・ロッカーの鍵の他に何か持ち出していないかチェックを受ける必要があるようで、コロナ禍の2020年12月現在はマスクを外して、マスクの裏に何も書いていないことを確認されました。試験問題の持ち出しが規約で禁止されていたので、その防止策と思われます。
さらに悪いことに、受験会場ではOracle社以外の資格試験も行われており、その日は英検の受験者が多く押しかけていました。当然スタッフはそちらの対応に割かれることになり、試験からの退室手続きの速度は(私に全く余裕がないこともあり)ひどくゆっくりだったように感じました。試験を終え、退室手続きを終え、私がお手洗いへたどり着いたのは試験開始から実に約120分後のことでした。試験会場での失禁という最悪のケースを回避することができた安堵と、見直しができなかったことからくる不安とが脳内で渦を巻いていました。
試験結果
試験後、30分~60分程してOracle社から試験結果のメールが届き、試験の合格を確認しました。
正答率は80%でした。尿意がなければ、もう少し良い結果を出せたのではないかという悔しさはありますが、失禁も落第もせずに済んだことにホッとしています。私のように試験と尿意の二方面作戦を強いられる人が一人でも減るように、この記事を書きました。
受験される方は、当日の水分摂取を控え、事前にお手洗いに行くことを忘れないでください。そして、途中で尿意に集中を乱されても戦えるだけの余裕を以って試験に臨まれることをお勧めします。
以上、Java SE8 Silver受験記でした。ご拝読ありがとうございました。試験を受けるに際して本当に読んでおくべき記事
下の記事がかなり詳しく書かれていて、大変助かりました。
調べるべきことを調べる前にこの記事にたどり着いてしまった方はこちらもどうぞ。・受験申し込み
Java Silver受験手続きの仕方と学習方法
- 投稿日:2020-12-06T13:41:15+09:00
【Java】文字列の一部を抜き出す
プログラミング勉強日記
2020年12月6日
文字列の一部を抜き出せるsubstringメソッドについて学んだので使い方をまとめる。substringメソッドとは
substringメソッドは文字列の一部を取得するためのメソッドである。文字列のbegin+1~end文字目を抜き出すことを抜き出すことができる。引数endを省略するともでき、省略した場合はbegin+1文字目から最後の文字列の末尾まで抜き出す。新しい文字列を返している。
public String substring(int begin [,int end]) // begin:開始位置 // end:終了位置サンプルコード
public class Main { public static void main(String[] args) { String str = "Good Morning Everyone."; // String型の変数strに文字を代入 System.out.println(str.substring(5)); // 6文字目から最後までを取得して出力 System.out.println(str.substring(5, 12)); // 6文字目から12文字目まで取得 } }実行結果Morning Everyone. Morning参考文献
substringメソッド
1分でわかる文字列の一部を切り出しするsubstringの使い方【Java Stringクラス】
- 投稿日:2020-12-06T13:15:55+09:00
SIM Toolkitを使ったSIMアプレットを触ってみる
本記事はNTTコミュニケーションズ Advent Calendar 2020 6日目の記事です。
昨日は@khrdさんのKindでVirtualClusterを試そうでした。はじめに
フルMVNOを提供している事業者の説明の中には「SIMアプレットを活用して機能を拡張する」というような文言が書いてあります。
本記事ではこのSIMアプレットに着目し、概要とSIMアプレット開発について見ていきます。SIM Toolkitとは
SIM ToolkitはSIMカード上で動作するアプリケーション(アプレット)に対して端末やネットワークと対話するための仕組みを提供しています。
対話とは具体的に言うと、
- SIMが持つ情報を端末に表示する
- ユーザーがSIMに情報を入力する
- SIMがネットワーク経由でSMSを送受信する
などが挙げられます。
このSIM Toolkitを使って作られたプログラムをSIMアプレットと呼びます。
SIMアプレットの特徴
SIMアプレットにはざっくりと以下のような2つの特徴があります。
1. 能動的にコマンドを実行する仕組みがある
アプレットは基本的に端末側からカード側へコマンド(APDU)を送り、カード側がそれに応答するという仕組みになっています。つまり、カード側を起点にして端末側に対してなにかデータを送ることはできません。
SIMアプレットではコマンドの応答コード(Status Word. HTTPでいう200, 404みたいなもの)にひと工夫あり、端末側からのコマンドに対して「送りたいデータがXXバイトある!」という応答コードを返すことでカード側から能動的にコマンドを発行することが可能になっています。この能動的なコマンドをProactive Commandと呼びます。これにより受動的な仕組みの上で能動的に振る舞えるようになっています。
2. OTAを使ってSIMにコマンドの発行ができる
SIMアプレットは端末が接続している通信回線を利用して外部のサーバと通信することが可能です。例えばサーバから端末にSMSを送信すると、その内容をもとに端末がSIMに対してコマンドを発行します。その実行結果をSMSやインターネット経由で外部のサーバに送信することもできます。
テキストを表示するSIMアプレットを触ってみよう
ここからは実際にSIMアプレットをスマートフォン上で動かしていきます。
なお検証で使用するSIMカードは研究開発用のもののため、自前で用意しない限り基地局と接続できません。そのため自端末の中で完結するアプレットを作っていきます。前提
- OS: Ubuntu 20.04
- 使用したSIMカード: sysmoUSIM-SJS1
- with ADM keys
- SIMカードにアプレットを書き込むために必要な鍵情報です。
- 現在は販売終了しているらしく、後継モデルが販売されています。
- カードリーダー
- Alcor Micro社 AU9540
- (ThinkPad T470sに内蔵されているもの)
- 検証に使用したスマートフォン
- LG isai(au LGL22)
- Android 4.4.2
- (iPhone 5cではアクティベーションを求められて先に進めず断念)
環境構築
SIMアプレットをコンパイルしSIMカードに書き込むために、JDKとSIM用ツールを準備します。
OpenJDK8のインストール
Java Cardを扱うためにまずJDKをインストールします。
$ sudo apt install openjdk-8-jdk筆者の環境では以下のバージョンがインストールされました。
$ java -version openjdk version "1.8.0_275" OpenJDK Runtime Environment (build 1.8.0_275-8u275-b01-0ubuntu1~20.04-b01) OpenJDK 64-Bit Server VM (build 25.275-b01, mixed mode)sysmoUSIM-SJS1向けツールの用意
使用するSIMカード(sysmoUSIM-SJS1)用にツールが提供されていますのでcloneします。
なお、sysmocomから提供されている本ツールの動作にはPython 2が必要です。$ git clone git://git.osmocom.org/sim/sim-toolsこの中に含まれる
shadysim.pyを使用します。
カードリーダーにSIMカードを挿した状態でスクリプトを実行するとICCID(SIMカード毎に固有な識別番号)が表示されます。sim-tools/shadysim$ python2 shadysim.py --pcsc ICCID: 8988211000000364102fまたこのリポジトリにはJava Card用のコンバーターやJava Card 2.1.2のAPI、SIM APIが含まれており、アプレットのソースコードがあればすぐにビルドができるようになっています。
サンプルアプレットをインストールする
環境の準備ができたところで、テキストを表示するサンプルアプレットをSIMカードに書き込んでみましょう。
サンプルアプレットの取得
サンプルアプレットが含まれるリポジトリをcloneします。
このとき、取得したhello-stkディレクトリは上で取得したsim-toolsディレクトリと同じ階層に置いてください。$ git clone git://git.osmocom.org/sim/hello-stk $ ls hello-stk sim-toolsアプレットのビルド
サンプルのjavaファイルからJava Card向けのcapファイルを作っていきます。
エラーが出なければビルド成功です。$ cd hello-stk/hello-stk hello-stk/hello-stk$ make mkdir -p ./build/classes mkdir -p ./build/javacard javac -target 1.1 -source 1.3 -g -d ./build/classes -classpath "../../sim-tools/javacard/lib/api21.jar:../../sim-tools/javacard/lib/sim.jar" src/org/toorcamp/HelloSTK/HelloSTK.java 警告: [options] ブートストラップ・クラスパスが-source 1.3と一緒に設定されていません 警告: [options] ソース値1.3は廃止されていて、今後のリリースで削除される予定です 警告: [options] ターゲット値1.1は廃止されていて、今後のリリースで削除される予定です 警告: [options] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。 警告4個 java -jar ../../sim-tools/javacard/bin/converter.jar \ -d ./build/javacard \ -classdir ./build/classes \ -exportpath ../../sim-tools/javacard/api21_export_files \ -applet 0xd0:0x70:0x02:0xca:0x44:0x90:0x01:0x01 org.toorcamp.HelloSTK.HelloSTK \ org.toorcamp.HelloSTK 0xd0:0x70:0x02:0xCA:0x44:0x90:0x01 1.0 Java Card 2.1.2 Class File Converter (version 1.2) Copyright (c) 2001 Sun Microsystems, Inc. All rights reserved. conversion completed with 0 errors and 0 warnings.ビルドが完了すると
buildディレクトリ以下に生成物が格納されます。そのうちHelloSTK.capをSIMカードに書き込んでいきます。hello-stk/hello-stk$ ls build/javacard/org/toorcamp/HelloSTK/javacard HelloSTK.cap HelloSTK.expSIMカードへ書き込み
アプレットのSIMカードへの書き込みには
shadysim.pyを使用します。
SIMカードに書き込むためにはアプレット領域に書き込むための認証鍵が2種類(KIC, KID)が必要です(sysmocomでSIMを購入するとカジュアルにメールで認証鍵情報が送られてきます)。以下のコマンドを実行すると書き込みが行われます。
$KIC,$KIDは各自のものに置き換えてください。
書き込みに成功すればICCIDのみが出力されて実行が終了します。$ python2 shadysim.py \ --pcsc \ -l ../../hello-stk/hello-stk/build/javacard/org/toorcamp/HelloSTK/javacard/HelloSTK.cap \ -i ../../hello-stk/hello-stk/build/javacard/org/toorcamp/HelloSTK/javacard/HelloSTK.cap \ --module-aid D07002CA44900101 \ --instance-aid D07002CA44900101 \ --nonvolatile-memory-required 0100 \ --volatile-memory-for-install 0100 \ --max-menu-entry-text 15 \ --max-menu-entries 05 \ --enable-sim-toolkit \ --kic $KIC \ --kid $KID ICCID: 8988211000000364102fスマホでの動作確認
それではお待ちかねの実機検証です。SIMカードをカードリーダーから取り出し、スマホに挿したら電源ON or 再起動しましょう。
Androidではアプリ一覧に「SIMツールキット」というアプリが表示されます。メニューにある
Hello, STKをタップするとメッセージが表示されます。
これにてサンプルアプレットの動作確認は完了です!
アプレットはSIMカード上で動作しているので、基本的にはSIMカードを他のスマートフォンに移し替えても同じアプレットを使うことができます。ソースコードを見てみよう
SIMアプレットはJavaのサブセットであるJava Cardで書かれています。
Java Cardではcharやfloat型や多次元配列が扱えなかったり、インスタンス変数はカードが持つEEPROMに書き込まれるなどの制限があります。HelloSTKの冒頭部分を見ていきましょう。
public class HelloSTK extends Applet implements ToolkitInterface, ToolkitConstants { // DON'T DECLARE USELESS INSTANCE VARIABLES! They get saved to the EEPROM, // which has a limited number of write cycles. private byte helloMenuItem; static byte[] welcomeMsg = new byte[] { 'W', 'e', 'l', 'c', 'o', 'm', 'e', ' ', 't', 'o', ' ', 'T', 'o', 'o', 'r', 'C', 'a', 'm', 'p', ' ', '2', '0', '1', '2' }; static byte[] menuItemText = new byte[] { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'S', 'T', 'K'};最初に、メニューやダイアログに表示するテキストを定義しています。char型が扱えないため、byte型の配列に1文字ずつ格納しています。
コメントにもあるように、インスタンス変数はEEPROMに書き込まれるため、変数をたくさん作ると書き込み可能回数がどんどん減っていくため注意です(RAMに書き込む方法もあります)。次の部分を見ていきましょう。
private HelloSTK() { // This is the interface to the STK applet registry (which is separate // from the JavaCard applet registry!) ToolkitRegistry reg = ToolkitRegistry.getEntry(); // Define the applet Menu Entry helloMenuItem = reg.initMenuEntry(menuItemText, (short)0, (short)menuItemText.length, PRO_CMD_SELECT_ITEM, false, (byte)0, (short)0); } // This method is called by the card when the applet is installed. You must // instantiate your applet and register it here. public static void install(byte[] bArray, short bOffset, byte bLength) { HelloSTK applet = new HelloSTK(); applet.register(); }ここではアプレットをカードに登録する処理を行っています。
コンストラクタではメニューの項目について、なにを表示するか、選択されたらどんなProactive Commandを発行するか、などを定義しています。項目を複数出したい場合は項目数だけinitMenuEntryを呼び出す必要があります。
install()はアプレットがカードに書き込まれたときにカードから呼び出されるメソッドです。アプレットからregister()を実行するとアプレットのインスタンスがカードに登録されます。
(参考:An Introduction to Java Card Technology - Part 1)次の部分を見ていきましょう。
// This processes APDUs sent directly to the applet. For STK applets, this // interface isn't really used. public void process(APDU arg0) throws ISOException { // ignore the applet select command dispached to the process if (selectingApplet()) return; } // This processes STK events. public void processToolkit(byte event) throws ToolkitException { EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler(); if (event == EVENT_MENU_SELECTION) { byte selectedItemId = envHdlr.getItemIdentifier(); if (selectedItemId == helloMenuItem) { showHello(); } } }
process()はSIM Toolkit(STK)を経由せず直接アプレットにAPDUが送られたときに呼び出されます。インターフェースとして必要ですが、今回は何も処理しないようにしています。
STKを経由するとprocessToolkit()が呼び出されます。
Envelopeとは端末からSIMに対して送られるデータです。端末で発生したイベント情報などが含まれます。EnvelopeHandlerを使ってアプレットからこのデータを扱えるようにしています。今回のケースでは以下のような判断をしています。
- 「メニューが選択された」というイベントか?
- 選択されたメニュー項目は
helloMenuItemか?判断から外れた場合はなにもせず処理が終了します。
では最後に
showHello()の中身を見ていきましょう。private void showHello() { ProactiveHandler proHdlr = ProactiveHandler.getTheHandler(); proHdlr.initDisplayText((byte)0, DCS_8_BIT_DATA, welcomeMsg, (short)0, (short)(welcomeMsg.length)); proHdlr.send(); return; }
ProactiveHandlerを用意してProactive Commandを扱えるようにしています。
次にinitDisplayText()を呼び出し、端末に送るテキスト表示のコマンドを作成します。
コマンドを作成したらsend()で端末側にProactive Commandを発行します。これにより端末側のディスプレイ上にテキストが表示されます。(おまけ)音を出してみよう
ETSI TS 102 223には様々なコマンドが記載されています。
その中で"PLAY TONE"というのが個人的に気になったのでトーン音を出すアプレットを作ってみました。ソースコードはgistにアップしました。
https://gist.github.com/staybuzz/5a76d15da57e6b1a31e1e6efa0c71192サンプルアプレットから追加した部分として、
- メニュー項目
- メニューが選択されたときの処理
- PLAY TONE用のProactive Commandの作成、送信
を実装しています。
音鳴らしてる pic.twitter.com/uFL2ye8Xf8
— すていばず (@staybuzz) December 6, 2020おわりに
本記事ではSIM Toolkitを使ったSIMアプレットについて、アプレットのビルドから書き込み、動作確認まで行いました。
SIMアプレットを活用することで、遠隔から端末の状態を取得できたりSIMから事業者に通知するなど、さまざまなことが出来そうです。明日は@suzusuzuさんです!
参考
- 投稿日:2020-12-06T13:09:06+09:00
【Java・SpringBoot】NamedParameterJdbcTemplateでCRUD操作(SpringBootアプリケーション実践編19)
ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
今回はNamedParameterJdbcTemplateでの基本的なCRUD操作を学びます^^
構成は前回/これまでの記事を参考にしてください⭐️前回の記事
【Java・SpringBoot】RowCallbackHandlerでコールバック / CSV出力機能(SpringBootアプリケーション実践編18)NamedParameterJdbcTemplate
- JdbcTemplateでは、
PreparedStatmentを使う際に、メソッドの引数の順番に注意を払う必要があったが、NamedParameterJdbcTemplateでは不要。リポジトリークラスを追加
- リポジトリークラスでは、UserDaoインターフェースをimplements
NamedJdbcTemplateの使い方
- NamedJdbcTemplateでは、PreparedStatementのSQL文に?ではなく
:<キー名>を使う *String sql = "INSERT INTO m_user(user_id," + " password," + " role)" + " VALUES(:userId," + " :password," + " :role)";SQL文に入れるパラメーターを設定
- SqlParameterSourceクラスをnewして、addValue()メソッドにキーと値をセット
- addValue()の第一引数にキー名、第二引数に値をセット
SqlParameterSource params = new MapSqlParameterSource().addValue("userId", user.getUserId())- NamedJdbcTemplateにSqlParameterSourceを渡せばOK
return jdbc.update(sql, params);NamedParameterJdbcTemplate.java//略(全文は下記参考) @Repository("UserDaoNamedJdbcImpl") public class UserDaoNamedJdbcImpl implements UserDao { @Autowired private NamedParameterJdbcTemplate jdbc; //Userテーブルの件数を取得. @Override public int count() { //SQL分 String sql = "SELECT COUNT(*) FROM m_user"; //パラメーター生成 SqlParameterSource params = new MapSqlParameterSource(); //全件取得してカウント return jdbc.queryForObject(sql, params, Integer.class); } //Userテーブルにデータを1件insert. @Override public int insertOne(User user) { //SQL文 String sql = "INSERT INTO m_user(user_id," + " password," + " user_name," + " birthday," + " age," + " marriage," + " role)" + " VALUES(:userId," + " :password," + " :userName," + " :birthday," + " :age," + " :marriage," + " :role)"; //パラメーター SqlParameterSource params = new MapSqlParameterSource() .addValue("userId", user.getUserId()) .addValue("password", user.getPassword()) .addValue("userName", user.getUserName()) .addValue("birthday", user.getBirthday()) .addValue("age", user.getAge()) .addValue("marriage", user.isMarriage()) .addValue("role", user.getRole()); //SQL実行 return jdbc.update(sql, params); } //Userテーブルの全データを取得. @Override public List<User> selectMany() { //SQL文 String sql = "SELECT * FROM m_user"; //パラメーター SqlParameterSource params = new MapSqlParameterSource(); //SQL実行 List<Map<String, Object>> getList = jdbc.queryForList(sql, params); //結果返却用のList List<User> userList = new ArrayList<>(); //取得データ分loop for(Map<String, Object> map: getList) { //Userインスタンスの生成 User user = new User(); //Userインスタンスに取得したデータをセットする user.setUserId((String)map.get("user_id")); //ユーザーID user.setPassword((String)map.get("password")); //パスワード user.setUserName((String)map.get("user_name")); //ユーザー名 user.setBirthday((Date)map.get("birthday")); //誕生日 user.setAge((Integer)map.get("age")); //年齢 user.setMarriage((Boolean)map.get("marriage")); //結婚ステータス user.setRole((String)map.get("role")); //ロール //Listに追加 userList.add(user); } return userList; } //Userテーブルを1件更新. @Override public int updateOne(User user) { //SQL文 String sql = "UPDATE M_USER" + " SET" + " password = :password," + " user_name = :userName," + " birthday = :birthday," + " age = :age," + " marriage = :marriage" + " WHERE user_id = :userId"; //パラメーター SqlParameterSource params = new MapSqlParameterSource() .addValue("userId", user.getUserId()) .addValue("password", user.getPassword()) .addValue("userName", user.getUserName()) .addValue("birthday", user.getBirthday()) .addValue("age", user.getAge()) .addValue("marriage", user.isMarriage()); //SQL実行 return jdbc.update(sql, params); } //略(全文は下記参考) }サービスクラス修正
- UserDaoNamedJdbcImplを使用する
UserService.java//略(全文は下記参考) @ServiceUserService public class UserService { @Autowired @Qualifier("UserDaoNamedJdbcImpl") UserDao dao; //略 }SpringBootを起動してホーム画面確認!
- http://localhost:8080/home
- ユーザー一覧に移ると、コンソールの表示がUserDaoNamedJdbcImplに変わっています
- NamedParameterJdbcTemplateを使ったクラスが使われていることがわかりました^^
- (注)NamedParameterJdbcTemplateではRowMapperは使えるが、BeanPropertyRowMapperは使えません
//コンソール メソッド開始: List com.example.demo.login.domain.repository.jdbc.UserDaoNamedJdbcImpl.selectMany() メソッド終了: List com.example.demo.login.domain.repository.jdbc.UserDaoNamedJdbcImpl.selectMany()(参考)コード全文
NamedParameterJdbcTemplate.javapackage com.example.demo.login.domain.repository.jdbc; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.stereotype.Repository; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Repository("UserDaoNamedJdbcImpl") public class UserDaoNamedJdbcImpl implements UserDao { @Autowired private NamedParameterJdbcTemplate jdbc; //Userテーブルの件数を取得. @Override public int count() { //SQL分 String sql = "SELECT COUNT(*) FROM m_user"; //パラメーター生成 SqlParameterSource params = new MapSqlParameterSource(); //全件取得してカウント return jdbc.queryForObject(sql, params, Integer.class); } //Userテーブルにデータを1件insert. @Override public int insertOne(User user) { //SQL文 String sql = "INSERT INTO m_user(user_id," + " password," + " user_name," + " birthday," + " age," + " marriage," + " role)" + " VALUES(:userId," + " :password," + " :userName," + " :birthday," + " :age," + " :marriage," + " :role)"; //パラメーター SqlParameterSource params = new MapSqlParameterSource() .addValue("userId", user.getUserId()) .addValue("password", user.getPassword()) .addValue("userName", user.getUserName()) .addValue("birthday", user.getBirthday()) .addValue("age", user.getAge()) .addValue("marriage", user.isMarriage()) .addValue("role", user.getRole()); //SQL実行 return jdbc.update(sql, params); } //Userテーブルのデータを1件取得 @Override public User selectOne(String userId) { //SQL文 String sql = "SELECT * FROM m_user WHERE user_id = :userId"; //パラメーター SqlParameterSource params = new MapSqlParameterSource() .addValue("userId", userId); //SQL実行 Map<String, Object> map = jdbc.queryForMap(sql, params); //結果返却用のインスタンスを生成 User user = new User(); //取得データをインスタンスにセットしていく user.setUserId((String)map.get("user_id")); //ユーザーID user.setPassword((String)map.get("password")); //パスワード user.setUserName((String)map.get("user_name")); //ユーザー名 user.setBirthday((Date)map.get("birthday")); //誕生日 user.setAge((Integer)map.get("age")); //年齢 user.setMarriage((Boolean)map.get("marriage")); //結婚ステータス user.setRole((String)map.get("role")); //ロール return user; } //Userテーブルの全データを取得. @Override public List<User> selectMany() { //SQL文 String sql = "SELECT * FROM m_user"; //パラメーター SqlParameterSource params = new MapSqlParameterSource(); //SQL実行 List<Map<String, Object>> getList = jdbc.queryForList(sql, params); //結果返却用のList List<User> userList = new ArrayList<>(); //取得データ分loop for(Map<String, Object> map: getList) { //Userインスタンスの生成 User user = new User(); //Userインスタンスに取得したデータをセットする user.setUserId((String)map.get("user_id")); //ユーザーID user.setPassword((String)map.get("password")); //パスワード user.setUserName((String)map.get("user_name")); //ユーザー名 user.setBirthday((Date)map.get("birthday")); //誕生日 user.setAge((Integer)map.get("age")); //年齢 user.setMarriage((Boolean)map.get("marriage")); //結婚ステータス user.setRole((String)map.get("role")); //ロール //Listに追加 userList.add(user); } return userList; } //Userテーブルを1件更新. @Override public int updateOne(User user) { //SQL文 String sql = "UPDATE M_USER" + " SET" + " password = :password," + " user_name = :userName," + " birthday = :birthday," + " age = :age," + " marriage = :marriage" + " WHERE user_id = :userId"; //パラメーター SqlParameterSource params = new MapSqlParameterSource() .addValue("userId", user.getUserId()) .addValue("password", user.getPassword()) .addValue("userName", user.getUserName()) .addValue("birthday", user.getBirthday()) .addValue("age", user.getAge()) .addValue("marriage", user.isMarriage()); //SQL実行 return jdbc.update(sql, params); } //Userテーブルを1件削除. @Override public int deleteOne(String userId) { //SQL文 String sql = "DELETE FROM m_user WHERE user_id = :userId"; //パラメーター SqlParameterSource params = new MapSqlParameterSource() .addValue("userId", userId); //SQL実行 int rowNumber = jdbc.update(sql, params); return rowNumber; } //SQL取得結果をサーバーにCSVで保存する @Override public void userCsvOut() { //M_USERテーブルのデータを全件取得するSQL String sql = "SELECT * FROM m_user"; //ResultSetExtractorの生成 UserRowCallbackHandler handler = new UserRowCallbackHandler(); //クエリー実行&CSV出力 jdbc.query(sql, handler); } }UserService.javapackage com.example.demo.login.domain.service; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Service public class UserService { @Autowired @Qualifier("UserDaoNamedJdbcImpl") UserDao dao; /** * insert用メソッド. */ public boolean insert(User user) { // insert実行 int rowNumber = dao.insertOne(user); // 判定用変数 boolean result = false; if (rowNumber > 0) { // insert成功 result = true; } return result; } /** * カウント用メソッド. */ public int count() { return dao.count(); } /** * 全件取得用メソッド. */ public List<User> selectMany() { // 全件取得 return dao.selectMany(); } /** * 1件取得用メソッド. */ public User selectOne(String userId) { // selectOne実行 return dao.selectOne(userId); } /** * 1件更新用メソッド. */ public boolean updateOne(User user) { // 判定用変数 boolean result = false; // 1件更新 int rowNumber = dao.updateOne(user); if (rowNumber > 0) { // update成功 result = true; } return result; } /** * 1件削除用メソッド. */ public boolean deleteOne(String userId) { // 1件削除 int rowNumber = dao.deleteOne(userId); // 判定用変数 boolean result = false; if (rowNumber > 0) { // delete成功 result = true; } return result; } // ユーザー一覧をCSV出力する. public void userCsvOut() throws DataAccessException { // CSV出力 dao.userCsvOut(); } /** * サーバーに保存されているファイルを取得して、byte配列に変換する. */ public byte[] getFile(String fileName) throws IOException { // ファイルシステム(デフォルト)の取得 FileSystem fs = FileSystems.getDefault(); // ファイル取得 Path p = fs.getPath(fileName); // ファイルをbyte配列に変換 byte[] bytes = Files.readAllBytes(p); return bytes; } }
- 投稿日:2020-12-06T10:25:11+09:00
VSCode拡張 Tomcat for Java でDocker上のTomcat を登録できない時の対処法
これは何
vscodeでtomcatの開発環境を構築する際,Tomcat for JavaにDocker上のtomcatを登録できずハマったときのメモ.
環境
- Windows10pro
- Docker Engine - Community Version: 19.03.13
- WSL2
- 使用イメージ tomcat:9.0.40-jdk11-openjdk-buster
- Visual Studio Code 1.51.1
- Tomcat for Java v0.11.3
- Remote Development v0.20.0
現象
Tomcat: Add Tomcat serverでtomcatのインストールされているディレクトリである/usr/local/tomcatを指定するも,うんともすんとも言わない.permissionの問題だと思い chmod で権限の変更を試みるものの,改善しない.
原因と暫定対処
現時点では不明
私の環境ではdevcontainer.jsonに以下のような設定をしていました.
しかしコンテナを起動してから拡張機能を無効→リロード→有効 と操作することでtomcatを追加できました."extensions": [ "vscjava.vscode-java-pack", "gabrielbb.vscode-lombok", "adashen.vscode-tomcat" ],よって対応としては
- ↑の設定からadashen.vscode-tomcatを削除
- コンテナのビルド後に手動でTomcat for Javaを追加
一応動作させることはできているのですが原因が不明のままです.
- 投稿日:2020-12-06T07:09:41+09:00
人事システム「COMPANY」のバッチ処理の裏(側) の話 - 番外編
せっかく 人事システム「COMPANY」のバッチ処理の裏(側) の話 を書いたのでこいつが如何なるやつかをもう少し書いておきたくなりました。
Develop fun!を体現する Works Human Intelligence Advent Calendar 2020
Develop fun!を体現する Works Human Intelligence #2 Advent Calendar 2020
Works Human Intelligence アドベントカレンダー 12/5 の記事の補足です。キューはどこからやってくるのでしょう
人事システム「COMPANY」のバッチ処理の裏(側) の話
上の話で出てきた「キュー管理サービス」というやつ。
キューは文字通りのQueueなんですがどうやって順番を管理しているかというとJava内のほかにもう一つ投入口が用意されている。下位互換的にそういう仕上がりになっている。「キュー受付テーブル」と一旦呼ぶことにしよう。batchmanager.java//「キュー受付テーブル」にレコードが入る。 // ↓ //「キュー管理スレッド」がそれを検知。 // ↓ // ジョブ処理待ちへ的な流れ。これはこれでただのバッチ処理機構の内輪の話で「キュー受付テーブル」にレコードを投入するには投入するための関数を使ってやるのが最も正式なお作法。
batchmanager.java// ★投入関数を使って呪文をとなえる(いくつか呪文には種類があります) // ↓ //「キュー受付テーブル」にレコードが入る。 // ↓ //「キュー管理スレッド」がそれを検知。 // ↓ // ジョブ処理待ちへと、言いたいのだが、さてこれが仕様であるとしてこの「バッチ処理」を使いたい各種プロダクトやサブシステムはどうしているか。「★のとおり呪文をとなえている」というのはもちろん大多数なんですがここで語りたい話ではない。とりあえず以下2つ知ってるからここに書いておく。
その1
★に代わる(使用者さんにとって)いい感じの投入関数を自分で作っている。
作っている。なぜそうされたかはわからないが自作されている。なんか、★で提供している関数が機能不足だったんだと思う。それで動いているのだからすごいけど、何だろう。今度真面目にこちら側から読まないといけないと思っている。
その2
「キュー受付テーブル」に自分でレコードを突っ込む。
もう少しオレオレなアプローチ。関数をいくら隠ぺいしてもテーブルはPublicでしたーーーーー!ってことだな。
内部にお詳しい者の昔からの知見と思われる。これも問題なく動いている。動いているけどさー、これもそろそろどうにかならないのかなと思っている。コンウェイの法則
ソフトウェアのどの部分であれ、それを作った組織の構造を反映する。
Any piece of software reflects the organizational structure that produced it
備考:原文ではこれの応用として「コンパイラを4つのグループで作れば、4パスコンパイラになる」というのが紹介されている2つの例が組織の構造を反映しているとして果たしてどう比喩されたもんだかなのですが、なんか密なのか疎なのかという以前にバッチ機構の中身が透けて見えているかのようなのでいい感じに作っておけば動くからまあいっかの結果なのかなあ。ていうかそれオープンな組織だね!(自慢じゃない
以上休日にゆるりと楽しんでいただきたい小話でした。
- 投稿日:2020-12-06T03:44:24+09:00
英語のテックジョークで使えそうなワード
良さげなワード
arrays(配列)-> a raise(昇給)
bugs(バグ) -> bugs(虫)
dark(IDEなどのダークモード) -> dark(暗闇)
table(DB table) -> table(机)
server(サーバ) -> server(レストラン店員)
cache(キャッシュ) -> cash(現金)
Java(プログラミング言語) -> java(コーヒー)今日の名言
I hope my death makes more cents than my life.(私はこの人生よりも硬貨な死を望む)
※ make cents(小金を稼ぐ)とmake sense(意味をなす)をかけている-アーサー(映画ジョーカーの主人公)


























