20200330のJavaに関する記事は9件です。

`get()`を使うな ~ 敗北者の Optional

Java の Optional に関する記事を見ると “この教え方では Optional の意味がまるっきりない使い方するやつばかりが生まれるぞ……” というのを数多く見てしまったので,せめてもの反抗にこの記事を書きます。

TL; DR

isPresent()get()しか知らない奴は

yamero.png

こういう書き方をしてはいけない

記事を見ていると

null チェックはOptional#isPresent()で行い,値はOptional#get()で取り出します。

みたいな解説が多すぎる。

そういうことをしてはいけない。それでは従来の null チェックとなにも変わらない。変わらないどころかタイプ数が増え,実行時間が増え,メモリ使用量が増え,デメリットだけが上乗せされている。メリットはなにも得られていないのに。

Optional<String> someStr = Optional.of("Hello, world!");

if (someStr.isPresent()) {
    System.out.println(someStr.get()); // やってはいけない
}

まず頭に叩き込んでほしいのはOptional#get()を使ってはいけないということだ。

大事なことなのでもう一度言う。

Optional#get()は使うな

Optional#get()それが null かどうかを気にせずに取り出すウンコみたいなメソッドだ。Unsafe なのだ。“どーしてもそれを使わないといけないとき” のためだけに用意されている。

“直前に null チェックしてるからいいじゃん” とか抜かすアホは Antony Hoare にケツをファックされてからこの記事に戻ってくるように。“null チェックしてるからいい” なら従来の方法で十分なのだ。無意味だ。お前が null チェックを忘れようがOptional#get()は使える。そしてクラッシュする。バカバカしい話だ。

ではどうするか。Optional#ifPresent()を使う。このメソッドはメソッド参照を引数に取る。ので,ラムダ式を中に記述していくが……ラムダ式もメソッド参照も知らなくてもそれっぽく書けばいい

Optional<String> someStr = Optional.of("Hello, world!");

someStr.ifPresent(x -> {
    System.out.println(x);
});

Optional#ifPresent()に渡されたメソッド参照は null でないときだけ実行されるx一時的に使える変数だと思っていい。もちろん名前は (かぶらない限りは) 自由だ。型は当然Optional<T>に対してTとなる。ラムダ式やメソッド参照について知っておくのが一番だが,知らなくても通常の if 文と見た目は似ているので十分のはずだ。

null でないときだけ実行したいのではなく,値の有無で動作を分けたい場合もあるかもしれない。

Optional<String> someStr = Optional.empty();

if (someStr.isPresent()) {
    System.out.println(someStr.get()); // もちろんやってはいけない
} else {
    System.out.println("値はなかったよ");
}

そういうときはOptional#ifPresentOrElse()を使用する

Optional<String> someStr = Optional.empty();

someStr.ifPresentOrElse(x -> {
    System.out.println(x);
}, () -> {
    System.out.println("値はなかったよ");
});

() -> ...は引数を持たないラムダ式を意味するが,もちろん“値を使わないときのおまじない”だと思ってくれても構わない。

なお,Optional#ifPresentOrElse()は Java 9 で実装されたので,Java 8 の環境では

Optional<String> someStr = Optional.empty();

someStr.ifPresent(x -> {
    System.out.println(x);
}); if (!someStr.isPresent()) {
    System.out.println("値はなかったよ");
}

とするしかないだろう。

なるべくOptional#ifPresent()も使うな

できればOptional#ifPresent()も濫用すべきではないOptional#map()Optional#flatMap()Optional#orElse()で十分なときが多いはずだ。こちらの記事などを参照。Optional の世界では null チェックなんてしないのが基本なのだ。

敗北者が多すぎて敗北者向けの記事が書かれる始末

敗北者を生む自称解説記事が多いためにOptional#get()はそもそも使わないとか,null チェック自体しないのが理想という基本の基本部分があまり知られていないので “Optional などないほうがマシである” というようなトンチンカンな記事が書かれてしまうことになる。

  • NullPointerExceptionNoSuchElementExceptionにすり替えます。プログラムがクラッシュすることには変わりありません。

もし、myOPointが実際の座標を保持していない場合、myOPoint.get().xNoSuchElementExceptionを投げてプログラムをクラッシュさせます。これは元のコードから何一つ良くなってはいません。なぜなら、プログラマの目的はすべてのクラッシュを回避することであって、NullPointerExceptionによるクラッシュだけを回避できれば良いわけではないからです。

繰り返しになりますが、コードはどちらも似たようなもので、Optionalが従来の参照方法よりも勝っているとは言えません。

soreha.jpg

まあ,この記事の筆者もたぶん敗北者が多すぎて呆れてこういう記事を書いたのだろう。それでもこいつ物知らねえなという記述は散見されるけど。

Optional#get()を使わざるを得ないかもしれない例

仕様の関係で例外が発生しうる処理を外側でハンドリングすることはできない

public static int half(int even) throws Exception {
    if (even % 2 == 0); else {
        throw new Exception();
    }

    return even / 2;
}

/* ... */

Optional<Integer> someInt = Optional.of(16);

/* できる */
try {
    if (someInt.isPresent()) {
        System.out.println(half(someInt.get()));
    }
} catch (Exception e) {
    System.out.println("偶数を渡してください");
}

/* できない */
try {
    someInt.ifPresent(x -> {
        System.out.println(half(x));
    });
} catch (Exception e) {
    System.out.println("偶数を渡してください");
}

これをなんとかするために Qiita でも様々なハックが紹介されている。Either を使うことでもなんとかできると思う。モナドばんじゃい。まあ Java 公式に Either ないんですけどね。

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

Elasticsearchにおける「32GBメモリの壁」の境界線を調べる

はじめに

Elasticsearchでは以前からJVMヒープメモリサイズの設定における「32GBの壁」の存在が語られてきました。
デフォルト設定はXms,Xmxともに1gですが、ノードに搭載されるメモリ量に応じてこのパラメータは適宜サイジングすることが必要です。

Elasticsearch 2.xの時代にはすでにElasticsearch Definitive Guideにて、JVMのヒープサイズの設定で32GBを超えないことが性能上のTipsとして言及されていました。
現在の公式documentにも、このサイズは「約」32GBを超えないこととして推奨がされています。

本記事ではある環境において、この正確なしきい値となるメモリサイズを調べる方法をまとめます。
なお、この内容はOSのバージョンやJDKのバージョンによって異なるため、お使いの環境における正確なサイズは下記の手順を実際に実行して確認するようにしてください。

「32GBの壁」とは何か(ざっくり)

最初に明確にしておくと、これはElasticsearchに限った話ではありません。JVMアプリケーション全般に当てはまる話です。
ここで言われている「32GBの壁」とはざっくり説明すると、64bitシステムにおいてJavaヒープに格納されるオブジェクトの参照ポインタの表現方法を圧縮(compressed)することでシステムリソースを節約する機能と関連があります。

以下のサイトに詳細な説明があるのですが(*1)、通常64bitシステムでのJavaヒープのアドレスは64bit分使うところを、もしもヒープサイズを32GB以下しか使わない場合に限り、次のような制約におさめることで32bitアドレスでうまく節約してしまおうというトリックになっています。

  • ヒープオブジェクトのアドレスを8の倍数のサイズに制限する(Object Alignmentと呼ばれます)
  • アドレスは32bitで表現し、実際のアドレスはその値に8(0x1000)を乗じた値とする

こうすることで、従来は32bitでは4GB(=2^32)しか表現できなかったところが、35bit(=2^35≒32GB)のアドレスを表すことができるようになります。この仕組みのことを"Compressed OOPs"(圧縮オブジェクト参照)と呼びます。

*1: https://www.baeldung.com/jvm-compressed-oops

hoge

当然、32GB以上のヒープメモリを扱う際にはこの方法は使えませんので64bitを使った通常のオブジェクト参照となります。この場合、Compressed OOPsを使った場合に比べてメモリの利用効率が落ちるため、32GBメモリ以上にヒープサイズを設定しないことが推奨されているというわけです。

検証手順

ここでは下記の環境を使い、ヒープサイズの大きさを変えながら、Compressed OOPsの「境界線」を探ります。結論から言うと、この境界線はGCの種類によっても変わるため、JDK11で使えるCMS:ConcMarkSweep)とG1GCの2種類それぞれについて調べています。

環境

Azure上で以下のスペックの仮想マシン/OSをデプロイしてOpenJDKとELasticsearchをインストールしました。

  • スペック
    • D13-2_v2 (2vCPU/56GBmem) ※メモリは32GB以上あるものであればOK
  • OS
    • Ubuntu18.04LTS
  • JDK
    • OpenJDK 11.0.6
  • Elasticsearch
    • Elasticsearch 7.6.1

GCタイプとヒープサイズの組み合わせ

以下の4つの組み合わせで検証を行いました。01,02と03,04では境界線のヒープサイズが微妙に違う点に注意してください。(30MB違います)
また、01と02、03と04のようにそれぞれのGCについて境界線となるサイズを超えるとCompressed OOPsが使えなくなります。
以下で実際にそれを確かめる手順をまとめます。

no. GCタイプ ヒープサイズ(Xms/Xmx) Compressed Oopsが使えるか?
01 CMS(ConcMarkSweep) 32766MB 使える
02 CMS(ConcMarkSweep) 32767MB 使えない
03 G1GC 32736MB 使える
04 G1GC 32737MB 使えない

確認手順① JVM単体での確認

最初にJVM単体で確認を行います。これは"-XX:+PrintFlagsFinal"オプションを付けてjavaコマンドを実行することでJVMがCompressed Oops付きで実行されているかを確認する方法を用いています。

01: CMSGCで境界線未満(32766MB)

以下の出力の最後の行に注目してください。「bool UseCompressedOops = true」となっている箇所が、Compressed Oopsが有効な際の出力です。つまり「32GBの壁」は超えていないことになります。

sodo@vm01:~$ java -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -Xms32766m -Xmx32766m 2>/dev/null | grep Compressed
   size_t CompressedClassSpaceSize                 = 1073741824                                {product} {default}
     bool UseCompressedClassPointers               = true                                 {lp64_product} {ergonomic}
     bool UseCompressedOops                        = true                                 {lp64_product} {ergonomic}

02: CMSGCで境界線オーバー(32767MB)

「bool UseCompressedOops = false」となっており「32GBの壁」を超えています。

sodo@vm01:~$ java -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -Xms32767m -Xmx32767m 2>/dev/null | grep Compressed
   size_t CompressedClassSpaceSize                 = 1073741824                                {product} {default}
     bool UseCompressedClassPointers               = false                                {lp64_product} {default}
     bool UseCompressedOops                        = false                                {lp64_product} {default}

03: G1GCで境界線未満(32736MB)

「bool UseCompressedOops = true」となっています。「32GBの壁」を超えていません。

sodo@vm01:~$ java -XX:+UseG1GC -XX:+PrintFlagsFinal -Xms32736m -Xmx32736m 2>/dev/null | grep Compressed
   size_t CompressedClassSpaceSize                 = 1073741824                                {product} {default}
     bool UseCompressedClassPointers               = true                                 {lp64_product} {ergonomic}
     bool UseCompressedOops                        = true                                 {lp64_product} {ergonomic}

04: G1GCで境界線オーバー(32737MB)

「bool UseCompressedOops = false」となっています。「32GBの壁」を超えています。

sodo@vm01:~$ java -XX:+UseG1GC -XX:+PrintFlagsFinal -Xms32737m -Xmx32737m 2>/dev/null | grep Compressed
   size_t CompressedClassSpaceSize                 = 1073741824                                {product} {default}
     bool UseCompressedClassPointers               = false                                {lp64_product} {default}
     bool UseCompressedOops                        = false                                {lp64_product} {default}

確認手順② Elasticsearchでの確認

次にElasticsearchにてjvm.optionファイルにヒープサイズを指定して確認を行います。結果はelasticsearch.logに起動時にCompressed Oopsモードかどうかがログ出力されるため、それを確認します。

01: CMSGCで境界線未満(32766MB)

jvm.optionファイルに以下のように設定を行います。3-7行目はデフォルトのままです。今回はOpenJDK11を使っているため、GCはCMSが使われます。(gc.logにも利用するGCタイプは出力されます)

jvm.options
-Xms32766m
-Xmx32766m

# 10-13:-XX:-UseConcMarkSweepGC
# 10-13:-XX:-UseCMSInitiatingOccupancyOnly
14-:-XX:+UseG1GC
14-:-XX:G1ReservePercent=25
14-:-XX:InitiatingHeapOccupancyPercent=30

この設定でElasticsearchを起動するとログに以下の行が確認されます。行末にある「compressed ordinary object pointers [true]」がCompressed Oopsが有効である印となります。

elasticsearch.log
[2020-03-30T07:26:14,269][INFO ][o.e.e.NodeEnvironment    ] [vm01] heap size [31.9gb], compressed ordinary object pointers [true]

02: CMSGCで境界線オーバー(32767MB)

jvm.optionファイルに以下のように設定を行います。1,2行目のヒープサイズのみ変えています。

jvm.options
-Xms32767m
-Xmx32767m

# 10-13:-XX:-UseConcMarkSweepGC
# 10-13:-XX:-UseCMSInitiatingOccupancyOnly
14-:-XX:+UseG1GC
14-:-XX:G1ReservePercent=25
14-:-XX:InitiatingHeapOccupancyPercent=30

今度は「compressed ordinary object pointers [false]」となりました。Compressed Oopsが無効化されたことがわかります。

elasticsearch.log
[2020-03-30T09:43:01,828][INFO ][o.e.e.NodeEnvironment    ] [vm01] heap size [31.9gb], compressed ordinary object pointers [false]

03: G1GCで境界線未満(32736MB)

ヒープサイズとあわせて、設定ファイル内にあるコメントに従ってGC設定を変更しています。今回はOpenJDK11を使っているため、この設定によりG1GCが使われます。(gc.logの出力も確認しました)

jvm.options
-Xms32736m
-Xmx32736m

10-13:-XX:-UseConcMarkSweepGC
10-13:-XX:-UseCMSInitiatingOccupancyOnly
11-:-XX:+UseG1GC
11-:-XX:G1ReservePercent=25
11-:-XX:InitiatingHeapOccupancyPercent=30

ここではCompressed Oopsが有効となっています。

elasticsearch.log
[2020-03-30T07:22:29,694][INFO ][o.e.e.NodeEnvironment    ] [vm01] heap size [31.9gb], compressed ordinary object pointers [true]

04: G1GCで境界線オーバー(32737MB)

ヒープサイズを1MBだけ増やします。その他は変更していません。

jvm.options
-Xms32737m
-Xmx32737m

10-13:-XX:-UseConcMarkSweepGC
10-13:-XX:-UseCMSInitiatingOccupancyOnly
11-:-XX:+UseG1GC
11-:-XX:G1ReservePercent=25
11-:-XX:InitiatingHeapOccupancyPercent=30

今度はCompressed Oopsが無効になりました。

elasticsearch.log
[2020-03-30T07:23:19,486][INFO ][o.e.e.NodeEnvironment    ] [vm01] heap size [31.9gb], compressed ordinary object pointers [false]

まとめ

Elasticsearchでよく言われる「32GBの壁」について、JVM全般で関連するCompressed Oops(圧縮オブジェクト参照)の仕組みの概要と、実際の環境でJVMおよびElasticsearch起動時にCompressed Oopsの有効・無効を確認する手順を紹介しました。
なお、実際には適切なヒープサイズを検討するにあたっては、Elasticsearchの用途(全文検索中心、メトリクスのソート/Aggregation中心など)や、Luceneが使うキャッシュメモリなど、いくつか考慮する点があります。不明点などはdiscuss.elastic.coなどのコミュニティサイトに聞いてみたり、Elasticsearch Tokyo User Groupなどのユーザコミュニティ/勉強会で有識者に聞いてみることもおすすめです。

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

java弄ってた時に出たエラー

Not only pythonエラー備忘録 but also java備忘録

メモ

文字列の設定
String型に値を設定する場合は""、char型に値を設定する場合は''を使おう。

エラー集

java.lang.IllegalStateException: closed

エラーメッセージ

example.java
example E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: example, PID: XXXXX
    java.lang.IllegalStateException: closed
        at okio.RealBufferedSource.select(RealBufferedSource.kt:83)
        at okhttp3.internal.Util.readBomAsCharset(Util.kt:254)
        at okhttp3.ResponseBody.string(ResponseBody.kt:171)
        at example.AsyncHttp$1.onResponse(AsyncHttp.java:107)
        at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

状況&原因
okhttpを使ってサーバ上のテキストを読み取って(GETして)response.body().string()で中身取り出してあれやこれややろうとした。
ココによるとresponse.body().string()は一度しか呼べないとのこと。
今回はログ出力で1回、変数格納で1回の計2回呼び出していた。
オチ
ログ出力の方をコメントアウトしたところ、問題なく動作した。
Responseはとっとと変数に格納するのがよさそうね。

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

ストライクガンダムでオブジェクト指向(java)

はじめに

オブジェクト指向の学習のために作成しました。
ストライクガンダムとストライクダガーの組み立てをイメージし、それぞれの特徴と共通する部分を意識して作成しました。

手順

以下の手順で作成しました。
1. インターフェースの作成
 →3種のストライカーパックをインターフェースとします。
2. 親クラスの作成
 →X100系フレームを親クラスとします。
3. 子クラスの作成
 →ストライクガンダムとストライクダガーを子クラスとします。
4. Factoryクラスの作成
 →Factoryクラスに出力するための記述を行います。

インターフェースの作成

エール、ソード、ランチャーをインターフェースの形で作成し、それぞれの武装を以下のように記述します。

IAile.java
package practice;

public interface IAile {

    public String WEAPON_Aile[] = {"57mm高エネルギービームライフル", "ビームサーベル", "対ビームシールド"};

}

エールストライカー

ISword.java
package practice;

public interface ISword {

    public String WEAPON_Sword[] = {"15.78m対艦刀「シュベルトゲベール」", "ビームブーメラン「マイダスメッサー」", "ロケットアンカー「パンツァーアイゼン」"};

}

ソードストライカー

ILauncher.java
package practice;

public interface ILauncher {

    public String WEAPON_Launcher[] = {"320mm超高インパルス砲「アグニ」", "120mm対艦バルカン砲", "350mmガンランチャー"};

}

ランチャーストライカー

親クラスの作成

ストライクガンダムとストライクダガーはX100系フレームを基本骨格に使用しています。
また、特徴としてストライカーパックシステムという機能があります。
これらの特徴をX100frame.javaに以下のように記述します。
ゲッターも忘れずここで記述します。

X100frame.java
package practice;

public class X100frame {
    private String name = "";
    private String type = "X100系フレーム";
    private String feature = "ストライカーパックシステム";

    public X100frame(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public String getType() {
        return this.type;
    }

    public String getFeature() {
        return this.feature;
    }

}

子クラスの作成

ストライクガンダムをSTRIKEクラス、ストライクダガーをDAGGERクラスとして作成します。
その際、X100frameクラスを継承し、手順1で作成した3つのインターフェースも使用できるようにします。

STRIKE.java
package practice;

// X100frameクラスを継承し、IAile, ISword, ILauncherを使用可に
public class STRIKE extends X100frame implements IAile, ISword, ILauncher {

    public STRIKE() {super("ストライクガンダム"); }
// 素ストライクのみの特徴
    private String Model = "GAT-X105";
    private String Armor = "フェイズシフト装甲";
    private double height = 17.72;
    private double weight = 64.80;
    protected String WEAPON[] = {"イーゲルシュテルン", "アーマーシュナイダー"};

// 素ストライクのみの特徴を出力するためメソッド 
    public void printData() {
        System.out.println("モビルスーツ名:" + this.getName());
        System.out.println("型式番号:" + this.Model);
    // フレームと特徴はX100frameクラスから継承
        System.out.println("フレーム:" + this.getType());
        System.out.println("特徴:" + this.getFeature());
        System.out.println("装甲:" + this.Armor);
        System.out.println("全高:" + this.height + "m");
        System.out.println("重量:" + this.weight + "t");
        System.out.println();
    }

// ストライカーパックを装備した場合の武装を出力
    public void printDataPuck() {
        System.out.println("エールストライカー装備時:");
        System.out.println(WEAPON_Aile[0] + "," + WEAPON_Aile[1] + "," + WEAPON_Aile[2]);
        System.out.println("------------");
        System.out.println("ソードストライカー装備時:");
        System.out.println(WEAPON_Sword[0] + "," + WEAPON_Sword[1] + "," + WEAPON_Sword[2]);
        System.out.println("------------");
        System.out.println("ランチャーストライカー装備時:");
        System.out.println(WEAPON_Launcher[0] + "," + WEAPON_Launcher[1] + "," + WEAPON_Launcher[2]);
        System.out.println();
    }

}

ストライクガンダム

DAGGER.java
package practice;

// X100frameクラスを継承し、IAile, ISword, ILauncherを使用可に
public class DAGGER extends X100frame implements IAile, ISword, ILauncher{

    public DAGGER() {super("ストライクダガー"); }
// 素ストライクダガーのみの特徴
    private String Model = "GAT-01A1";
    private String Armor = "ラミネート装甲(胴体部のみ)";
    private double height = 18.00;
    private double weight = 57.05;
    protected String WEAPON[] = {"イーゲルシュテルン", "52mm機関砲ポッド", "ビームカービン", "12.5mm対人機関銃×2"};

// 素ストライクダガーのみの特徴を出力するためメソッド
    public void printData() {
        System.out.println("モビルスーツ名:" + this.getName());
        System.out.println("型式番号:" + this.Model);
    // フレームと特徴はX100frameクラスから継承
        System.out.println("フレーム:" + this.getType());
        System.out.println("特徴:" + this.getFeature());
        System.out.println("装甲:" + this.Armor);
        System.out.println("全高:" + this.height + "m");
        System.out.println("重量:" + this.weight + "t");
        System.out.println();
    }

// ストライカーパックを装備した場合の武装を出力
    public void printDataPuck() {
        System.out.println("エールストライカー装備時:");
        System.out.println(WEAPON_Aile[0] + "," + WEAPON_Aile[1] + "," + WEAPON_Aile[2]);
        System.out.println("------------");
        System.out.println("ソードストライカー装備時:");
        System.out.println(WEAPON_Sword[0] + "," + WEAPON_Sword[1] + "," + WEAPON_Sword[2]);
        System.out.println("------------");
        System.out.println("ランチャーストライカー装備時:");
        System.out.println(WEAPON_Launcher[0] + "," + WEAPON_Launcher[1] + "," + WEAPON_Launcher[2]);
        System.out.println();
    }

}

ストライクダガー

Factoryクラスの作成

コンソールに出力するための記述などをまとめます。

Factory.java
package practice;

public class Factory {

    public static void main(String[] args) {
        STRIKE s = new STRIKE();
        DAGGER d = new DAGGER();
        s.printData();
        System.out.println("武装一覧:");
        System.out.println("-------------------------");
        System.out.println(s.WEAPON[0] + "," + s.WEAPON[1]);
        System.out.println("------------");
        s.printDataPuck();
        System.out.println("----------------------------------------------------------------------------------------");
        System.out.println();
        d.printData();
        System.out.println("武装一覧:");
        System.out.println("-------------------------");
        System.out.println(d.WEAPON[0] + "," + d.WEAPON[1] + "," + d.WEAPON[2] + "," + d.WEAPON[3]);
        System.out.println("------------");
        d.printDataPuck();
    }

}

出力結果

このように出力されました。

モビルスーツ名:ストライクガンダム
型式番号:GAT-X105
フレーム:X100系フレーム
特徴:ストライカーパックシステム
装甲:フェイズシフト装甲
全高:17.72m
重量:64.8t

武装一覧:
-------------------------
イーゲルシュテルン,アーマーシュナイダー
------------
エールストライカー装備時:
57mm高エネルギービームライフル,ビームサーベル,対ビームシールド
------------
ソードストライカー装備時:
15.78m対艦刀「シュベルトゲベール」,ビームブーメラン「マイダスメッサー」,ロケットアンカー「パンツァーアイゼン」
------------
ランチャーストライカー装備時:
320mm超高インパルス砲「アグニ」,120mm対艦バルカン砲,350mmガンランチャー

----------------------------------------------------------------------------------------

モビルスーツ名:ストライクダガー
型式番号:GAT-01A1
フレーム:X100系フレーム
特徴:ストライカーパックシステム
装甲:ラミネート装甲(胴体部のみ)
全高:18.0m
重量:57.05t

武装一覧:
-------------------------
イーゲルシュテルン,52mm機関砲ポッド,ビームカービン,12.5mm対人機関銃×2
------------
エールストライカー装備時:
57mm高エネルギービームライフル,ビームサーベル,対ビームシールド
------------
ソードストライカー装備時:
15.78m対艦刀「シュベルトゲベール」,ビームブーメラン「マイダスメッサー」,ロケットアンカー「パンツァーアイゼン」
------------
ランチャーストライカー装備時:
320mm超高インパルス砲「アグニ」,120mm対艦バルカン砲,350mmガンランチャー

以上となります。

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

Activity内に定義したAsyncTaskのネストクラスに警告「This 'AsyncTask' class should be static or leaks might occur」

この記事は、
非staticネストクラスが握っちゃう、エンクロージングオブジェクトの暗黙的参照」と題した投稿の続きです。
ですので、先にそっちをご覧いただければ幸いです。
そっちの記事は、頭ごなし的に言っちゃえば、「ネストクラスには、static修飾子を付けろ」です。

いきなりカウントアップしだすアプリを作ってみた、ら...

起動したとたんに、勝手に1秒刻みでカウントアップが始まります。
START!から始まったら「9」まで数え上げたらお終いです。
(このアニメーションGIFは、3まで至ったらまたSTART!を繰り返しているアニメーションですが、このQiitaに貼り付けたアニメーションGIFを止めるすべがないのでごめんなさい)

countup.gif

環境は以下の通りです。

Android Studio 3.6.1
Build #AI-192.7142.36.36.6241897, built on February 27, 2020
Runtime version: 1.8.0_212-release-1586-b04 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
GC: ParNew, ConcurrentMarkSweep
Memory: 1237M
Cores: 8
Registry: ide.new.welcome.screen.force=true
Non-Bundled Plugins:

Kotlinで開発しません。Javaでやります。

Activityのコード上に警告が出ます

このアプリのActivityは以下の通りです。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    private TextView textView;

    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.text_view);
        task = new MyTask();
        task.execute();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        task.cancel(true);
    }

    class MyTask extends AsyncTask<Void, String, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            for (int i = 0; i < 10; i++) {                if (isCancelled()) {
                    return null; // break;でもいいかも
                }
                try {
                    Thread.sleep(1000);
                    publishProgress(String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            textView.setText(values[0]);
        }
    }
}

そしたらAndroid Studioがこんな警告を出すんです。

thisasynctaskclassshouldbestaticorleaksmightoccur.png

This 'AsyncTask' class should be static or leaks might occur

MyTaskと名付けたネストクラスに、static修飾子が付いていないことを咎めています。

この警告を出さないようにする(した)のが、この記事の目的です。

これがベストな(主流の)解決方法のようです

まず先に、結論のコードを掲載します。
エンクロージングクラスであるMainActicityオブジェクトの弱い参照を、staticネストクラスのオブジェクトは保持することにします。ということで、java.lang.ref.WeakReferenceを使います。

最善を尽くした修正
public class MainActivityGood extends AppCompatActivity {

    // TextViewのフィールドはやめます。

    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.text_view);
        // this渡してる?なんで?それはね、MyTaskにコンストラクタを設けたからさ。
        task = new MyTask(this);
        task.execute(textView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        task.cancel(true);
    }

    static class MyTask extends AsyncTask<Void, String, Void> {

        WeakReference<Activity> activityReference; // ここ注目!

        // コンストラクタを設けました。
        public MyTask(Activity activity) {
            // 弱い参照でエンクロージングクラスのオブジェクトを保持することにします。
            activityReference= new WeakReference<>(activity);
        }

        @Override
        protected Void doInBackground(Void... voids) {
            for (int i = 0; i < 10; i++) {                if (isCancelled()) {
                    return null; // break;でもいいかも
                }
                try {
                    Thread.sleep(1000);
                    publishProgress(String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            // エンクロージングクラスのオブジェクトの弱い参照は、getメソッドで取得できます。
            Activity activity = activityReference.get();
            // そして、その取得した弱い参照がnullになっちゃってないかのチェックはしておきましょう。
            if (activity == null || activity.isFinishing()) {
                return; // ※
            }

            TextView textView = activity.findViewById(R.id.text_view);
            textView.setText(values[0]);
        }
    }
}

結論はこうなんですが、せっかくなので途中私がこの結論に至るまでの逡巡を挟みつつ、最後には大団円を迎えたいと思います。

ネストクラスにstatic修飾子を付ける

Android Studioの警告の仰せの通りに、盲目的に(笑)MyTaskと名付けたAsyncTaskサブクラスにstatic修飾子を付けました。

ただし、それだけの措置を施しただけですと、エンクロージングクラスの非staticフィールドをアクセスする際に以下の画像のように怒られます。なぜなのか。それは、 staticネストクラスは、エンクロージングのstaticメンバーにのみアクセス可能 だからです。

Non-static field.png

だったら、

MainActivityをこう修正すればイイじゃない?という安易な気持ちで
public class MainActivity extends AppCompatActivity {

    private static TextView textView;

    // 以下略
}

と修正するのは、否、それはチョット違うんでないかい?と気が咎めるのです。

我ながらひどいコードを書いたもんだ

わざとらしいのですが、まあ見てやってください。でも、こんなんで、Android Studioの例の警告は消えます。

MainActivityDasai.java
public class MainActivityDasai extends AppCompatActivity {

    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.text_view);
        task = new MyTask();
        task.execute(textView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        task.cancel(true);
    }

    static class MyTask extends AsyncTask<TextView, Object, Void> {
        @Override
        protected Void doInBackground(TextView... textViews) {
            for (int i = 0; i < 100000; i++) {
                if (isCancelled()) {
                    return null; // break;でもいいかも
                }
                try {
                    Thread.sleep(1000);
                    publishProgress(textViews[0], String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Object... values) {
            ((TextView)values[0]).setText((String)values[1]);
        }
    }
}

私の逡巡が、我が身と心を迷走させております。

  • このMyTaskは、このMainActivityでしか使われないクラスだから、ネストクラスにしたい。
  • しかしstaticネストクラスにせよとIDEが警告を出す。
  • ところがTextTview型のフィールドはstaticフィールドにはしたくない。
  • となると、このMyTaskとこのMainActivityは、別クラスに分けるか。でもそんなことをしたら、今はこのTextTview1個っきりだからまあいいけど、他にもいろんなViewを複数扱うということになると、参照の受け渡しが面倒だ。
  • そしたらやっぱりネストクラスにするか...。

この私の逡巡が、「 では、TextViewをエンクロージングクラスのフィールドにすること自体をやめよう! 」という思いに至って、このようなダサいプログラムになりました。

而して、このダサいプログラムを書いた自分自身を咎めたくなりました。
どこが気にくわないかと言うと、ジェネリクスにObjectを指定する、というところに嫌気がさします。
おかげで、「((TextView)values[0]).setText((String)values[1]);」の部分なんて、キャストしまくり、配列のインデックスどっちがどっち?と惑わせます。
加えて、doInBackgroundメソッドの引数そしてAsyncTaskの第1ジェネリクスをTextViewにしたことも気にくわないです。TextView以外のViewが増えたらどうするんだよ、と。

WeakReferenceを使って、もらったActivityを"弱い参照"で扱う

java.lang.ref.WeakReferenceを使って 弱い参照 として扱うことにしました。前掲のMainActivityGood.javaをご覧ください。

ポイントは、

  • staticネストクラスにしたMyTaskにコンストラクタを定義する。
  • エンクロージングクラスをジェネリックスで指定したWeakReference型フィールドも定義する。
  • そしてそれをコンストラクタの引数を使ってnewする。これにてMainActivityGoodの弱い参照を保持!
  • あとは必要に応じて、WeakReferenceからMainActivityGoodをゲット(メソッド名もget())して使う。

前出のダサいやり方と違って、これならTextView以外のViewを扱うことになっても対応が簡易になりますし、ジェネリクスにObjectを指定するという愚なこともせずにすみます。

以上です。

参考

Qiitaのこの記事「This Handler class should be static or leaks might occur」に近い話です。

AsyncTaskはAPI レベル Rから非推奨になりました

android.os.AsyncTaskは、API レベル R1から非推奨になります。
この記事も、廃れる運命にあるわけです...。

代替策は、

とのことです。後者は、要はKotlinのCoroutinesのことです。


  1. APIレベルは数字で示されるものなのに、この記事執筆時点では「R」となってます。 

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

HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)

はじめに

こんにちは。よろず相談担当 すぎもん:yum:です。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント(アダプタ)の開発をやってみようと思います。全3部作で完結するよう記載してます。

今回やること

今回は、第3弾としてHULFT IoT EdgeStreaming Plugin SDKで開発したプラグインのビルドと実行をしてみようと思います。第1弾、第2弾の記事は、下記にありますので参考にしてみてください。

:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)

プラグインのビルド

今回、作成したプラグインをビルドしてみます。※Windows PCを使用して試しています。
なお、
$SDK_HOMEは、EdgeStreaming SDKホームディレクトリを指します。
$DATASPIDER_HOMEは、EdgeStreamingのホームディレクトリを指します。

ビルド

プラグインのビルドは、$SDK_HOME/dev/sample_adapterから以下のコマンドを実行します。

$ ant

image.png

コマンド実行後、BUILD SUCCESSFULと出力され、下記のファイル類が作成されていればビルドは成功です。

$SDK_HOME/dev/sample_adapter/build
 sample_adapter.jar
$SDK_HOME/dev/sample_adapter/build/plugin/sample_plugin
 各ターゲット向けのgoの実行ファイル

image.png

アイコンファイルのコピー

実際にEdgeStreaming Studio画面で使用するアイコンファイルを作成します。
ビルドに成功後、$SDK_HOME/dev/sample_adapter/META-INFが生成されるので、アイコンのサンプルファイルをコピーしリネームします。

・Source Operation用アイコンのコピー
$SDK_HOME/dev/conf/operation.source.icon
⇒ $SDK_HOME/dev/sample_adapter/META-INF/operation.sample_source.icon

・Sink Operation用アイコンのコピー
$SDK_HOME/dev/conf/operation.sink.icon
⇒ $SDK_HOME/dev/sample_adapter/META-INF/operation.sample_sink.icon

・UDSF Operation用アイコンのコピー
$SDK_HOME/dev/conf/operation.udsf.icon
⇒ $SDK_HOME/dev/sample_adapter/META-INF/operation.sample_udsf.icon

module.propertiesファイルの確認

$SDK_HOME/dev/sample_adapter/META-INF/module.propertiesファイルが正常に作成されたことを確認します。

このプロパティファイルは、$SDK_HOME/dev/sample_adapter/config.propertiesファイルで定義されているプロパティを基に作成されています。
image.png

プラグインのインストール

アダプタのインストールは$SDK_HOME/dev/sample_adapterからantコマンドの第一引数にinstallターゲットを指定して実行します。

$ ant install

image.png

ant installを実行後、下記のようにインストールがされます。
・Javaモジュールがインストールされます。
$DATASPIDER_HOME/server/plugin/data_processing/modules配下

・Golangモジュールがインストールされます。
$DATASPIDER_HOME/server/es-agent/plugin配下

※インストールしたプラグインを有効にするには、EdgeStreaming Studioの再起動が必要です。

実行してみましょう

それでは、作成したプラグインの実行をしてみましょう。

EdgeStreamingの起動

・EdgeStreamingを起動します。

image.png

・作成したプラグインを確認してみましょう。
新規プロジェクトから、プロジェクトを作成します。
image.png

スクリプトを作成します。
image.png

プラグインの確認

作成したプラグインが、「ツールパレット」にできていることを確認します。
「ツールパレット」の「Sample」配下に、それぞれ以下のプラグインができていると思います。

image.png

簡単な処理を作成してみましょう

・入力処理(一定時間間隔で疑似乱数を生成するオペレーション)
Source Operationで作成した処理を配置します。
ツールパレットのSampleからSample sourceをドラッグ&ドロップで中央へ移動させます。
※Intervalには、取得する間隔を指定してください。

image.png

・出力処理(有効小数点桁数で切り捨ててログに出力するオペレーション)
Sample Sinkで作成した処理を配置します。
ツールパレットのSampleからSample sinkをドラッグ&ドロップで中央へ移動させます。
※Decimalには、任意の有効小数点桁数を指定してください。

image.png

マッピング処理
入力処理アイコンをドラッグ&ドロップして、出力処理までアイコンをつなげます。
つなげた線を右クリックし、マッピングの追加を選択します。
image.png

マッピングアイコンを開きます。
image.png

・入力元の「value」と出力先の「value」をつなげます。
・出力先の「formula」にツールパレット->文字列->基本->文字列定数を配置し、
任意の文字列を指定し、「formula」につなげます。
image.png

スクリプト全体
このようにスクリプトが作成できました。
image.png

実行してみましょう

画面メニューの緑矢印から作成したスクリプトを実行してみましょう。

image.png

疑似乱数が指定したIntervalごとに生成され、指定した小数点桁数で値が出力されることが確認できると思います。
image.png

また、作成したUDSFのオペレーションアイコンを使用することで、取得した値に対して四則演算ができますので、
組み合わせて、いろいろと試してみてください。

最後に

3部作の最終編として、今回はプラグインのビルドから実行までやってみました。
このようにSDKを使用してプラグインの処理を実装することが可能です。本記事がプラグイン開発の道しるべになれたら幸いです。

このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。

これからも是非チェックいただき、宜しければフォローをお願いします。

それでは、また!

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

HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)

はじめに

こんにちは。よろず相談担当 すぎもん:yum:です。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント(アダプタ)の開発をやってみようと思います。全3部作で完結するよう記載してます。

今回やること

今回は、第2弾としてHULFT IoT EdgeStreaming Plugin SDKを使用してプラグインを開発してみます。他にセットアップ編、実行編の記事もあるので是非参考にしてみてください。

:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)

プラグインの作成概要

EdgeStreamingのプラグイン作成の構成は、以下のようになります。
image.png

image.png

実行環境構成

Studioから指定されたStreaming処理を実行するRuntime部分になります。

SourceOperation
Streamingデータ(Tuple)を生成するオペレーションです。「入力処理」となります。
※JSON形式となります

SinkOperation
Streamingデータ(Tuple)を外部へ出力するオペレーションです。「出力処理」となります。

UDSFOperation
Streamingデータ(Tuple)の変換を行うオペレーションです。
また、変換されたStreming(Tuple)を出力します。「入出力(変換)処理」

開発環境構成

RunTimeにStreaming処理をどのように行うかを指示するBQL(SQLライクな文法)を生成します。

AdapterModuleComponent
コンポーネント(アダプタ)を表すクラスです。オペレーションと多対1となります。

BQLPluginSourceOperationFactory
SourceOperartionのプロパティを定義します。「入力処理」
Sourceオペレーションを生成するBQL(Create Source文)を出力します。

BQLPluginOutputOperationFactory
SinkOperationのプロパティを定義します。「出力処理」
Sinkオペレーションを生成するBQL(Create Sink文)を出力します。

BQLPluginUDSFOperationFactory
UDSFのプロパティを定義します。「入出力(変換)処理」
UDSFオペレーションを生成するBQL(Select文)を出力します。

プラグインの作成

今回は、以下のような形でプラグイン作成をしてみました。

・基本情報
image.png

・オペレーション情報
image.png

実行環境側(Golang)の実装

Studioから指定されたStreaming処理の実装をします。

環境準備
1. モジュールディレクトリの作成
$SDK_HOME/dev/go/src/github.sis.saison.co.jp/sherpa/es-agent/sample
 ※ここに、ソースファイルを格納しました。(source.go, sink.go, udsf.go)
2. pluginディレクトリの作成
$SDK_HOME/dev/go/src/github.sis.saison.co.jp/sherpa/es-agent/sample/plugin
3. externalディレクトリの作成
$SDK_HOME/dev/go/src/github.sis.saison.co.jp/sherpa/es-agent/sample/external

ソースファイルの作成
モジュールディレクトリ(ここではsample)に以下のファイル名でソースファイルを作成していってみます。

・ファイル構成は、以下のとおりです。

├─sample
│   ├─source.go(入力処理)
│   ├─sink.go(出力処理)
│   ├─udsf.go(入出力変換処理)
│   │ 
│   ├─external
│   │   ├─plugin_main.go(main関数)
│   │   │ 
│   └─plugin
│   │   ├─plugin.go(各sample配下のソースファイルを登録)
│   │   │ 

では、ソースファイルを作成していってみましょう。

・source.go(入力処理)
一定時間間隔で疑似乱数を生成する処理を作成します。
主な流れは、以下のとおりです。
1. 整数型のパラメータ”interval”を受け取る
2. intervalの時間間隔で疑似乱数を生成する
3. JSONスキーマのデータを持つTupleを生成する

Tuple
{
  "type": "object",
  "required": ["payload"],
  "properties": {
    "payload": {
      "type": "object",
      "required": ["value"],
      "properties": {
        "value": {
          "type": "number"
        }
      }
    }
  }
}

出力されるTupleのJSONデータは、以下のような形になります。

{"payload": {"value": 3.5423242}}

source.goは、以下のように作成してみました。

source.go
package sample

import (
    "math/rand"
    "time"

    "gopkg.in/sensorbee/sensorbee.v0/bql"
    "gopkg.in/sensorbee/sensorbee.v0/core"
    "gopkg.in/sensorbee/sensorbee.v0/data"
)

type source struct {
    interval time.Duration
    term     chan struct{}
}

func (s *source) GenerateStream(ctx *core.Context, w core.Writer) error {
    rand.Seed(time.Now().UnixNano())

    next := time.Now()
    for {
        val := rand.Float64()
        m := data.Map{"value": data.Float(val)}
        t := core.NewTuple(data.Map{"payload": m})
        if s.interval > 0 {
            t.Timestamp = next
        }
        ctx.Log().Debug("generation: ", val)
        if err := w.Write(ctx, t); err != nil {
            return err
        }

        if s.interval > 0 {
            now := time.Now()
            next = next.Add(s.interval)
            if next.Before(now) {
                next = now.Add(s.interval)
            }

            select {
            case <-s.term:
                return core.ErrSourceStopped
            case <-time.After(next.Sub(now)):
            }
        }
    }
    return nil
}

func (s *source) Stop(ctx *core.Context) error {
    s.term <- struct{}{}
    return nil
}

func CreateSource(ctx *core.Context, ioParams *bql.IOParams, params data.Map) (core.Source, error) {
    interval, err := getInterval(params)
    if err != nil {
        return nil, err
    }

    return &source{
        interval: interval,
        term:     make(chan struct{}),
    }, nil
}

func getInterval(params data.Map) (time.Duration, error) {
    interval := 1 * time.Second
    if v, ok := params["interval"]; ok {
        i, err := data.ToDuration(v)
        if err != nil {
            return interval, err
        }
        interval = i
    }
    return interval, nil
}

・sink.go(出力処理)
有効小数点桁数で切り捨ててログに出力する処理を作成します。

主な流れは、以下のとおりです。
1. 整数型のパラメータ「decimal」を受け取る
2. 「decimal」の有効小数点桁数で、受け取った値を標準出力に出力
3. JSONデータのスキーマのデータを持つTupleを受け取る`

Tuple
{
  "type": "object",
  "required": ["payload"],
  "properties": {
    "payload": {
      "type": "object",
      "required": ["value", "formula"],
      "properties": {
        "value": {
          "type": "number"
        }
        "formula": {
          "type": "string"
        }
      }
    }
  }
}

sink.goは、以下のように作成してみました。

sink.go
package sample

import (
    "fmt"
    "math"

    "gopkg.in/sensorbee/sensorbee.v0/bql"
    "gopkg.in/sensorbee/sensorbee.v0/core"
    "gopkg.in/sensorbee/sensorbee.v0/data"
)

type sink struct {
    decimal int
}

func (s *sink) Write(ctx *core.Context, tuple *core.Tuple) error {
    p, ok := tuple.Data["payload"]
    if !ok {
        return fmt.Errorf("the tuple doesn't have the required field: payload")
    }

    payload, err := data.AsMap(p)
    if err != nil {
        return err
    }

    v, ok := payload["value"]
    if !ok {
        return fmt.Errorf("the tuple doesn't have the required field: value")
    }

    value, err := data.AsFloat(v)
    if err != nil {
        return err
    }

    f, ok := payload["formula"]
    if !ok {
        return fmt.Errorf("the tuple doesn't have the required field: formula")
    }

    formula, err := data.AsString(f)
    if err != nil {
        return err
    }
    shift := math.Pow(10, float64(s.decimal))
    value = math.Floor(value*shift) / shift
    ctx.Log().Infof("formula: %s", formula)
    ctx.Log().Infof("value: %f", value)
    return nil
}

func (s *sink) Close(ctx *core.Context) error {
    return nil
}

func CreateSink(ctx *core.Context, ioParams *bql.IOParams, params data.Map) (core.Sink, error) {
    decimal, err := getDecimal(params)
    if err != nil {
        return nil, err
    }

    return &sink{
        decimal: decimal,
    }, nil
}

func getDecimal(params data.Map) (int, error) {
    node, ok := params["decimal"]
    if !ok {
        return 0, fmt.Errorf("decimal is required")
    }
    decimal, err := data.AsInt(node)
    if err != nil {
        return 0, fmt.Errorf("decimal must be a int:%s", err)
    }
    return int(decimal), nil
}

・udsf.go(入出力変換処理)
udsfオブジェクトは、以下をデータを受け取ります。
- 文字列型のパラメータ:stream_name(入力Stream名)
- operaror(演算子)
- 浮動小数点型パラメータ: initial_value(初期値)

udsfオブジェクトは、受け取ったTupleのvalue要素の値を、現在の値(スタート時はinitial_value(初期値))に指定した演算子で、演算し続けます。

udsf.goは、以下のように作成してみました。

udsf.go
package sample

import (
    "fmt"

    "gopkg.in/sensorbee/sensorbee.v0/bql/udf"
    "gopkg.in/sensorbee/sensorbee.v0/core"
    "gopkg.in/sensorbee/sensorbee.v0/data"
)

type operator byte

const (
    none    = ' '
    plus    = '+'
    minus   = '-'
    times   = '*'
    divided = '/'
)

type udsf struct {
    cur float64
    ope operator
}

func (u *udsf) Process(ctx *core.Context, tuple *core.Tuple, w core.Writer) error {
    p, ok := tuple.Data["payload"]
    if !ok {
        return fmt.Errorf("the tuple doesn't have the required field: payload")
    }

    payload, err := data.AsMap(p)
    if err != nil {
        return err
    }

    v, ok := payload["value"]
    if !ok {
        return fmt.Errorf("the tuple doesn't have the required field: value")
    }

    value, err := data.AsFloat(v)
    if err != nil {
        return err
    }

    var formula string
    newVal := u.cur
    switch u.ope {
    case plus:
        newVal += value
    case minus:
        newVal -= value
    case times:
        newVal *= value
    case divided:
        newVal /= value
    }
    formula = fmt.Sprintf("%f %s %f", u.cur, string(u.ope), value)
    ctx.Log().Debug("calculate: " + formula)
    m := data.Map{
        "value":   data.Float(newVal),
        "formula": data.String(formula),
    }
    if err := w.Write(ctx, core.NewTuple(data.Map{"payload": m})); err != nil {
        return err
    }
    u.cur = newVal
    return nil
}

func (u *udsf) Terminate(ctx *core.Context) error {
    return nil
}

func CreateUDSF(decl udf.UDSFDeclarer, params data.Map) (udf.UDSF, error) {
    inputStream, err := getStreamName(params)
    if err != nil {
        return nil, err
    }

    operator, err := getOperator(params)
    if err != nil {
        return nil, err
    }

    initialValue, err := getInitialValue(params)
    if err != nil {
        return nil, err
    }

    if err := decl.Input(inputStream, nil); err != nil {
        return nil, err
    }

    return &udsf{
        ope: operator,
        cur: initialValue,
    }, nil
}

func getStreamName(params data.Map) (string, error) {
    node, ok := params["stream_name"]
    if !ok {
        return "", fmt.Errorf("stream_name is required")
    }
    streamName, err := data.AsString(node)
    if err != nil {
        return "", fmt.Errorf("stream_name must be a string:%s", err)
    }
    return streamName, nil
}

func getOperator(params data.Map) (operator, error) {
    node, ok := params["operator"]
    if !ok {
        return none, fmt.Errorf("operator is required")
    }
    operatorStr, err := data.AsString(node)
    if err != nil {
        return none, fmt.Errorf("operator must be a string:%s", err)
    }

    switch operatorStr {
    case "plus":
        return plus, nil
    case "minus":
        return minus, nil
    case "times":
        return times, nil
    case "divided":
        return divided, nil
    default:
        return none, fmt.Errorf("invalid oparator")
    }
}

func getInitialValue(params data.Map) (float64, error) {
    initialValue := 0.0
    node, ok := params["initial_value"]
    if !ok {
        return initialValue, nil
    }
    initialValue, err := data.AsFloat(node)
    if err != nil {
        return initialValue, fmt.Errorf("initial_value is invalid")
    }
    return initialValue, nil
}

ソースファイルの登録
pluginディレクトリに、plugin.goを作成し、Source、Sink、 UDSF Operationの登録処理(BQLとして使用するため)を実装します。

image.png

plugin.goは、以下のように作成してみました。

plugin.go
package plugin

import (
    "github.sis.saison.co.jp/sherpa/es-agent/sample"
    "gopkg.in/sensorbee/sensorbee.v0/bql"
    "gopkg.in/sensorbee/sensorbee.v0/bql/udf"
)

func init() {
    bql.MustRegisterGlobalSourceCreator("sample_source", bql.SourceCreatorFunc(sample.CreateSource))
    bql.MustRegisterGlobalSinkCreator("sample_sink", bql.SinkCreatorFunc(sample.CreateSink))
    udf.MustRegisterGlobalUDSFCreator("sample_udsf", udf.MustConvertToUDSFCreator(sample.CreateUDSF))
}

main関数の作成
最後に、単体の実行モジュールとして処理を呼び出すmain関数をexternalディレクトリに、plugin_main.goを作成し実装します。

plugin_main.goは、以下のように作成してみました。

plugin_main.go
package main

import (
    "os"

    "github.sis.saison.co.jp/sherpa/es-agent/external/plugin"
    _ "github.sis.saison.co.jp/sherpa/es-agent/sample/plugin"
)

func main() {
    if err := plugin.NewServer().ListenAndServe(); err != nil {
        os.Exit(1)
    }
}

ここまでで、Runtime側の準備が完了しました。
次に、開発環境側の実装をしていきます。

開発環境側(Java)の実装

RuntimeにStreaming処理をどのように行うかを指示するBQL(SQLライクな文法)の生成を実装します。

環境準備
モジュールディレクトリの作成
$SDK_HOME/dev/sample_adapterディレクトリを作成します。

ファイルのコピー
$SDK_HOME/dev/confディレクトリにある、build.xml、config.propertiesをモジュールディレクトリにコピーします。
$SDK_HOME/dev/conf/build.xml
$SDK_HOME/dev/sample_adapter/build.xml
$SDK_HOME/dev/conf/config.properties
$SDK_HOME/dev/sample_adapter/config.propertites

コピーしたconfig.propertiesファイルを編集します。

Implementation-Title=SampleAdapter
Implementation-Vendor=sugimon
Implementation-Version=0

module.category=Sample
module.label=Sample Plugin
display.name=Sample Plugin Adapter

plugin.name=sample_plugin
esagent.plugin.package=github.sis.saison.co.jp/sherpa/es-agent/sample

ソースファイル用ディレクトリを作成
$SDK_HOME/dev/sample_adapterにsrcディレクトリを作成します。
($SDK_HOME/dev/sample_adapter/src)

  次に作成するjavaファイルのパッケージcom/appresso/ds/dp/modules/adapter/sample
になるように、以下のようなパッケージ用のディレクトリを作成します。
($SDK_HOME/dev/sample_adapter/src/com/appresso/ds/dp/modules/adapter/sample)

ソースファイルの作成
パッケージディレクトリに以下のファイル名でソースファイルを作成します。

・SampleAdapterModuleComponent.java
・SampleSinkOperationFactory.java
・SampleSourceOperationFactory.java
・SampleUDSFOperationFactory.java

├─ sample_adapter
│      │  build.xml
│      │  config.properties
│      ├─ src
│      │   └com
│      │     └appresso
│      │       └ds
│      │         └dp
│      │           └modules
│      │              └adapter
│      │                 └sample
│      │                    SampleAdapterModuleComponent.java
│      │                    SampleSinkOperationFactory.java
│      │                    SampleSourceOperationFactory.java
│      │                    SampleUDSFOperationFactory.java

こちらもそれぞれ、ソースファイルを作成していってみましょう。

・SampleSourceOperationFactory.java(入力処理)
Sourceオペレーションのプロパティを保持するオブジェクトを返したり、オペレーションオブジェクトを返したりします。(継承元クラス:BQLPluginSourceOperationFactoryクラス)

SampleSourceOperationFactory.javaは、以下のように作成してみました。

SampleSourceOperationFactory.java
package com.appresso.ds.dp.modules.adapter.sample;

import com.appresso.ds.common.spi.constraint.NumberFillin;
import com.appresso.ds.common.spi.param.SimpleParameter;
import com.appresso.ds.common.xmlfw.xml.XmlHandler;
import com.appresso.ds.dp.share.adapter.bql.common.BQLPluginSourceOperationFactory;
import com.appresso.ds.dp.spi.OperationConfiguration;
import com.appresso.ds.dp.spi.OperationConfigurator;
import org.xml.sax.SAXException;

import static com.appresso.ds.common.bql.BQLSimpleParameterType.FLOAT;

public class SampleSourceOperationFactory extends BQLPluginSourceOperationFactory {

    @Override
    protected String getLabel() {
        return "Sample source";
    }

    @Override
    protected String getPluginName() {
        return "sample_plugin";
    }

    @Override
    public String getOperationName() {
        return "sample_source";
    }

    @Override
    protected String getTypeName() {
        return "sample_source";
    }

    @Override
    protected void setupOperationConfigurator(OperationConfigurator operationConfigurator) {
        operationConfigurator.addSimpleParameter(createIntervalParameter());
    }

    @Override
    protected void setupOutputSchema(XmlHandler handler, OperationConfiguration conf) throws Exception {
        handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
        writeElement(handler, "value");
        handler.endElement("", "payload", "payload");
    }

    protected void writeElement(XmlHandler handler, String name) throws SAXException {
        handler.startElement("", name, name, EMPTY_ATTRIBUTE);
        handler.endElement("", name, name);
    }

    static SimpleParameter createIntervalParameter() {
        NumberFillin fillin = new NumberFillin();
        fillin.setMinValue(0.001);
        fillin.setMaxValue(1314000);
        fillin.setAllowMin(true);
        fillin.setAllowMax(true);
        fillin.setPrecision(10);
        fillin.setDecimal(3);
        fillin.setAllowDouble(true);
        fillin.setLabel("Interval[sec]");
        fillin.setRequired(true);
        return new SimpleParameter(FLOAT.toParameterKey("interval"), fillin);
    }
}

・SampleSinkOperationFactory.java(出力処理)
Sinkオペレーションのプロパティを保持するオブジェクトを返したり、オペレーションオブジェクトを返したりします。(継承元クラス:BQLPluginSinkOperationFactoryクラス)

SampleSinkOperationFactory.javaは、以下のように作成してみました。

SampleSinkOperationFactory.java
package com.appresso.ds.dp.modules.adapter.sample;

import com.appresso.ds.common.spi.constraint.NumberFillin;
import com.appresso.ds.common.spi.param.SimpleParameter;
import com.appresso.ds.common.xmlfw.xml.XmlHandler;
import com.appresso.ds.dp.share.adapter.bql.common.BQLPluginOutputOperationFactory;
import com.appresso.ds.dp.spi.OperationConfiguration;
import com.appresso.ds.dp.spi.OperationConfigurator;
import com.appresso.ds.dp.spi.OperationContext;
import org.xml.sax.SAXException;

import static com.appresso.ds.common.bql.BQLSimpleParameterType.INTEGER;

public class SampleSinkOperationFactory extends BQLPluginOutputOperationFactory {
    @Override
    protected String getLabel() {
        return "Sample sink";
    }

    @Override
    protected String getPluginName() {
        return "sample_plugin";
    }

    @Override
    public String getOperationName() {
        return "sample_sink";
    }

    @Override
    protected String getTypeName() {
        return "sample_sink";
    }

    @Override
    protected void setupOperationConfigurator(OperationConfigurator operationConfigurator) {
        operationConfigurator.addSimpleParameter(createDecimalParameter());
    }

    protected void setupInputSchema(XmlHandler handler, OperationConfiguration conf, OperationContext context)
            throws Exception {
        handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
        writeElement(handler, "formula");
        writeElement(handler, "value");
        handler.endElement("", "payload", "payload");
    }

    protected void writeElement(XmlHandler handler, String name) throws SAXException {
        handler.startElement("", name, name, EMPTY_ATTRIBUTE);
        handler.endElement("", name, name);
    }

    static SimpleParameter createDecimalParameter() {
        NumberFillin fillin = new NumberFillin();
        fillin.setMinValue(0);
        fillin.setMaxValue(10);
        fillin.setAllowMin(true);
        fillin.setAllowMax(true);
        fillin.setLabel("Decimal");
        fillin.setRequired(true);
        return new SimpleParameter(INTEGER.toParameterKey("decimal"), fillin);
    }
}


・SampleUDSFOperationFactory
UDSFオペレーションのプロパティを保持するオブジェクトを返したり、オペレーションオブジェクトを返したりします。

SampleUDSFOperationFactory.javaは、以下のように作成してみました。

SampleUDSFOperationFactory.java
package com.appresso.ds.dp.modules.adapter.sample;

import com.appresso.ds.common.bql.UDSFFromArgument;
import com.appresso.ds.common.bql.UDSFFromTemplate;
import com.appresso.ds.common.spi.constraint.Item;
import com.appresso.ds.common.spi.constraint.Multi;
import com.appresso.ds.common.spi.constraint.NumberFillin;
import com.appresso.ds.common.spi.param.SimpleParameter;
import com.appresso.ds.common.xmlfw.xml.XmlHandler;
import com.appresso.ds.dp.share.adapter.bql.common.BQLPluginUDSFOperationFactory;
import com.appresso.ds.dp.spi.OperationConfiguration;
import com.appresso.ds.dp.spi.OperationConfigurator;
import com.appresso.ds.dp.spi.OperationContext;
import org.xml.sax.SAXException;

import java.util.stream.Stream;

import static com.appresso.ds.common.bql.BQLSimpleParameterType.FLOAT;
import static com.appresso.ds.common.bql.BQLSimpleParameterType.STRING;

public class SampleUDSFOperationFactory extends BQLPluginUDSFOperationFactory {
    @Override
    protected String getLabel() {
        return "Sample UDSF";
    }

    @Override
    public String getPluginName() {
        return "sample_plugin";
    }

    @Override
    protected String getTypeName() {
        return "sample_udsf";
    }

    @Override
    public String getOperationName() {
        return "sample_udsf";
    }

    @Override
    protected void addArgs(UDSFFromTemplate template) {
        template.addArg(new UDSFFromArgument(STRING.toParameterKey("operator")));
        template.addArg(new UDSFFromArgument(FLOAT.toParameterKey("initial_value")));
    }

    @Override
    protected void setupOperationConfigurator(OperationConfigurator operationConfigurator) {
        setStreamConfigurationParameter(operationConfigurator);
        operationConfigurator.addSimpleParameter(createOperatorParameter());
        operationConfigurator.addSimpleParameter(createInitialValueParameter());
    }

    @Override
    protected void setupInputSchema(XmlHandler handler, OperationConfiguration conf, OperationContext context)
            throws Exception {
        handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
        writeElement(handler, "value");
        handler.endElement("", "payload", "payload");
    }

    @Override
    protected void setupOutputSchema(XmlHandler handler, OperationConfiguration conf, OperationContext context)
            throws Exception {
        handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
        writeElement(handler, "formula");
        writeElement(handler, "value");
        handler.endElement("", "payload", "payload");
    }

    protected void writeElement(XmlHandler handler, String name) throws SAXException {
        handler.startElement("", name, name, EMPTY_ATTRIBUTE);
        handler.endElement("", name, name);
    }

    static SimpleParameter createInitialValueParameter() {
        NumberFillin fillin = new NumberFillin();
        fillin.setPrecision(10);
        fillin.setDecimal(3);
        fillin.setAllowDouble(true);
        fillin.setLabel("Initial value");
        fillin.setRequired(true);
        return new SimpleParameter(FLOAT.toParameterKey("initial_value"), fillin);
    }

    static SimpleParameter createOperatorParameter(){
        Multi multi = new Multi(Operator.getItems());
        multi.setLabel("Operator");
        multi.setRequired(true);
        SimpleParameter param = new SimpleParameter(STRING.toParameterKey("operator"), multi);
        return param;
    }

    enum Operator {
        Plus("+","plus"),
        Minus("-","minus"),
        Times("*","times"),
        Divided("/","divided");

        public String getDisplayName() {
            return displayName;
        }

        public String getValue() {
            return value;
        }

        private final String displayName;

        private final String value;

        private Operator(String displayName, String value) {
            this.displayName = displayName;
            this.value=value;
        }

        Item toItem(){
            return new Item(value,displayName);
        }

        static Item[] getItems(){
            return Stream.of(Operator.values()).map(s->s.toItem()).toArray(Item[]::new);
        }
    }
}

・SampleAdapterModuleComponent
コンポーネント(アダプタ)を表すクラスです。

SampleAdapterModuleComponent.javaは、以下のように作成してみました。

SampleAdapterModuleComponent.java
package com.appresso.ds.dp.modules.adapter.sample;

import java.util.ArrayList;
import java.util.List;

import com.appresso.ds.common.kernel.modules.LicenseManager;
import com.appresso.ds.common.license.LicensePackageType;
import com.appresso.ds.dp.spi.AdapterModuleComponent;
import com.appresso.ds.dp.spi.OperationFactory;
import com.appresso.ds.dp.spi.ResourceFactory;

public class SampleAdapterModuleComponent extends AdapterModuleComponent {

    private static final String MODULE_COMPONENT_NAME = "Sample Adapter";

    @Override
    public OperationFactory[] getOperationFactories() throws Exception {
        List<OperationFactory> operationFactories = new ArrayList<>();
        operationFactories.add(new SampleSourceOperationFactory());
        operationFactories.add(new SampleUDSFOperationFactory());
        operationFactories.add(new SampleSinkOperationFactory());
        return operationFactories.toArray(new OperationFactory[operationFactories.size()]);
    }

    @Override
    public ResourceFactory[] getResourceFactories() throws Exception {
        return new ResourceFactory[]{};
    }

    public void checkLicense() throws Exception {
        LicenseManager licenseManager = getContext().getProxy(LicenseManager.class);
        licenseManager.checkLicense(getModuleComponentName(), getPermittedPackageType());
    }

    private String getModuleComponentName() {
        return MODULE_COMPONENT_NAME;
    }

    private int[] getPermittedPackageType() {
        return new int[]{LicensePackageType.TYPE_BASIC_SERVER};
    }
}

最後に

今回は、実際に実行環境側と開発環境側に分けて処理の実装をしてみました。ここまででプラグインの作成処理が完了しました。次回はこれらをビルドして実行をしてみたいと思います。

このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。

これからも是非チェックいただき、宜しければフォローをお願いします。

それでは、また!

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

HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)

はじめに

こんにちは。よろず相談担当 すぎもん:yum:です。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント(アダプタ)の開発をやってみようと思います。全3部作で完結するよう記載してます。

今回やること

今回は、第1弾としてHULFT IoT EdgeStreaming SDKを使用するためのセットアップ編として環境準備をしてみようと思います。既に環境あるよ~って方は、読み飛ばしていただき下記の【開発編】、【実行編】の記事を参考にしてみてください。

:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)

環境準備

今回、プラグインを作成してみた際に事前に必要となる事項を記載します。
※Windows PCを使用して試しています。

必要なソフトウェア

各ソフトウェアをダウンロードし、インストールします。

ソフトウェア 今回インストールしたバージョン
JDK AdoptOpenJDK 11 (HotSpot)
ビルドツール Apache ant 1.10.0 以降
Golang Go 1.13 以降
EdgeStreaming HULFT IoT EdgeStreaming Ver.2.1.0
SDK HULFT IoT EdgeStreaming-sdk Ver.2.1.0

ちなみにですが、私が試した環境は以下のとおりです。
・JDK(jdk 11.0.6.10 HotSpot)
・ビルドツール(Ant 1.10.7)
・Golang(1.13.7)
・HULFT IoT EdgeStreamingとHULFT IoT EdgeStreaming SDK(2.1.0)

環境変数の設定

各インストールしたソフトウェアの環境変数を設定します。

・JDKのパスを設定します。
JAVA_HOMEに、JDKがインストールされているパスを指定します。
image.png

・ビルドツールのパスを設定します。
ANT_HOMEに、ANTがインストールされているパスを指定します。
image.png

・Golangのパスを設定します。
GOROOTにGolangがインストールされているパスを指定します。image.png

・PATH環境変数の設定
コマンドの実行パスを通すためPATH環境変数を設定します。
%JAVA_HOME%\bin
%ANT_HOME%\bin
%GOROOT%\bin

(設定例)
image.png

コマンドプロンプトから、各ソフトウェアのコマンドを実行し実行パスの確認をします。
java –version
ant –version
go version

(実行結果)
image.png

画像のように、それぞれのバージョンが表示されていれば問題ありません。

HULFT IoT EdgeStreamingのパスを設定

SDKを解凍した先にある「$SDK_HOME\dev\build.properties」ファイルを編集します。
dataspider.homeプロパティに、HULFT IoT EdgeStreaming Ver.2.1.0をインストールしたディレクトリパスを設定します。

######################################################################
# Build Configuration
#
#dataspider.home=<dataspider.server.installed.directory>
#encoding=<file.encoding>
#
######################################################################
dataspider.home=C:\\EdgeStreaming-v210
encoding=UTF-8

image.png

 

ここまでで、プラグイン作成の環境準備が完了しました。
次回以降、実際にプラグインの開発をしていきたいと思います。

最後に

今回は、プラグイン作成前に必要となる環境構築をやってみました。必要となるソフトウェアのセットアップとパスが通れば開発環境の準備が完了です。開発編の方で実装をしてみたいと思います。

このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。

これからも是非チェックいただき、宜しければフォローをお願いします。

それでは、また!

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

Eclipse インストール時に Failed to create the Java Virtual Machine.

はじめに

Mac に Eclipse をインストールしようとしたら "Failed to create the Java Virtual Machine." って怒られました。

install-error.png

インストール時にこんな屈辱を受けたのは初めてだ

もちろん Java は入ってる

結論から言うと、案の定いつものやつ

-vm を ini ファイルに設定するあれだ

手順

最初に注意点

原因がよくわからないが、 ini ファイルを vim で修正するとこんなエラーが出る。

usodaro.png

vim でやる限り、何としてでも "Eclipse Installer.app" をゴミ箱にぶちこんでやらないと気が済まないらしい。

ふざけんな、という気持ちを押し込めつつ、GUI でやるとうまくいきます。

謎い

手順概要

  1. "Eclipse Installer.app" を適当な場所にコピー
  2. Finder で "Eclipse Installer.app" を右クリック > パッケージの内容を表示 > Contents/Eclipse/eclipse-inst.ini をテキストエディタで開く
  3. eclipse-inst.ini に Java8 のパスを設定

1. "Eclipse Installer.app" を適当な場所にコピー

通常 eclipse-inst-mac64.dmg をダウンロードしてクリックすると、eclipse installer が /Volumes/Eclipse Installer にマウントされるはず。

で、その中に "Eclipse Installer.app" があるので、それを適当な場所にコピーする。

ここは GUI でも CLI でもいい。

なお、 /Volumes/Eclipse Installer 以下は Read Only なので、コピーしないと編集できません。

2. Finder で "Eclipse Installer.app" を右クリック > パッケージの内容を表示 > Contents/Eclipse/eclipse-inst.ini をテキストエディタで開く

ここは特に補足説明なし。

前述の通り vim でやらないように

3. eclipse-inst.ini に Java8 のパスを設定

eclipse-inst.ini にこの二行を追加する。

-vm
/Users/tommarute/.jenv/versions/1.8/bin/java

※ もちろん Java のパスはご自身の環境のものを設定してください

なぜ Java8 かというと、自分の環境の最新 (14) だとインストーラーが動かなかったから。

Java9 だと動くのか、とかは試していない。

Java8 がない場合はこんな感じで入れてください。

$ brew tap AdoptOpenJDK/openjdk
$ brew cask install adoptopenjdk8

蛇足ですが、最新の Java が入っている場合は jenv などの管理ツールがあったほうが良いでしょう。

インストーラーを再実行

ini ファイルの修正をしたら Finder から "Eclipse Installer.app" をダブルクリックしよう。

いつもの画面が見れるはず。

installer.png

Thanks,

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