ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

2022年3月12日

[kotlin] Windowsでコンパイル

最近、話が長くなっていかん。
なので、先にまとめた内容を書いて、あとにだらだらと書き綴ろう。

 

まとめ

  • kotlinc を初回実行するときには結構な量のダウンロードが行われる
  • Kotlin の Array<E> は配列、ArrayList<E> は List<E> の仲間
  • ArrayList の初期値ありは arrayListOf() で作ればよい
  • ArrayList<Byte> を ByteArray に変換するには .toByteArray() を使えばよさそうだ

 


おはなし

 

Android で Kotlin の勉強しつつやっているのだが、勉強のために Android アプリを作るのはめんどくさい。
PC 上で動かせるようにしよう。

Kotlin command-line compiler | Kotlin
https://kotlinlang.org/docs/command-line.html

GitHub から一式をダウンロード(今の最新は 1.6.10) して、zip を展開。
あんまりファイルが大きくなくて良かった。

Hello World の ktファイルを作ってコンパイル。

>bin\kotlinc.bat test.kt

うわー、ダウンロードが始まった・・・。
しかも結構かかる・・・。

最終的に、 %USERPROFILE%.konan に 1GB くらいダウンロードされた。
JavaScript に対する TypeScript みたいな関係だと思っていたけど、Windowsネイティブなコードを吐くようにしているからだろうか?

まあいい。
深くは考えまい。

なお、コンパイルすると program.exe という実行ファイルができた。


初回だし、次回やるかも分からんので、気になっていることを調べて終わろう。
やりたいのは、Array<Byte> でもらったデータを ByteArray に変換したい、だ。

それぞれ違いはなんじゃ?と調べてみたのだが、

  • ArrayList<Byte> は Javaでいうところの Byte[]
  • ByteArray は Javaでいうところの byte[]

らしい。

java - Difference between ByteArray and Array<Byte> in kotlin - Stack Overflow
https://stackoverflow.com/questions/9457942/difference-between-bytearray-and-arraybyte-in-kotlin

もしかしたら ArrayList<Byte> かもしれんが、ともかくオブジェクトかプリミティブ型かの違いが大きいということだろう。
Kotlin は「[]」みたいな配列の作り方ではなく「Array<T>」のような形でしか書けないので、T がプリミティブ型だと効率が悪いだろうってことで ByteArray 型が別に用意されているんだと思う。

 

変換したい背景をもう少し話すと、React Native 側のデータが Uint8Array 型で、それを Android 側で処理することになった。
React Native と Android の型は異なるので、この型はこうなるというマッピングがある。

Argument Types
https://reactnative.dev/docs/native-modules-android#argument-types

JavaScript の Uint8Array は Array だろうから、Java だと ReadableArray というものになる。
ReadableArray は React Native で提供される型でこうなっている。

react-native/ReadableArray.java at main · facebook/react-native
https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java

この中で一番 ByteArray に近そうなのはこれだろう。

ArrayList<Object> toArrayList();

・・・と思ったのだが、ArrayList<Object> なので ArrayList<Byte> ではない。
これは as で変換できてほしい(願望)。

そして ArrayList<Byte> を ByteArray に変換したい、というところなのだ。
いや、それは正確ではないな。実際にやりたいのはファイル保存なのだ。 ByteArrayInputStream というクラスがあったので ByteArray に変換したいだけであって、InputStream になるなら ByteArray である必要はないし、ファイルに保存できるなら InputStream である必要もない。

ノウハウがないときは近道が分からないから困りますな。


Kotlin の ArrayList には toByteArray() というメソッドがある。
これでいいのかな?

toByteArray - Kotlin Programming Language
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/to-byte-array.html

 

では、まず ArrayList を作ろう。
・・・書き方が分からん。
Kotlin は ArrayList<T> しかないと思っていたのだが、 Array<T> もあるのか。

Arrays
https://kotlinlang.org/docs/basic-types.html#arrays

Array なら arrayOf() で初期値が作れそうなのだが、 ArrayList は?
そもそも、 Array と ArrayList の違いは何なのか??

 

ArrayList はこんな感じなので、MutableList の仲間。

class ArrayList<E> : MutableList<E>, RandomAccess

class ArrayList<E> : MutableList<E>, RandomAccess, AbstractMutableList<E>

List を Array っぽく使えるようにしただけで、あまり Array と関係はないのだろう。
なので初期化も ArrayList のコンストラクタを使えばよかろう。

 

ArrayList のコンストラクタは 2つ。
空の ArrayList を作るか Collection<E> で初期値を持つか。
Collection に arrayListOf() という関数があるそうだ。

arrayListOf - Kotlin Programming Language
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/array-list-of.html

いやー、これは ArrayList の方にもリンクを貼っておいてほしいよ。わからんよ、探したよ。
全体的に~Of() という関数は ~型 の new関数みたいに作ってあるように見える。

 

01: fun main() {
02:     // val va: ArrayList<Byte> = arrayListOf(1, 2, 3, 253, 254, 255)
03:     val va: ArrayList<Byte> = arrayListOf(1, 2, 3, -3, -2, -1)
04:     val v8: ByteArray = va.toByteArray()
05:     for (v in v8) {
06:         println(v)
07:     }
08: }

>.\program
1
2
3
-3
-2
-1

Java と同じく Byte 型は int8 の範囲なので 128~255 はダメだった。
まあ、問題はそれだけだ。

2018年7月9日

[java]64bit値を扱うのにBigIntegerもある

Cからもらったunsignedな64bit値をビット演算するだけでよいかと思っていたら、数値としても扱う箇所があるのに気付いた。

うーん、uint64_tはプリミティブだから、Javaもlongで受け取って、中で数値に変換できないものか。。。


調べていると、BigIntegerというものがあった。
これを使って、引数はlongで受取り、中で16進数文字列に置き換え、それをBigIntegerにすればよさそうだ。

https://gist.github.com/hirokuma/f365c2a85dae3962ff3d5f19562b9029/a0e48bef4148691ce3fcc135c9911662ce21afc3

結果

long: -81985529216486896
long 2byte: -2
BigInteger: 18364758544493064720
BigInteger 2byte: 254

意味も無くビットシフトなんかさせてみたが、ちゃんと正の数として扱っている。

文字列は好きではないのだが、C側でやらなくてよいので、まあよかろう。

2018年7月7日

[java]uint64_t相当のデータをビット演算したい

JNIを使って、CとJavaをつなごうとしている。
C側でuint64_tを使っている箇所があるので、Javaで何とかしなくてはならん。


Javaのlongは64bitなのだが、Javaにはunsignedがないので、たぶん最上位ビットが立ってたら負の数と思うだろう。
byteもそうだったし。

確認しておくべし。


longだけではなく、BigDecimalも使ってみよう。

https://gist.github.com/hirokuma/c8a67b1db8e44ce45236942088d81865/1aaa1e1893b3f83f9d1d14d494b1d4902da454ee

同じようなことをさせたいが、BigDecimalにビットシフトがないので、割り算で代用。
処理が遅くなりそうだが、まあ動作確認だからな。

結果

big decimal 2byte: fe
long 2byte: fffffffffffffffe

やはり、そうなるのか。
BigDecimalは演算してるから2byte分きっちりになったが、longはビットシフトで符号が引きずられたんだ。


そういえば、Javaには「>>>」があったな。

https://gist.github.com/hirokuma/c8a67b1db8e44ce45236942088d81865/0161393d1d388b1ad97754dbc84d3efb2eff8370

結果

big decimal 2byte: fe
long 2byte: fe

おお!
なんか、うれしくなりますな。


これはHEXで出しているから、変数として正負どちらになっているかわからん。
数字で出力させて確認。

BigDecimal : 正
longで>>:負
longで>>>:正

まあ、予想通りか。
当然だが、これをbyte型にキャストして代入すると、どれも負の数になる。

2018年6月9日

[intellij]gitignoreをどうするのか(2018/06/09)

3箇所にあるPCで、Javaの開発・・・・というほどでもないことをやっている。
どのPCで作業するかわからないので、GitHubにプロジェクトを突っ込んでいるだけだ。

2台はIntelliJ、1台はAndroid Studioを使っている。
Android Studioである必要はないのだが、IntelliJとAndroid Studioをわざわざインストールするのもなぁ、ということで、試しにやっている程度だ。


C/C++のイメージでやっていたので、ソースファイルをgitに載せていればなんとかなるんだろう、と思っていたのだが、そうではないらしい。
とにかく、どの環境で作業しても、しょっぱなからgitに差分があるのだ。
ソースファイルだったらよいのだけど、設定ファイルが変わっている。
変わるというよりは、ファイルごと削除になっていることが多い。
作業するディレクトリが違うからかと思ったのだが、どうもそれだけでもなさそう。


腹が立ってきたので、調べることにした。


まずは、直近の記事を検索。

JetBrains IDE(IntelliJ IDEAなど)を使う時のgitignoreについて
https://qiita.com/mokrai/items/1f668dd0d1bee15b56f0

記事の中のリンクに、JetBrainsの記事がある。
Updatedが2018/06/08なので、定期的に更新されるページなのだろう。


「.idea」というディレクトリがあるかどうかで、対応が変わるらしい。
うちのにはあるので、そっちを読む。

  • .ideaディレクトリの中の全部だけど、workspace.xmlとtasks.xmlは除外する
  • 拡張子が.imlのもの(IntelliJ IDEAが反映したもの : applies to IntelliJ IDEA)

意外と、シンプルな方針だ。
Qiitaの人が読んだときと、まだ変わっていないようだ。


それ以外に「Be careful」と「not to shared」も書いてある。
「Be careful」はパスワード関連のようだからよいとして、「not to shared」だ。

  • 拡張子が.imlのもの(GradleやMavenがベースになったプロジェクト)
  • gradle.xml
  • dictionaries
  • .idea/libraries以下にある拡張子が.xmlのファイル(GradleやMavenが生成したもの)


まてやー!
.imlでも、共有したりしなかったりがあるのかぁぁぁぁ。


これ、ちょっと、無理じゃないかい?
今の時点でGitHubのJetBrains gitignore例は2018/May/10で、JetBrainsの記事はJune/08だから、まだそっちには反映されていないのかもしれないし、今のままでもよいのかもしれない(QAを全部読む気力は無いのだ)。
読み間違えているかもしれんので、画像を残しておこう。


image


で、だ。


いま、別の場所のIntelliJで作業して、commit / pushしたものを、また別の場所で開いている。
こちらもIntelliJだ。

.idea/libraries/*.xmlは、書いてあるとおりに除外されている。
除外というか、削除されたことになっている。
.gitignoreの「.idea/**/libraries」が効いているのだろうか?
いや、私はその行を削除していたな。


ただ、JetBrainsが言う通りに共有されていないにしても、どうしてよいのか困る。
プロジェクトの設定でDependenciesを見ると、そいつらが名前だけ載っていて、赤文字になっているのだ。
いかにも「そのファイルはありません」みたいな感じで、ビルドも失敗する。
復活させればビルドできるから、その行をコメントアウトしたこと自体がダメだったのか。


なんとなくだが、変なことをしていなければ、Mavenだか何だか知らないが、持っていないライブラリは勝手にダウンロードしてくれているようだ。
プロジェクトを開いたときに妙に時間がかかることがあるから、あれはそうなんだろう。



今月はまだJavaを扱わないといけないようだから、急いで結論を出すことはあるまい。

[java][ubuntu]bitcoinjをコンソールでビルドしてみよう

bitcoinjをビルドしようとした。
環境としてJDKはインストールされていて、Android Studioもあったので、それからできるだろうと思った。

が、protobufのコンパイラが入っていないので、途中でエラーになってしまった。
Windows向けのprotocもありそうだったが、そこまでしてビルドしたいという気分でもない(なんだそりゃ?)。

READMEを見ると、Gradleでビルドするのが普通のようなのだ。
かといって、Windows環境にGradleだけインストールしたいという気分でもない。
Android Studioのところからパスを通せば良いのだろうが、結局はprotobufがいるだろう。


ごにょごにょ書いたが、Azure上にUbuntu 16.04 VMを立てているので、そっちでビルドできるようにしておいた方が後々いいんじゃなかろうかということで、やってみよう。


Gradleのインストール方法を見ると、SDKMAN!というものをインストールするのが楽らしい。
うちのUbuntuにzipやunzipが入っていなかったので、curlするスクリプトがエラーを返してくれた。
足りないものを入れていけばインストールできるだろう。

sudo apt install openjdk-8-jdk-headless zip unzip (他にいるもの...)
curl -s "https://get.sdkman.io" | bash
source "/home/xxx/.sdkman/bin/sdkman-init.sh"
sdk install gradle 4.8


protobufはaptでもいけそうだが、後々使いそうな気もするので、今の時点で最新のv3.5.1から持ってこよう。

mkdir protobuf
cd protobuf
wget https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip
unzip protoc-3.5.1-linux-x86_64.zip
export PATH=$HOME/protobuf/bin


protocにパスが通っているのを確認したら、bitcoinjをビルドしよう。

git clone https://github.com/bitcoinj/bitcoinj.git
cd bitcoinj
gradle clean build
....

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':wallettemplate:compileJava'.
> Compilation failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1m 56s
24 actionable tasks: 21 executed, 3 up-to-date


エラーで終わってしまった。。。
が、findでjarファイルを探すとできていたから、いいや。


これを書いている時点での最新リリースバージョンはv0.14.7だが、masterはそれよりも進んでいる。
構成に変更があるようで、例えばorg.bitcoinj.core.ScriptExceptionがorg.bitcoinj.script.ScriptExceptionになっていた。

まあ、私も「使い始めました」という程度なので、何がどう変わったのかという以前に、使い方を覚えねば。。。
しかし、ExamplesのawaitRunning()が動かないので、最新版は私には無理かもしれんな。

・・・guavaのバージョンが古かっただけみたい。
しくみが、しくみが分かりません。。。

2018年6月2日

[bitcoij]IntelliJでbitcoinjを使ってみよう

「唐突」の「唐」は、隋の次の唐だろうか?
隋の時代が長いこと続いていたのに、いきなり唐の時代に突入したとか、そんなことだろうか。


というわけで、今回は唐突にbitcoinjを使ってみようと思う。
どちらかといえば、IntelliJの設定方法を忘れそうなので、それをメモとして残しておきたいだけなのだがね。


まず、IntelliJと、JDK8をインストールする。
環境変数JAVA_HOMEなんかも設定しないといかんのかもしれんな。
今回は、jdk 1.8.0 172だ。


IntelliJの設定も、適当にやっておこう。
うちのWindows環境では年に数回しかIntelliJを起動しないので、起動するたびに最新版へのアップデートがいる。
今回は、2018.1だ。



当然、IntelliJの使い方も把握していないのだが、雰囲気でやっておこう。

まず、新規でプロジェクトを作る。
テンプレートからコンソールを選択しておく。


本題であるbitcoinjを追加。
どうやるのが簡単なのか分からんが、「File > Project Structure ...」でModulesを選び、Dependenciesタブから追加した。

  1. 右側の「+」をクリック
  2. 「2. Library...」「From Maven...」
  3. 出てきたダイアログから検索できるので、bitcoinj-coreを検索して、追加
  4. 同じようにして、slf4j-nopを検索して、追加

image

slf4j-nopではなく、slf4j-simpleでもよい。
デバッグ時にはそっちの方が良いのかもしれん。
両方置いておくと、このリストで上にある方が選ばれるようだ。
ただ「こっちを選んだ」みたいなログが出てうっとうしいので、片方だけ見えるようにしておいた方が良かろう。
Scopeを「Test」にしたのは、後でまた検索し直すのが面倒だったので、リストに置いておくためだ。


最後に、適当にコードを書いて実行。
というと、がんばって調べたように見えるが、こちらを読んだだけだ。

bitcoinjの入門 - Bitcoin blog
http://adrenaline2017.hatenablog.com/entry/2017/11/23/000000


オリジナルは、本家のサイトだろうが、日本語になっているだけでありがたい。

Getting started in Java
https://bitcoinj.github.io/getting-started-java

01: package com.hiro99ma.blogpost.bitcoinj_test01; 02: 03: import org.bitcoinj.core.Address; 04: import org.bitcoinj.core.ECKey; 05: import org.bitcoinj.core.NetworkParameters; 06: import org.bitcoinj.kits.WalletAppKit; 07: import org.bitcoinj.params.TestNet3Params; 08: 09: import java.io.File; 10: 11: class BitcoinjTest { 12: private NetworkParameters params; 13: private WalletAppKit wak; 14: 15: BitcoinjTest() { 16: params = TestNet3Params.get(); 17: wak = new WalletAppKit(params, new File("./wallet"), "wak") { 18: @Override 19: protected void onSetupCompleted() { 20: if (wallet().getKeyChainGroupSize() < 1) { 21: wallet().importKey(new ECKey()); 22: } 23: } 24: }; 25: wak.startAsync(); 26: } 27: 28: void exec() { 29: wak.awaitRunning(); 30: Address addr = wak.wallet().currentReceiveAddress(); 31: System.out.println("addr = " + addr.toString()); 32: } 33: } 34: 35: public class Main { 36: public static void main(String[] args) { 37: BitcoinjTest bjt = new BitcoinjTest(); 38: bjt.exec(); 39: } 40: }


着金アドレスをコンソールに出力させるだけだ。
あれこれやりたかったら、Examplesを見るのが早いだろう。
IntelliJの設定が分かったので、私は満足した。。。

2017年10月27日

[android][nfc]JPKIのASN.1を読む

OpenSCに入っていた、JPKを読込む部分を参考にして、例のカードの総合認証証明書プロファイルのASN.1ダンプを表示させるアプリをCSharpで作っていたものを、Androidでも動くようにした。

https://github.com/hirokuma/AndroidReadJpk/tree/8b68a801b254368f6a6a884ad843cd965aa5441f


起動させると、アプリのタイトルが「Read JPK PubKey」になっているが、あまり気にしないでおくれ。。。
それと、何が出力されているのかは聞かないでおくれ。。。

内容についてはよく知らんのだ。
暗証番号などを入力せずに読めるから、きっと公開鍵かなんかだろうと思ったんだけど、これはたぶん証明書にあたるものだ。
この中のどこかに、公開鍵が入っているんじゃなかろうかね?


元ネタは、こちらだ。
マイナンバーカードでSSHする - AAA Blog

元ネタの方では、2番目にSELECT FILEするのが0x0018になっているが、暗証番号を打ち込まないと読めないやつは怖いので、認証証明書というやつを読込むことにしたのだった。
データ構造はこれになるのかな?
https://www.jpki.go.jp/ca/pdf/cross_certificate_profile.pdf

このアプリではAPIを使ってダンプさせているため、文字列以外は中身が出てきていないようだ。
応用する方法が思いつけば何か作ろうかと思ったが・・・まあ、そういうのは今回は無しだな。

2017年10月26日

[java]ASN.1をデコードするライブラリを探す

急に、AndroidでASN.1をデコードしたくなった。
人生は唐突だ。


OpenSSLのコマンドでダンプ出来た気がするのだが、どうもOpenSSLではASN.1の一部しかサポートしていないらしい。
今回読みたいデータがどうなのかわからないけど、二度手間は避けたいところだ。
https://stackoverflow.com/questions/5964269/asn-1-encoding-decoding

Androidの標準にはないような気がするので、Javaで検索した。
Parsing ASN.1 binary data with Java - Stack Overflow

ASN1InputStreamというclassがあるらしい。
親切にサンプルソースへのリンクも載っていて、org.bouncycastle.asn1たちをimportしている。


ここまでは分かったのだが、Android StudioにASN1InputStreamと書いても、いつものようにAlt+Enterで自動的に対処してくれなかった。
ということは、外部JARなどを追加しないといけないのだろう。

それはわかるのだが、これからどうしたらよいのだろう?


こちらが本家だと思われる。
The Legion of the Bouncy Castle Java Cryptography APIs

latest releasesページにJARファイルがあるので、それを使えばよいのか。


それはそれでよさそうだが、StackOverflowに貼ってあったサンプルソースのリンク先を見てみると、ディレクトリ構造?の先頭が「Maven-Central」になっている。
そういえば、pyCharmなんかでは、ライブラリを検索して追加する機能があったけれども、AndroidStudioにも同じようなものがあるかもしれない。

これか。

image

「bouncycastle」で検索すると、大量に出てきた・・・。
たぶん、mavenのこんなページと同じ感じで出てきているんではなかろうか。
https://mvnrepository.com/artifact/org.bouncycastle

bouncycastleのドキュメントページのこちらにasn.1があったから、今回は「provider」というやつを使えばよいのだろう。
latest releasesでいえばこれか。


ただ、ここまでわかっても、まだDependencyのところで絞り込み方がわからん。。。
「org.bouncycastle:bcprov-jdk15on:1.58」でよい気がするのだが、自信はない。
とりあえず追加してやると、ASN1InputStreamをAlt+Enterでimportしてくれたが、それでも自信が持てない。。。

2016年8月4日

[java?]ライブラリを作るときはRuntimeExceptionに気を配ろう

考えたらわかるだろう?と言うようなことが、ときどき、しばしば、あると思う。
自分から言ったり、相手から言われたり。

 

Androidのライブラリを作るお仕事をしているのだが、今日、バグが見つかった。
ライブラリに作ったAPIをアプリが呼んで、そのAPI内で例外が発生すると、状態が残ったままになる、というものだ。

APIでは、呼ばれたときにフラグを変化させて、終わると戻すようにしていたのだ。
だから、APIが「終わる」ということにならないと、フラグが戻らない。

そのことはわかっていたから、メソッドを抜けるルートにフラグを戻す処理を書いていたし、IDEが「このexceptionどうする?」と尋ねれば、捕捉するようにしていた。

 

しかし・・・RuntimeExcetpionが考慮から漏れていた・・・。
Android Studioが賢いのか言語仕様が賢いのかわからんけど、Exceptionをcatchしなかったら警告が出てくれるじゃないか。
あれは、全部チェックしていたのだ。

でも、RuntimeExceptionはそういう対象ではないから、そういう例外が発生することを想定すらしていなかったのですわ。
これがアプリであれば、catchしてなかったら死ぬし、catchしたら処理すれば良いだけだ。
しかしライブラリの場合、自分でスルーしてアプリでcatchされて継続された場合、自分が中断されて状態がおかしくなっていることに気付かないことになってしまうのだ。

 

なので、RuntimeExceptionはIDEなどが検出できないからcatchしなくても気付かないのもわかるし、APIのreturnルートに終了処理を組み込んでいるからreturnしないとダメというのもわかっているのだけど、両方が重なると「あれ??」となります。

言われてみればわかるのだけど、ライブラリってどう使われるかよくわからないから、そういう作りにしているところは「Exception」でcatchせざるを得ない。
「RuntimeException」で済むのかもしれないけど、なんかもう面倒だし。

 

あんまり例外のある言語を使ってないから。。。という言い訳をしたいのだけど、あまり意味がないから止めよう。

2016年8月2日

[java]@Overrideは、正しければ書かなくても怒られない

「Androidとも、しばらくこれでお別れか、ふっ」となることを期待していたのだが、ちゃんと動かない。。。

悪魔の証明じゃないけど、この問題は私が作った部分には絶対に関係していません!というのを証明するのは、なかなか難しいことですな。
お仕事の発注元の人に期待大なのだが、こちらもやれることはやっておきたいし、どちらかといえば、こういうのが「ノウハウ」になってしまうのだな。


さて、それはともかく。

自分で書いたコードを見直していて、見覚えのないメソッドが出てきた。
そのファイル内では使っていない。
見覚えがないだけで、自分しか触っていないし、自分が作ったファイルだから、覚えがないだけで自分が作ったことに間違いは無い。
呼ばれないのに、なぜ。。。。

と深刻になるまでもなく、基底classをextendsしているメソッドだった。
@Override、と書いておけば気付いたのだろうが、書いていなかったのだな。
書いておくと、違っていれば、Android Studioは警告してくれるようだ。

しかし、書いていなければ、比較のしようがない。
それにそのメソッドは、abstractじゃなくて、デフォルト実装を書いていたから、判断のしようがない。

うまく言えないが、一方通行の道路をその方向に進んでいるから、一方通行かどうか知らなかったとしても問題がない、みたいなものか。
いや、うちの前は一方通行なのだけど、道が太いから逆走するのが多くてね。。

2016年7月12日

[java]非同期に翻弄される

人間、いろいろあると、不信感に襲われてしまいますな、いやいや。

状態変数のチェックをメソッドの始まりで2回やって、そこまでチェックをくぐり抜けたら処理する、とやってたのだけど、すり抜けるのをはじめて目にしてしまってね。
もう何日もテストして無事だったのに、なんでいまさらーっ?

 

タイトルからわかるように、非同期で状態変数を変更する処理が呼ばれていたのですな。

メソッド() {
    if (状態変数のチェック1) {
        return 成功でよい;
    }
    //★
    if (状態変数のチェック2) {
        return 失敗と見なす;
    }
    実行();
}

別メソッド(setVal) {
    状態変数 = setVal;
}

★のところでディスパッチして、別メソッドが呼ばれて、チェック1で引っかかるような値が代入されてしまったのですよ。
チェック1で引っかかる値はチェック2でも引っかかるので、先にチェック1で見て、既にこの状態だったらOK、ということだったのだ。

いやいや、そのタイミングでディスパッチできるとは、やりますな。


とまあ、教科書にあるような失敗例だったので、教科書にあるような対処法で済む。
Javaにはsynchronizedという便利なものがあるので、それを使えばよいだろう。

例では短く書いているけど、本当はもっと長くて、メソッド自体をsynchronizedしてしまうとデッドロックしてしまうのだ。
なので、「実行()」の手前までディスパッチを禁止してくれれば良いのだ。

ただ、そんなにひどいことをさせられないのか、synchronized(オブジェクト)、みたいな書き方なのですな。
「このオブジェクトに掛けて、排他してやる!」みたいな意気込みになっているように思える。

finalじゃないといかんようだ。
まあ、どんどんオブジェクトが変わってしまうと排他にならないからか。

 

final Object lockObj = new Object();

メソッド() {
  synchronized(lockObj) {
    if (状態変数のチェック1) {
        return 成功でよい;
    }
    //★
    if (状態変数のチェック2) {
        return 失敗と見なす;
    }
  }
  実行();
}

別メソッド(setVal) {
  synchronized(lockObj) {
    状態変数 = setVal;
 
}
}


で、問題はどちらかというとこれからで、本当にこの対策で現象が出なくなるのか?だ。
実は別の場所の方に問題があって、これではだめなのかもしれない。

しかし、再現性が元々かなり低いので、自然に発生するのを待つことはできないだろう。
どうするかというと、割込が入りやすいように、★のところにThread.sleep()を入れた。
まず、synchronizedを外した状態で動かして、発生していたエラーと同じことが起きることを期待する。
そして、synchronizedを付けて、エラーが起きないことを期待する。

 

うん、今回はうまくいったようだ。


ただねぇ、作りをあれこれ変えていったので、実はこういうチェック無しでもいけるんじゃないの?という気がしている。
その決断を下す勇気がないというか、判断ができないというかで、現状維持しているというところ。

ここら辺は、付け焼き刃の知識じゃダメだなー、と思う。
空気感が読めないというのかね。

2016年7月11日

[android]Log.d()の文字列結合をStringBuilderにしたが、やはり残る

Android Lintはすぐ終わったので、Find Bugsに掛けることにした。
そうすると、ログ出力で文字列を足し算しているところに警告が出た。
StringBuilderとかがいいね、みたいなことを言っている。

うん、わかっているのだけど、ログ出すだけやん、とも思うのだ。
それに、最終的には出力されないし。

 

hiro99ma blog: [android]ProGuardでLog.d()がStringBuilderで残る
そういえば、ProGuardに掛けても文字列生成するところが残ってしまったのだが、もしかして最初からStringBuilder形式で書いておくと消してくれるのでは?

 

・・・ダメだった。
StringBuilderをnewして、append()して、最後にStringでnew(sb)したけど、全部残る。
しかも、以前は最適化してくれたせいかStringの足し算もStringBuilderに変換され、最後にStringに戻す部分がLod.d()がなくなることで不要になったのだけど、書き換えると明示的にStringBuilderからStringに変換する部分が残ってしまい、かえってコードが増えてしまっている。

 

だいたい、代入する人がいないのにnew StringBuilder()するところなんか、うまいこと消せないものなのだろうか。
C++だとできそうだけど、Javaはそういう書き方をすることが多いから無理か。。

 

とりあえず、こうしておこう。

  • Log.d()程度の文字列操作で、ProGuardでログ自体削除するなら、文字列は足し算で結合させた方が最終的に小さくなりやすい
    • StringBuilderからStringに変換する実装を明示的に書くと、それが残ってしまう
  • 自作で、結合させたい文字列を引数に取るログ出力メソッドを作って、文字列の結合処理をメソッドの中に押し込め、ProGuardで抑止させれば消えるのかもしれん。
    • Cなんかだとときどきある、DEBUG_LOG1(p1), DEBUG_LOG2(p1,p2)みたいに引数ごとにメソッドを作るとか

 

うーん、Javaにもプリプロセッサ、プリーズ!!
まあ、あったらあったでやっかいなのだけどね。。。

[java]finalをつけましょう、と言われた

ソースが安定してきたので、Android Lintに掛けてみた。

 

以前、finalについてはここやここで考えたことがあった。
そのときは、finalはコンパイラレベルの話なので、finalを付けても付けてなくてもバイトコードには変化がないよね、というくらいで終わっていた。

今回指摘されたのは、フィールドでnewし、そのまま使い続けるやつと、コンストラクタの引数でしか代入されないやつらだった。
そういえば、C++のメンバ変数を参照型にするときも、コンストラクタの初期化子にしておけば設定可能だったから、同じ要領なのか。

 

今回は、意識してfinalにできるようになったわけじゃないので、正直なところどうでもよい。
私が最初からfinalにしたいのは、だいたいstatic finalのような定数的なものだけだ。

finalはC++のconstと同じで、変数への代入は怒ってくれるけど、アドレス先の更新までは考慮してくれない。
まあ、そうじゃないとやってられないだろうが。

 

どうしようかと思ったが、finalを付けることにした。
静的なツールだから仕方ないのだけど、今書かれているソースの内容でしか判断できないから、実はこれを拡張する予定があって・・・、なんてのには対応できない。
あまり言語仕様をわかっていないとツールの言いなりになってしまいそうだが、やってればそのうちわかるんじゃないの?くらいでやることにした。

2016年7月8日

[java]breakラベルはうらやましい

あまり、Javaに対して「ここがよい」とか「ここが悪い」とかは思わない。
まあ、unsignedがないのはめんどくさいとは思うのだが、そういう言語だしね、と思うことにしている。

ただ、breakについてはJavaがうらやましいと思った。
多重ループを抜けるときが、Cだとあまりきれいに行かないのだ。
私はこういうときのgoto文を許容するのだけど、プロジェクトとかによってはgoto一切禁止、ということもある。
多少は戦うのだけど、そういう文化のところなんだから仕方ない、とあきらめることの方が多い。

そういえば、switch以外でのbreak禁止、なんてところもあった。
ループを抜けるためにフラグを追加したりしたけど、逆にわかりづらいと思うのだけどねぇ。


そういえば、C++だとgotoでスコープを抜けたらデストラクタが動くんだろうか?
いや、動くのだと思うけど、けっこうめんどくさくないか。
どうやって「スコープを外れた」というのを監視してるんだろう。。。

だいたい関数に入ったら、レジスタをいくつかPUSHして、スタック変数を確保して、とやっていると思う。
ただ、抜けるときの動きがよくわからんな。

#include <stdio.h>

class ClassA {
public:
    ClassA()
    {
        printf("ClassA ctor\n");
    }

    ~ClassA()
    {
        printf("ClassA dtor\n");
    }
};

class ClassB {
public:
    ClassB()
    {
        printf("ClassB ctor\n");
    }

    ~ClassB()
    {
        printf("ClassB dtor\n");
    }
};


int main(int argc, char* argv[])
{
    printf("main - begin\n");

    for (int lp1 = 0; lp1 < 10; lp1++) {
        ClassA cla;
        for (int lp2 = 0; lp2 < 80; lp2++) {
            ClassB clb;

            if (lp2 == 71) {
                goto CATCH;
            }
        }
    }

CATCH:
    printf("main - end\n");
    return 0;
}

$ ./tst
main - begin
ClassA ctor
ClassB ctor
ClassB dtor
ClassB ctor
ClassB dtor
ClassA dtor
main - end

うん、"main - end"の前にClass Aのデストラクタが動いている。
Class Bよりも後なので、スコープが外れる内側から順番に呼ばれているのだろう。

arm-none-eabi-g++ -Sでアセンブラにしてみたのだが、なんかよくわからん。
main()の中でClassAのデストラクタだけ2回呼ぶ処理が入っている。
__cxa_end_cleanup()があって、これは何か最終処理っぽいものだったような気がする。

71と比較してイコールだったときのルートを追っていくと、確かにClassBのデストラクタ→ClassAのデストラクタという順で呼んでいた。
最後のClassAデストラクタも呼んでいるので、このときに2回呼ばれることになる。

ClassAのデストラクタを見たけれども、1回処理したら2回目以降はしない、みたいな処理は書いてないように見えるなぁ。。。
あまり読み方がわかってないせいかもしれん。

2016年7月6日

[java]CountDownLatchはawait()前にcountDown()されることも考慮しよう

非同期処理の待ち合わせに、CountDownLatchを使っていた。
Effective Javaにも書いてあったので、だいたいそうするのだろう。

 

これをタイムアウト有りでawait()させていたのだが、ときどきタイムアウトが発生していることに気付いた。
通信なので相手とのタイミングはときどき変わるものの、タイムアウトするような要素はない。
なぜだ。。。

 

今回、await()やcountDown()でチェックしたいものがあったので、それらをラップしたメソッドを作っていた。
で、await()させるほうのメソッドでCountDownLatchをnewし、非同期で終わったときにcountDown()させるようにしていた。
一応、異常時にawait()せずに非同期処理だけが動くパターンがあるので、その場合はcountDown()させないようにしていた。

そしたら、await()が呼ばれる前に非同期処理の方が先に完了していたのだ。
もしcountDown()をそこで呼んでいればawait()はtrueで終わるのだけど、異常時と同じルートを通ったためcountDown()せず。。。

 

いや、言い訳だけすると、これでも非同期処理が先に終わるのは考慮したつもりだったのだよ。
ただなんとなく、Javaのスレッドってメソッド単位とかでやってくれるんじゃないの、などと思っていたのだ。
適当は、いかんね。

 

対処は、非同期処理を呼ぶ前にCountDownLatchをnewさせ、異常ルートでもcountDown()を呼ぶようにした、だ。
カウントダウンする準備さえ整えておけば、怖いものではない。

 

ただ、そこに行くまでが紆余曲折してね。。。
スレッドだから、ディスパッチ禁止させようとしてメソッドをsynchronizedにしてしまい、デッドロックしたとかね。。。

2016年7月5日

[java]byte解析の恐怖

何度かやらかしたアレを、またやってしまった。。。

hiro99ma blog: [java]符号無し演算にはまる

プロトコル解析処理で、byte配列のデータを扱っていたのだけど、ところどころ間違っていた。

たとえば、res[5]にデータ長が入っていたとして、

int length = res[5];

何てことを平気でやっていたのだ。
テストデータも長さが短かったのですり抜けていて、負荷テスト用の大きいデータにしたら、もういきなり「配列サイズに負の数を指定してる例外」とか「バッファサイズが足りないよ例外」とか、「チェックサムが一致してナインだけど」とか、あれこれ言われ始めた。。。

 

unsignedな自分のために、対策を書いておこう。

  • byte変数を比較するだけだったら、あまり気にしなくて良い。
    相手が直値だったら、(byte)とか付けてやることもあるだろうが、まあエディタの段階でわかるだろう。
  • 計算するときは、「& 0xFF」としておくのだ。
    byte以外に代入するときも、やっておこう。
    チェックサムは案外いけるんじゃないかと思ったけど、計算方法次第か。
    あとで見て迷うと思うから、論理積を取っておこう。

計算するとint型になる、だったはず。

 

では、byte型を演算して、その結果をbyte型と比較するんだったら、ここまでしなくても大丈夫だろうか?
前回も「ビットが小さくなる方は勝手に削られるからよかろう」としている。
ただ、演算した方をbyte型にキャストするのをわすれんようにせないかん。

 

1箇所こういうのを見つけると、急に不安になるから困りますな。
もうbyte演算恐怖症です。。。

[android]今の時間を文字列でほしい

TextViewにログを出そうとしている。
日付はいらないのだが、ミリ秒までの表示はいる。

ネットで検索していて、こういうのを使っていた。

long currentTimeMillis = System.currentTimeMillis();
Date date = new Date(currentTimeMillis);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss SSS");
return simpleDateFormat.format(date);

これはこれで動いていて気にしなかったのだが、Android Studioでgitのcommitをしようとしたら警告が出てきた。
そうか、Android Studioはなにかチェックしてからcommitしてくれるんだ。。。

image

長いよ。
なんか、ロケールを考慮するんだったらちゃんとやれ、みたいなことを言っているようだ。
検索すると、こちらが出てきた。

Y.A.M の 雑記帳: SimpleDateFormat ではなく android.text.format.DateFormat を使おう

まあ、そんなに難しくなく置き換えられそうだ。

return (String) DateFormat.format("HH:mm:ss SSS", Calendar.getInstance());

 

ただ気になったのが、format()がイタリック体で表示されていることだ。
ほぼAndroid Studioのデフォルトで使っているのだが、どういう意味だろう?
そういえばgetInstance()もイタリックだから、クラスメソッドという意味だろう、きっと。

 

で、これを使うと・・・ミリ秒のつもりが"SSS"とそのまま出力された。。
でも、format()→SimpleDateFormatとたどると、ミリ秒はやはり"S"だ。
Examplesもそうなっているので、文字列コピーして使ってみたが、やっぱり"SSS"。

musyamusya: 【Android】日時の取得でミリ秒までとりたい時【メモ】
やはり、"SSS"になってしまうらしい。
理由はわからんが、最初の書き方にロケールを書くしかないか。

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss SSS",
      Locale.getDefault());
return simpleDateFormat.format(new Date());

すっきりせんが、仕方ない。


さて、これでだいたい終わりなのだが、DateFormatとは関係なくもうちょっと気になることがある。

こちらがformat()の説明なのだが、戻り値がCharSequence型だ。
第1引数も戻り値もCharSequence型。
引数には"HH:mm"など書いているので、String型になっていると思うのだけど、特に怒られない。
だが、String型のreturnを直接format()にすると、これはキャストしろと怒られるのだ。
CharSequenceはなんだろう?

 

何かと思ったら、CharSequenceはjava.lang.CharSequenceというinterfaceとのこと。
まあ、ダウンキャストを自動でされると、ちょっと気持ち悪いしね。

2016年6月28日

[java?]doReturn()は複数書けるし、ArgumentCaptorは複数取得している

まだまだテスト中だ。
とにかく、Java自体に自信がないので、テストパターンを多く作りすぎているような気がする。
まあいいや(仕様変更が来ないことを祈ろう)。

 

テストするメソッドで、別のメソッドを呼んでいると扱いが面倒なので、spy()していちいちdoReturn()とかdoNothing()とか書いている。
しばらくやっていると、同じ引数で1回目と2回目で違う値を返さないといけないパターンが発生。
まあ、レスポンス解析だからありがちだと思う。

どうやったらいいか調べていたのだが、単にdoReturn()をずらずらつなげればよいみたいだ。

doReturn(new byte[] {1, 2, 3}).doReturn(new byte[] {4, 5, 6}).when(xx).read(anyInt());

ほほう。

 

あと、呼び出した疑似関数の引数をArgumentCaptorで保持する方法も知った。

ArgumentCaptor<byte[]> writeCaptor = ArgumentCaptor.forClass(byte[].class);
doNothing().when(xxx).write(writeCaptor.capture());

//テスト実施後
byte[] captorData = writeCaptor.getValue();
assertThat(Arrays.equals(captorData, COMPARE_DATA), is(true));

assertThat()で配列の比較方法がよくわからんので、もうArrays.equals()にしてしまったが、まあいいや。

 

この方法だと、captorDataには最後に呼び出したものが残るようですな。
複数回はできるのだろうか?
できるのだ。
java - Can Mockito capture arguments of a method called multiple times? - Stack Overflow

さすがだねぇ。
と思って試すが、なんか動かない。。。

ああ、書いてあるのが、ArgumentCaptorの生成とverify()が並んで書いてあったから、そういう書き方なのかと思ったけど、verify()はいつも通りテスト実施後に呼べばいいだけか。

ArgumentCaptor<byte[]> writeCaptor = ArgumentCaptor.forClass(byte[].class);
doNothing().when(xxx).write(writeCaptor.capture());

//テスト実施後
verify(xxx, times(4)).write(writeCaptor.capture());
List<byte[]> captorData = writeCaptor.getAllValues();
assertThat(Arrays.equals(captorData.get(2), COMPARE_DATA), is(true));

つまり、Captorは動いていて、取得方法だけ変えればよいというわけだ。
verify()は呼ばないとだめなようです。
times()できっちりじゃなくても、atLeast()でいけるみたいだ。

2016年6月27日

[android]カバレッジを取りたい

Android Studio 2.1.2 + Nexus5で、カバレッジを取りたい。
いまの環境(2016/06/27)で、なるべく手間を掛けず、LCOVみたいなカバレッジ結果が見たいのだ。

Local Unit Testsの場合は、Project Explorer...という名前じゃないかもしれないが、左側に出てくるツリーでTestクラス名を右クリックすると「with Coverage」というメニュー項目があるからわかった。

では、instrumented Unit Testsの場合は?


検索すると、jacocoというものを使う人と、使わない人がいる。
なんとなく、使わなくてもよくなっていそうなので、この人のを参考にした。

baroqueworksdevの日記: コードカバレッジを使って、視覚的/統計的にテスト状況を確認する

terminalからgradlewコマンドを実行せずとも、Android Studioの右上くらいにある「Gradle」をクリックするとツリーが出てきて、その中に「verification/createDebugCoverageReport」みたいなのがあるから、それをダブルクリックすると同じ効果があるようだ。

 

で、そもそもこれで何が実行されるのかがわかっていなかったけど、Instrumented Unit Tests側のフォルダ(androidTestと書いてある方)のテストが実行されるようだ。

ただ、実機側のログに「java.lang.ClassNotFoundException: org.jacoco.agent.~~」や「TestRunner: Failed to generate emma coverage. 」と出ているので、jacocoを明示的に指定しなくてもよくなっただけかも。

あと、テストがちゃんと動かせてないのかも。。。
でも、reportフォルダの中にはそれらしいものが生成されているから、まったくダメというわけでもないだろう。

 

この辺りが役立った。
BDS pluginの生成するソースでは、Handlerを使っているのだ。
AndroidのInstrumented Unit Testsを試す - mnatsu31’s blog

 

あと解決できてないのが、同じく生成されているServiceだ。
これが動いてくれないと、何もできないに等しいのだ。。。

でも、下回りを全部動かすテストって、もうUnit Testじゃないよねぇ。
そうそう、そもそもは、単にアプリを動かしたときのカバレッジがほしかっただけなのだ。

初心を忘れていましたね。


しかし、勝手にカバレッジを取ってくれるような方法は見つけられてない。
gccで-coverageを付けてビルドしたアプリを動かしたときのようなやつだ。

正常系はボタンとか操作して動かすことでカバレッジを取っておいて、それで動いていない箇所だけLocal Unit Testsでやろうか、と思っていたのだ。

 

JavaやAndroidだから、そういうのもできるのかも、と思ったけど、やり方がわからんだけか。。。
なんか「最新の科学で解決しました」みたいなイメージでいるから、よくわからないけどやってくれそうな気がしたのだよ。

[android]Instrumented Unit Testsじゃないとandroid.jarは例外/デフォルト値を返す

こんなテストコードを書いた。

@Test
public void testTest() throws Exception {
    SparseArray<String> strMap = new SparseArray<>();
    strMap.put(0x00, "moji");
    String err1 = strMap.get(0x00, "momo");
    System.out.println(err1);
}

実行すると、nullになった。。。
newしてるのにnullか。


よくわからずに書いている箇所があるが、build.gradleのreturnDefaultValuesもそうだ。
これをコメントアウトすると、実行するとエラーになる。

java.lang.RuntimeException: Method put in android.util.SparseArray not mocked

これだ。
実機を動かさずに行うUnitTestではandroid.jarが空実装だから、例外になるようだ。
これを、例外を出さずにデフォルト値を返すようにするのが、returnDefaultValuesというわけだ。

 

SparseArrayは特にAndroid実機の機能を使っているわけでは無いのだろうけど、これを解決させるのに悩むくらいだったら、実機で動かしてしまった方が楽そうだ。

そういう、Mockなどを使わずに動かしてテストもしたいなら、Instrumented Unit Testsの方になるようだ。


Local Unit Testsを始めるときと同じように、書いてあるとおりにやると動いた。
テストを右クリックで生成するときに、どのフォルダに格納するか聞いてくるが、あれの先が「androidTest」になるのだな。

そして、build.gradleも「testCompile」じゃなくて「androidTestCompile」になる。
returnDefaultValuesはtrueのままでよいようだ。

実行すると、アプリが実機に転送されて実行し、ちゃんとassertThat()なんかも判定している。

 

android.jarのところも含めてMockにしてメソッドを動かしていくならLocal Unit Tests、android.jarはそのまま流して動かしたいならInstrumented Test Unitsがよいかな。