20200530のJavaに関する記事は20件です。

Coursierで特定のmaven artifactの依存関係を確認する

Androidでの開発やJavaなどのJVM言語での開発で、あれ、このライブラリって何に依存しているんだっけ?って思うことよくありますよね?
皆さんはどのように確認しているでしょうか?
https://mvnrepository.com/ などのサイトを見たり、 ./gradlew dependenciesで確認するなどさまざまだと思います。

今日はScalaで書かれたCoursierというCLIツールが便利だったので紹介します。
https://get-coursier.io/

使い方

ターミナル上で coursier resolve maven_artifactという感じで確認できます

$ coursier resolve io.circe:circe-core_2.12:0.10.0
io.circe:circe-core_2.12:0.10.0:default
io.circe:circe-numbers_2.12:0.10.0:default
org.scala-lang:scala-library:2.12.6:default
org.scala-lang:scala-reflect:2.12.6:default
org.typelevel:cats-core_2.12:1.4.0:default
org.typelevel:cats-kernel_2.12:1.4.0:default
org.typelevel:cats-macros_2.12:1.4.0:default
org.typelevel:machinist_2.12:0.6.5:default

-tでツリー状にも依存関係を確認できます。

$ cs resolve -t io.circe::circe-generic:0.12.3
  Result:
└─ io.circe:circe-generic_2.13:0.12.3
   ├─ com.chuusai:shapeless_2.13:2.3.3
   │  └─ org.scala-lang:scala-library:2.13.0
   ├─ io.circe:circe-core_2.13:0.12.3
   │  ├─ io.circe:circe-numbers_2.13:0.12.3
   │  │  └─ org.scala-lang:scala-library:2.13.0
   │  ├─ org.scala-lang:scala-library:2.13.0

インストール方法

Macだと以下のようにしてインストールできるようです。
https://get-coursier.io/docs/cli-installation

brew install coursier/formulas/coursier

Google Maven Reposiotryにあるリポジトリを見る

Androidの開発で欠かせないGoogle Maven Reposiotryを見るには以下のようにして行います。

cs resolve -t  -r https://maven.google.com -r https://jcenter.bintray.com androidx.ui:ui-livedata:0.1.0-dev12|view -
└─ androidx.ui:ui-livedata:0.1.0-dev12
   ├─ androidx.compose:compose-runtime:0.1.0-dev12
   │  ├─ androidx.annotation:annotation:1.1.0
   │  ├─ org.jetbrains.kotlin:kotlin-stdlib:^[[33m1.3.70 -> 1.3.71^[[0m
   │  │  ├─ org.jetbrains:annotations:13.0
   │  │  └─ org.jetbrains.kotlin:kotlin-stdlib-common:1.3.71
   │  ├─ org.jetbrains.kotlin:kotlin-stdlib-common:^[[33m1.3.70 -> 1.3.71^[[0m
   │  ├─ org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3
   │  │  ├─ org.jetbrains.kotlin:kotlin-stdlib:^[[33m1.3.50 -> 1.3.71^[[0m
   │  │  │  ├─ org.jetbrains:annotations:13.0
   │  │  │  └─ org.jetbrains.kotlin:kotlin-stdlib-common:1.3.71
   │  │  └─ org.jetbrains.kotlin:kotlin-stdlib-common:^[[33m1.3.50 -> 1.3.71^[[0m
   │  ├─ org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6
   │  │  ├─ org.jetbrains.kotlin:kotlin-stdlib:1.3.71
   │  │  │  ├─ org.jetbrains:annotations:13.0

終わりに

依存関係を調べるときに手軽に調べられるので便利そうでした。
依存性の確認以外にも、まとめてjarにしてくれる機能や起動してくれる機能などがあって便利だったので、公式サイトを確認してみてください。
https://get-coursier.io/docs/overview

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

【Kotlin】大容量のpngを生成したかった【Java】

やりたかったこと

とある案件で大量の重めのpng画像サンプルが必要になりました。
一方、単純に四角をfillするような形で画像を生成した場合、圧縮されてしまって高々数KB程度になってしまうなど、容量が確保できませんでした。

方針

圧縮というものは大概以下2点に当てはまる場合強く作用します。

  • 同じパターンが連続する
  • 同じ色が連続する

よって、「ランダムな色のランダムな線を沢山引けば圧縮しにくいだろう」というノリでやりました。

目標は1940 x 500のサイズで1枚2MBです。
それを達成していれば一旦構わないため、更なる大容量化や処理時間に関しては気にせずにやっていきます。

実装

${プロジェクトルート}/build/generatedディレクトリに2000枚の画像を生成するサンプルです。
linesが100000有れば安定して2MBを超えてくれました。

parallelStream()しているのは「ちょっとは早くなるんじゃね」程度のノリで、実際に早くなってるのかは確認していません。

import java.awt.Color
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import kotlin.random.Random

const val width = 1940
const val height = 500
const val lines = 100000 // ライン数
const val count = 2000

fun main() {
    val generateTarget = System.getProperty("user.dir") + "/build/generated/"

    List(count) { it }.parallelStream().forEach { index ->
        val img = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)
        (img.graphics as Graphics2D).apply {
            repeat(lines) {
                // 色は少しでも大容量化することを狙って透過度も指定
                this.color = Random.nextBytes(4).let {
                    Color(it[0] + 128, it[1] + 128, it[2] + 128, it[3] + 128)
                }
                this.drawLine(Random.nextInt(width), Random.nextInt(height), Random.nextInt(width), Random.nextInt(height))
            }
            dispose()
        }
        ImageIO.write(img, "png", File("$generateTarget${index}.png"))
    }
}

実行結果

2000枚で4.81GB、平均2.4MBでした。
負荷は高く実行時間はそれなりにかかったので、暇なときに回しておく位がちょうどいいと思います。

こんな感じの画像が生成されます。
0.png

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

【Kotlin】面積当たりの容量が大きいpngを生成したかった【Java】

やりたかったこと

とある案件で大量の重めのpng画像サンプルが必要になりました。
一方、単純に四角をfillするような形で画像を生成した場合、圧縮されてしまって高々数KB程度になってしまうなど、容量が確保できませんでした。
必要な画像のフォーマットも決まっていたため、単純に面積を増やして容量を稼ぐことも難しかったです。

方針

圧縮というものは大概以下2点に当てはまる場合強く作用します。

  • 同じパターンが連続する
  • 同じ色が連続する

よって、「ランダムな色のランダムな線を沢山引けば圧縮しにくいだろう」というノリでやりました。

目標は1940 x 500のサイズで1枚2MBです。
それを達成していれば一旦構わないため、更なる大容量化や処理時間に関しては気にせずにやっていきます。

実装

${プロジェクトルート}/build/generatedディレクトリに2000枚の画像を生成するサンプルです。
linesが100000有れば安定して2MBを超えてくれました。

parallelStream()しているのは「ちょっとは早くなるんじゃね」程度のノリで、実際に早くなってるのかは確認していません。

import java.awt.Color
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import kotlin.random.Random

const val width = 1940
const val height = 500
const val lines = 100000 // ライン数
const val count = 2000

fun main() {
    val generateTarget = System.getProperty("user.dir") + "/build/generated/"

    List(count) { it }.parallelStream().forEach { index ->
        val img = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)
        (img.graphics as Graphics2D).apply {
            repeat(lines) {
                // 色は少しでも大容量化することを狙って透過度も指定
                this.color = Random.nextBytes(4).let {
                    Color(it[0] + 128, it[1] + 128, it[2] + 128, it[3] + 128)
                }
                this.drawLine(Random.nextInt(width), Random.nextInt(height), Random.nextInt(width), Random.nextInt(height))
            }
            dispose()
        }
        ImageIO.write(img, "png", File("$generateTarget${index}.png"))
    }
}

実行結果

2000枚で4.81GB、平均2.4MBでした。
負荷は高く実行時間はそれなりにかかったので、暇なときに回しておく位がちょうどいいと思います。

こんな感じの画像が生成されます。
0.png

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

JDKの設定方法をまとめてみた【VSCode】

VSCodeでJDKのパスを設定する方法をまとめてみました。
今回使ったJDKはOracleJDKとAdoptOpenJDKです。
まだJDKをダウンロードしていない方は以下のサイトでダウンロードしましょう。
おすすめはAdoptOpenJDKです。

AdoptOpenJDKのダウンロードサイト
https://adoptopenjdk.net/
OracleJDKのダウンロードサイト
https://www.oracle.com/java/technologies/javase-downloads.html

JDKを設定する前に知っておきたいこと

パスとは何か

パスとはファイルやフォルダを保存している場所のことです。
JDKのデフォルトのパスは
AdoptOpenJDK C:\Program Files\AdoptOpenJDK\バージョン
OracleJDK C:\Program Files\Java\バージョン
となっています。
バージョンにはJDKのバージョンが入ります。

パスの優先順位

VSCodeでは環境変数で設定したパスとVSCodeのJava_homeで設定したパスが競合しないようにパスの優先順位が決まっています。

拡張機能のLanguage Support for Java(TM) by Red Hatによるとパスの優先順位は以下の通りです。

  1. VScodeのJava.home
  2. 環境変数 JDK_HOME
  3. 環境変数 JAVA_HOME
  4. 現在のシステムPATH(おそらく環境変数 PATH。例外あり?)

例えば、JAVA_HOMEでjdk-11を、Java.homeでjdk-14を設定しているとJava.homeの方が優先順位が高いためJDK-14が適用されます。

VSCodeでどのパスが使用されているか確認する

Ctrl+Shift, Pでコマンドパレットを開きConfigure Javaと打ってConfigure Java Runtimeを選択します。
するとJava Development Kitの画面が開き、現在どのパスが使用されているのか確認することができます。
ここではパスの確認の他、JDKのダウンロードもできます。

私の環境では以下のようになりました。
image.png
Configureを見ると4つのパスが並んでいて、3つのパスが認識されていることが分かります。
パスの順番は先ほど紹介した優先順位に対応しています。
複数のパスが認識されていますが、使用されるパスは優先順位が1番高いjava.homeで設定したパスだけです。
使用されているパスの隣にはCurrentが表示されます。

JDKの設定方法

ここから本題。
おすすめ順に紹介しています。

方法1 VSCodeのJava.homeを設定する

環境変数をいじる必要がないため、この方法が一番安全かつ簡単です。
また、Java.homeのパスは優先順位が一番高いため、パスの優先順位を気にする必要がありません。

Ctrl + ,で設定を開きJavahomeと入力します。
image.png
Java:HomeのEdit in settings.json(日本語ならsettings.jsonで編集)をクリックすると、settings.jsonが開かれて、以下のコードが自動生成されるかと思います。

settings.json
{
    "java.home": ""
}

自動生成されたコードの""の間にJDKのパスを入力します。
(ここではバージョン11のAdoptOpenJDKのパスを入力していますが、自分がインストールしたJDKのパスを適宜入力してください)

settings.json
{
    "java.home": "C:\\Program Files\\AdoptOpenJDK\\jdk-11.0.7.10-hotspot"
}

編集し終わったらsettings.jsonを保存します。
保存すると画面右下に再起動しろというメッセージが表示されるのでボタンを押して再起動します。(再起動しないと設定した内容が反映されません)
メッセージが表示されないときは、VSCodeを一度終了して再度起動してください。

以上でパスの設定は終わりです。
Java Development Kitの画面を開くと、java.homeの部分に先ほど設定したパスが表示されます。
image.png

方法2 環境変数を設定する

この方法でパスを通せばコマンドプロンプトなど他のアプリケーションでも使用することができますが、環境変数をいじることになるので注意が必要です。
また、環境変数 JAVA_HOMEよりも優先順位が高いjava.homeでパスが設定されていると、そちらのパスが適用されてしまいます。

パスの通し方はこちらのサイトを参考にしてください。
https://www.javadrive.jp/start/install/index4.html
(AdoptOpenJDKはダウンロードの仕方によって自動で設定されている場合があります)

パスを通したらVSCodeを再起動します。

ここではバージョン11のAdoptOpenJDKのパスを設定しました。
Java Development Kitの画面を開くと、設定したパスがJAVA_HOMEとOtherに表示されます。
image.png

この状態でjava.homeにバージョン14のAdoptOpenJDKのパスを設定するとバージョン14の方が適用され、バージョン11が使えなくなってしまいます。
image.png

方法3 OracleJDKを使う パスの設定なし

OracleJDKをダウンロードするだけです。パスの設定は不要です。
この方法はなぜできるのかよく分かっていませんが、OracleJDKをダウンロードしているとデフォルトの保存先(C:\Program Files\Java\バージョン)を自動で検索してくれるみたいです。

OracleJDKをインストールしてパスの設定をしないで実行したときの画像です。
image.png
環境変数のPathでパスを設定していないためターミナルでjavaコマンドが認識されていませんが、なぜか右ウィンドウのOtherでOrcleJDKを認識できていることが分かります。
実行もできています。

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

java SE 11 Silver 合格体験記【現役理系大学生ver】

概要

java SE 11 Silver のテストを受けてきたので体験記を残しとこうと思います。
Silverはたくさん体験記があるみたいなので、実務経験がなくプログラムを作ったことが無い人に向けたことに重点を置いて書こうと思います。

使用した本

徹底攻略Java SE 11 Silver問題集
https://www.amazon.co.jp/%E5%BE%B9%E5%BA%95%E6%94%BB%E7%95%A5Java-SE-11-Silver%E5%95%8F%E9%A1%8C%E9%9B%86%EF%BC%BB1Z0-815%EF%BC%BD%E5%AF%BE%E5%BF%9C-%E5%BE%B9%E5%BA%95%E6%94%BB%E7%95%A5%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-ebook/dp/B07YWKWBBD/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=3AOWI1BXV0Y5G&dchild=1&keywords=java+se+11+silver&qid=1590841315&sprefix=java+SE+%2Caps%2C276&sr=8-1

黒本と言われてるやつです。画像そのまま載せていいのか分からないのでリンクだけ貼っときます。
黒本の内容は、問題集です。

オラクル認定資格教科書 Javaプログラマ Silver SE11
https://www.amazon.co.jp/%E3%82%AA%E3%83%A9%E3%82%AF%E3%83%AB%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC%E6%95%99%E7%A7%91%E6%9B%B8-Java%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E-Silver-SE11%EF%BC%88%E8%A9%A6%E9%A8%93%E7%95%AA%E5%8F%B71Z0-815%EF%BC%89-%E5%B1%B1%E6%9C%AC-ebook/dp/B07YDM92JZ/ref=sr_1_2?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=3AOWI1BXV0Y5G&dchild=1&keywords=java+se+11+silver&qid=1590841315&sprefix=java+SE+%2Caps%2C276&sr=8-2

これは紫本と言われているのですかね?こちらもリンクだけ貼っときます。
こっちは参考書ですね。

受験した感想

私の点数

まずは私の得点率ですが、得点率は87%でした。90%いけたらいいなーと思っていたのでちょっと残念です。

試験時間

時間的にはかなり余ります。たしか受験時間が3時間くらいだったと思いますが、1時間ぐらいで一回目の見直しまで終わるくらいのボリュームでした。

私の初期値

javaは今回の受験で初めて本格的に触りました。
が、Android javaをちょこっとかじっていたので完璧な無知識かと言われると違うと思います。

もし無知識なら受験勉強の前にjavaの入門書はたくさんあるのでレンタルなどでさらっとjavaという言語の概要をつかんどくといいかもしれません。
Qiitaでもたくさんそういった記事を書いて下さる方がいらっしゃるのでそちらを参考にしてみても良いですね。
私も人並みのjavaプログラマーになったらjavaという言語についてまとめてみたいと思います。

難易度

こんなの誰も分からないだろと思うようないわゆる捨て問のようなものはなく、基本に忠実で「ちゃんと知識を持っていますか?」というような知識を問う問題が多いという印象で、思考力を問う問題は少ないので基本を押さえ問題演習をしていれば合格はたやすいと思います。

勉強量

期間はだいたい一か月ですね。正確な勉強時間は覚えていませんが、だいたい一日平均1~2時間だったと思います。

もう一回知識ゼロの状態から受験するとするなら

ハードモード

①黒本を初見で解き、やり直しで知識を蓄える。
②紫本で黒本でカバーしきれていない知識を補足する。
③一週目で間違えた問題を復習する。
③黒本・紫本で計4回分模試があるので実力を確かめる。ここで得点率60%以下なら②で間違えた問題を解く。
④繰り返し間違える問題を復習する。
⑤テスト本番

テスト勉強開始時点の知識が乏しい人は①が分からないことだらけできついと感じると思います。そんな時は下のノーマルモードを参考にして下さい。
逆にすでに実務経験などで知識を持っている人は黒本だけでも充分ではないかなと思います。
いづれにせよ本番のテストと模試の内容がとても似ているので、模試の点数を参考にテストに挑戦するかどうか決めるのが良いでしょう。

ノーマルモード

①紫本を読み進めながら大雑把に知識を入れていく。
②黒本で問題を解きながら定着を図る。間違えたその都度紫本でその分野を見ると良い。
③紫本・黒本の問題で間違えた問題を解きなおす。
③黒本・紫本で計4回分模試があるので実力を確かめる。ここで得点率60%以下なら②で間違えた問題を解く。
④繰り返し間違える問題を復習する。
⑤テスト本番
こちらのメリットは紫本でゆるやかに知識を入れていけることですね。モチベーションを保つのに有効だと思います。ただ、問題を解きながら知識を入れていった方がより実践的な知識が付き即効性があるので、始めにゆるやかに知識を入れていく分こちらの方が時間はかかるのではないかと思います。

まとめ

色々書きましたが、はっきり言って参考書と全く同じ問題が出ることがあるなど難易度的にはそこまで高くないので気構えし過ぎず自由に勉強してもいいのではないかなと思います。
その中でも一点だけ注意することがあるとするなら初見殺し的な問題や、特定の場合の仕様を聞く問題があるので一回は問題演習をしておきましょう。

近いうちに java SE 11 Gold の合格体験記も出せたらいいなと思います。
拙いところや不具合等がありましたらコメント欄にてご指摘・ご指導のほどよろしくお願いします。では、失礼します。

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

【Java入門】配列の操作について(1次元配列、2次元配列の宣言、インスタンス化、初期化および使用)

目的

Java言語を含めたプログラミングの学習を始めたばかりの方、既学習者の方は復習用に、
今回は配列について学ぶために書いています。

【Java入門目次】
変数と型
型変換
変数のスコープ
・文字列の操作(準備中)
・配列の操作 ←今ここ
・演算子(準備中)
・条件分岐(準備中)
・繰り返し処理(準備中)
・クラスについて(準備中)
・抽象クラス(準備中)
・インターフェース(準備中)
・カプセル化(準備中)
・モジュールについて(準備中)
例外処理について

配列とは

変数は、一つの変数に対して一つの値データを入れる入れ物ですが(変数についてはこちら)、

配列を使用することで、同じデータ型の複数のデータを一つの配列で管理することができる。

500個分の値がある時、変数の場合では500個分用意しなければいけませんが、
配列の場合では、一つ配列を用意してその中に500個分のデータを格納できるということです。

また、その配列の中のデータを並び替えをしたり、一番大きな値を取得したりなども容易に出来てしまいます。

1次元配列の宣言

配列を作成する時は、変数と同様にまずどのような値を扱うかのデータ型を決め、名前をつけて宣言する必要があります。

データ型[] 配列名と宣言します。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers; // int型の値を扱える配列の宣言

    // [] は、配列名の後ろでも構わない。
    String names[]; // String型の値を扱える配列の宣言
  }
}

1次元配列のインスタンス化

宣言した配列にいくら値を詰めるのか、領域の確保をしてあげなければいけません。
new [要素数]での確保する領域の大きさ決めます。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers; // int型の値を扱える配列の宣言
    String names[]; // String型の値を扱える配列の宣言

    numbers = new int[50]; // numbers配列に50個分の値を格納するための領域を確保
    names = new String[3]; // name配列に3個分の値を格納するための領域を確保
  }
}

配列の宣言と領域の確保を同時に行う事も可能です。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers = new int[50];// int型の値を扱える配列の宣言と50個分の領域を確保
    String names[] = new String[3]; // String型の値を扱える配列の宣言と3個分の領域を確保
  }
}

要素数を指定しなければ、コンパイルエラーになるので注意しましょう。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers = new int[];// 要素数を指定していないため、コンパイルエラー
    String names[] = new String[]; // 要素数を指定していないため、コンパイルエラー
  }
}

配列の要素数は後から変更することは出来ず、
最初に配列の要素数を5で指定した場合は、その後の要素数はずっと5で固定されるので覚えておきましょう。

また、要素数は整数ではないとはないといけない点も覚えおきましょう。

1次元配列の添え字(インデックス)と値の格納

配列の宣言、インスタンス化で値を格納する準備が整いました。
配列への値を代入は、添え字(インデックス)を使用します。
添え字(インデックス)は、配列の各々の要素につけられた通し番号で、0から始まります。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers = new int[5]; // int型の5つの要素が格納出来るnumbers配列を定義
    numbers[0] = 100; // 1番目には、100
    numbers[1] = 200; // 2番目には、200
    numbers[2] = 300; // 3番目には、300
    numbers[3] = 400; // 4番目には、400
    numbers[4] = 500; // 5番目には、500
  }
}

配列の各要素にアクセスする際も添え字(インデックス)を用います。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers = new int[5]; // int型の5つの要素が格納出来るnumbers配列を定義
    numbers[0] = 100; // 1番目には、100
    numbers[1] = 200; // 2番目には、200
    numbers[2] = 300; // 3番目には、300
    numbers[3] = 400; // 4番目には、400
    numbers[4] = 500; // 5番目には、500

    System.out.println(numbers[0]); // 100 が出力される
    System.out.println(numbers[3]); // 400 が出力される
  }
}

1次元配列の初期化

上記までは配列の宣言、領域の確保、値(初期値)の代入を順を追って行っていましたが、

配列の宣言、領域の確保、値(初期値)の代入を全て同時に行うことが出来ます。(配列の初期化)

値を{}で囲み、カンマ区切りで各要素を記述していきます。
データ型 [] 配列名 = {初期値1, 初期値2, 初期値3, 初期値4, 初期値5};

また、作成した要素の数を調べるにはlengthを使用します。
配列名.length; で要素数を取得する事が出来ます。

Main.java
class Main {
  public static void main(String[] args) {
    int[] numbers = {100, 200, 300, 400, 500}; // 配列の初期化
    int size = numbers.length; // numbers配列の要素数を取得 この場合、5が格納される

    System.out.println(numbers[0]); // 100 と出力される
    System.out.println(numbers[1]); // 200 と出力される
    System.out.println(numbers[2]); // 300 と出力される
    System.out.println(numbers[3]); // 400 と出力される
    System.out.println(numbers[4]); // 500 と出力される
    System.out.println(size); // 5 と出力される

    // この記述方法での配列の初期化も有効
    int[] id = new int[]{1, 2, 3};

    System.out.println(id[0]); // 1 と出力される
    System.out.println(id[1]); // 2 と出力される
    System.out.println(id[2]); // 3 と出力される
    System.out.println(id.length); // 3 と出力される
  }
}

配列の要素外にアクセスした時

配列の要素のアクセスするには添え字(インデックス)を用いてアクセスしていましたが、
配列の要素外にアクセスをしようとしている時、コンパイルエラーにはなりませんが、実行時エラー(例外)が発生します。
詳しくは、例外処理の記事をご覧ください。

こちらでも軽く見てみましょう。

Main.java
class Main {
  public static void main(String[] args) {
    int[] id = {1, 2, 3, 4, 5}; // 配列の要素数は5個

    // for文で6回のループを回す = id配列の要素を超える
    for(int i = 0; i < 6; i++) {
      // 一つずつ出力している
      System.out.println(id[i]);
    }
    System.out.println("id配列の中身を全て出力し終えました。");

  }
}

出力結果は、

Terminal
1
2
3
4
5
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
        at Main.main(Main.java:195)

上記の様に出力されました。
ArrayIndexOutOfBoundsException例外が発生しています。
そして、id配列の中身を全て出力し終えました。と表示されていません。
この様に配列へアクセスしようとした時、要素外にアクセスしている場合、
例外が発生し、途中で処理が止まってしまうので気をつけましょう。

2次元配列の宣言

添え字(インデックス)を2個で管理する2次元配列、それ以上の多次元配列もあります。
今回は2次元配列を説明していきます。

2次元配列では、[]を2つ使って、
データ型[][] 配列名と宣言します。

Main.java
class Main {
  public static void main(String[] args) {
    int[][] numbers; // int型の値を扱える2次元配列の宣言

    // [][] は、配列名の後ろでも構わない。
    String strs[][]; // String型の値を扱える2次元配列の宣言
  }
}

2次元配列のインスタンス化

1次元配列の時と同様に、宣言した配列にいくら値を詰めるのか、領域の確保をしてあげなければいけません。

Main.java
class Main {
  public static void main(String[] args) {
    int[][] numbers; // int型の値を扱える2次元配列の宣言
    String strs[][]; // String型の値を扱える2次元配列の宣言

    // 3つの要素をもつ配列で、numbers[0]からnumbers[2]の各要素に、4つの要素をもてる領域を確保
    numbers = new int[3][4];

    // 2つの要素をもつ配列で、strs[0]からstrs[1]の各要素に、2つの要素をもてる領域を確保
    strs = new String[2][2];
  }
}

1次元配列と同様に、
配列の宣言と領域の確保を同時に行う事も可能。

Main.java
class Main {
  public static void main(String[] args) {
    // int型の値を扱える2次元配列の宣言と領域を確保
    int[][] numbers = new int[3][4];

    // String型の値を扱える2次元配列の宣言と領域を確保
    String strs[][] = new String[2][2];
  }
}

また、1次元目の配列の領域確保のみを行うことも可能。
その場合、後から2次元目の要素数を決めることが出来ます。

Main.java
class Main {
  public static void main(String[] args) {
    int[][] array; // int型の値を扱える2次元配列の宣言
    array = new int[3][]; // 3つの要素をもつ配列の領域を確保

    array[0] = new int[5]; // arrayの1番目の配列は5つの要素を格納できる
    array[1] = new int[3]; // arrayの2番目の配列は3つの要素を格納できる
    array[2] = new int[4]; // arrayの3番目の配列は4つの要素を格納できる

    // 配列の宣言と領域の確保を同時に行った時も同様
    String[][] strs = new String[2][]; // String型の配列の宣言、領域確保
    strs[0] = new String[6]; // strsの1番目の配列は6つの要素を格納できる
    strs[1] = new String[3]; // strsの2番目の配列は3つの要素を格納できる
  }
}

2次元配列の場合も要素数を後から変更することはできず、要素数は整数ではないといけません。

2次元配列の添え字(インデックス)と値の格納

1次元配列と同様に添え字(インデックス)を用いて値の格納をします。

Main.java
class Main {
  public static void main(String[] args) {
    int[][] numbers = new int[2][2]; // int型の値を扱える2次元配列の宣言
    numbers[0][0] = 100;
    numbers[0][1] = 200;
    numbers[1][0] = 300;
    numbers[1][1] = 400;

    System.out.println(numbers[0][0]); // 100 と出力される
    System.out.println(numbers[0][1]); // 200 と出力される
    System.out.println(numbers[1][0]); // 300 と出力される
    System.out.println(numbers[1][1]); // 400 と出力される
  }
}

2次元配列の初期化

2次元配列でも、
配列の宣言、領域の確保、値(初期値)の代入を全て同時に行うことが出来ます。(配列の初期化)

同様に値を{}で囲み、カンマ区切りで各要素を記述していきます。
データ型 [][] 配列名 = {
{初期値1, 初期値2, 初期値3, 初期値4, 初期値5},
{初期値6, 初期値7, 初期値8, 初期値9, 初期値10}
};

また、作成した要素の数を調べるにはlengthを使用します。
配列名.length; で要素数を取得する事が出来る。
さらにその配列の中の配列の長さを取得するには、
配列名[添え字(インデックス)].lengthで要素数を取得できます。

Main.java
class Main {
  public static void main(String[] args) {
    // 1次元配列と同様に、配列の宣言、領域の確保、値の代入を一度に行うも可能。
    int[][] numbers = {
      {1, 2, 3, 4, 5},
      {6, 7, 8, 9, 10},
      {11, 12, 13},
      {16, 17}
    };
    System.out.println("numbers[0][4]の値 : " + numbers[0][4]); // numbers[0][4]の値 : 5 と出力される
    System.out.println("numbers[3][0]の値 : " + numbers[3][0]); // numbers[3][0]の値 : 16 と出力される

    System.out.println("numbersの長さ : " + numbers.length); // numbersの長さ : 4 と出力される
    System.out.println("numbers[0]の長さ : " + numbers[0].length); // numbers[0]の長さ : 5 と出力される
    System.out.println("numbers[2]の長さ : " + numbers[2].length); // numbers[2]の長さ : 3 と出力される
    System.out.println("numbers[3]の長さ : " + numbers[3].length); // numbers[3]の長さ : 2 と出力される
  }
}

配列の使用方法の実例

ここからは1次元配列と2次元配列の簡単な使用方法を紹介します。

小さい順に並び替え

java.util.Arraysクラスを使用するので、最初にimportする事を忘れないでください。
OracleのArraysクラスについてはこちら

Main.java
import java.util.Arrays;

class Main {
  public static void main(String[] args) {
    // numbers配列を初期化(中の整数の順番はランダム)
    int[] numbers = {10, 1, 5, 6, 9};

    // Arraysクラスのsortメソッドを用いて小さい順に並び替えをする
    Arrays.sort(numbers);

    // numbers配列を一つずつ出力
    for(int number : numbers) {
      System.out.print(number + " "); // 1 5 6 9 10 と出力される
    }
  }
}

ランダムな整数が格納されていたnumbersは、sortメソッドを用いることで小さい順に並び替えできています。

次は文字列を並び替えてみます。

Main.java
import java.util.Arrays;

class Main {
  public static void main(String[] args) {
    // names配列を初期化(中の名前の順番はランダム)
    String[] names = {"tanaka", "abe", "suzuki", "maeda"};

    // Arraysクラスのsortメソッドを用いてアルファベット順に並び替えをする
    Arrays.sort(names);

    // names配列を一つずつ出力
    for(String name : names) {
      System.out.print(name + " "); // abe maeda suzuki tanaka と出力される
    }
  }
}

ランダムな文字列が格納されていたnamesは、sortメソッドを用いることでアルファベット順に並び替えできています。

最大値、最小値を取得する

Main.java
class Main {
  public static void main(String[] args) {
    // 2次元配列numbersを初期化(中の整数の順番はランダム)
    int[][] numbers = {
      {2, 5, 6, -10, 100, 3},
      {-1000, 1, 20},
      {999, 12, 300, 50}
    };


    // 最大値を入れるmax変数を定義
    int max = 0;
    // 最小値を入れるmin変数を定義
    int min = 0;


    // numbers配列の1次元目を一つずつ見ていく
    for(int i = 0; i < numbers.length; i++) {

      // numbers[0]、numbers[1]、numbers[2]、の中身を一つずつ見ていく
      for(int j = 0; j < numbers[i].length; j++) {

        // numbers[0]、numbers[1]、numbers[2]の中で、maxより大きい数字があれば
        if(max < numbers[i][j]) {

          // その数値をmax変数に代入
          max = numbers[i][j];

        }

        // numbers[0]、numbers[1]、numbers[2]の中で、minより小さい数字があれば
        if(min > numbers[i][j]) {

          // その数値をmin変数に代入
          min = numbers[i][j];

        }

      }
    }

    System.out.println("numbers配列の最大値 : " + max); // numbers配列の最大値 : 999 と出力される
    System.out.println("numbers配列の最小値 : " + min); // numbers配列の最小値 : -1000 と出力される

  }
}

for文(繰り返し処理に関しては別記事を記載します)を用いて配列の要素を一つずつ比較しています。

大きい数字、小さい数字があれば、その都度最大値max変数、最小値min変数に値を代入しています。

終わりに

簡単にではありますが、配列について学びました。

配列は要素数は固定でしたが、可変であるListというものもあります。
そちらは別記事で取り上げたいと思います。

複数のデータを用いることができるため、使用する機会は多いです。しっかりおさえておきたいですね。

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

Stateパターンのメリットを映画のレイティング判定で説明する

ユーザが見ることができる作品を、映画のレイティングシステムに従って選別できるように実装します。

映画のレイティングシステムとは、年齢制限する規制のことです。
G:全年齢
PG12:12歳以上
R15+:15歳以上
R18+:18歳以上
といった区分があります。

クラス図

赤枠内がStateパターンで、それをUserが使用するという形をとっています。
image.png
RatingSystemをインターフェースとし、G/PG12/R15で各制限を実装します。
R18が無いのは、R18の映画を扱っていないという想定です。

メリット

・年齢制限をRatingSystemの実装クラス(G/PG/R15)に限定できる。(実装の局所化)
・R18映画を扱うことになり、仕様追加になっても容易に対応できる。(変更容易性)

ソースコード

interface RatingSystem

RatingSystem.java
package state.rating;

public interface RatingSystem {
    public boolean checkLimit(int age);
}

class G

G.java
package state.rating;

public class G implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        return true;
    }
}

class PG12

PG12.java
package state.rating;

public class PG12 implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        if(12 <= age)
            return true;
        return false;
    }
}

class R15

R15.java
package state.rating;

public class R15 implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        if(15 <= age)
            return true;
        return false;
    }
}

class Movie

Movie.java
package state.rating;

public class Movie {
    private String _title;
    private RatingSystem _rating;

    public Movie(String title, RatingSystem rating) {
        _title = title;
        _rating = rating;
    }

    public String title() {
        return _title;
    }

    public boolean checkLimit(int age) {
        return _rating.checkLimit(age);
    }
}

class User

User.java
package state.rating;

public class User {
    private String _name;
    private int _age;

    User(String name, int age){
        _name = name;
        _age = age;
    }

    public String name() {
        return _name;
    }

    public int age() {
        return _age;
    }

    public void play(Movie movie) {
        System.out.println(movie.title() + "を再生します。");
        if(!movie.checkLimit(_age)) {
            System.out.println(" >>>年齢制限エラーのため、再生できません。");
            stop(movie);
        }
    }

    public void stop(Movie movie) {
        System.out.println(movie.title() + "を停止します。");
    }
}

Main(実行)

Main.java
package state.rating;

public class Main {
    public static void main(String... args) {

        Movie torasan = new Movie("男はつらいよ・寅次郎夕焼け小焼け", new G());
        Movie titan = new Movie("進撃の巨人 ATTACK ON TITAN", new PG12());
        Movie jingi = new Movie("仁義なき戦い", new R15());

        User shinnosuke = new User("野原しんのすけ", 5);
        System.out.println(shinnosuke.name());
        shinnosuke.play(torasan);
        shinnosuke.play(titan);
        shinnosuke.play(jingi);
        System.out.println();

        User taro = new User("太郎", 12);
        System.out.println(taro.name());
        taro.play(torasan);
        taro.play(titan);
        taro.play(jingi);
        System.out.println();

        User hanako = new User("花子", 17);
        System.out.println(hanako.name());
        hanako.play(torasan);
        hanako.play(titan);
        hanako.play(jingi);
        System.out.println();
    }
}

実行結果

各ユーザの年齢によって、年齢制限に引っかかる映画は強制的に停止させられてしまいます。

野原しんのすけ
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
>>>年齢制限エラーのため、再生できません。
進撃の巨人 ATTACK ON TITANを停止します。
仁義なき戦いを再生します。
>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。

太郎
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。

花子
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。

R18+の要件追加への対応

実務では仕様変更はよくあることです。
ここでは「R18映画も扱う追加要件が発生した」状況を想定しています。

「R18の映画も扱うことにしたから、改修してもらえますか?」
と言われたら、すぐに変更できた方がいいですよね?

Stateパターンであれば、R18クラスを追加すれば完了です。
年齢制限の判定処理(if文)をあちらこちらから探してきて修正する必要はありません。

class R18

R18.java
package state.rating;

public class R18 implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        if(18 <= age)
            return true;
        return false;
    }
}

Main(実行)

Main.java
package state.rating;

public class Main {
    public static void main(String... args) {

        Movie torasan = new Movie("男はつらいよ・寅次郎夕焼け小焼け", new G());
        Movie titan = new Movie("進撃の巨人 ATTACK ON TITAN", new PG12());
        Movie jingi = new Movie("仁義なき戦い", new R15());
        Movie devil = new Movie("悪魔の毒々モンスター", new R18());   // ※追記箇所

        User shinnosuke = new User("野原しんのすけ", 5);
        System.out.println(shinnosuke.name());
        shinnosuke.play(torasan);
        shinnosuke.play(titan);
        shinnosuke.play(jingi);
        shinnosuke.play(devil); // ※追記箇所
        System.out.println();

        User taro = new User("太郎", 12);
        System.out.println(taro.name());
        taro.play(torasan);
        taro.play(titan);
        taro.play(jingi);
        taro.play(devil);   // ※追記箇所
        System.out.println();

        User hanako = new User("花子", 17);
        System.out.println(hanako.name());
        hanako.play(torasan);
        hanako.play(titan);
        hanako.play(jingi);
        hanako.play(devil); // ※追記箇所
        System.out.println();

        // ※追記箇所
        User isekai = new User("異世界転生した大学生", 18);
        System.out.println(isekai.name());
        isekai.play(torasan);
        isekai.play(titan);
        isekai.play(jingi);
        isekai.play(devil);
        System.out.println();
    }
}

実行結果

上記のUserの中で、R18映画(悪魔の毒々モンスター)を見ることができるのは、
18歳である「異世界転生した大学生」だけです。

野原しんのすけ
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
>>>年齢制限エラーのため、再生できません。
進撃の巨人 ATTACK ON TITANを停止します。
仁義なき戦いを再生します。
>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。
悪魔の毒々モンスターを再生します。
>>>年齢制限エラーのため、再生できません。
悪魔の毒々モンスターを停止します。

太郎
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。
悪魔の毒々モンスターを再生します。
>>>年齢制限エラーのため、再生できません。
悪魔の毒々モンスターを停止します。

花子
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
悪魔の毒々モンスターを再生します。
>>>年齢制限エラーのため、再生できません。
悪魔の毒々モンスターを停止します。

異世界転生した大学生
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
悪魔の毒々モンスターを再生します。

まとめ

・状態による判定や機能を、特定のクラスに限定できる。(実装の局所化)
・仕様追加、変更に対する修正が比較的容易である。(変更容易性)

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

Spring Bootで問い合わせアプリ作成

今回もSpring Bootで、勉強も兼ねてアプリ作成しました。
随時機能追加する予定です。

使用環境

・Windows10 (64bit)
・spring-boot:2.2.6
・Eclipse:4.9.0
・H2
・Bootstrap

完成図

簡単な問い合わせアプリです。

image.png

Entity

Inquiry.java
import java.time.LocalDateTime;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.springframework.format.annotation.DateTimeFormat;

@Entity
public class Inquiry {

    @Id
    @GeneratedValue
    private int id;

    private String name;
    private String email;
    private String contents;

    @DateTimeFormat(pattern="yyyy-MM-dd")
    private LocalDateTime created;

    public Inquiry() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getContents() {
        return contents;
    }

    public void setContents(String contents) {
        this.contents = contents;
    }

    public LocalDateTime getCreated() {
        return created;
    }

    public void setCreated(LocalDateTime created) {
        this.created = created;
    }
}

Repository

InquiryDao.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface InquiryDao extends JpaRepository<Inquiry, Integer> {

}

Service

InquiryService.java
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class InquiryService {

    @Autowired
    InquiryDao inquiryDao;

    //保存処理
    public Inquiry save(Inquiry inquiry) {

        return inquiryDao.saveAndFlush(inquiry);
    }

    //検索処理
    public List<Inquiry> find() {

        return inquiryDao.findAll();
    }
}

Form

入力した値を保持するためにFormクラスを用意します。
バリデーションチェックもここで設定します。

InquiryForm.java
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

public class InquiryForm {

    @Size(min=1, max=20, message="名前の入力チェックエラー")
    private String name;

    @NotEmpty(message="未入力です")
    private String email;

    @NotEmpty(message="未入力です")
    private String contents;

    public  InquiryForm() {}

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getContents() {
        return contents;
    }
    public void setContents(String contents) {
        this.contents = contents;
    }
}

Controller

InquiryController.java
import java.time.LocalDateTime;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@RequestMapping("/inquiry")
public class InquiryController {

    @Autowired
    InquiryService inquiryService;

    //一覧画面処理
    @GetMapping()
    public String index(Model model) {

        //全件検索処理
        List<Inquiry> list = inquiryService.find();
        model.addAttribute("list", list);
        model.addAttribute("title", "一覧画面");
        return "index_boot";
    }

    //問い合わせ入力処理
    @GetMapping("form")
    public String form(@ModelAttribute("inquiryForm") InquiryForm inquiryForm, Model model, @ModelAttribute("complete") String complete) {

        model.addAttribute("title", "問い合わせフォーム");
        return "form_boot";
    }

    //確認ページから「戻る」ボタン押下で飛んできた処理
    @PostMapping("form")
    public String formGoBack(@ModelAttribute("inquiryForm") InquiryForm inquiryForm, Model model) {

        model.addAttribute("title", "問い合わせフォーム");
        return "form_boot";
    }

    /*入力内容を打ち込んで「確認ページ」押下した際の処理
     * @Validatedでフォームクラスの入力チェックを実施して、
     * チェック結果をBindingResultに格納する
    */
    @PostMapping("confirm")
    public String confirm(@ModelAttribute("inquiryForm") @Validated InquiryForm inquiryForm, BindingResult res, Model model) {

        //BindingResultの結果、エラーならエラーメッセージを出力する
        if(res.hasErrors()) {
            model.addAttribute("title", "問い合わせフォーム");
            return "form_boot";
        }
        model.addAttribute("title", "確認ページ");
        return "confirm_boot";
    }

    //「保存」ボタン押下した際の処理
    @PostMapping("complete")
    public String complete(@ModelAttribute("inquiryForm") @Validated InquiryForm inquiryForm, BindingResult res, Model model, RedirectAttributes redirectAttributes) {

        if(res.hasErrors()) {
            return "form_boot";
        }

        //InquiryFormの入れ物からInquiryに詰めなおす作業
        Inquiry inquiry = new Inquiry();
        inquiry.setName(inquiryForm.getName());
        inquiry.setEmail(inquiryForm.getEmail());
        inquiry.setContents(inquiryForm.getContents());
        inquiry.setCreated(LocalDateTime.now());

        //保存処理
        inquiryService.save(inquiry);

        redirectAttributes.addFlashAttribute("complete", "保存完了しました");
        return "redirect:/inquiry/form";
    }
}

index_boot.html

index_boot.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Jekyll v4.0.1">
    <title th:text="${title}">Starter Template · Bootstrap</title>

    <link rel="canonical" href="https://getbootstrap.com/docs/4.5/examples/starter-template/">

    <!-- Bootstrap core CSS -->
<link href="/docs/4.5/dist/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

    <!-- Favicons -->
<link rel="apple-touch-icon" href="/docs/4.5/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/docs/4.5/assets/img/favicons/manifest.json">
<link rel="mask-icon" href="/docs/4.5/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon.ico">
<meta name="msapplication-config" content="/docs/4.5/assets/img/favicons/browserconfig.xml">
<meta name="theme-color" content="#563d7c">


    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }
    </style>
    <!-- Custom styles for this template -->
    <link href="starter-template.css" rel="stylesheet" th:href="@{/css/starter-template.css}">
  </head>
  <body>
    <div th:replace="~{header::headerA}"></div>

<main role="main" class="container">

  <div class="starter-template">
    <h1 th:text="${title}"></h1>
    <p class="lead">問い合わせ一覧画面になります</p>
  </div>
<!-- テーブルの一覧項目 -->
<table class="table table-striped">
  <thead>
    <tr>
      <th scope="col">id</th>
      <th scope="col">氏名</th>
      <th scope="col">Eメール</th>
      <th scope="col">内容</th>
      <th scope="col">日付</th>
    </tr>
  </thead>
  <tbody>
  <!--th:eachで値を繰り返し出力する  -->
    <tr th:each="list:${list}">
      <th scope="row" th:text="${list.id}">1</th>
      <td th:text="${list.name}">
      <td th:text="${list.email}">
      <td th:text="${list.contents}">
      <td th:text="${list.created}">
    </tr>
  </tbody>
</table>
</main><!-- /.container -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
      <script>window.jQuery || document.write('<script src="/docs/4.5/assets/js/vendor/jquery.slim.min.js"><\/script>')</script><script src="/docs/4.5/dist/js/bootstrap.bundle.min.js" th:src="@{/js/bootstrap.bundle.min.js}" integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd" crossorigin="anonymous"></script></body>
</html>

form_boot.html

form_boot.html
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../../../favicon.ico">

    <title>Starter Template for Bootstrap</title>

    <!-- Bootstrap core CSS -->
    <link href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="starter-template.css"  th:href="@{/css/starter-template.css}" rel="stylesheet">
  </head>

  <body>


    <div th:replace="~{header::headerA}"></div>
    <main role="main" class="container">

      <div class="starter-template">
        <h1 th:text="${title}">Bootstrap starter template</h1>
        <p class="lead">問い合わせ内容をここで入力できます</p>
      </div>


    <div th:unless="${#strings.isEmpty(complete)}" >
        <div th:text="${complete}" class="alert alert-success" role="alert">
          A simple success alert?check it out!
        </div>
    </div>
    <!-- 「戻る」ボタンが押下された時、この入力画面で入れた値を保持するために
            th:valueを使用する -->
    <!--th:ifの結果trueの場合、th:errorsが適用される(各項目に設定したバリデーションが出力される)  -->
    <form method="post" action="#" th:action="@{/inquiry/confirm}" th:object="${inquiryForm}">
      <div class="form-group">
        <label for="name">Name</label>
        <input type="text" name="name" class="form-control" id="name" th:value="*{name}">
      </div>
      <div class="text-danger mb-4" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
      <div class="form-group">
        <label for="email">Email</label>
        <input type="text" name="email" class="form-control" id="email"  th:value="*{email}">
      </div>
      <div class="text-danger mb-4" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
      <div class="form-group">
        <label for="contents">Inquiry</label>
        <textarea  name="contents" class="form-control" id="detail" rows="3" th:field="*{contents}"></textarea>
      </div>
      <div class="text-danger mb-4" th:if="${#fields.hasErrors('contents')}" th:errors="*{contents}"></div>
      <button type="submit" class="btn btn-primary">確認</button>
    </form>
    </main><!-- /.container -->

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script>window.jQuery || document.write('<script src="@{/js/jquery-slim.min.js}"><\/script>')</script>
    <script src="../../assets/js/vendor/popper.min.js"  th:src="@{/js/popper.min.js}"></script>
    <script src="../../dist/js/bootstrap.min.js"  th:src="@{/js/bootstrap.min.js}"></script>
  </body>

confirm_boot.html

confirm_boot.html
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../../../favicon.ico">

    <title>Starter Template for Bootstrap</title>

    <!-- Bootstrap core CSS -->
    <link href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="starter-template.css"  th:href="@{/css/starter-template.css}" rel="stylesheet">
  </head>

  <body>

    <div th:replace="~{header::headerA}"></div>
    <main role="main" class="container">

      <div class="starter-template">
        <h1 th:text="${title}">Bootstrap starter template</h1>
        <p class="lead">この画面で入力内容のご確認が可能です。</p>
      </div>

<!--inquiryFormに詰めている各値を取り出す  -->
<div th:object="${inquiryForm}">
<div class="mb-5">
<ul class="list-group">
  <li th:text="*{name}" class="list-group-item"></li>
  <li th:text="*{email}" class="list-group-item"></li>
  <li th:text="*{contents}"class="list-group-item"></li>
</ul>
</div>
<!--hiddenを使用すると、ボタンを押下することで、値を送信することができる  -->
<form method="post" th:action="@{/inquiry/form}">
    <input type="hidden" name="name" th:value="*{name}">
    <input type="hidden" name="email" th:value="*{email}">
    <input type="hidden" name="contents" th:value="*{contents}">
    <button type="submit" class="btn btn-primary">戻る</button>
</form>
<form method="post" th:action="@{/inquiry/complete}" th:object="${inquiryForm}">
    <input type="hidden" name="name" th:value="*{name}">
    <input type="hidden" name="email" th:value="*{email}">
    <input type="hidden" name="contents" th:value="*{contents}">
    <button type="submit" class="btn btn-primary">投稿</button>
</form>
</div>

    </main><!-- /.container -->

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script>window.jQuery || document.write('<script src="@{/js/jquery-slim.min.js}"><\/script>')</script>
    <script src="../../assets/js/vendor/popper.min.js"  th:src="@{/js/popper.min.js}"></script>
    <script src="../../dist/js/bootstrap.min.js"  th:src="@{/js/bootstrap.min.js}"></script>
  </body>
</html>

処理パターン

ヘッダーの「問い合わせフォーム」押下してフォーム画面に遷移します。
氏名、メール、内容を入力して、「確認」ボタン押下します。
確認画面に遷移しますが、「戻る」ボタン押下して、前の画面に戻ります。
内容等を変更して、再度「確認」ボタンを押下します。
確認画面で「投稿」ボタンを押下すると、内容が保存されます。

image.png

image.png

image.png

image.png

image.png

参考

Spring入門

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

SpringframeworkのコントローラーをJunitでテスト

対象読者

・SpringFramework初学者
・Junit初学者
・JunitでSpringframeworkのコントローラーをテストしたい!!!

プロジェクトver

・Springframework 4.1.6
・Junit 4.1.2
・servlet 3.1.0
・hamcrest 2.2
・mockito 3.3.3

ファイル構造

//メイン層
src.main.java
  |
  +--- jp.co.demo                         
        +--- controller 
        +--- dto
        +--- form
        +--- entity
        +--- mapper
        +--- service

//テスト層
src.test.java
  |
  +--- jp.co.demo                         
  |     +--- controller 
  |
src.test.resources
  |
  +--- META-INF
  |           |
  |           +--- sql
  |                  |
  |                  +--- insert_data.sql //テスト用データ作成のSQL
  |
  +--- spring
            |
            +--- mvc-config.xml //Bean定義ファイル

テスト

SQL実行ファイル

insert_data.sql
DELETE FROM messages;
DELETE FROM users;

INSERT INTO users(id,account,password,name,branch_id,department_id) VALUES (1,'testuser','$2a$10$i0FlsKe6FiEuViMhclA90uCjCCKeLhtcswz01Rwl9qTIsIY1c.ohO','ALH太郎',1,1);

ALTER TABLE messages MODIFY id int not null; --オートインクリメントリセットするために一時的にオートインクリメントリセット
ALTER TABLE messages AUTO_INCREMENT = 1; --オートインクリメントリセット
ALTER TABLE messages MODIFY id int not null auto_increment; --オートインクリメント付与

INSERT INTO messages(title,text,category,user_id, created_date)VALUES('タイトル','本文','カテゴリ',1,'2020-05-27 01:02:03'); --3
INSERT INTO messages(title,text,category,user_id, created_date)VALUES('タイトル','本文','カテゴリ',1,'2020-05-28 02:03:04'); --4
INSERT INTO messages(title,text,category,user_id, created_date)VALUES('タイトル','本文','カテゴリ',1,'2020-05-23 03:04:05'); --1
INSERT INTO messages(title,text,category,user_id, created_date)VALUES('タイトル','本文','カテゴリ',1,'2020-05-24 04:05:06'); --2
INSERT INTO messages(title,text,category,user_id, created_date)VALUES('タイトル','本文','カテゴリ',1,'2020-05-29 05:06:07'); --5

コントローラーテスト

DemoControllerTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration //コンテキスト使う準備
@ContextConfiguration(locations = "classpath:spring/mvc-config.xml") //どのBean定義ファイルを使うか指定
@Transactional //テスト終了後にロールバックするために定義
public class DemoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext wac; //コンテキストを用意

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Before
    public void setUp() {
        executeScript("/META-INF/sql/insert_data.sql"); //SQLの実行
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void test_1ホーム画面表示() throws Exception {
        MvcResult mvcResult = mockMvc.perform(get("/home")) //performでGET通信の振る舞い
                .andDo(print()) //コンソールにプリント
                .andExpect(status().isOk()) //statusコードの確認
                .andExpect(view().name("home")) //Viewが合っているか
                .andExpect(model().attributeExists("allUserMessage")) //対象のモデルが存在するかの確認
                .andExpect(model().attributeExists("userComment")) //対象のモデルが存在するかの確認
                .andExpect(model().attributeExists("deleteCommentForm")) //対象のモデルが存在するかの確認
                .andExpect(model().attributeExists("commentForm")) //対象のモデルが存在するかの確認
                .andExpect(model().attributeExists("userMessageForm")) //対象のモデルが存在するかの確認
                .andReturn(); //結果をmvcResultへ返却

        ModelAndView mav = mvcResult.getModelAndView();
        int[] expectMessageIdList = { 5, 2, 1, 4, 3 }; //昇順確認用

        @SuppressWarnings(value = "unchecked")
        List<UserMessageForm> actualMessageList = ((List<UserMessageForm>) mav.getModel().get("allUserMessage"));

        int expectMessageListSize = 5;
        assertEquals(expectMessageListSize, actualMessageList.size()); //作成したデータと表示されているモデルのListサイズが合っているか

        for (int i = 0; i < 5; i++) {
            assertEquals(expectMessageIdList[i], actualMessageList.get(i).getId()); //メッセージ内容が昇順になっているか?
        }

    }

    @Test
    public void test_2_1ログイン画面表示出来るか() throws Exception {
        mockMvc.perform(get("/login"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(model().attributeExists("loginForm"));
    }

    @Test
    public void test_2_2ログイン出来るか() throws Exception {
        LoginForm loginForm = new LoginForm();
        loginForm.setAccount("testuser");
        loginForm.setPassword("password");

        mockMvc.perform(post("/login").flashAttr("loginForm", loginForm))
                .andDo(print())
                .andExpect(view().name("redirect:/home")) //ログインが成功した時の遷移先
                .andExpect(status().isFound());
    }

    @Test
    public void test_2_3パスワードを間違えた時エラー出力してログイン画面に遷移() throws Exception {
        LoginForm loginForm = new LoginForm();
        loginForm.setAccount("testuser");
        loginForm.setPassword("誤ったパスワード");

        MvcResult mvcResult = mockMvc.perform(post("/login")
                .flashAttr("loginForm", loginForm))
                .andDo(print())
                .andExpect(view().name("login"))
                .andExpect(status().isOk())
                .andExpect(model().attributeHasErrors("loginForm")) //Modelにエラーがあるか
                .andReturn();

        ModelAndView mav = mvcResult.getModelAndView();
        BindingResult result = (BindingResult) mav.getModel()
                .get("org.springframework.validation.BindingResult.loginForm");

        String actualErrorMessage = result.getFieldError("account").getDefaultMessage();
        String expectErrorMessage = "ログインIDまたはパスワードが間違っています";
        assertThat(expectErrorMessage, is(actualErrorMessage)); //エラーメッセージの確認
    }

    @Test
    public void test_2_4存在しないアカウントでログインしようとした時エラーを画面に出力してログイン画面に遷移() throws Exception {
        LoginForm loginForm = new LoginForm();
        loginForm.setAccount("存在しないアカウント");
        loginForm.setPassword("password");

        MvcResult mvcResult = mockMvc.perform(post("/login")
                .flashAttr("loginForm", loginForm))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(model().attributeHasErrors("loginForm")) //Modelにエラーがあるか
                .andReturn();

        ModelAndView mav = mvcResult.getModelAndView();
        BindingResult result = (BindingResult) mav.getModel()
                .get("org.springframework.validation.BindingResult.loginForm");

        String actualErrorMessage = result.getFieldError("account").getDefaultMessage();
        String expectErrorMessage = "アカウントが存在しません";
        assertThat(expectErrorMessage, is(actualErrorMessage)); //エラーメッセージの確認
    }

    public void executeScript(String file) {
        Resource resource = new ClassPathResource(file, getClass());

        ResourceDatabasePopulator rdp = new ResourceDatabasePopulator();
        rdp.addScript(resource);
        rdp.setSqlScriptEncoding("UTF-8");
        rdp.setIgnoreFailedDrops(true);
        rdp.setContinueOnError(false);

        Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
        rdp.populate(conn);
    }

}

感想

あくまでもコントローラーの単体テストであり、View側のレンダリング結果を取得してテストする等は考慮していません(またログインフィルター等も掛けていない)。
HtmlUnitを使えば、レンダリング結果を得られるらしいので、別の機会にQiitaに挙げていこうと思います。

参考文献

Java + Spring によるテストデータ管理(3)
[SpringBoot] Controllerのテストの書き方
Springテスト公式ドキュメント

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

NEM(現行バージョン)のノード(NIS1: NEM Infrastructure Server)の構築方法

NEMブロックチェーンでは、現在、次期バージョンSymbolのローンチに向けて、テストネット上で様々な準備が行われています。そういったタイミングのさなかでもあり、次期バージョンSymbolのテストネットのノードの構築方法については、ドキュメントや参考情報が非常に充実していると感じます。
しかし、意外にも現行バージョンのNEMブロックチェーンのノードの構築方法については、ドキュメントや参考情報を探すのに苦労しました。(私自身のググラビリティの問題もあるかもしれません...)
自身の備忘録のためにも、Google Cloud Ploatform上にNEMの現行バージョンのノード(=NIS1と呼ばれる... NEM Infrastructure Serverの略称)を構築した方法をこの記事にまとめておこうと思います。
何か間違いや、より良い方法等あれば、ぜひ教えて頂けると助かります。よろしくお願いします。

概要

  1. ファイアーウォールルールを作成
  2. GCEでVMを作成しSSHでログイン
  3. Javaのインストール
  4. NIS1の本体をダウンロードして解凍
  5. ノードの起動スクリプトを修正
  6. ノードを起動
  7. 起動後の確認

手順

1. ファイアーウォールルールを作成

NIS1では、httpでは7890番ポート、WebSocketでは7778番ポートを使用するようです。また、httpsでは7891番ポート、WebSocket(SSL)では7779番ポートを使用するのが慣例のようです。以下の例の通り、ファイアーウォールルールを作成しておきます。名前やターゲットタグは自由ですが、ターゲットタグは後ほどVMの設定で使用します。

  • 左上のナビゲーションメニュー(三本線アイコン)をクリック
  • 「VPCネットワーク」をクリック(下の方にスクロールすると出てくる)
  • 「ファイアウォール」をクリック
  • 「ファイアウォールルールを作成」をクリックして以下2個のファイアーウォールルールを「作成」する

ファイアウォールルール1個目: http用

  • 名前: nem-http-allow
  • ターゲットタグ: nem-http-allow
  • ソースIPの範囲: 0.0.0.0/0
  • プロトコルとポート: 指定したプロトコルとポートのラジオボタンにチェックを入れ、「tcp」の左横のチェックボックスにチェックを入れ、その右横のインプットボックスに7890, 7778
  • 「作成」をクリック

ファイアウォールルール2個目: https用

  • 名前: nem-https-allow
  • ターゲットタグ: nem-https-allow
  • ソースIPの範囲: 0.0.0.0/0
  • プロトコルとポート: 指定したプロトコルとポートのラジオボタンにチェックを入れ、「tcp」の左横のチェックボックスにチェックを入れ、その右横のインプットボックスに7891, 7779
  • 「作成」をクリック

2. GCEでVMを作成

事前に調べたり噂を聞いたりした結果、それなりに重い処理が走りそうな印象だったため、最初はそれなりにスペック高めの設定にしてみました。
初期設定時の同期が終わって状態が安定したら、コストも考えてスペックを絞ることを検討したほうが良いと思います。
VMの設定例は以下の通りです。

VMの設定例

  • 名前: nem-mainnet-1
  • リージョン: asia-northeast1(東京)
  • ゾーン: asia-northeast1-b
  • マシンの構成
    • シリーズ: E2
    • マシンタイプ: カスタム
    • コア数: 2 vCPU
    • メモリ: 16 GB (適当に確保しすぎな感がありますが一応...)
  • ブートディスク
    • 「変更」をクリック
    • オペレーティングシステム: Ubuntu
    • バージョン: Ubuntu 20.04 LTS
    • ブートディスクの種類: 標準の永続ディスク
    • サイズ: 400 GB (適当に確保しすぎな感...以下略)
    • 「選択」をクリックしてブートディスクの設定を保存
  • ファイアウォール
    • 「HTTPトラフィックを許可する」チェックON
    • 「HTTPSトラフィックを許可する」チェックON
  • 管理、セキュリティ、ディスク、ネットワーク、単一手ナンシーをクリックし、ネットワーキングタブを選択
    • ネットワークタグ: nem-http-allownem-https-allowを設定
    • ネットワークインターフェース: 編集アイコンをクリックして設定
    • 「外部IP」を「エフェメラル」から「IPアドレスを作成」を選択し、ダイアログで適当な名前(例えばnem-mainnet-1)を入力し「予約」をクリック
    • 「完了」をクリックしてネットワークインターフェースの設定を保存
  • 「作成」をクリックするとVMが作成される

VMにSSHログイン

VM作成後、しばらくすると、VMの起動が完了し、VMの一覧表の「接続」という列の「SSH」というセレクトリストがクリックできるようになるのでクリックするとブラウザでVMにSSHログインした画面が開きます。以降の操作は主にその画面内で行います。

3. Javaのインストール

NIS1の実行には Java 8 が必要なのでインストールします。ついでに他の更新もやっておきましょう。以下のコマンドを実行します。

更新

sudo apt update

Java 8 (正確にはOpenJDK8)のインストール

sudo apt install -y openjdk-8-jdk

Java 8 インストールの確認

以下コマンドを実行し、

java -version

以下のような結果が得られたならインストール成功です。

openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1ubuntu1-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

4. NIS1の本体をダウンロードして解凍

LinuxでNIS1を実行するためのツールはtgzファイルに圧縮されたものが以下URLより配布されているので、ダウンロードして解凍して使用します。
2020/05/30時点の最新バージョン: https://bob.nem.ninja/nis-0.6.97.tgz
他バージョンや関連するデータのリンクが表示されたページ: https://bob.nem.ninja/

ダウンロード

以下コマンドでnis1.tgzというファイル名としてダウンロードできます。nis1.tgzのファイル名は拡張子さえ.tgzになっているならなんでもOKですが、次の解凍のコマンドではファイル名の箇所を適切に読み替えてください。

curl -o nis1.tgz https://bob.nem.ninja/nis-0.6.97.tgz

解凍

(ダウンロード時にnis1.tgzというファイル名でダウンロードしていた場合、)以下コマンドで解凍できます。解凍すると、packageというフォルダができているでしょう。

tar -xzvf nis1.tgz

5. ノードの起動スクリプトを修正

以下コマンドでpackageフォルダに移動し、

cd package

以下コマンドでnix.runNis.shファイル(ノード起動のシェルスクリプト)をエディタで開いて修正します。ここではエディタとしてnanoを使う例を示しますが、エディタはお好みのものをお使いください。

nano nix.runNis.sh

最低限、変更すべき点は、以下の通りです。

メモリ使用量の最大値を緩和

デフォルトの設定値では、メモリが足りず、正常に起動できないようです。今回はメモリは16GBとかなりの余力を持ってVMを作成しているので、思い切ってNIS1のプロセスに14GBまでメモリを使用できるような設定として試してみます。(おそらくもう少し控えめな最大メモリ割り当てでも動くと思います。)

起動コマンドの先頭にnohupを追加、起動コマンドの末尾に&を追加

ノード起動のシェルスクリプトをデフォルトで実行すると、NIS1がフォアグランドで実行されてしまいます。これではログオフするとNIS1が強制的に終了となってしまいます。起動コマンドの先頭にnohupを追加、起動コマンドの末尾に&を追加することで、バックグラウンドで、ログインしていなくても実行され続けるようにすることができるようです。

変更前のnix.runNis.sh

#!/bin/bash

cd nis
java -Xms512M -Xmx1G -cp ".:./*:../libs/*" org.nem.deploy.CommonStarter
cd -

変更後のnix.runNis.sh

#!/bin/bash

cd nis
nohup java -Xms512M -Xmx14G -cp ".:./*:../libs/*" org.nem.deploy.CommonStarter &
cd -

エディタとしてnanoを使用した場合、上書き保存するには、まず「Ctrl」 + 「X」で、次に「Y」、ファイル名表示されたら「Enter」という手順になります。

6. ノードを起動

以上でノード起動の準備が整ったので、以下コマンドでノードを起動します。

./nix.runNis.sh

7. 起動後の確認

バックグランドで動作するように設定しているので、画面には起動後も特段メッセージ等は表示されないため、外部からブロック高さやノード情報等を取得するAPIを叩いて、適切な値が返ってくるか確認しておきましょう。IPアドレスは自分の環境に読み替えてください。

ブロック高さ

http://35.200.86.73:7890/chain/height

  • レスポンスの例
{"height":44967}

立ち上げ直後なので、まだ同期が終わっておらず、最新のブロック高さよりもずいぶん低いところまでしか同期が済んでいないことがわかります。この数字が、少しずつ増えていき、いずれは最新のブロック高さに追いつくとノードの構築は一旦区切りといったところでしょうか。

本来であれば、この同期にかかる時間を短くするために、ある程度直近までのブロックチェーンのDB情報を別途ダウンロードして展開しておくという方法が可能なので、どこかでその方法も追記したいと思っています。

ノード情報

http://35.200.86.73:7890/node/info

  • レスポンスの例
{
    "metaData": {
        "features": 1,
        "application": null,
        "networkId": 104,
        "version": "0.6.97-BETA",
        "platform": "Private Build (1.8.0_252) on Linux"
    },
    "endpoint": {
        "protocol": "http",
        "port": 7890,
        "host": "35.200.86.73"
    },
    "identity": {
        "name": "NBAKSE34ZOWCIZC4AUU2XXMNAANH7BSXMGV4AYEC",
        "public-key": "79140632da5b00327bba8d5cb3b258d04843c10520cb757fde1a5a859f0ae567"
    }
}

その他にノード情報のAPI等も叩いておくと、自身の設定への確信が深まるでしょう。networkIdが104はMAIN_NETを示していて、NIS1とJavaのバージョンが示されていたり、APIのプロトコルやポートやホスト名が示されているのがわかると思います。また、少し面白いなと思ったのは、ノード起動時に自動的にidentityとして、NEMのアカウントが生成され、識別子として利用されている点です。(スーパーノードになるには保有残高は全く足りていない(泣)のですが、)スーパーノードとしての設定を行う際等には、これらのアカウントが関係してくるのかな?と思ったりしています。

所感

一通り作業してみて、「実はシェルスクリプトを起動しているだけなのか!」と思いました。(だから、説明するまでもない...という部分が多く、かっちりとした手順のような情報があまり見つけられないのかもしれませんね...もちろんシェルスクリプトを起動した後に内部で行われていることはすごく複雑そうな印象を受けるのですが...)

とりあえず起動してほったらかしにする!というレベルで良いなら(≒今回の記事レベル)、簡単な印象を受けました。

ただ、メモリの最大値の修正箇所や、デフォルトだとフォアグランド実行になってしまう点や、NIS1本体プログラムの置き場所はどこ?といった点等、それなりに「ハマりポイント」があるような気もするので、デフォルトでそれらハマり要素が無いと、今後NIS1を新た構築するユーザーにとっては幸せだな...と感じました。

追加(したい)予定の内容

以下のような内容をいずれ追記していきたいと思います。

  • サービス化等の適切な管理
  • ドメインの設定
  • SSL化
  • ハーベストの受入設定
  • 次期バージョンローンチ後のインセンティブ等に関する設定

参考情報

以下の情報を参考にさせて頂きました。先駆者の皆様、ありがとうございます。

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

javadocコマンド入門

第6章:Javadocを生成する

今回は、Javadocの仕様や書き方といった実装面ではなく、
あくまでもjavadocコマンドの使い方、動作に絞って触れていきたいと思います。
コメントの書き方に関しては、Javadocの書き方に関する書籍などを学習した際に
まとめてみたいなと思います。

Javadocの概要

Javadocコマンドは、Javaソースファイルにある宣言、コメントを解析し、デフォルトでは
public クラス、protected クラス、入れ子にされたクラス (匿名の内部クラスは除く)、
インタフェース、コンストラクタ、メソッド、およびフィールドについての実装ドキュメントを
HTML形式で生成します。

Javadocのページ一覧

以降の説明をしやすくするため、javadocコマンドの成果物であるJavadocにはどういった
ページが生成されるのか、以下に例を示します。

基本内容ページ

  • 概要ページ(overview-summary.html)
    Javadocのトップページ。API、パッケージセットという全体の単位の概要が書かれている。
  • パッケージページ(package-summary.html)
    パッケージごとに存在するページ。パッケージの概要が書かれている。
  • クラスページ/インタフェースページ(classname.html)
    クラス、インターフェースごとに存在するページ。
    クラスに所属するメンバやメソッドに関する情報が書かれている。

〜概要ページ〜
スクリーンショット 2020-05-29 15.34.14.png

〜パッケージページ〜
スクリーンショット 2020-05-29 15.38.02.png

〜クラスページ〜
スクリーンショット 2020-05-29 15.38.38.png

相互参照ページ

  • クラス階層ページ(overview-tree.html)
    Javadoc1つに対して1つ存在する。所属する要素を階層化し、一覧できる。
  • クラス階層ページ(package-tree.html)
    パッケージ1つに対して1つ存在する。パッケージに所属する要素を階層化し、一覧できる。
  • 仕様ページ(package-use.html)
    対象のクラス、インターフェースを使用しているパッケージやクラスなどを一覧できる。
  • 非推奨APIページ(deprecated-list.html)
    推奨されない(将来削除される可能性のある)クラス、メソッドなどを一覧できる。
  • 定数フィールド値ページ(constant-values.html)
    staticフィールドの値用のページ
  • 直列化されたフォームページ(serialized-form.html)
    直列化(外部化)可能なクラスに関するページ。
  • 索引(index-*.html)
    すべてのクラス、インタフェース、コンストラクタ、フィールド、およびメソッドが、
    アルファベット順に並んで一覧できる。

サポートページ

  • ヘルプページ(help-doc.html)
    ナビゲーションバーや各ページに関する説明が記載されている。

HTMLフレーム

javadocコマンドは、2〜3つのHTMLフレームを作成する。
パッケージが1つの場合は左下にクラス用のフレーム、右側に詳細用のフレームが作成され、
パッケージが複数ある場合は左上にパッケージ用のフレームが追加される。

javadocコマンドの形式

# javadocコマンドの引数の指定順序は任意です
$ javadoc [options] [packagenames | sourcefilenames] [-subpackages pkg1:pkg2:...]
  • options
    javadocコマンドに渡されるコマンド行オプションです。
  • packagenames
    処理対象のパッケージ名です。java.lang java.awtのように指定します。
    対象のパッケージを個別に指定する必要があり、ワイルドカードは使用できません。
    回帰的に指定するには、後述の-subpackagesを使用します。
  • sourcefilenames
    処理対象のソースファイル名です。 各ファイルはパスで始まり、アスタリスク (*) などの
    ワイルドカードを含めることができます。
  • -subpackages pkg1:pkg2:...
    ソースファイルから指定されたパッケージおよびそのサブパッケージ内に再帰的に
    ドキュメントを生成します。パッケージ名、ソースファイル名を指定する必要はありません。

ソースファイルの指定

ソースファイルの種類

Javadocを生成する際に解析される対象ファイルには、以下の4つがある。

  • クラスファイル
    末尾が.javaのファイル。Javaの規約上無効なクラス名であった場合、処理対象外となる。
    例)数字で始まる。「-」を含む。など
  • パッケージコメントファイル
    パッケージ毎に配置することが可能で、.javaのファイルと同じ階層に配置される。
    パッケージの概要について記載する。ファイル名が決まっており、以下2つの仕様がある。
    • JDK1.5より前:package.html
    • JDK1.5以降:package-info.java
  • 概要コメントファイル
    概要ページを生成するためのファイル。ファイル名、配置場所は任意だが、通常は
    overview.htmlにして、ソースツリーの最上位レベルに置く。
    -overview オプションを使って概要コメントファイル名を指定することで生成される。
  • doc-filesディレクトリ内のファイル
    例えばSampleクラスというクラスのJavadocにおいて、画像ファイルを挿入したいなど
    .javaのルールから外れたファイルを使用したい場合、.javaと同じ階層にdoc-filesという
    ディレクトリを作成し配置することで、格納されたファイルを解析対象にできる。

様々なソースファイルの指定方法

ソースファイルの指定には、主に以下の3つの方法がある。

1. パッケージを指定する
ワイルドカードが使用できないため、以下の形式で半角スペースで区切りながら、
すべてのパッケージを個別で指定します。指定は完全指定である必要があります。
java.lang java.lang.reflect java.awt

2. ソースファイルのパスを指定する
ソースファイルのパスを指定します。絶対パス、相対パスどちらでも指定可能で、
相対パスの場合はカレントディレクトリからの相対パスを指定します。
ワイルドカードの使用が可能で、複数指定する場合は半角スペースで区切ります。
/src/java/awt/Sample.java src/java/awt/*Bar.java

3. -subpackagesオプションで再帰的にパッケージを指定する
パッケージを指定すると、サブパッケージを含めて指定範囲にすることができます。
ワイルドカードの使用はできません。指定時は、以下の形式でコロン(:)で区切り指定します。
-subpackages java:javax.swing

通常はパッケージを個別で指定することは滅多にありません。
ソースファイルのパスをワイルドカードで指定するか、-subpackagesオプションで
再帰的に指定するのが主です。

Ex. パッケージで指定する場合について
ソースファイルをパッケージで指定する際は、
パッケージルートを指定しなければパッケージ階層をたどることはできません。
よって、パッケージの起点となるディレクトリを指定する必要があります。
ディレクトリの指定には、以下の2つの方法があります。

  • カレントディレクトリを起点のディレクトリに移動させてjavadocコマンドを実行する
  • -sourcepathオプションで起点のディレクトリを指定する

javadocのオプション

javadocコマンドに渡せるオプションには、
「javadocコマンドのオプション」と「ドックレットが提供するオプション」の2つがあります。
ドックレットについては詳しく書きませんが、
javadocのフォーマットを提供するテンプレートのようなものだと思ってください。
明示的にカスタムのドックレットを指定しない場合は、標準ドックレットが選択されます。

主要なオプション

  • -overview <パス¥ファイル名>
    任意の場所に配置したoverview.htmlをパスで指定することで、overview.htmlを解析し、
    概要ページ (overview-summary.html) に配置する。
    パスには、-sourcepathで指定したディレクトリからの相対パスを指定する。
  • -d <パス>
    指定したパスに生成したJavadocを出力する。
  • -source <バージョン>
    ソースコードのバージョンを指定する。バージョンはコンパイルのものに合わせる。
    • 1.5 : JDK1.5で導入されたコードを受け付ける。デフォルト値。
    • 1.4 : JDK1.4で導入された、アサーションを含むコードを受け付ける。
    • 1.3 : JDK1.3以降に導入されたアサーション、総称、他の言語機能をサポートしない。
  • -classpath <パスリスト>
    参照クラス(.class)を検索するパスを指定する。
    参照クラスは、ドキュメント化されるクラスに加え、それらのクラスが参照するすべての
    クラスを含む。指定したパス以下のサブディレクトリが検索範囲になる。 クラスパスが設定されていない場合は、カレントディレクトリがクラスパスになる。
  • -sourcepath <パスリスト>
    ソースファイルのクラスパスを指定する。(パッケージまたは-subpackagesを渡す場合)
    指定したパス以下のサブディレクトリが検索範囲になる。このオプションを使って、
    -sourcepathを省略した場合、クラスパスと同値が設定され、
    -classpath も省略した場合はカレントディレクトリが設定される。
  • -J
    javadocを実行する実行時システムjavaにオプションを渡す。(-J-Xmx32mなど)

ドキュメント化対象の指定系のオプション

  • -subpackages <パッケージ1:パッケージ2...>
    指定パッケージとそのサブパッケージ以下を生成範囲にできる。(複数パッケージの指定可能)
    -subpackagesを使用する場合は-sourcepathが必須である。
  • -bootclasspath <パスリスト>
    ブートクラスが存在するパスを指定する。

ドキュメント化対象の制御系のオプション

  • -public
    publicクラスおよびメンバだけを表示する。
  • -protected
    protectedおよびpublicのクラスとメンバだけを表示する。デフォルトの設定。
  • -package
    package、protected、およびpublicのクラスとメンバだけを表示する。
  • -private
    すべてのクラスとメンバを表示する。
  • -exclude <パッケージ1:パッケージ2...>
    指定したパッケージとそのサブパッケージを-subpackagesで指定したリストから除外する。

ドックレット系のオプション

  • -doclet <クラス>
    標準ドックレット以外のドックレットを指定する。完全指定で起動クラスを指定する。
    起動クラスの指定の形式は-doclet com.example.doclets.SampleDoclet
    起動クラスへのクラスパスは、-docletpath オプションによって定義されます。
  • -docletpath <クラスパス1:クラスパス2...>
    -docletオプションで指定したドックレット開始クラスファイル、および依存するjarファイル
    へのパスを指定する。開始クラスファイルが jar ファイル内にある場合はjarファイルのパス
    のみ指定でよい。絶対パスまたは現在のディレクトリからの相対パスを指定できる。
    目的のドックレット開始クラスが検索パス内にある場合は、このオプションは不要。
# SampleDockletは/src/com/example/doclets直下にあるとする
-doclet com.example.doclets.SampleDoclet -docletpath /src:/lib/doclet.jar

デバッグ系のオプション

  • -verbose
    javadocの実行中に詳細なメッセージを表示する。
    指定しない場合、ソースファイルのロード時とドキュメントの生成時(ソースファイルごと)、
    およびソート時(1回の実行で1回のみ)にメッセージが表示される。
    指定した場合、各Javaソースファイルの解析に要した時間(ミリ秒単位)など、詳細な
    メッセージが表示される。
  • -quiet
    エラーメッセージ、警告メッセージ以外のメッセージを抑制する。

国際化系のオプション

  • -locale <ロケール>
    Javadocのロケールを設定する。ロケールが設定されるのはJavadoc側で生成される
    テキスト群であって、ソースファイル内のコメントではない。
    -localeは全てのドックレット系のオプションより左に設定する必要がある。
  • -encoding <エンコード名>
    ソースファイルのエンコーディング(EUCJIS/SJISなど)を指定する。このオプションが
    指定されていない場合は、プラットフォームのデフォルトコンバータが使われる。

実行

ここでは、今まで使ってきたサンプルコードにコメントとJavadoc生成用のファイル群を
追加した以下の構成をサンプルコードとします。
スクリーンショット 2020-05-30 16.14.37.png

UseCommons.java
package com.example.app;

import org.apache.commons.lang3.StringUtils;
import com.example.util.StrFactory;

/**
 * Apache Commonsを使用するためのクラスです。
 */
public class UseCommons {

    public static void main(String[] args) {
        System.out.println(CommonsHelper.returnNgStrIfHasProbrem(null));
        System.out.println(CommonsHelper.returnNgStrIfHasProbrem(""));
        StrFactory strFactory = new StrFactory();
        System.out.println(CommonsHelper.returnNgStrIfHasProbrem(strFactory.sayHelloWorld()));
    }

    static class CommonsHelper {
        static String returnNgStrIfHasProbrem (String target) {
            if (StringUtils.isEmpty(target)) {
                return "NG";
            } else if (StringUtils.isBlank(target)) {
                return "NG";
            }
            return target;
        }
    }
}
StrFactory.java
package com.example.util;

/**
 * 様々な文字列を生成するためのファクトリークラスです。
 */
public class StrFactory {
    /**
     * "Hello World!"という文字列を返却します。
     */
    public String sayHelloWorld() {
        return "Hello World!";
    }
}
package.html
<html>
<body>
サンプルのパッケージコメントです。
</body>
</html>
overview.html
<html>
<body>
サンプルの概要コメントです。
</body>
</html>

上記の通り、ソースファイルを準備したら、以下のコマンドを実行します。

# カレントディレクトリは/java-sampleです。
$ javadoc -classpath ./src:./lib/commons-lang3-3.10.jar -subpackages com.example -overview ./overview.html -d ./docs

実行すると、出力先として指定した/java-sample/docsにJavadocが生成されます。
Javadocを見るには、index.htmlを開きます。
スクリーンショット 2020-05-30 16.26.32.png

以下、実際の出力です。
スクリーンショット 2020-05-30 16.28.02.png
スクリーンショット 2020-05-30 16.28.16.png
screencapture-file-Users-matsushitafuu-Documents-java-command-javac-docs-index-html-2020-05-30-16_28_55.png
screencapture-file-Users-matsushitafuu-Documents-java-command-javac-docs-index-html-2020-05-30-16_29_36.png

おわりに

サンプルコードでは必要最低限のコメントもかけていませんが、
実際にはコメントにHTMLを使用したり、画像を挿入したり、外部のライブラリを参照したり
様々なことができますので、それらの実装に関しては専門書籍を読む、調べるなどして
勉強していただければと思います。

メインページに戻る

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

駆け出しエンジニアに与えられがちなタスク

メーカーの社内SEになって、早一年

 こんにちは、ひかるです。私はとあるメーカーに、社内SEとして新卒入社しました。大学時代はプログラミングなんてやったことがなく、入社後の研修で知識を詰め込んだのですが、まあ現場ではなかなか通用しないこと……
 今回は私がそのとき、現場で先輩からもらったタスクの一部を紹介します。こういった「初心者がもらえる仕事」というのは、どこの職場でも似たようなものだと思ったので、何かしら参考になればと思います。なお、私の職場は、言語はJava、テンプレートエンジンにStruts、ビルドツールにAnt、バージョン管理はCVSと、レガシーな環境ですので、その点を踏まえていただけると幸いです……

①画面に入力項目を増やして、DBに登録してくれ

先輩「ユーザーから、製品の在庫区分を追加できるようにしてくれって要望が来たから、実装してね」
一年目私「はい(白目)」

 ありがちなタスクですね。画面に入力項目を増やして、それをDBに登録するという実装です。このタスクでは以下の知識が求められます。

  • htmlとテンプレートエンジンの連携における仕組み
  • DBに値を永続化するO/Rマッパーの使い方

初心者にはテンプレートエンジンとかO/Rマッパーなんて言葉は聞きなれないですよね。平たく言うと、テンプレートエンジンは、画面に入力した値を次の画面に持っていくための仕組みで、O/Rマッパーはデータベースと入力した値を結びつける仕組みです。

 この二つがないと、せっかくユーザーがせっせと入力した内容も、次の画面に移ったら綺麗さっぱり消えてしまうし、登録ボタンを押してもその内容がデータベースに反映されなくなってしまう。

 つまり典型的なCRUD処理の基本を押さえ、理解しておけばいいのです。そのためには、簡単なアプリケーションを自作してみることをお勧めします。今はQiitaやUdemyでいくらでもやり方を学ぶことができますし、ひとまずはコードを写経してみて、動く喜びを味わってみてはいかがでしょうか。学習のコツはプログラムが動いてから、理解するです。これが逆だとアウトプットが遅くなって実戦的な力が身に付きません。モチベーションの維持も大変です。

 駆け出しエンジニアの方は、そういった「画面上の値を保持する仕組み」に焦点を当てて学ぶと良いと思います。大丈夫、私も最初はさっぱりわかりませんでしたが、今はなんとか仕組みを覚えてやっていますから。

②ユーザーが間違えて入力しないよう、バリデーションを設けてくれ

先輩「誤った値をユーザーが入力してしまうケースが多発している。そんなことがないように、次画面に遷移する前にチェック機能を設けてくれ」
一年目私「はい(白目)」

 このチェック機能はとても大切です。ユーザーは人間ですから、画面上の入力を常に正しく行うとは限りません。例えば製品名を「HOGEHOGE2012」と入力すべきところを「OGEHOGE2012」と先頭のHを抜かして入力してしまうかもしれません。

 では、どうやったらそれを防げるでしょうか?

 私は「HOGEHOGE2012」と入力すべきところを、と言いました。つまり入力には正解があったはずです。ということは、その正解をあらかじめシステム上で登録しておき、時と場合によって、その正解と入力を照合すれば良いのです。

 バリデーションを設けることはシステム開発において基本中のことですが、大事なのはいかに正解の基準をアプリケーションに定義しておくかです。先ほどの製品名で言えば、マスタ(≒ひな形)にあらかじめ正解の製品名「HOGEHOGE2012」を登録しておき、入力するときにその正解と内容を照らし合わせる。で、少しでも間違っていたら「OGEHOGE2012」なんてねえよ!とエラーを表示させる。そこでユーザーは間違いに気が付くわけです。

 具体的には、製品名でデータベースを検索し、ヒットしなかったらエラー。ヒットしても、その画面で入力すべきデータでなかったら、やはりエラーを出す。例えばこの工程では「HOGEHOGE2012」が正解であって、「FUGAFUGA2019」は間違いですよ、と。その判断をするためには工程と製品をあらかじめ結びつけておく必要があります。

 ただ、そこまでやるくらいだったら、データベースから必要な値(製品名)を引っ張ってきて、プルダウン形式で選ばせる方が間違いはありません。今回はあくまでバリデーションという点に注目して説明しましたので、その方法は無視しています……

 初心者の方は、If文の使い方や、型の特性等を理解することも大事ですが、どこから正解を引っ張ってくるかというこにも注目してみてくださいね。

終わりに

 初心者はわからないことだらけでしんどいと思います。私は今でもそうです(笑) ただね、少しずつわかってくると、視野が広がっていって、「ああ、こういうことだったんだ」と納得できます。そのときまでどうかあきらめないでくださいね! 

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

Scanner 正常入力になるまで無限ループ

Scanner 正常入力になるまで無限ループ

前回 の振り返り

前回はtry-catch構文を使って、例外クラスが使われた時の処理を実装するという内容をやりました。

今回は入力のパターンを全て考えてif文、try-catch構文、while文を使い入力が異なる場合ループさせる方法についてご説明します。

てことで例題いっちゃいましょ〜!

例題

・入力が正の整数値の場合だけ数値を出力、他の場合は入力に戻る。

コンソール
スクリーンショット 2020-05-30 17.43.38.png

まず、気をつけて欲しいのが0は正の整数ではないということです。(中学生内容)
ループごとの改行は見やすさのために行いました。
処理の通知の文を出力してコードの動きを追いやすくしてみました。

特にbreak;continue;に注目してください。
コメントアウトの詳しい説明は後ろの方に載っているので分からなくてもとりあえず読み進めてください(^^;;

コード
スクリーンショット 2020-05-30 17.43.54.png

コンソール画面(もう一度)
スクリーンショット 2020-05-30 17.43.38.png

全体の構造

  • try-catch構文をwhile文でループさせています。特にエラーが起きる可能性のない処理をtryブロック内に入れているのは変数のスコープ(有効範囲)を広げないためです。合ってるかはわかりません。
  • 基本は無限ループ、continue;breakを使って流れを制御

  • 無限ループの書き方はwhile(true)for(;;)などが簡単だと思います。

プログラムの流れ(基本的には無限ループの中にあることを忘れずに)

  1. まずint型変数num に標準入力を代入します。
  2. ここで例外(文字や少数)が発生した場合はcatchブロックの処理を行いwhile文により次のループへ移行します。-> 1へ
  3. 例外が発生しなかった(正or負の整数、0)場合、if文によってnumを場合分けします。
  4. numが0または負の整数だった場合、メッセージを出力してcontinue;以下のwhileブロックの処理をスキップします。 -> 1へ
  5. numが正の整数だった場合、ifブロック内の処理を行い、tryブロックの次の行に移行します。
  6. 正常処理を終えた場合は無限ループから脱出しないといけないのでbreak;を使って無限ループを抜けます。
  7. Scannerクラスを閉じます。

これで入力に対して全ての場合わけをして、正常処理を行うまで再入力を求めるプログラムができました。

無限ループについて

プログラム中に予期せぬ無限ループが発生したらCtrl + cで止まるので焦らず対処しましょう。
Macユーザーは、環境設定 > 一般 > エディター > キー の画面から終了のキーを設定しましょう。
以下、デフォルトの終了キー設定
スクリーンショット 2020-05-30 14.52.40.png

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

工数管理ツールを開発してみた

やったこと

工数を管理するウェブアプリを開発しました。
言語:Java(Spring), JavaScript

手順
1. プロジェクトを作成(この際に、プロジェクトコードを登録する)
2. 1.で登録したプロジェクトコードを入力することで、追加/編集/閲覧したいプロジェクトに遷移
3. タスク追加(タスク名、工数、開始日、終了日、担当者名など)
4. タスク閲覧(担当者がある期間にどんなタスクをどれだけの工数で終えたのか、プロジェクトの担当者がそれぞれどれだけの工数を費やしているか、など)

画面

プロジェクトコード入力画面
スクリーンショット 2020-05-30 13.53.52.png

タスク追加(および修正)画面
スクリーンショット 2020-05-30 13.54.28.png

タスク閲覧画面
スクリーンショット 2020-05-30 13.53.05.png

詰まったこと

JavaScriptとThymeleafの連携?

前回の記事にも書きましたが、例えば、JavaScriptで

target.html('<a th:href="@{/hello}">サンプル</a>');

と書いても、aタグが機能しなかったりしました。

解決策

<script type="text/javascript" th:inline="javascript">
    const link = /*[[@{/hello}]]*/'';
    target.html('<a href="' + link + '">サンプル</a>');
</script>

と書いてあげることで解決しました。
このように、JavaScriptでThymeleafのth:が使えない、といったことに苦戦しました。

tableタグ内にformタグを書けない問題

例えば、

<table>
    <tr>
        <th>#</th>
        <th>名前</th>
        <th>ボタン</th>
    </tr>
    <tr>
        <form>
            <td>1</td>
            <td><input type="text"/></td>
            <td><input type="submit"></td>
        </form>
    </tr>
    <tr>
        <form>
            <td>2</td>
            <td><input type="text" /></td>
            <td><input type="submit"></td>
        </form>
    </tr>
</table>

画像だと、
スクリーンショット 2020-05-30 14.21.26.png

こんなケースです。これをChromeのディベロッパーツールで見てみると、、、
スクリーンショット 2020-05-30 14.27.43.png

おかしい。。。formタグ、そこで閉じちゃダメ。。。

解決策

  1. formタグにid属性を付与する
  2. inputタグのform属性に1.で付与したidを記載する

こうすることで、formタグのidに対応したinputのvalue値を送信することができます。
ちなみに、formタグはinputタグの上でも下でもどこでも記載して良いそうです。

上の例だと

<table>
    <tr>
        <th>#</th>
        <th>名前</th>
        <th>ボタン</th>
    </tr>
    <tr>
        <form id="form_01"></form>
        <td>1</td>
        <td><input type="text" form="form_01" /></td>
        <td><input type="submit" form="form_01"></td>
    </tr>
    <tr>
        <form id="form_02"></form>
        <td>2</td>
        <td><input type="text" form="form_02" /></td>
        <td><input type="submit" form="form_02"></td>
    </tr>
</table>

ディベロッパーツールで見ると

スクリーンショット 2020-05-30 14.32.57.png

このようになっているはずです!おそらくこれでうまくいきます。

以上です。最後まで読んでくださりありがとうございました。

参考

formタグは入れ子にできない&その対処法
HTML5 FORMとINPUTを分けて記述する方法

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

log4j2

いつも忘れるので、ここでメモ。

jar ファイル

最低限、以下のファイルが必要。
log4j-core-2.13.3.jar
log4j-api-2.13.3.jar
(2.13.3 はこれを書いている時点の最新バージョン)

★ commons-logging と接続したい場合は、log4j-jcl-2.13.3.jar も必要です。
詳細はこちら:https://stackoverflow.com/questions/41462181/commons-logging-with-log4j2

pom.xml

pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.13.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.13.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jcl -->
<!-- https://stackoverflow.com/questions/41462181/commons-logging-with-log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.13.3</version>
</dependency>

log4j2.xml

log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project>
<Configuration status="off">
    <Properties>
        <Property name="format1">%d{yyyy/MM/dd HH:mm:ss.SSS} [%t] %-6p %c{10} %m%n</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <pattern>${format1}</pattern>
            </PatternLayout>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="trace">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

参考リンク:
https://qiita.com/mato-599/items/979e10135c1cb54ceda9
https://qiita.com/pica/items/f801c74848f748f76b58

以上

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

log4j 1.2.x のadditivity, ConsoleAppender

注: On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life.
...なのだけど今更log4j-1.2について調べている。

元々
log4j-1.2.8
しか居なかったアプリケーション上に

log4j-1.2.12
が同梱されたことにより何か振る舞いがおかしい。
ログが2重に出る。そして意図せずSystemOutに出る...
この2点に関するメモです。

2重に出る

まず2重に出る点、関係していそうなのはこの辺。Additivity

log4jのLoggerの階層構造とadditivityの振舞について

Additivity is set to true by default, that is children inherit the appenders of their ancestors by default.
Additivity はデフォルトで true に設定されており、子はデフォルトで先祖のアペンダーを継承します。

意図せずSystemOutに出る

SystemOutのほうは、log4j変更履歴 を見てみると以下が怪しい。

引用。

  • 1.2.12 TRACE level introduced, ConsoleAppender modified to follow redirection of System.out
  • 1.2.13 TRACE level missing info fixed, ConsoleAppender.follow added to make redirection following an optional behavior.

Appender

ここで 1.2.17 を試す範囲では挙動は 1.2.8 と同様に戻る。

この 1.2.12 の怪しいところをもう少し調べて試すか、業務上もう 1.2.17 に上げてしまうか。一旦、ここまでに調べたことを書いておく。

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

Log4j 1.2.x のadditivity, ConsoleAppender

注: On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life.
...なのだけど今更log4j-1.2について調べている。

元々
log4j-1.2.8
しか居なかったアプリケーション上に

log4j-1.2.12
が同梱されたことにより何か振る舞いがおかしい。
ログが2重に出る。そして意図せずSystemOutに出る...
この2点に関するメモです。

2重に出る

まず2重に出る点、関係していそうなのはこの辺。Additivity

log4jのLoggerの階層構造とadditivityの振舞について

Additivity is set to true by default, that is children inherit the appenders of their ancestors by default.
Additivity はデフォルトで true に設定されており、子はデフォルトで先祖のアペンダーを継承します。

意図せずSystemOutに出る

SystemOutのほうは、log4j変更履歴 を見てみると以下が怪しい。

引用。

  • 1.2.12 TRACE level introduced, ConsoleAppender modified to follow redirection of System.out
  • 1.2.13 TRACE level missing info fixed, ConsoleAppender.follow added to make redirection following an optional behavior.

Appender

ここで 1.2.17 を試す範囲では挙動は 1.2.8 と同様に戻る。

この 1.2.12 の怪しいところをもう少し調べて試すか、業務上もう 1.2.17 に上げてしまうか。一旦、ここまでに調べたことを書いておく。

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

Log4j 1.2.12 のadditivity, ConsoleAppender

注: On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life. / Log4jバージョン1のサポートは終了
...なのだけど今更log4j-1.2について調べている。

元々
log4j-1.2.8
しか居なかったアプリケーション上に

log4j-1.2.12
が同梱されたことにより何か振る舞いがおかしい。
ログが2重に出る。そして意図せずSystemOutに出る...
この2点に関するメモです。

2重に出る

まず2重に出る点、関係していそうなのはこの辺。Additivity

log4jのLoggerの階層構造とadditivityの振舞について

Additivity is set to true by default, that is children inherit the appenders of their ancestors by default.
Additivity はデフォルトで true に設定されており、子はデフォルトで先祖のアペンダーを継承します。

意図せずSystemOutに出る

SystemOutのほうは、log4j変更履歴 を見てみると以下が怪しい。

引用。

  • 1.2.12 TRACE level introduced, ConsoleAppender modified to follow redirection of System.out TRACE レベルが導入され、ConsoleAppender が System.out のリダイレクトに追従するように変更されました。
  • 1.2.13 TRACE level missing info fixed, ConsoleAppender.follow added to make redirection following an optional behavior. TRACEレベルの欠落情報を修正し、ConsoleAppender.followを追加し、任意の動作に続くリダイレクトを行うようにしました。
  • log4j version 1.2.13 Bug #37122 : Console appender now behaves as before to fix compatibility problem with JBoss introduced in 1.2.12 release due to fix for bug 31056. Can still be configured to detect changes in the System.out and System.err streams as needed by setting the follow property. ConsoleAppender は、バグ 31056 の修正により 1.2.12 リリースで導入された JBoss との互換性の問題を修正するため、以前のように動作するようになりました。以下のプロパティを設定することで、必要に応じて System.out および System.err ストリームの変更を検出するように設定できるようになりました。

ConsoleAppender の動きがやはり変わっていたように見える。1
ここで 1.2.17 を試す範囲では挙動は 1.2.8 と同様に戻る。

互換性

API/ABI changes review for log4j

業務上もう 1.2.17 に上げてしまってよいのか、一旦、ここまでに調べたことを書いておく。またLog4j には信頼性のないデータのデシリアライゼーションに関する脆弱性 というのがあるらしいが一応該当していなそうだったのでそれもメモ。

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

【入門】AndroidStudio Hello Worldをエミュレータ上に表示させる

はじめに

AndroidStudioでプロジェクトをビルドして、HelloWorldをエミュレータ上に表示されるところまで確認したいと思います。AndroidStudioインストール後の動作確認として試してみます。

環境と前提条件

  • windows10(64bit)
  • AndroidStudio 3.6.3
  • AndroidStudio 日本語化済み
  • API 29(Android 10.0)

インストール方法はこちら
日本語化はこちら
を参考にしてみてください。

プロジェクト作成

まずはプロジェクトを作成します。

はじめに、AndroidStudioを起動します。
下記のようなウィンドウが開いたら新規AndroidStudioプロジェクトの開始を押下します。
2020-05-29 (23).png

AndroidStudioにはマップなど、いくつかのテンプレートが用意されています。
空のアクティビティーが最もシンプルで実装が簡単なので今回はこちらを選びます。
次へを押下します。
2020-05-29 (24).png

以下、それぞれ入力します。
- 名前:今回はHello Worldにしました。入力と同時にパッケージ名も自動で入力されます。
- パッケージ名:今回は自動入力されたものをそのまま使います。
- 言語:ネットに参考記事の多いJavaを選択しました。
- 最小SDK:最小SDKより古いOSにはアプリをインストールできない仕様になっているようです。一先ず、最新のAPI 29(Android 10.0)を選びます。API 28以下を選択する場合は、一通り動作確認できた後にチャレンジするほうが色々と理解しやすいと思います。
2020-05-29 (25).png

下記のようなウィンドウが開きました。
2020-05-29 (27).png

プロジェクトの作成は完了です。

AVDを作成する

ツール/AVD マネージャを開きます。
2020-05-29 (28).png

仮想デバイスの作成を開きます。
2020-05-29 (29).png

仮想ハードウェアを決めます。
ネットで調べて人気のありそうだったPixel 3aを選びました。他でも問題ありません。
2020-05-29 (30).png

システムイメージをダウンロードして選択します。
色々あり困惑しますが、、、

  • 最小SDKでAPI29を選択したのでAPI29以上(29しかないですね)の中から選びます。
  • 現在64bit端末が多く流通しているようなので、x86_64を選択してみます。尚、x86でも問題なく動作するはずです。
  • 一先ず、Google Playを選びます。今回の目的であればAPIsでも問題ありません。

違いの詳細はこちらの記事が分かりやすかったです。
では、対象のリリース名欄のDownloadを押下します。同時に、ダウンロードが始まります。
2020-05-29 (31).png

ダウンロードされるまでしばし待ちます。
2020-05-29 (32).png

完了を押下します。
2020-05-29 (33).png

この画面に戻ります。
ダウンロードしたイメージを選択し、次へ進みます。
2020-05-29 (34).png

AVD名を入力します。
AVDを複数作った際に混同すると厄介なので、余計なことはせずデフォルトのまま進めます。
2020-05-29 (35).png

AVDの作成が完了しました。ウィンドウを閉じます。
2020-05-29 (36).png

ビルドする

赤枠の三角マークを押下してビルドを開始します。
この時、上記で作成したAVD名が隣に表示されていることを確認します。
2020-05-29 (37).png

ビルドが始まると、赤枠部分にビルド実行中の表示が出ます。
表示が消えるまで待ちます。初回ビルドは結構長いです。
2020-05-29 (38).png

Androidエミュレータが別ウィンドウで起動し、HelloWorldと表示されたら動作確認完了です。
2020-05-29 (39).png

さいごに

HelloWorldがエミュレータ上に表示されることを確認しました。

参考にしたサイト

https://feel-log.net/android/api-level-target-version-min-version/

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

Raspberry Pi 4 で Minecraft サーバ

Minecraft サーバを AWS で作る
https://qiita.com/nanbuwks/items/601832b2da00b451d55d」

では、あっけなく動いたのでその知見を元に Raspberry Pi 4 で Minecraft サーバを作ってみる。

環境

  • Raspberry Pi 4 Model B 4GB (Raspberry Pi 3 Model B でも検証したが厳しい感じ)
  • 2020-02-13-raspbian-buster-lite.zip
  • 有線接続LAN

インストール

micro SD カードの書き込みはこちらの方法を使用

「Ubuntu Linux で Raspian ZIP ファイルを microSD カードに書き込む」
https://qiita.com/nanbuwks/items/2c296549ff72dacf7adc

また、/boot に ssh を作っておきます

初期設定

ログオンしてからの初期設定は

$ sudo apt update
$ sudo apt upgrade

の後、

$ sudo raspi-config

として、

「Raspberry Pi 最初の設定をCUIで行う(ディスプレイ、キーボードがある場合)」
https://qiita.com/nanbuwks/items/9a1d46c22e898178015c

のように行いました。

なお、ssh で行うとキーボード設定はできない感じです。

IP アドレスの固定化

サーバとして使う時は IPアドレスを固定にしておくほうが便利です。

sudo vi /etc/dhcpcd.conf

として、以下をファイル末尾に付け加えます。

interface eth0
static ip_address=192.168.42.104/24
static routers=192.168.42.1
static domain_name_servers=192.168.42.1

保存終了した後、以下で再起動。

$ sudo reboot

102.168.42.104 でログインし直します。

Javaをインストール

Raspbianで用意されているうちの最新のものを使用しました。

sudo apt install openjdk-11-jre

起動

以下のようにして起動します

$ java -Xmx1024M -Xms1024M -jar server.jar nogui

テスト時などはこの通りでいいですが、長期で稼働しっぱなしなどにする場合は & を最後に付けたり screen セッションでつかったり、あるいは本格的に自動起動設定やサービス化するのがいいでしょう。

Raspberry Pi 4 Model B ( 4G ) で動かした所、以下のようにして2分ほどで起動しました。

[20:29:36] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[20:29:36] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[20:29:36] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[20:29:36] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[20:29:36] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[20:29:36] [Server thread/INFO]: Starting minecraft server version 1.15.2
[20:29:36] [Server thread/INFO]: Loading properties
[20:29:36] [Server thread/INFO]: Default game type: SURVIVAL
[20:29:36] [Server thread/INFO]: Generating keypair
[20:29:37] [Server thread/INFO]: Starting Minecraft server on *:25565
[20:29:38] [Server thread/INFO]: Using default channel type
[20:29:38] [Server thread/INFO]: Preparing level "world"
[20:29:39] [Server thread/INFO]: Reloading ResourceManager: Default
[20:30:10] [Server thread/INFO]: Loaded 6 recipes
[20:30:27] [Server thread/INFO]: Loaded 825 advancements
[20:30:28] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:34] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:35] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:36] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:36] [Server thread/INFO]: Preparing spawn area: 0%
[20:30:36] [Server thread/INFO]: Preparing spawn area: 48%
[20:30:49] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:49] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:49] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:50] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:53] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:54] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:54] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:54] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:55] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:55] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:56] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:56] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:57] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:30:57] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:30:58] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:58] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:30:59] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:31:00] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:31:00] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:31:00] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:31:01] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:31:01] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:31:02] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:31:02] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:31:03] [Server-Worker-1/INFO]: Preparing spawn area: 83%
[20:31:03] [Server-Worker-2/INFO]: Preparing spawn area: 83%
[20:31:04] [Server-Worker-3/INFO]: Preparing spawn area: 83%
[20:31:04] [Server-Worker-3/INFO]: Preparing spawn area: 84%
[20:31:05] [Server-Worker-3/INFO]: Preparing spawn area: 84%
[20:31:05] [Server-Worker-3/INFO]: Preparing spawn area: 86%
[20:31:06] [Server-Worker-2/INFO]: Preparing spawn area: 91%
[20:31:06] [Server-Worker-1/INFO]: Preparing spawn area: 91%
[20:31:07] [Server-Worker-1/INFO]: Preparing spawn area: 92%
[20:31:07] [Server-Worker-2/INFO]: Preparing spawn area: 93%
[20:31:08] [Server-Worker-3/INFO]: Preparing spawn area: 95%
[20:31:08] [Server-Worker-3/INFO]: Preparing spawn area: 98%
[20:31:09] [Server thread/INFO]: Preparing spawn area: 99%
[20:31:09] [Server thread/INFO]: Time elapsed: 41122 ms
[20:31:09] [Server thread/INFO]: Done (90.443s)! For help, type "help"
[20:31:13] [Server thread/WARN]: Can't keep up! Is the server overloaded? Running 2008ms or 40 ticks behind

プレイしてみる

動かしてみて、問題ないことを確認します。

インターネット外部に公開

詳細は割愛しますが、TCP25565 と UDP25565 の公開をルータなどに設定すればOKです。

おまけ Raspberry Pi 3 で動かした時のログ

なお、最初にRaspberry Pi 3 で動かした時は、動きはしましたがかなり苦しそうでした。

[19:23:59] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[19:23:59] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9,
 0 0 0]
[19:24:00] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[19:24:00] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[19:24:00] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[19:24:00] [Server thread/INFO]: Starting minecraft server version 1.15.2
[19:24:00] [Server thread/INFO]: Loading properties
[19:24:00] [Server thread/INFO]: Default game type: SURVIVAL
[19:24:00] [Server thread/INFO]: Generating keypair
[19:24:07] [Server thread/INFO]: Starting Minecraft server on *:25565
[19:24:07] [Server thread/INFO]: Using default channel type
[19:24:08] [Server thread/INFO]: Preparing level "world"
[19:24:10] [Server thread/INFO]: Found new data pack vanilla, loading it automatically
[19:24:10] [Server thread/INFO]: Reloading ResourceManager: Default
[19:26:04] [Server thread/INFO]: Loaded 6 recipes
[19:26:06] [Server thread/INFO]: Loaded 825 advancements
[19:26:56] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[19:26:57] [Server-Worker-2/INFO]: Preparing spawn area: 0%
[19:26:57] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:26:57] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:26:58] [Server-Worker-2/INFO]: Preparing spawn area: 0%
[19:26:58] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:26:59] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:26:59] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:00] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:01] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:01] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:01] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:02] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:02] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:03] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:03] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:04] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:04] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:05] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:05] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:06] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:06] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:07] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:07] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[19:27:08] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:08] [Server-Worker-3/INFO]: Preparing spawn area: 0%
[19:27:09] [Server-Worker-2/INFO]: Preparing spawn area: 0%
[19:27:09] [Server-Worker-2/INFO]: Preparing spawn area: 0%
[19:27:10] [Server-Worker-2/INFO]: Preparing spawn area: 0%
[19:27:10] [Server-Worker-2/INFO]: Preparing spawn area: 0%
[19:27:11] [Server-Worker-2/INFO]: Preparing spawn area: 1%
[19:27:11] [Server-Worker-3/INFO]: Preparing spawn area: 1%
[19:27:12] [Server-Worker-3/INFO]: Preparing spawn area: 1%
[19:27:12] [Server-Worker-2/INFO]: Preparing spawn area: 1%
・
・
・
[19:29:27] [Server-Worker-2/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-2/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-2/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-3/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-2/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-3/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-3/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-3/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-2/INFO]: Preparing spawn area: 92%
[19:29:27] [Server-Worker-2/INFO]: Preparing spawn area: 92%
[19:29:30] [Server-Worker-3/INFO]: Preparing spawn area: 97%
[19:29:30] [Server-Worker-1/INFO]: Preparing spawn area: 97%
[19:29:30] [Server-Worker-3/INFO]: Preparing spawn area: 97%
[19:29:30] [Server-Worker-2/INFO]: Preparing spawn area: 97%
[19:29:30] [Server-Worker-1/INFO]: Preparing spawn area: 97%
[19:29:30] [Server thread/INFO]: Time elapsed: 153775 ms
[19:29:30] [Server thread/INFO]: Done (321.899s)! For help, type "help"
[19:29:32] [Server thread/WARN]: Can't keep up! Is the server overloaded? Running 2159ms or 43 ticks behind
[19:30:05] [Server thread/WARN]: Can't keep up! Is the server overloaded? Running 17806ms or 356 ticks behind
[19:34:58] [Server thread/WARN]: Can't keep up! Is the server overloaded? Running 7821ms or 156 ticks behind
・
・
・

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