- 投稿日:2020-03-30T20:58:15+09:00
`get()`を使うな ~ 敗北者の Optional
Java の Optional に関する記事を見ると “この教え方では Optional の意味がまるっきりない使い方するやつばかりが生まれるぞ……” というのを数多く見てしまったので,せめてもの反抗にこの記事を書きます。
TL; DR
isPresent()
とget()
しか知らない奴はこういう書き方をしてはいけない
記事を見ていると
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 などないほうがマシである” というようなトンチンカンな記事が書かれてしまうことになる。
NullPointerException
をNoSuchElementException
にすり替えます。プログラムがクラッシュすることには変わりありません。もし、
myOPoint
が実際の座標を保持していない場合、myOPoint.get().x
はNoSuchElementException
を投げてプログラムをクラッシュさせます。これは元のコードから何一つ良くなってはいません。なぜなら、プログラマの目的はすべてのクラッシュを回避することであって、NullPointerException
によるクラッシュだけを回避できれば良いわけではないからです。繰り返しになりますが、コードはどちらも似たようなもので、
Optional
が従来の参照方法よりも勝っているとは言えません。まあ,この記事の筆者もたぶん敗北者が多すぎて呆れてこういう記事を書いたのだろう。
それでもこいつ物知らねえなという記述は散見されるけど。
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 ないんですけどね。
- 投稿日:2020-03-30T18:54:56+09:00
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
当然、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などのユーザコミュニティ/勉強会で有識者に聞いてみることもおすすめです。
- 投稿日:2020-03-30T15:26:30+09:00
java弄ってた時に出たエラー
Not only pythonエラー備忘録 but also java備忘録
メモ
文字列の設定
String型に値を設定する場合は""、char型に値を設定する場合は''を使おう。エラー集
java.lang.IllegalStateException: closed
エラーメッセージ
example.javaexample 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はとっとと変数に格納するのがよさそうね。
- 投稿日:2020-03-30T11:50:52+09:00
ストライクガンダムでオブジェクト指向(java)
はじめに
オブジェクト指向の学習のために作成しました。
ストライクガンダムとストライクダガーの組み立てをイメージし、それぞれの特徴と共通する部分を意識して作成しました。手順
以下の手順で作成しました。
1. インターフェースの作成
→3種のストライカーパックをインターフェースとします。
2. 親クラスの作成
→X100系フレームを親クラスとします。
3. 子クラスの作成
→ストライクガンダムとストライクダガーを子クラスとします。
4. Factoryクラスの作成
→Factoryクラスに出力するための記述を行います。インターフェースの作成
エール、ソード、ランチャーをインターフェースの形で作成し、それぞれの武装を以下のように記述します。
IAile.javapackage practice; public interface IAile { public String WEAPON_Aile[] = {"57mm高エネルギービームライフル", "ビームサーベル", "対ビームシールド"}; }エールストライカー
ISword.javapackage practice; public interface ISword { public String WEAPON_Sword[] = {"15.78m対艦刀「シュベルトゲベール」", "ビームブーメラン「マイダスメッサー」", "ロケットアンカー「パンツァーアイゼン」"}; }ソードストライカー
ILauncher.javapackage practice; public interface ILauncher { public String WEAPON_Launcher[] = {"320mm超高インパルス砲「アグニ」", "120mm対艦バルカン砲", "350mmガンランチャー"}; }ランチャーストライカー
親クラスの作成
ストライクガンダムとストライクダガーはX100系フレームを基本骨格に使用しています。
また、特徴としてストライカーパックシステムという機能があります。
これらの特徴をX100frame.javaに以下のように記述します。
ゲッターも忘れずここで記述します。X100frame.javapackage 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.javapackage 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.javapackage 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.javapackage 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ガンランチャー以上となります。
- 投稿日:2020-03-30T09:56:11+09:00
Activity内に定義したAsyncTaskのネストクラスに警告「This 'AsyncTask' class should be static or leaks might occur」
この記事は、
「非staticネストクラスが握っちゃう、エンクロージングオブジェクトの暗黙的参照」と題した投稿の続きです。
ですので、先にそっちをご覧いただければ幸いです。
そっちの記事は、頭ごなし的に言っちゃえば、「ネストクラスには、static
修飾子を付けろ」です。いきなりカウントアップしだすアプリを作ってみた、ら...
起動したとたんに、勝手に1秒刻みでカウントアップが始まります。
START!から始まったら「9」まで数え上げたらお終いです。
(このアニメーションGIFは、3まで至ったらまたSTART!を繰り返しているアニメーションですが、このQiitaに貼り付けたアニメーション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.javapublic 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がこんな警告を出すんです。
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メンバーにのみアクセス可能 だからです。
だったら、
MainActivityをこう修正すればイイじゃない?という安易な気持ちでpublic class MainActivity extends AppCompatActivity { private static TextView textView; // 以下略 }と修正するのは、否、それはチョット違うんでないかい?と気が咎めるのです。
我ながらひどいコードを書いたもんだ
わざとらしいのですが、まあ見てやってください。でも、こんなんで、Android Studioの例の警告は消えます。
MainActivityDasai.javapublic 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
は、別クラスに分けるか。でもそんなことをしたら、今はこのTextTview
1個っきりだからまあいいけど、他にもいろんな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から非推奨になります。
この記事も、廃れる運命にあるわけです...。代替策は、
- java.util.concurrentパッケージをスタンダードに使うか、
- Kotlin concurrency utilitiesを使ってね。
とのことです。後者は、要はKotlinのCoroutinesのことです。
APIレベルは数字で示されるものなのに、この記事執筆時点では「R」となってます。 ↩
- 投稿日:2020-03-30T09:25:51+09:00
HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)
はじめに
こんにちは。よろず相談担当 すぎもんです。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント(アダプタ)の開発をやってみようと思います。全3部作で完結するよう記載してます。今回やること
今回は、第3弾としてHULFT IoT EdgeStreaming Plugin SDKで開発したプラグインのビルドと実行をしてみようと思います。第1弾、第2弾の記事は、下記にありますので参考にしてみてください。
HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)プラグインのビルド
今回、作成したプラグインをビルドしてみます。※Windows PCを使用して試しています。
なお、
$SDK_HOMEは、EdgeStreaming SDKホームディレクトリを指します。
$DATASPIDER_HOMEは、EdgeStreamingのホームディレクトリを指します。ビルド
プラグインのビルドは、
$SDK_HOME/dev/sample_adapter
から以下のコマンドを実行します。
$ ant
コマンド実行後、
BUILD SUCCESSFUL
と出力され、下記のファイル類が作成されていればビルドは成功です。・
$SDK_HOME/dev/sample_adapter/build
sample_adapter.jar
・$SDK_HOME/dev/sample_adapter/build/plugin/sample_plugin
各ターゲット向けのgoの実行ファイルアイコンファイルのコピー
実際に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
ファイルで定義されているプロパティを基に作成されています。
プラグインのインストール
アダプタのインストールは
$SDK_HOME/dev/sample_adapter
からantコマンドの第一引数にinstallターゲットを指定して実行します。
$ ant install
ant installを実行後、下記のようにインストールがされます。
・Javaモジュールがインストールされます。
$DATASPIDER_HOME/server/plugin/data_processing/modules
配下・Golangモジュールがインストールされます。
$DATASPIDER_HOME/server/es-agent/plugin
配下※インストールしたプラグインを有効にするには、EdgeStreaming Studioの再起動が必要です。
実行してみましょう
それでは、作成したプラグインの実行をしてみましょう。
EdgeStreamingの起動
・EdgeStreamingを起動します。
・作成したプラグインを確認してみましょう。
新規プロジェクトから、プロジェクトを作成します。
プラグインの確認
作成したプラグインが、「ツールパレット」にできていることを確認します。
「ツールパレット」の「Sample」配下に、それぞれ以下のプラグインができていると思います。簡単な処理を作成してみましょう
・入力処理(一定時間間隔で疑似乱数を生成するオペレーション)
Source Operationで作成した処理を配置します。
ツールパレットのSampleからSample sourceをドラッグ&ドロップで中央へ移動させます。
※Intervalには、取得する間隔を指定してください。・出力処理(有効小数点桁数で切り捨ててログに出力するオペレーション)
Sample Sinkで作成した処理を配置します。
ツールパレットのSampleからSample sinkをドラッグ&ドロップで中央へ移動させます。
※Decimalには、任意の有効小数点桁数を指定してください。マッピング処理
入力処理アイコンをドラッグ&ドロップして、出力処理までアイコンをつなげます。
つなげた線を右クリックし、マッピングの追加を選択します。
・入力元の「value」と出力先の「value」をつなげます。
・出力先の「formula」にツールパレット->文字列->基本->文字列定数を配置し、
任意の文字列を指定し、「formula」につなげます。
実行してみましょう
画面メニューの緑矢印から作成したスクリプトを実行してみましょう。
疑似乱数が指定したIntervalごとに生成され、指定した小数点桁数で値が出力されることが確認できると思います。
また、作成したUDSFのオペレーションアイコンを使用することで、取得した値に対して四則演算ができますので、
組み合わせて、いろいろと試してみてください。最後に
3部作の最終編として、今回はプラグインのビルドから実行までやってみました。
このようにSDKを使用してプラグインの処理を実装することが可能です。本記事がプラグイン開発の道しるべになれたら幸いです。このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。
これからも是非チェックいただき、宜しければフォローをお願いします。
それでは、また!
- 投稿日:2020-03-30T09:25:37+09:00
HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
はじめに
こんにちは。よろず相談担当 すぎもんです。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント(アダプタ)の開発をやってみようと思います。全3部作で完結するよう記載してます。今回やること
今回は、第2弾としてHULFT IoT EdgeStreaming Plugin SDKを使用してプラグインを開発してみます。他にセットアップ編、実行編の記事もあるので是非参考にしてみてください。
HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)プラグインの作成概要
EdgeStreamingのプラグイン作成の構成は、以下のようになります。
実行環境構成
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文)を出力します。プラグインの作成
今回は、以下のような形でプラグイン作成をしてみました。
実行環境側(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.gopackage 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.gopackage 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.gopackage 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として使用するため)を実装します。plugin.goは、以下のように作成してみました。
plugin.gopackage 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.gopackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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}; } }最後に
今回は、実際に実行環境側と開発環境側に分けて処理の実装をしてみました。ここまででプラグインの作成処理が完了しました。次回はこれらをビルドして実行をしてみたいと思います。
このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。
これからも是非チェックいただき、宜しければフォローをお願いします。
それでは、また!
- 投稿日:2020-03-30T09:25:23+09:00
HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
はじめに
こんにちは。よろず相談担当 すぎもんです。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント(アダプタ)の開発をやってみようと思います。全3部作で完結するよう記載してます。今回やること
今回は、第1弾としてHULFT IoT EdgeStreaming SDKを使用するためのセットアップ編として環境準備をしてみようと思います。既に環境あるよ~って方は、読み飛ばしていただき下記の【開発編】、【実行編】の記事を参考にしてみてください。
HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)
HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
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がインストールされているパスを指定します。
・ビルドツールのパスを設定します。
ANT_HOMEに、ANTがインストールされているパスを指定します。
・Golangのパスを設定します。
GOROOTにGolangがインストールされているパスを指定します。・PATH環境変数の設定
コマンドの実行パスを通すためPATH環境変数を設定します。
%JAVA_HOME%\bin
%ANT_HOME%\bin
%GOROOT%\bin
コマンドプロンプトから、各ソフトウェアのコマンドを実行し実行パスの確認をします。
java –version
ant –version
go version
画像のように、それぞれのバージョンが表示されていれば問題ありません。
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
ここまでで、プラグイン作成の環境準備が完了しました。
次回以降、実際にプラグインの開発をしていきたいと思います。最後に
今回は、プラグイン作成前に必要となる環境構築をやってみました。必要となるソフトウェアのセットアップとパスが通れば開発環境の準備が完了です。開発編の方で実装をしてみたいと思います。
このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。
これからも是非チェックいただき、宜しければフォローをお願いします。
それでは、また!
- 投稿日:2020-03-30T01:30:24+09:00
Eclipse インストール時に Failed to create the Java Virtual Machine.
はじめに
Mac に Eclipse をインストールしようとしたら "Failed to create the Java Virtual Machine." って怒られました。
インストール時にこんな屈辱を受けたのは初めてだ
もちろん Java は入ってる
結論から言うと、案の定いつものやつ
-vm
を ini ファイルに設定するあれだ手順
最初に注意点
原因がよくわからないが、 ini ファイルを vim で修正するとこんなエラーが出る。
vim でやる限り、何としてでも "Eclipse Installer.app" をゴミ箱にぶちこんでやらないと気が済まないらしい。
ふざけんな、という気持ちを押し込めつつ、GUI でやるとうまくいきます。
謎い
手順概要
- "Eclipse Installer.app" を適当な場所にコピー
- Finder で "Eclipse Installer.app" を右クリック > パッケージの内容を表示 >
Contents/Eclipse/eclipse-inst.ini
をテキストエディタで開く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" をダブルクリックしよう。
いつもの画面が見れるはず。
Thanks,