20200310のJavaに関する記事は11件です。

【アルゴリズム入門】Javaで選択ソートを実装する

備忘録を兼ねて
アルゴリズムそのものの話はあんまりしません

ソースコード

SelectionSort.java
class SelectionSort {
  public static void main(String[] args) {
    int[] data = {30, 60, 70, 90, 40};
    sort(data);
    for(int element : data) {
      System.out.println(element);  
    }

  public static void sort(int[] data) {
    for(int i = 0; i < data.length;/*...⒈*/ i++) {
      int min = i;//...⒉

      for(int j = i + 1; j < data.length; j++) {
        if(data[j] < data[min]) { 
          min = j;
        }
      }

     //...⒊
      int temp = data[i];
      data[i] = data[min];
      data[min] = temp;
    }
  }
}

選択ソートについてはいくらでも優れた教材があるのでアルゴリズムそのものについての解説や考察は省略

そうではなく、自分で書いてて大事(難しい)だと思ったり、今後実務でコードを書く上で、他のメンバーとのディスコミュニケーションの原因になりかねないと思った所について考えたい。

⒈ ループ回数

外側のループのループ回数が(data.length - 1)回になっているが、これは配列の要素数-1回目の並べ替えが終わった時点で残り一つの位置が自動的に決まるため。今回に限らずループ回数は間違えやすいので気を付けたい。具体的な数値でトレースすると間違うリスクは減らせそう。

⒉ 変数minの意味

int min = i;

ここで定義している変数minは配列dataの要素の最小値の添字なんだけども、初見では最小値そのものだと勘違いしてしまった。つまるところこの変数が何を意味するのかちょっと分かりにくい。(分かりにくい…よね?)

その後のコードを読めば理解できたんだが、チーム開発を想定すると変数は見てすぐに意味ができるような命名の方が望ましいと思う。(その意味ではループの制御変数をiやjにするのもベストな選択ではないような気がする)

例えばminではなく、min_indexと命名して添え字であることを分かりやすくするのが良いんじゃないだろうか。(コメントで変数の意味を説明するのは却ってコードを読む手間が増えそう。また、コメントで注釈をつける箇所とつけない箇所の基準が明確でないとチーム内のコミュニケーションコストが増えて生産性も下がる)

⒊ 要素の並び替え時の注意点

data[i]とdata[min]を入れ替える際に、単に
data[i] = data[min]
data[min] = data[i]
としてしまうと一行目でdata[min]の値を代入したdata[i]を二行目でdata[min]に代入するという不毛な作業をしてしまうことになる。

それを避けるために一時的にdata[i]の値を格納しておく変数tempを用意するという方法が使われている。他のアルゴリズムでも見るので確実に押さえておきたい。

tempやworkと命名されることが多いが、「一時的に値を格納しておく」という目的を考えるとworkよりもtempの方が適していると思う。

まとめ

アルゴリズムの学習用に書いたコードであっても分かりやすい変数の命名など、チーム開発を意識しておきたい。

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

Kotlinでクラスを書いてJavaで呼んでみる

去年Kotlinでコード書いたはずだ!とドキュメントを見てみるが、見覚えが無いなぁと思っていたら、去年書いたのはGoだった・・・乗り掛かった舟ということでKotlinを見てみました。

開発環境はIntelliJ

KotlinのチュートリアルのGetting Startedで「Getting Started with IntelliJ IDEA」とIntelliJが1番に出てくるので流行っているのかな?コミュニティ版をダウンロードしてインストールします。

IntelliJ.PNG

Kotlinファイル作成&実行

以前見たときよりダークなだけでカッコいい感じがするIntelliJ。
intellij2.png
新規にプロジェクトを作って、Kotlinファイルを追加して実行してみるまでは「Getting Started with IntelliJ IDEA」通りにサクサクできる。

fun main() {
    println("Hello Kotlin!")
}

IntelliJインストール時になんとなく、そうかなぁと思いましたがKotlinのファイル拡張子「.kt」なんですね。(初心者)

.ktファイルを編集しているコードの左にRunボタンがあるのでクリックすると実行できる。IntelliJとても便利。
kt_run.png

ファイル名がKotlinClassSono1.ktとなっていますが、Kotlinファイルってクラスファイルなのかと思ってました。

このmain関数をJavaからどうやって呼ぶのかな?ってことでJarファイルがいるだろうなぁ・・・IntelliJで「Build Project」を実行しても特に何も出力されないので調べてみました。

Jarファイルを出力

FileメニューからProject Structureを開きます。
project-structure.png
Project Settings>Artifacts で「+」ボタンをクリック JAR>From modules with dependencies
add-jar.png
Main Class は指定しないで「OK」ボタンをクリック
createjarfrommodule.png
「Include in project build」にチェックをします。
includeinprojectbuild.png
もう一度「Build Project」を実行してみると・・・
プロジェクトフォルダー下にJarファイルが出力されてます。ありがとうIntelliJ
outputdir.PNG
ちなみにプロジェクト名は「KotlinComponent」になっています。
Jarファイルの中を覗いてみると
jar1.PNG
「ファイル名+Kt」という名前の「KotilnClassSono1Kt.class」が含まれているのが確認できました。

Javaから呼んでみる

Javaのプロジェクトで作成したJarファイルを読み込んでみると、

public class Main {
    public static void main(String[] args) {
        KotlinClassSono1Kt.main();
    }
}

KotlinClassSono1Ktが見えて、
javaexec.PNG
JavaからKotlinのコードを実行できました。
クラス名にKtついてるし、なんだかなぁ~ということで、IntelliJに戻って追加でpackageとclassを追加してみました。
パッケージのsrcフォルダでコンテキスメニュー New>Package でパッケージ作成。普通です。
作成したPackageでコンテキストメニュー New>Kotlin File/Class !?
newclass.png
ここで初めてFileとClassの違いに気が付きました。

package ari

class KotlinClassSono2 {
}

Packageには「ari」を指定してます。
KotlinのClassのドキュメントを真似してクラスに関数「test」を持たせてみます。コンストラクタはおまけです。

package ari

class KotlinClassSono2 constructor(name: String){
    init {
        println("name = ${name}")
    }

    constructor(name: String, age: Int) : this("$name"){
        println("age = ${age}")
    }

    fun test() {
        println("test call OK !")
    }
}

再度「Build Project」を実行してJarファイルを作成してJavaで読み込みます。
「ari」パッケージの下に「KotlinClassSono2」クラスが見えました。
クラス名に「Kt」が付いていません。なるほど~
javacode.PNG
コンストラクタを使ってインスタンスも作れました。
関数呼び出しも成功です。
javaexec2.PNG

最後に

Kotlinいいですね。
使わないんですけどreferenceをもう少し読んでみようかなぁと思いました。

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

安定結婚問題を解くアルゴリズムをJavaで作ってみた。

安定結婚問題って知ってます…?

男性と女性が3人ずついたとします。

  • 男A
  • 男B
  • 男C
  • 女P
  • 女Q
  • 女R

すると3組の夫婦が作れますよね。
でも異性の好みは人それぞれですし、なるべくみんなが満足するような結果にしたい。

そこで個人個人がランキングを持っているとします。

1位 2位 3位
男Aの好み 女P 女Q 女R
男Bの好み 女Q 女R 女P
男Cの好み 女P 女Q 女R
女Pの好み 男C 男B 男A
女Qの好み 男B 男A 男C
女Rの好み 男C 男A 男B

さて、例えば下記のようなマッチングを考えたとしましょう。

  • 男A ⇔ 女Q
  • 男B ⇔ 女R
  • 男C ⇔ 女P

男Cと女Pはお互いに1位の相手と結婚しているので幸せです。
しかし、男Aと男Bはどうでしょう。妻をとりかえっこしたほうがもっと幸せですよね。
そこでマッチングを変更し、下記のようにしたとしましょう。

  • 男A ⇔ 女R
  • 男B ⇔ 女Q
  • 男C ⇔ 女P

これ以上とりかえっこすると、むしろ悪化しそうですね。
このように、これ以上とりかえっこが必要ないマッチング結果のことを「安定マッチング」といいます。

「安定結婚問題」とは、ランキングをもとにして、安定マッチングを見つける問題のことです。
数学の業界では、組合せ最適化問題と呼ばれる問題のひとつとして知られています。

Gale-ShapleyのアルゴリズムをJavaで試作

安定結婚問題はWikipediaにも載っているほど有名な問題です。
安定結婚問題 - Wikipedia

男女3人ずつなら簡単ですが、人数が増えるほど考えられる組合せは爆発的に増え、難しくなります。

Wikipediaを見てみるとGale-Shapleyのアルゴリズムというのが書いてありますね。
安定結婚問題の考案者である Gale と Shapley が提案したアルゴリズムで、
安定マッチングを確実に求められるらしい…。

ということでJavaを使ってさくっと実装してみました。

ただし、このソースコードは下記の理由によりほとんど参考にならないため注意!

  • とりあえず動くものをスピード重視で作っただけで、可読性や処理速度を一切考えていない
  • 入力(ランキングデータ)がハードコーディング
  • 出力(マッチング結果)が標準出力されるだけ

ソースコード

package hoge;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

public class randomMatching {

    public static HashMap<String, ArrayList<String>> ranking;
    public static HashMap<String, String> matching;

    public static void main(String args[]){

        // 変数初期化
        matching = new HashMap<String, String>();
        matching.put("男A", "");
        matching.put("男B", "");
        matching.put("男C", "");
        ranking = new HashMap<String, ArrayList<String>>();
        ranking.put("男A", new ArrayList<>());
        ranking.put("男B", new ArrayList<>());
        ranking.put("男C", new ArrayList<>());
        ranking.put("女P", new ArrayList<>());
        ranking.put("女Q", new ArrayList<>());
        ranking.put("女R", new ArrayList<>());
        Collections.addAll(ranking.get("男A"), "女P", "女Q", "女R");
        Collections.addAll(ranking.get("男B"), "女Q", "女R", "女P");
        Collections.addAll(ranking.get("男C"), "女P", "女Q", "女R");
        Collections.addAll(ranking.get("女P"), "男C", "男B", "男A");
        Collections.addAll(ranking.get("女Q"), "男B", "男A", "男C");
        Collections.addAll(ranking.get("女R"), "男C", "男A", "男B");

        while(true)
        {
            // 現在の状況を表示
            dump();

            // 独身男性を探す
            String singleManName = "";
            for (String manName : matching.keySet()) {
                if(matching.get(manName) == "")
                {
                    singleManName = manName;
                    break;
                }
            }

            // 独身男性がもういない場合
            if(singleManName == "")
            {
                System.out.println("フリーの人がいなくなったため、プログラムを終了します。");
                return;
            }

            // 独身男性の好みの女性を調べてプロポーズする
            String bestWomanName = ranking.get(singleManName).get(0);
            System.out.println(singleManName + "が、一番好きな" + bestWomanName + "にアタックしました。");

            // この女性が独身か調べる
            String husbandName = "";
            for (String manName : matching.keySet()) {
                if(matching.get(manName) == bestWomanName)
                {
                    husbandName = manName;
                    System.out.println("ところが" + bestWomanName + "は" + husbandName + "と付き合っています。");
                    break;
                }
            }

            // この女性が独身だった場合は婚約
            if(husbandName == "")
            {
                System.out.println(bestWomanName + "は現在フリーのため" + singleManName + "と付き合うことにしました。");
                matching.put(singleManName, bestWomanName);
                continue;
            }
            else
            {
                // 婚約中の hasbandName と、プロポーズしてきた singleManName 、どちらが好みか調べる
                for(String str : ranking.get(bestWomanName))
                {
                    // husbandName のほうが好みの場合
                    if(str == husbandName)
                    {
                        // singleManはこの女性に今後二度とプロポーズしない
                        System.out.println(bestWomanName + "は" + singleManName + "より" + husbandName + "が好きです。" + singleManName + "は" + bestWomanName + "を諦めました。");
                        ranking.get(singleManName).remove(bestWomanName);
                        break;
                    }

                    // husbandName のほうが好みの場合
                    if(str == singleManName)
                    {
                        // 婚約
                        System.out.println(bestWomanName + "は" + husbandName + "より" + singleManName + "が好きなので、乗り換えることにしました。");
                        matching.put(singleManName, bestWomanName);
                        matching.put(husbandName, "");
                        break;
                    }
                }
            }
        }

    }

    // 現在の状態を表示する関数
    public static void dump()
    {
        System.out.println("");

        System.out.println("■現在のランキング");
        for (String key : ranking.keySet()) {
            System.out.println("・" + key + " " + ranking.get(key));
        }

        System.out.println("◆現在のマッチング");
        for (String manName : matching.keySet()) {
            System.out.println("・" + manName + " ⇔ " + matching.get(manName));
        }

        System.out.println("");
    }
}

実行結果

夫婦がまったく成立していない状態から、
進行状況を段階的にSystem.out.println()するように作ってあります。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女P, 女Q, 女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 
・男A ⇔ 
・男B ⇔ 

男Cが、一番好きな女Pにアタックしました。
女Pは現在フリーのため男Cと付き合うことにしました。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女P, 女Q, 女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 女P
・男A ⇔ 
・男B ⇔ 

男Aが、一番好きな女Pにアタックしました。
ところが女Pは男Cと付き合っています。
女Pは男Aより男Cが好きです。男Aは女Pを諦めました。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女Q, 女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 女P
・男A ⇔ 
・男B ⇔ 

男Aが、一番好きな女Qにアタックしました。
女Qは現在フリーのため男Aと付き合うことにしました。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女Q, 女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 女P
・男A ⇔ 女Q
・男B ⇔ 

男Bが、一番好きな女Qにアタックしました。
ところが女Qは男Aと付き合っています。
女Qは男Aより男Bが好きなので、乗り換えることにしました。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女Q, 女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 女P
・男A ⇔ 
・男B ⇔ 女Q

男Aが、一番好きな女Qにアタックしました。
ところが女Qは男Bと付き合っています。
女Qは男Aより男Bが好きです。男Aは女Qを諦めました。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 女P
・男A ⇔ 
・男B ⇔ 女Q

男Aが、一番好きな女Rにアタックしました。
女Rは現在フリーのため男Aと付き合うことにしました。

■現在のランキング
・男C [女P, 女Q, 女R]
・男A [女R]
・女Q [男B, 男A, 男C]
・男B [女Q, 女R, 女P]
・女R [男C, 男A, 男B]
・女P [男C, 男B, 男A]
◆現在のマッチング
・男C ⇔ 女P
・男A ⇔ 女R
・男B ⇔ 女Q

フリーの人がいなくなったため、プログラムを終了します。

以上が実行結果となります。
安定マッチングが無事に求まりました。

このアルゴリズムのすごいところ

(1) 必ず安定マッチングが求まる

実行結果の途中で

女Pは男Aより男Cが好きです。男Aは女Pを諦めました。

と出力されているところがあります。
このように男のアタックを女が断ったとき、
男は「諦める」、つまりその男のランキングから該当の女を除外します。
この除外操作により、男が同じ女に繰り返しアタックすることは絶対に発生しません。
無限ループに陥ることなく、アルゴリズムが必ず終了するのはこのためです。

(2) 結婚だけでなく応用がきく

今回は「どの男とどの女を夫婦にする?」という題材で試してみましたが、
ほかにも例えば学校で「どのゼミにどの学生を入れる?」という題材でもできそうですよね。
ゼミの教授は好きな学生のランキングを作れるでしょうし、
学生も入りたいゼミのランキングを作れるでしょう。

お互いのランキングさえあれば、あらゆる題材にこのアルゴリズムは実行可能なのです。
そして求まるのは「安定マッチング」。
求まったマッチング結果に文句を言う人はいないはず!

まとめ

突貫工事ではありますが、JavaでGale-Shapleyのアルゴリズムを実装することができました。
実装してみたいアルゴリズム、解決してみたい組合せ最適化問題があれば、また何か挑戦してみたいと思います。

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

SpringBootでフォームクラスのバリデーションテスト

javax側が持っているバリデーション(アノテーションで定義出来る)とSpring側のバリデーションのテスト方法について困惑したため、下記の通り纏めます。
ちなみに、javax側のカスタムアノテーションで全て作ればいいじゃんって思うかもしれませんが、思うようにテストが出来なかったため、やむを得ずSpring側で作成しました。

テスト実行時に、自作したValidation内のフィールドへDIする方法

やりたいこと

まずは対象となるクラスを確認します。

PasswordForm.java
package com.ssp_engine.user.domain.model;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;

import com.ssp_engine.user.domain.model.validation.ConfirmPassword;
import com.ssp_engine.user.domain.model.validation.ValidGroup1;
import com.ssp_engine.user.domain.model.validation.ValidGroup2;
import com.ssp_engine.user.domain.model.validation.ValidGroup3;
import com.ssp_engine.user.domain.model.validation.ValidGroup4;

import lombok.Data;

@Data
@ConfirmPassword(field = "password", groups = ValidGroup4.class)
public class PasswordForm {

    @NotBlank(groups = ValidGroup1.class)
    private String currentPassword;

    @NotBlank(groups = ValidGroup1.class)
    @Length(min = 4, max = 8, groups = ValidGroup2.class)
    @Pattern(regexp="^[a-zA-Z0-9]+$", groups = ValidGroup3.class)
    private String password;

    @NotBlank(groups = ValidGroup1.class)
    private String confirmPassword;

}

こんな感じの、一般的なWebサービスの管理画面にある自身のパスワードを編集する時のフォームクラスです。
currentPasswordは、現在ログイン中のパスワード
passwordは、変更するパスワード
confirmPasswordは、確認用パスワード

で、それぞれに@NotBlankやら@Patternの正規表現のバリデーションがあります。
currentPasswordに現在ログインしているパスワードと比較するバリデーションがあり、それが下記の通りです。

LoginPassAndFormPassValidator.java
package com.ssp_engine.user.domain.model.validation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import com.ssp_engine.user.domain.model.PasswordForm;

@Component
public class LoginPassAndFormPassValidator implements Validator {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public boolean supports(Class<?> clazz) {
        return PasswordForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        PasswordForm form = (PasswordForm) target;
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        UserDetails principal = (UserDetails) auth.getPrincipal();
        String userPass = principal.getPassword();

        if (form.getCurrentPassword() == null) {
            return;
        }

        if (!this.passwordEncoder.matches(form.getCurrentPassword(), userPass)) {
            errors.rejectValue("currentPassword",
                               "LoginPassAndFormPassValidator.PasswordForm.currentPassword",
                               "ログイン中のパスワードと異なります。");
        }
    }
}

コントローラー側で、@InitBinderしてから使用しています。
これらのSpring側とJavax側のバリデーターをテストしていきます。

テスト

そして、テストですが、コントローラー側のテストとフォームクラスのテストで分けたほうが長くならず、テスト内容を切り分けやすいとのことで、こんな感じになりました。

PasswordFormTests.java
@RunWith(SpringRunner.class)
@SpringBootTest
public class PasswordFormTests {

    private PasswordForm passwordForm = new PasswordForm();
    private BindingResult bindingResult = new BindException(passwordForm, "PasswordForm"); //①

    @Autowired
    @Qualifier("loginPassAndFormPassValidator") //②
    /* Spring側 */
    private org.springframework.validation.Validator loginPassAndFormPassValidator;
    /* javax側 */
    private static Validator validator; //③

    @BeforeClass
    public static void 初期化処理() { //④
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); 
        validator = validatorFactory.getValidator(); 
    }

    @Before
    public void 値をセット() throws Exception{ //⑤
        this.passwordForm.setCurrentPassword("currentpassword");
        this.passwordForm.setConfirmPassword("password");
        this.passwordForm.setPassword("password");
    }

}

①・・・バリデーターを実行した後に、結果を受け取るためのフィールド
②・・・明示的にどのクラスかを指定してあげる必要があったため、@Qualifierで指定しました。
③・④・・・@AutoWiredで指定して、Beanをゲットしたかったのですが、上手く行かなかったので、明示的にBeanを作っています。
⑤・・・対象となるオブジェクトに値をセットしています。

準備が整ったところで、ゴリゴリテストをしていくわけですが、こんな感じになりました。
まずは、エラーが出ないことを確認。

PasswordFormTests.java
    @Test
    @WithMockUser(username = "username",
                  password ="$2a$10$p3/Malw3/KWyfOlPwWoUCulx4iDb2C/nmo6x8P2svXjfJQ5ETLhG2",
                  roles = "USER")
    public void エラー無し() throws Exception{
        loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult); //①
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup1.class,ValidGroup2.class,ValidGroup3.class,ValidGroup4.class); //②
        assertThat(bindingResult.getFieldError(), is(nullValue())); //③
        assertThat(violations.size(), is(0)); //④
    }

①・・・Spring側のバリデーションを実行しています。第一引数に対象のオブジェクト、第二引数に、結果を格納するオブジェクトbindingResultを指定してあげています。
②・・・ConstraintViolationは制約違反の内容を格納したオブジェクトのセットを返し、validateの第一引数には、対象のオブジェクト。第二引数には、ValidGroupを指定していたため、どのバリデーションを有効にするか指定しています。
②・・・Spring側の結果をbindingResultで受け取り、Nullかどうかチェックしています。
③・・・Javax側の結果をviolations.size()でサイズを図り、0かチェックしています。

@WithMockUserは、loginPassAndFormPassValidatorでログイン情報を取得する必要があったため、ログイン状態にしています。

これで、エラーが出ないことが分かったら、こんな感じで書いていきました。

PasswordFormTests.java
    @Test
    @WithMockUser(username = "username",
                  password ="currentpassword",
                  roles = "USER")
    public void ログインパスと入力パスが違う() throws Exception{
        loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        assertThat(bindingResult.getFieldError("currentPassword"), is(bindingResult.getFieldError()));
        assertThat(bindingResult.getFieldError().getDefaultMessage(), is("ログイン中のパスワードと異なります。"));
    }

    @Test
    public void 現在のパスワードがBlank() throws Exception{
        this.passwordForm.setCurrentPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "currentPassword"), is(instanceOf(NotBlank.class))); //①
    }

    private Annotation getAnnotation(Set<ConstraintViolation<PasswordForm>> violations, String path) { //②
        return violations.stream()
                .filter(cv -> cv.getPropertyPath().toString().equals(path))
                .findFirst()
                .map(cv -> cv.getConstraintDescriptor().getAnnotation())
                .get();
    }

①・・・どのアノテーションでエラーが出てるか確認しています。
②・・・エラーで弾かれたアノテーションのインスタンスを取得するために、メソッドを作っています。

全体像

全体像はこんな感じになりました。

PasswordFormTests.java
package com.ssp_engine.user.domain.model;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.lang.annotation.Annotation;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;

import com.ssp_engine.user.domain.model.validation.ConfirmPassword;
import com.ssp_engine.user.domain.model.validation.ValidGroup1;
import com.ssp_engine.user.domain.model.validation.ValidGroup2;
import com.ssp_engine.user.domain.model.validation.ValidGroup3;
import com.ssp_engine.user.domain.model.validation.ValidGroup4;


@RunWith(SpringRunner.class)
@SpringBootTest
public class PasswordFormTests {

    private PasswordForm passwordForm = new PasswordForm();
    private BindingResult bindingResult = new BindException(passwordForm, "PasswordForm");

    @Autowired
    @Qualifier("loginPassAndFormPassValidator")
    /* Spring側 */
    private org.springframework.validation.Validator loginPassAndFormPassValidator;
    /* javax側 */
    private static Validator validator;

    @BeforeClass
    public static void 初期化処理() {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        validator = validatorFactory.getValidator();
    }

    @Before
    public void 値をセット() throws Exception{
        this.passwordForm.setCurrentPassword("currentpassword");
        this.passwordForm.setConfirmPassword("password");
        this.passwordForm.setPassword("password");
    }

    @Test
    @WithMockUser(username = "username",
                  password ="$2a$10$p3/Malw3/KWyfOlPwWoUCulx4iDb2C/nmo6x8P2svXjfJQ5ETLhG2",
                  roles = "USER")
    public void エラー無し() throws Exception{
        loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup1.class,ValidGroup2.class,ValidGroup3.class,ValidGroup4.class);
        assertThat(bindingResult.getFieldError(), is(nullValue()));
        assertThat(violations.size(), is(0));
    }

    @Test
    @WithMockUser(username = "username",
                  password ="currentpassword",
                  roles = "USER")
    public void ログインパスと入力パスが違う() throws Exception{
        loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        assertThat(bindingResult.getFieldError("currentPassword"), is(bindingResult.getFieldError()));
        assertThat(bindingResult.getFieldError().getDefaultMessage(), is("ログイン中のパスワードと異なります。"));
    }

    @Test
    public void 現在のパスワードがBlank() throws Exception{
        this.passwordForm.setCurrentPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "currentPassword"), is(instanceOf(NotBlank.class)));
    }

    @Test
    public void パスワードがBlank() throws Exception{
        this.passwordForm.setPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(NotBlank.class)));
    }

    @Test
    public void 確認用パスワードがBlank() throws Exception{
        this.passwordForm.setConfirmPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "confirmPassword"), is(instanceOf(NotBlank.class)));
    }

    @Test
    public void 確認用パスワードと入力パスワードが異なる時() throws Exception{
        this.passwordForm.setPassword("aiueo");
        this.passwordForm.setConfirmPassword("kakikukeko");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup4.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(ConfirmPassword.class)));
    }

    @Test
    public void パスワードが8文字以上の時() throws Exception{
        this.passwordForm.setPassword("aiueokakikukeko");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup2.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(Length.class)));
    }

    @Test
    public void パスワードが4文字以下の時() throws Exception{
        this.passwordForm.setPassword("aiu");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup2.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(Length.class)));
    }

    @Test
    public void パスワードが半角英数字以外の時() throws Exception{
        this.passwordForm.setPassword("テストです");
        Set<ConstraintViolation<PasswordForm>> violations =
                validator.validate(this.passwordForm,ValidGroup3.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(Pattern.class)));
    }

    private Annotation getAnnotation(Set<ConstraintViolation<PasswordForm>> violations, String path) {
        return violations.stream()
                .filter(cv -> cv.getPropertyPath().toString().equals(path))
                .findFirst()
                .map(cv -> cv.getConstraintDescriptor().getAnnotation())
                .get();
    }
}

参考になった方がいらっしゃいましたら、幸いです。

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

インターセプタークラスでkeycloak認証の情報を取得する方法

keycloak認証からユーザーの認証情報を持った状態でAPIにリクエストを投げた際、
controllerクラスのメソッド呼び出される前にintercepter(インターセプター)から
ユーザー情報を取得するのにgetUserPrincipal()メソッドがあることを知らず手こずったのでメモ。

intercepterTestClass.java
  @Autowired
  private UserDto userDto;

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    //

    //認証情報を持っていない場合は
    if (request.getUserPrincipal() == null) {
      System.out.println("認証情報なしの為、ダミー設定");
      userDto.setUserid("U0001");
      userDto.setUserName("guest");
      return true;
    }

    //
    final KeycloakSecurityContext context = KeycloakPrincipal.class.cast(request.getUserPrincipal()).getKeycloakSecurityContext();

    userDto.setUserName(context.getToken().getPreferredUsername());
    userDto.setUserId(String.valueOf(context.getToken().getOtherClaims().get("user_id")));

    //
    return true;

keycloak認証の実装については別記事参照。インターセプターで認証情報を取得することで
各Controllerクラスに記述する必要がなくなって汎用性はよくなった。
今まで認証機能はSpringSecurityしか使って来なかったけど、
APIの実装だとSpringSecurityはあんまり適さないみたい。

※ちなみにintercepterTestClass下部でtoString()ではなく、String.valueOfでユーザーIDを
取得しているのは、nullだった時にtoString()だとヌルポで落ちる為。

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

インターセプタークラスでkeycloakの認証情報を取得する方法

keycloak認証からユーザーの認証情報を持った状態でAPIにリクエストを投げた際、
controllerクラスのメソッド呼び出される前にintercepter(インターセプター)から
ユーザー情報を取得するのにgetUserPrincipal()メソッドがあることを知らず手こずったのでメモ。

intercepterTestClass.java
  @Autowired
  private UserDto userDto;

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    //

    //認証情報を持っていない場合は
    if (request.getUserPrincipal() == null) {
      System.out.println("認証情報なしの為、ダミー設定");
      userDto.setUserid("U0001");
      userDto.setUserName("guest");
      return true;
    }

    //
    final KeycloakSecurityContext context = KeycloakPrincipal.class.cast(request.getUserPrincipal()).getKeycloakSecurityContext();

    userDto.setUserName(context.getToken().getPreferredUsername());
    userDto.setUserId(String.valueOf(context.getToken().getOtherClaims().get("user_id")));

    //
    return true;

keycloak認証の実装については別記事参照。インターセプターで認証情報を取得することで
各Controllerクラスに記述する必要がなくなって汎用性はよくなった。
今まで認証機能はSpringSecurityしか使って来なかったけど、
APIの実装だとSpringSecurityはあんまり適さないみたい。

※ちなみにintercepterTestClass下部でtoString()ではなく、String.valueOfでユーザーIDを
取得しているのは、nullだった時にtoString()だとヌルポで落ちる為。

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

【Android】Snackbarを任意の位置から表示させる

概要

Snackbarを画面の下部から出すのではなく、任意の位置から表示させる

レイアウトファイルでCoordinatorLayoutを入れる

fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:layout_marginTop="200dp"
            android:gravity="center" />

     </androidx.coordinatorlayout.widget.CoordinatorLayout>

</LinearLayout>

Snackbarを表示させたいViewをCoordinatorLayoutでラップする

Snackbarの実装

build.gradle
dependencies {
    implementation 'com.google.android.material:material:1.1.1'
}

Snackbarを使うためにbuild.gradlecom.google.android.materialimplementationする
※バージョンはよしなに変えてください

MainFragment.java
import com.google.android.material.snackbar.Snackbar;

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    Button button = view.findViewById(R.id.button);
    button.setText("button");
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            showSnackbar("test");
        }
    });
    super.onViewCreated(view, savedInstanceState);
}

private void showSnackbar(String message) {
    View view = getView();
    if (view == null) return;

    Snackbar snackbar = Snackbar.make(view.findViewById(R.id.button), message, Snackbar.LENGTH_SHORT);
    snackbar.show();
}

Snackbar表示用のメソッドを作成
Snackbar.makeの第一引数を表示させたい位置のViewのidにする(今回はButton
ボタンを押した時にボタンのViewの下からSnackbarを出すようにする

余分な記載が多いし、もうちょっと良い方法があると思うけど備忘録なので一旦これでいいかな。。

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

#01-02 学習内容 2日目 (2020/03/10)

今日の目標

まだ本が手に入っていないので、
- Future Codersのゲーム一つ作る。
- Java練習問題集の8章

Future codersのゲーム一つ

  • 25分でできました。
  • 今日は集中力が持たない感じ。後で昼寝しよう。。
  • GameOverの判定方法の書き方が勉強になった! boolean isGameOver{ return 10 < words.size(); }
  • if (isGameOver){ text("GameOver", 10, 10); }

Java練習問題集の8章

  • クラスの継承
  • public class Student{   String name;   int id;   attend(){} }
  • public class School extends Student{   test(){} }
  • メソッドのオーバーライド
    • 同様のメソッド名で中身を変更するだけ。
    • 変更前のメソッドが利用したかったら、super.メソッド名として呼び出す。

達成度

今日の目標達成!
メソッドの継承はそこまで難しくはなかった。

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

初心者から始めるJava、クラス宣言・オブジェクト生成

はじめに

この記事は備忘録である。
参考書レベルの内容だが、本記事に掲載するコードについては、
間違えたものが中心となる。これは実際にコーディング中に間違えた部分を掲載し、自分で反省するために投稿するという目的によるもの。
また、後日にJavaSilver試験問題の勉強を兼ねて復習するため、深い部分の話はここでは触れない。

なお今回は間違えを扱わない。クラスの話はJavaにおいて重要なうえ、狙って間違えられるほど僕の理解も進んでいない。定義を正しい形で再確認するため、ご容赦いただきたい。

環境

言語:Java11、JDK13.0.2
動作環境:Windows10

クラス

Hello,world!.java
class helloWorld
{
public static void main(String[] args)
  {
    System.out.println("Hello,world!");
  }
}

これは一番簡単なプログラムであり、JavaにおけるHello,world!出力を行うものである。最初の行に出てきているが、今回のテーマはクラス(class)。Javaを扱う上で必ず扱う機能となる。
参考書の「やさしいJava 第7版」によると

クラスとは、
モノの状態・性質や、それにかかわる機能をまとめながらプログラムを作成していくために使う概念

である。今の僕の理解だとclassは、具体的な個々のモノを扱う前に参考となる見本、いわば雛型を用意する認識だ。

オブジェクト

Cat.java
class Cat
{
String name;
double weight;
double size;
String color;
int ID;
String voice;
//挙げ始めるとキリがない!!
}

このクラスには「一般的な猫」が持つステータスのごく一部を宣言させた。だがこれは「僕の猫」の特徴が入っているわけではない。
実際に「僕の猫」ステータスを持つmycatをメインメソッドの処理中に作ってみる。

myCat.java
class myCat
{
  public static void main(String[] args)
  {
    Cat mycat = new Cat();
    //mycat変数にCatクラスのオブジェクトを生成

    mycat.name = "テト";
    mycat.color = "brown";
    mycat.ID = 3;

   System.out.println("僕の猫、名前は" + mycat.name + "。");
   System.out.println("色は" + mycat.color + "、" + mycat.ID + "番目の猫だ。");
  }
}

//出力結果:僕の猫、名前はテト。
//色はbrown、3番目の猫だ。

class Catで定義され、Cat mycat = new Cat();でmycatに作られたのがオブジェクト(object)あるいはインスタンス(instance)である。

終わりに

次回の予定は、プログラムを触った人なら必ず最初に書くHello,World!を出力するプログラムを扱うつもりだ。本来なら真っ先に書くべきだとは思ったが、Pythonから入った身としてはJavaHello,Worldは最初からクラスを使う必要がありとても難しかったため、ここまで待った。

次の次の回では、今回割愛したフィールドメソッドを扱う予定。
今回は内容が薄かったが、ここから先があまり自信がない部分なので、今の実力の指標として分けさせてもらう。

参考

出来るだけ自分で変数や式を書いてコンパイルしているので、完全に引用する場合はその旨記述する。

やさしいJava 第7版
Java SE11 Silver 問題集(通称黒本)

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

Rim fdraw As require ’pop->File->Draw'

ファイルの有無を調べなければインストール

Sik pop @popのインストール
echo Sik pop @こうすれば同時にログも取れて安心(あればtrueなければfalse)
Rim require "pop" @ライブラリの取り込み
Rim fdraw As require "pop->file->draw"

Rimはファイル自身。requireは文字列の名前空間を探す。asは別名を作成

@代入にはset簡単でしょ
n set loop t of range 10 to if t->mod 2 = 1:{1,t->tostr}:{2,t->tostr}
call T t->add1 n 0

@メソッド定義
Trait T
bind f h m n = h->each->(m)->append n
Trait end

@メソッド定義
Method T extends Trait
@めんどくさけりゃusing ? repeat $だぜ!
using bind repeat $
$add1 h m = f h m 1 @h->each->(0)->inc とも書ける
$inc h m = h->each->(m)->inc
$dec h m = h->each->(m)->dec 
Proc Construct me-lily ,grp set {}
Lily grp set grp
img set{fdraw'{}'->Format C:\Data\Mg.png ->Image 1}
ndir set nodir
chdir img->(0)->Getdir
Lily geekimg set img
Proc End-> nil
Method End
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntuで使う言語のインストール方法とか環境構築とか

最近はバックエンド言語毎にVMで環境用意して勉強したりしてて、その環境構築方法の管理を最近はGistでしてるのですが、何となくQittaに。※※但し、Gistは英語で書いてるので。

environment

  • host OS: Windows
  • VM: Virtual Box with Vagrant
    • Ubuntu 18.0

CUIまたはGUIの仮想環境をUbuntuを使って構築するのはこっち。

Ruby on Rails

Install latest version

terminal
# install in one time
sudo apt install autoconf bison build-essential libssl-dev libreadline-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev

# install rbenv
# rbenv is tool to manage a few of ruby versions and enable to change ruby ver. project by project.
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build

# Install ruby
rbenv install --list
rbenv install 2.〇.〇
rbenv global 2.〇.〇

# Instal yarn
# Rails6 needs webpacker, and Webpacker needs yarn to install
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt install yarn

# Install Rails
gem install rails --no-document

# install webpacker
# inner App
rails webpacker:install

Install RubyonRails by "apt install"

terminal
sudo apt install -y ruby ruby-dev build-essential
sudo apt install yarn

sudo gem install rails
  • "-y" means "All Yes"
  • build-essential contain information about package to build Debian pack.
    • If do not build Debian, build~ is not needed
    • Reference

Nodejs

rails6 uses webpacker, which needs nodejs

terminal
# first, install nodejs and npm
sudo apt install -y nodejs npm

# install n-package
sudo npm install n -g

# by n-package, install node
sudo n stable

# uninstal old nodejs and npm, and re-login
sudo apt purge -y nodejs npm
exec $SHELL -l

# confirm
node -v

Rust

when discord changed golang to Rust, I just tried this and coded a little.

terminal
sudo apt install build-essential

# install rust
curl https://sh.rustup.rs -sSf | sh

# add the pass
source $HOME/.cargo/env

Java

terminal
sudo apt update
sudo apt install git
sudo apt install openjdk-11-jdk

# confirmation
java --version

PHP

Python

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