プログラミング雑談

メインページに戻る
2008年~2023年 2024年~

Jsoupの使い方がよくわかんない

このWebページを生成するプログラムをC++11以前の古いC++で作っていたのをjavaでjsoupを使って書き換えようという気分になって書き換え始めたのだけれど、jsoup自体初心者なので細かい問題にぶつかってばかり。
最初はjsoupでnodeとかdocumentとかelementとかの使い分けがわからないところからスタートしているのでjavadocっぽいドキュメントを頑張って読んでいました。
その後はxmlヘッダーやドキュメントヘッダーの付け方に悩み、今は改行の付け方に悩んでいます。
一応このページはXHTML 1.0 Strictで作っているので、改行は<br />なのですが、appendElement("br /")だと閉じタグが生成されて<br /></br />になってしまうんですよね。
appendElement("br")ならばならば閉じタグが作られず<br>だけが生成されるのに。
もしかしてjsoupでこれはxhtmlだとかhtml5だとか教えることができて、その辺の設定ができるとうまいことやってくれるのかしら?
(追記)やっとわかった。XHTMLだから、jsoupをhtmlモードではなくxmlモードで動かさなければならない模様。

doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);

してからdoc.outerHtml();すれば良いんだ!
とりあえずXHTMLの骨組みはこんな感じで生成できそう

doc = new Document("");
XmlDeclaration xmlDecl = new XmlDeclaration("xml", false);
 
xmlDecl.attr("version", "1.0");
xmlDecl.attr("encoding", doc.charset().displayName());
DocumentType docType = new DocumentType("html", 
"-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
 
doc.appendChild(xmlDecl);
doc.appendChild(docType);
 
Element html = doc.appendElement("html");
html.attr("xmlns","http://www.w3.org/1999/xhtml");
html.attr("xml:lang","ja");
html.attr("lang","ja");
Element head = html.appendElement("head");
head.append("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />");
head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");
head.append("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />");
doc.title("MyPage");
Element body = html.appendElement("body");
 
Element ddd = body.appendElement("div");
ddd.appendChild(new Comment("Comment Node"));
ddd.appendChild(new TextNode("TextNode"));
ddd.appendElement("br");
ddd.appendText("&");
 
doc.outputSettings().indentAmount(0);
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
 
doc.outerHtml();

誰かのお役に立てれば。

2024年01月13日 プログラミング雑談

VSCodeでmavenでJUnitとかjacocoとか

割と最近までVisualStudioでC++の人だったのでjavaとかVSCodeとか、何かしようとするたびに壁にぶつかる今日この頃。
ググりまくりなのだけれど、右も左もわからない初心者なのでググるキーワードが多分不適切なのでしょう。全然情報が見つかりません・・・(涙)
VSCodeでExtension Pack for JavaとかMaven for Javaを入れて、簡単なコードを書いて、JUnitのテストコードを書いて実行してみてます。
コマンドパレットで"Maven Execute Commands..."で"install"を実行すると多分実行jarを作ってくれて、JUnitのテストを実行してくれて、カバレッジまで測ってくれるようです。ステキ。
そしたら間もなく
[WARNING] Rule violated for method com.segu.Index.addText(org.jsoup.nodes.Element, java.lang.String): complexity total count is 7, but expected maximum is 5
とか言われ始めました。
悪かったね! どうせ私のコードはスパゲッティだよ! とか思いながら、とりあえずcomplexityのmaximumを8とかにすれば良いんでしょ?ってことで、VSCodeのsettings.jsonとかに何か書けばいいんだろうなと思いながらググること何時間?。結局解決策がわからず、
[ERROR] Failed to execute goal org.jacoco:jacoco-maven-plugin:0.8.4:check
で"install"がエラーになってから手動でJUnitのテストを実行してました。
pom.xmlを見ると

<maximum>${jacoco.unit-tests.limit.method-complexity}</maximum>

という行があったので、method-complexityというパラメタ名でググったけれどわからず。
あきらめてcomlexityのチェック行をpom.xmlから全削除するという暴挙に出てERRORを回避して何日か過ごしました。
で、
別件でpom.xmlを見てたら

<jacoco.unit-tests.limit.method-complexity>5</jacoco.unit-tests.limit.method-complexity>

なんて行があるではないですか。なんだ、pom.xmlの中で上限値を設定してたのかよ!ってことで、ここで上限値を8とかにしたらWARNINGもERRORも消えてすっきり

で、さらに私の前に(多分客観的には低い)壁が出現するわけですよ
JUnitのテストはlog4jで何のテストを実行したかログを出力したいので、log4j2.xmlが見えるようにクラスパスを設定したいのだけれど、どうやったら設定できるかわからずに半日ググった結果、このサイトを見つけました。
いや、見つけたってなんだよ。VSCodeのMavenプラグインが設定してくれたpom.xmlにmaven-surefire-pluginって書いてあんだから、surefireのオフィシャル見に行くのは当然だろ!って気もしますが、わかんないことばかりでどこを見に行って何を読めばいいかわかんないんですよ....と言い訳しておきます。
私はプロジェクトのルートにlog4j2.xmlを置いているのでpom.xmlに次のように書けばよいみたいです。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>${maven-surefire-plugin.version}</version>
  <configuration>
    <additionalClasspathElements>
      <additionalClasspathElement>./</additionalClasspathElement>
    </additionalClasspathElements>
  </configuration>
</plugin>

数日経つと忘れちゃうんだろうなという事で記録を残してみます。備忘録かよっ

2024年01月20日 プログラミング雑談

javaで正規表現

このページをテキスト形式からhtmlに変換するプログラムはC++で2009年に書いたものです。
これをjavaでjsoupを使って書き換えようと思い始めて多分2週間。
機能実現検証用の試作が動きました・・・。
もう若くないので夜更かしはできないし、平日昼間は仕事だし、このwebページをjava版の変換プログラムで作るのは2月に入ってからかなぁ。
秀丸の置換とかsedとかgawkで正規表現を便利に使っていますがjavaの場合のやり方を少しメモ。

Pattern pattern = Pattern.compile("^[ \t]*&([^:]+):") ;
Matcher matcher = pattern.matcher(inLine);
if(matcher.find()) {
    if(matcher.group(1).equals("end")) {

とかすると、"&end:"が検出できる模様。Patternの行からfindの行までは1行で書けるので、それなりにすっきり書くこともできるのですね。
はまったポイントは数字とマッチする"\d"は"\"自身がエスケープ文字なので"\\d"と書かねばならないとか、find()まで実行しないとgroupが使えないところですかね。

2024年01月21日 プログラミング雑談

java版のこのwebページ生成ツール 仮運用開始

正月が明けてからテレビアニメも見ずにプログラムを書いていました。
タイトルの通り、今日からこのwebページのhtmlはjavaで作り直したプログラムで生成を開始しました。
ページの見た目はcssファイルの方なのであまり変わっていないはず。個々のアーティクルへのリンクは極力従来通りに生成できるようにコードを書いたので、htmlの方もhtml自体をテキストエディタなどで見比べない限り(見比べても)わからないのではないかな。
もちろん何もないところからwebページのhtmlが出てくるわけではないので原稿があります。原稿はテキストファイルです。今回java版を作って従来の原稿を流すまで気が付きませんでしたが、これまでShift-JISファイルを使っていましたが、今回はUTF-8 BOM無しのテキストファイルを原稿の文字コードにしました。
その他、原稿のリンクや箇条書きなどはマークダウン風の記法にしました。今まではいくつかの独自記法でコード記載ブロックを切り分けていましたが、その他は原稿にhtmlタグを書くというweb黎明期でもほとんどはやらなかったであろう地味な作業をしていましたが、これももう不要です。改行のbrタグすら手書きだったものなぁ。
今回取り入れたマークダウン風の記法は

あたりです。
いままでコード記載ブロックの処理にバグがあって空行が表示されていませんでしたが、これも表示されるようになったはず。しばらく気が付きませんでしたが、気が付いた時に空行ではなくスペース一つ入力して回避してたりしました。
htmlのエスケープ文字もjsoupが自動で変換してくれるはず。
コーディング上は、15年前のC++版でも途中まで単体テストを入れていましたが、コーティングが進むにつれてメソッドの複雑度が上がってテストを書くのを断念してしまっていました。それだけでなく15年の歳月の間に流行りのc++テストツールが世間的に交代してしまったようで、その意味でもテストを書いて動かし続けるのが困難な状態でした。
今回はメソッドのコード複雑度(サイクロマチック)5以下でここまで作り切っていますので、今度こそ単体テストを後付けで作っていけるはず。
たくさんコードを書いたようなつもりだったけれどかぞえチャオで数えたら1,500ステップもなかった

2024年01月29日 プログラミング雑談,このページのリフォーム

サイクロマチック数 max 5の世界

久しぶりにプログラムを書くといろいろ勉強になりますね。
今回の一番の収穫はメソッドのサイクロマチック数をmax 5で作るのも良いものだ、という事を実感したことかもしれません。
最初はmavenの環境をチュートリアル見ながら構築したらデフォルトでそのような設定になっていたというだけなのですが、これがデフォルトという事は何か意味があるに違いないと思ってmax 5縛りで作ってみました。
最初に影響が出たのがswitch文。こんなの使うとあっという間にサイクロマチック数5を超えてしまいます。ファクトリーメソッドで処理を切り分けるようにしました。ファクトリーメソッドでもファクトリー部分で分岐が出ないようにArrayListとかに選択肢を詰め込んでおいてget(key)で探したりforeachで探したり。
次にif文の使用。メソッドの責務をシンプルにして、メソッドの上から下まで条件分岐なしでいかに流しきるか考えるようになりました。今回作っていたプログラムは、テキストファイルを読み込んでhtmlに変換して書き出すというシンプルなコマンドラインプログラムでした。メソッドの入り口で「入力ファイルを確認する」みたいなメソッドでチェックして、NGならエラーリターンし、その後の処理は余計なことは考えずに上から下まで一気に流す、みたいなスタイルになっていきました。

そのうちメソッドのサイクロマチック数 max 5はまあ普通に守れるようになってきましたが、次にクラスでmax 20という条件で引っかかるようになりました。メソッドが1つあればサイクロマチック数も最低1あるという数え方ですので、クラスのメンバー変数がいくつかあってsetter、getterを書き始めるとたちまち超えます。setter、getterは今回はあまり書かないで作ってみました。これが正しい対処法かどうかわかりませんが、
それよりも「もし入力ファイルが無かったら」はFileReaderクラスの責務、「もし入力ファイルの記載が間違っていたら」は処理するクラスの責務、みたいにクラスの責務を整理して、一つのクラスで処理しなければならないメソッドを減らすことに注力してました。
テキストファイルを読み込んだり、htmlをファイルに書き込んだりするのはhtmlを生成するクラスの責務ではないよね、ということで、ファイル読み書きは別クラスに切り出したり。

おかげで単体テストコードも書きやすいです。まだあまり書けていませんが、これまでコードの構造のせいでテストが書きにくいといった事には出くわしていません。
まだ単体テストを一通り書いていないのにこんなことを言うのもなんですが、単体テストってプログラマーの意図通りコードが書けているかどうかのテストなのだなぁと実感していました。単体テストが通っているのに出力されたhtmlファイルが意図通りではないことが頻発しています。これはplaywrightみたいなツールで結合テストするしかないのかもしれないですね

そうそう、サイクロマチック数とは関係ないかもですが、今回は(今回こそ)設計書をちゃんと書くぞと(まだ)思っています。DoxygenとかplantUMLとか使ってちまちま書いています。
私が飽きるのが先か、テストや設計書を作り切るのが先か、どうなるんでしょうね?

2024年02月04日 プログラミング雑談

jsoupで最初つまづいたところ

今度のお休みに書こうと思っていることをメモ
jsoupで私のwebページを書こうとして結構ずっと混乱していたのがappendChild()とappendElement()の使い方。

Element ddd = new Element("body");
ddd.appendElement("div").attr("name", "segu");

と書くことはできるけれど

Element ddd = new Element("body");
ddd.appendChild(new Element("div")).attr("name", "segu");

と書くと多分思ってたのと違う結果になる。
前者は<body><div name="segu">で
後者は<body name="segu"><div>になるはず。
jsoupのAPIリファレンスに書いてある通りだし、今考えるとこれが当然の挙動なのだけれど、その辺が理解できるまで思ったようなhtmlにならなくてだいぶ混乱してました

2024年02月08日 プログラミング雑談

3日なんてあっという間

jsoupについて何か書こうと思っていたけれど、gradleとかgroovyとかkotlinとか調べてたら土日が終わって、sonarQubeとかいうのの存在を知って使い方を調べて今回書いたコードをみてもらったら指摘がドバドバあって、月曜も終わってしまいました。

2024年02月12日 プログラミング雑談

javaの本

C++がある程度わかってきたら、書籍 プログラミング言語C++で勉強しなおすとすごくためになると思っています。
同様にjavaの場合にプログラミング言語Javaを読めばいいのかというと、なんか違う気がします。おとといの土曜まで知りませんでしたが、この本はJava 1.5までの内容しか書かれていないそうじゃないですか。せめて1.8のラムダとかメソッド参照とかstreamとか書かれていないといまどきのプログラミング言語として物足りないと思います。どおりでこの本をいくら調べてもメソッド参照について書かれていないわけだ!
昨日いろいろ書籍をググっていましたが、多分Effective Javaあたりから読むとよいのかなと。またはJavaによる関数型プログラミングかな。
他の言語も同様ではあるのですが、入門書はたくさんあるのですが、例えばif文を極力使わないでプログラミングしようとか、Exceptionをどのようなケースで使うのが良いのかとか、ファクトリーメソッドでどのタイミングでインスタンス生成すると良いのかとか、疑問は尽きません.
今からjavaを勉強しても勉強したことを生かしてプログラミングすることももう無さそうなんだけど。

2024年02月19日 プログラミング雑談

デザインパターンを勉強せずになんとなく使っているので説明できない

C++とかJavaとかで継承を学ぶと使いたくなるのがFactory Method。
その時はわかったような気分になるのですが、時々こんがらがってくるのでメモ。
「乗り物」を作る「工場」がある
「乗り物」は「自転車」もあれば「三輪車」もある。
「自転車」を作る「自転車工場」や「三輪車」を作る「三輪車工場」がある。

「乗り物」がProduct、「自転車」や「三輪車」がConcreteProduct
「工場」がCreator、「自転車工場」や「三輪車工場」がConcreteCreator

で、ここから先の正しい定義がいまだによくわからないのです。動的に、その時作りたい乗り物をswitch文などで切り分ける処理は何と呼べばいいのでしょう?
GoFの実装例ではCreatorクラスのCreator::Create()でMINEとかYOURESとかに応じて適切なProductをnewしているようです。
やっぱりちゃんとGoFのデザインパターンをちゃんと勉強しなきゃだな。もしかしてFactory Methodは適切なProductをnewするところまでで、どのように「乗り物」を作るかはAbstruct Factoryとかなのかしら。デザインパターンはつまみ食い状態だから全体像がつかめていない・・・

2024年02月23日 プログラミング雑談

Java AutoValueの私的なメモ

Effective Javaで今更ながらJavaを勉強中
最初の方の章でdataクラスを作るならequalとかちゃんとやらなきゃだめだよとか書いてあるみたい。自分で定義した値クラスを比較してないなら問題ないんでしょ? と思いながら、ちょっとお試し。
githubのAutoValue
detailed documentationをまるっとコピーしてしまうと、コード側の使い方はこんな感じらしい。@AutoValueというアノテーションを書くこととabstractクラスにすることがポイントらしい。インスタンス生成するためにabstractじゃないクラス名が必要だからcreateメソッドも必要ってことかな。

import com.google.auto.value.AutoValue;

@AutoValue
abstract class Animal {
  static Animal create(String name, int numberOfLegs) {
    return new AutoValue_Animal(name, numberOfLegs);
  }

  abstract String name();
  abstract int numberOfLegs();
}

mavenの場合pom.xmlには次のように書くみたい。${auto-value.version}は適切なバージョンに置き換える。

<dependencies>
  <dependency>
    <groupId>com.google.auto.value</groupId>
    <artifactId>auto-value-annotations</artifactId>
    <version>${auto-value.version}</version>
  </dependency>
</dependencies>

これだけだとAutoValue_Animalなんてクラス知らないって言われるので次の記載も必要みたい

<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>com.google.auto.value</groupId>
            <artifactId>auto-value</artifactId>
            <version>${auto-value.version}</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

これでビルドするとtarget/generated-sourcesの下にAutoValue_Animal.javaが作られる。
私はたいしてjavaのコードを書いていないからこれまでこういうことを知らなくても実害はなかったけれど、コンテナに自作クラス(のインスタンス)を入れて何かさせる場合はequalとかちゃんと作らなきゃならないようなので、今は実害が無くてもちゃんと作る習慣を今からつけていかないと将来発見が難しいエラーを作りこんでしまうって事だろうな。

2024年02月24日 プログラミング雑談

匍匐前進

今年作っていたjavaのプログラム、VSCodeでMavenを使っていましたが、gradleも使ってみたくて。

VSCodeなのでGradle for Javaのプラグインを入れて、コマンドパレットで"Create a Gradle Java Project"でKotlinでJUnit5だったかな、そんな感じで選んでプロジェクトを作成。
もう一歩進むたびにやり方をググって・・・
依存パッケージの設定はbuild.gradle.ktsに

dependencies {
    // This dependency is used by the application.
    implementation("org.jsoup:jsoup:1.17.2")
    implementation("org.apache.logging.log4j:log4j-api:2.22.0")
    implementation("org.apache.logging.log4j:log4j-core:2.22.0")
}

とか書いて、VSCodeの左の象のアイコン→Tasks→build→buildしたらエラー。
どうやらbuild.gradleを更新したらUpdateする必要があるみたい。Mavenの時はpom.xmlを更新すると更新しますかみたいなメッセージボックスか何かが出てUpdateされていたみたいですが、まだ私の環境はbuild.gradleを更新しますかみたいなメッセージボックスか何かが出ているのですが反映されていないみたい。(追記)JDKとJREをインストールしなおしたら解決しました
とりあえずVSCodeを終了して立ち上げなおせば反映される。

Javaのソースファイルとテスト用データファイルを一式コピーしてbuild。今度はコンパイルが通ったけれど今度はテストでエラー。Webページのhtmlファイルを作るプログラムなので、生成したファイルの中の文字をチェックするのだけれど、UTF-8の文字列にもかかわらずShift-jisのつもりで比較している模様。UTF-8のソースファイルなのにShift-JISとしてコンパイルされているみたい。文字コードが誤認識されていてもビルドが通るとは恐るべし
VSCodeのエクステンションの設定かしらと思ってEncodeとか頑張って探すけれど設定する箇所が見つからない。
いろいろググってたらbuild.gradleで設定するらしい。このサイトの記載をそのまま使わせていただきました。

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
}

すっかり忘れていたけれど、Mavenの時はpom.xmlに

  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

みたいなのを書いた気がします。

VSCodeのjavaのプラグイン(?)でCreate Java Project→Maven→archetype-quickstart-jdk8とかで入ってくるcheckstyleとかjacocoとかはまだどうやったら使えるのかわからないし、log4jもまだlog4j2.xmlにパスが通っていないみたいでログが出てこないし、しばらくは前途多難な匍匐前進なのだな
でもググれば何かわかるというのは実に助かります。良い時代ですね。

2024年03月02日 プログラミング雑談

javaでifを使わずにnullじゃなかったら実行する

javaでもC++でも良いのですけれど、コードを書いているとnullチェックのif文が並んで、なんだかなぁ、と思うことが多いです。
例えばjavaで次のような感じ。クラスAaa、Bbbなどは省略します。

public class App {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            create(args[i]);
        }
    }

    private static void create(String devName) {
        Map<String, Supplier<Xxx>> factory = new HashMap<>();
        factory.put("A", Aaa::new);
        factory.put("B", Bbb::new);
        Supplier<Xxx> sup = factory.get(devName);
        if (sup != null) { // ★
            Xxx xxx = sup.get();
            System.out.println(xxx.getProduct());
        }
    }
}

factory.get()のところをgetOrDefault()使ってnullが返らないようにする手もありますが、このケースでは本来エラーになるケースでsup.get()で無駄なインスタンスを生成してしまうのでいやだなぁと。
もう20世紀じゃない。javaでNullじゃなければ実行する、みたいなスマートな方法があるはず。どう調べればよいかわからないのでたどり着くのに時間がかかりましたが、こんな方法があるようです

    private static void create(String devName) {
        Map<String, Supplier<Xxx>> factory = new HashMap<>();
        factory.put("A", Aaa::new);
        factory.put("B", Bbb::new);
        Optional<Supplier<Xxx>> optxxx = Optional.ofNullable(factory.get(devName));
        optxxx.ifPresent(sup -> System.out.println(sup.get().getProduct())); // supにoptxxxが入る
    }

java.util.Optionalを使ってofNullableで「この変数、nullかもしれない」と申告しておいて、ifPresentで「もしnullじゃなかったらヨロシク」ってことらしい。
このコードではifPresentで実行までしていますが、生成したインスタンスを返して戻り先で実行させるならこんな感じ。「nullかもしれない」値をOptionalに入れて処理して、最後にorElseでインスタンスを取り出すみたい。

        Optional<Supplier<GenHtml>> maker = Optional.ofNullable(factory.get(htmlMode.toLowerCase(Locale.getDefault())));
        Optional<GenHtml> makerMethod = maker.map(zz -> zz.get());
        return makerMethod.orElse(null);

最初の方の例はこのページに書くためにでっち上げたコードだったけれど、こっちは自分のコードからコピペ。
多分読み慣れればifでnullチェックするよりも読みやすくなるのでしょうね。

問題というほどの問題ではないのですが、ifPresentやmapの中でラムダ式を使うのでjacocoではサイクロマチック数を一つ使ってしまうのですよね。Optionalの処理をメソッドにして追い出してしまうとかいう使い方を想定しているのでしょうかねぇ。

2024年03月10日 プログラミング雑談

どこまでも文字コードが付きまとう

javaでgradle使ってプログラム作るの第2弾。poiでexcelファイルを作る。

cell.setCellValue("日本語文字列");

ってやったら文字化けした。
poi側で何か文字コード設定があるのかと思ってググるのだけれどあまりヒットしない。日本語フォントを設定すれば文字化けが直るとか書いてあるサイトもあったけれどフォントを指定しても直らない。
しばらく悩んで

System.out.println("日本語文字列");

したらこっちも文字化けした(笑)
そうだった、build.gradle.ktsに

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
}

って書いてUTF-8でビルドしなきゃならないんだった

引き続き、
Excelのxlsxファイルを読み込んで条件付きで文字色を変更して別ファイルに保存させるプログラムを書こうと四苦八苦。OPCPackageってなんだかわからないけれどpoiのサイトのサンプルのコピペで

OPCPackage pkg = OPCPackage.open(new File("indata.xlsx"));

って書いてファイルを読むと読み出ししたいだけなのにファイルが何か更新されてしまうみたい。結局

XSSFWorkbook wb_r = (XSSFWorkbook) WorkbookFactory.create(new File("indata.xlsx"), null, true);

って書いて解決。create()の3番目の引数がread onlyにするフラグらしい。
でもなんか別ファイルを作って保存ってpoiではセル一つ一つを別ファイルにコピーする必要があるみたいで面倒らしい。poiの新しめのバージョンではメソッド一つでシートごとコピーできたりしないのかな・・・。後で調べてみる。

2024年03月20日 プログラミング雑談

Android StudioをJavaで使うときライブラリのjarはどこに置くの?

私のWebページのスタティックhtmlファイルを生成するjavaプログラムをAndroidアプリ化しようとトライ中。
ググっていると広告ばっかりでイライラしてくるので入門書を購入。
あら、でもAndroid Studioでjavaのライブラリjarをどうやったら使えるか書いていない本を買ってきてしまったみたい。
(1) プロジェクトのviewでappフォルダ右クリック → New → Directory でlibsフォルダを作成
(2) エクスプローラーアプリからjarファイルをドラッグしてきてAndroid Studioのプロジェクトのviewの今作ったばかりのlibsフォルダにドロップ
(3) Copyするファイル名とフォルダを確認するダイアログが出るのでOKで閉じる
(4) プロジェクトのviewでCopyしたjarファイルを右クリック → Add as Library → Addするモジュールの確認ダイアログがでるのでドロップダウンリストから選択。デフォルトで良いみたい。
するとbuild.gradle.ktsに「implementation(files("libs\\jsoup-1.17.2.jar"))」みたいな行が追加されて、ライブラリが使えるようになりました。

jsoupはこれで使えたけれど、log4j2が実行時にエラーになる。まだAndroid Studioの使い方が全然わかっていないのでどんなエラーか把握できていないしどうやってデバッグすればいいかも勉強中。そもそもlog4jの使い方もわかっていないのでlog4jの使い方をちゃんと読めばいいのかな。

2024年04月07日 プログラミング雑談

実行可能jarを作る

javaのCLIでの実行jarの作り方を覚えてられないのでメモ
マニフェストファイルを作ります。

Manifest-Version: 1.0
Class-Path: .
Main-Class: com.my.App

classフォルダで

jar cvf [作りたいjarファイル名] [classファイルが入っているフォルダ名。例えばcom]
jar uvfm [作ったjarファイル名] [マニフェストファイル名]

2024年04月08日 プログラミング雑談

OpenJDKでJavaFXを使う

新しいバージョンのJavaが使えると限らないけれど、Java 8くらいならばどこでも使えそうな気がするので私が書いているJavaプログラムはターゲットをJava 8にしています。
OracleのJREはJava 8でもバージョンに気を付ければ使えるけれど、JDKはOracleのアカウント登録が必要で使いにくいのでOpenJDKを使っています。
今回買ってきたJavaの本はGUIアプリでJavaFXを使っているので本のチュートリアルに書いてあるHello World!をポチポチ入力。でもjavafx.application.Applicationがimportできない。この本はOracleのJDKをインストールしている前提なので、JavaFXなんて使えて当たり前でしょ? みたいなノリで全く説明なし。
いろいろググった結果、JavaFXを使うならばAmazon CorrettoにはJavaFXが入っているので簡単なようです。
adoptiumのOpenJDKをアンインストールしてCorrettoを入れてみました。とりあえずチュートリアルのJavaFXのHello World!はあっさりコンパイルできて動きました。

JavaのUI、Swingならばたいていどこでも使えるだろうと思ってちょっとHello Worldを動かしたことがありますが、Windowsのディスプレイの拡大/縮小はプログラマが意識してコードを書く必要がありそうで、私の200%拡大設定ではUIが小さくてなんだかなぁと思っていました。JavaFXはディスプレイの拡大/縮小の設定が効いているように見えますし、ファイル選択のコモンダイアログもWindowsのを使えているみたいなので良いかも。
ChromeBookでもJavaFXは使えるかな?
jarを作ってChromeBookに持って行ったけれど動かない。javaFxが入っていないのかな。

sudo apt-get insatll openjfx

しても"Package 'openjfx' has no installation candidate"って言われてインストールできませんでした。

2024年04月08日 プログラミング雑談

Androidアプリでlog4j2が使えない

自作javaアプリをAndroidアプリに作り替えようとしているのですけれど、使っているライブラリ、jsoupとlog4j2のうちjsoupはAndroidでも使えそうなのですけれどlog4j2の方はビルドすらできませんでした。
ちゃんとエラーメッセージを見たらlog4j2のパッケージのjarにMETA-INF/DEPENDENCIESというファイルがあるのがダメらしい。このファイルをjarから削除したらビルドだけは通るようになりました。
でも、Androidアプリで使おうとするとエラーになってダメなのは変わらないのですけれどね。ファイルに書き込もうとするときに何か制限があるのかしら。Androidアプリに関しては初心者未満だから手も足も出ない感じ。

2024年04月10日 プログラミング雑談

原稿ファイルにinclude機能を追加してました

毎回同じことを言っていますが、このwebページはテキストファイルからスタティックのhtmlファイルにプログラムで変換しています。
だいたい一つのテキストファイルが一つのhtmlファイルに相当しますので、indexページ(トップページ)と過去ログページと話題ごとのまとめページ、例えばプログラミング雑談に同じ文章をコピペしていました。これが地味にめんどくさかったので原稿のテキストファイルに別ファイルをincludeできるように機能追加してました。
原稿テキストファイルにincludeするファイル名を書いておいて、includeファイルが見つかったら挿入するだけの話なんですけれど、includeするファイルは原稿テキストファイルの相対パスにしておかないとユーザーの使い勝手が悪いし、このプログラムは将来Androidにもっていきたいので絶対パスはどうも使わない方が良いらしい。ってことで

Path currentFile = Paths.get(原稿ファイル名);
Path includeFile = Paths.get(includeファイル名);
String includeFileString = currentFile.resolveSibling(includeFile).toString();

でincludeファイル名を処理してから読み込んでいます。
html生成プログラム実行時のカレントフォルダは原稿ファイルと別フォルダですけれど、原稿ファイルとincludeファイルは同じフォルダという条件では今のところこのコードで動いています。原稿ファイルとincludeファイルが違うフォルダのケースはまだテストしていないので、テストしなきゃなぁ・・・

2024年04月20日 プログラミング雑談

VisualStudioCodeでmavenプロジェクトを作る

多分5~6行でできるような処理でもプログラムを作り始めるとなるとプロジェクトを作るといった日頃やらないことをしなきゃならないので気が重い。プロジェクトを作ってコードを書き始めればあとはそれなりにやる気が出るのですが、とにかくプロジェクトを作るところがめんどくさい。
半年ぶり位に数行のプログラムを書かねばというなけなしの意欲を振り絞って作業に着手。
1. 新しいフォルダを作る
2. VisualStudioでメニューからView→Command Palette...→java:Create Java Project...→Maven create from archetype→archetype-quickstart-jdk8
で後は適当に。
Hello World!のスケルトンが自動生成されるので
mvn installでjarを作って

java -classpath jarファイル パッケージ名+メインのクラス名

でHello World!が動く。
私の場合はパッケージ名はcom.seguにすることが多いし、デフォルトだとメインのクラスはAppなので

java -classpath example-1.0-SNAPSHOT.jar com.segu.App

みたいになる
WindowsでC++ばっかり使ってきた身からすると実行時にクラスパスを指定したりパッケージ名とメインクラス名をいちいち書かねばならないのはめんどくさい。
pom.xmlにどこかのサイトで教えてもらったあれこれを書けばjava -jar jarファイル名で実行できるようになるけれど・・・

  <build>
    <plugins>
      <!-- 実行可能jarファイル用のプラグイン -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.segu.App</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

前に作ったプロジェクトからこれをコピペしてくるのすらめんどくさい。

2024年05月11日 プログラミング雑談

Androidアプリのファイルとりまわし

今日は普通にPCで入力中。
Androidアプリ、javaでよちよち作成中。
今までPCでコマンドラインアプリでtxt→html変換アプリ作っていたときは、 htmlに変換する入力テキストファイルの選択は昔ながらのmakeを使っていたので個々のファイルの変換は気にしてなくて、原稿のtxtファイルが更新されてたらhtmlファイルを生成するという仕組みでした。そのあたりの仕組みをAndroidアプリに組み込むのが面倒だったので、原稿ファイルを一つ一つUIで選択してhtmlを生成する事にしました。

        //ファイル選択ボタンのリスナー登録
        btnFile.setOnClickListener(view -> {
            Intent is = new Intent(Intent.ACTION_OPEN_DOCUMENT);
            is.setType("text/plain");
            startForFilenameResult.launch(is);
        });

とかやってファイルマネージャーでファイルを選択して、

        // ファイル選択ボタン押下時
        ActivityResultLauncher<Intent> startForFilenameResult = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(), result -> {
                    Intent i = result.getData();
                    if (i != null) {
                        Uri fileUri = i.getData();

とかやるとfileUriにファイルのURIが入って、

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                        getContentResolver().openInputStream(fileUri)
                )
        )) {
            Stream<String> lines = reader.lines();
            // ファイルから1行読み込みList<String>に入れる
            lines.forEachOrdered(inString::add);
        }

という感じでファイルを読み込む。インテント経由で選択したファイルは読み込めるのだけれど、同じフォルダに拡張子だけhtmlに変更したファイルを書き込もうにも同じフォルダにファイルを作る事すらままならない。
Uriからフォルダはわからないけれどファイル名はとれる。ググると別の方法が出てきたような気がしますが、Androidデベロッパーサイトのチュートリアルを見ると、次の方法でファイル名がとれるとのこと

        String inFileName = DocumentFile.fromSingleUri(this, fileUri).getName();

チュートリアルサイトを見ていたらフォルダのパスとアクセス権を取得する方法も記載されてました

        // フォルダ選択ボタンのリスナー登録
        btnFolder.setOnClickListener(view -> {
            Intent is = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
            is.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
            startForFolderResult.launch(is);
        });

受け取り側は

        // フォルダ作成ボタン押下時
        ActivityResultLauncher<Intent> startForFolderResult = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(), result -> {
                    Intent i = result.getData();
                    if (i != null) {
                        Uri folderUri = i.getData();
                        if (folderUri != null && result.getResultCode() == RESULT_OK) {
                            DocumentFile folder = DocumentFile.fromTreeUri(this, folderUri);

URIは扱いが面倒だけれど、DocumentFileならばこのフォルダの下にファイルを作るのもできそう。

DocumentFile outFile = folderUri.createFile("text/plain", fileName);

でフォルダの中にファイルが作れる。

Androidアプリの基礎を知っていれば簡単なことなのかもしれないけれど、PCでjava 8でコマンドラインアプリをちまちま書いてた程度の知識レベルではここまでたどり着くのにものすごく時間がかかりました。先週の土曜から今週の土曜まで仕事をしていない時間はずっとこれを調べてたと言っても過言ではない!(いや、過言だから)
どうせ公開しないアプリなのでUIはやっつけ仕事。
アプリのUIまで手が回らない

せっかく作ったのに自分のスマホでデバッグはできるけれどリリースビルド版がインストールできずにこれまたググりまくりだったわけですが、それはまた気が向いたら。

2024年06月16日 プログラミング雑談

久しぶりにjavaプログラミング三昧

職場ではなかなか落ち着いてプログラムを書けないので土日に試作。職場のテストデータを持ち帰れないので架空のデータを使用。
DBから読み込んだデータに基づきツールで生成したhtmlファイルなのだけれど、DBのデータに一部不備があるとかで、現在のhtml生成ツールを直すか、生成したhtmlを別ツールで直すかという選択。
本来はhtml生成ツール側を直すのが正しいと思うけれど、データの不正が検出できるメソッドとそのデータを使って表などを生成するメソッドが遠くて、データの不正が発覚した時点ではもうhtml生成の面で後戻りできないように見えたので、別ツールを作ることに。
javaでjsoupを使う。
jsoup自体はこの年末年始に結構戯れていたのでそれなりに使えるはず。
でもjavaの基礎的なところが頭に入っていないのでMapの初期化ってどうやるんだっけ? という次元のところでめんどくさい。
どうせ忘れてしまうので備忘録
HashMapの初期化

    private static Map<String, String> fileToStrMapE = new HashMap<>();

    static {
        fileToStrMapE.put("indexx.html", "STRING_for index.html");
        fileToStrMapE.put("somefile.html", "STRING_for index.html");
        fileToStrMapE.put("nofile.html", "no title line");
        fileToStrMapE.put("errorfile.html", "empty title string");
    }

List<String[]>の初期化
String[][] tableStrを作る

new String[][]{{"aaa", "bbb"}, {"cc", "dd"}, {"e", "f"}}

で、作ったtableStrを使って

        List<String[]> listStringArray = new ArrayList<>();
        Collections.addAll(listStringArray, tableStr);

とかやればList<String[]>にデータが入ってくれるみたい。

あとは何だったっけ。そうそう、Mapから文字列を取り出すのだけれど、keyに対応するvalueが無い場合にnullが返ってきたりするのでOptional.ofNullableとか

        String fileOnly = Paths.get(inFile).getFileName().toString();
        Optional.ofNullable(MyResources.selectToString(fileOnly))
            .ifPresent(o -> { // selectToStringでMapから取り出した値が存在する場合
                // 何かする
            });

ありゃ? よく見ると肝心の「Mapから取り出した値」を使っていない(笑)

gradleを使うつもりだったけれどその設定も忘れ去っているのでググりまくり。
build.gradle.ktsにいろいろコピペしました

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    java
    application
    checkstyle
    jacoco
}
dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation(libs.junit.jupiter)

    testRuntimeOnly("org.junit.platform:junit-platform-launcher")

    // This dependency is used by the application.
    // implementation(libs.guava)
    implementation("org.jsoup:jsoup:1.18.1")
}

jacoco {
    toolVersion = "0.8.11"
    applyTo(tasks.run.get())
}

tasks.register<JacocoReport>("applicationCodeCoverageReport") {
    executionData(tasks.run.get())
    sourceSets(sourceSets.main.get())
}

tasks.jacocoTestReport {
    dependsOn(tasks.test) // tests are required to run before generating the report
    reports {
        xml.required.set(false)
        csv.required.set(false)
    }
}

tasks.named<Test>("test") {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

tasks.javadoc {
    options.encoding = "UTF-8"
}

こんなところかな。VSCodeのコンソールの文字コード設定なのかjavadocのコンソール出力の文字コードの問題なのか、何か文字化けするけれどjavadoc出力には問題ないみたいだからとりあえずこれでいいかな。
それにしてもcheckstyleさん、指摘が細かい。

2024年08月04日 プログラミング雑談

Jsoup、XHTMLで&nbsp;が出ない・・・

このwebページはテキスト形式の原稿からjavaで自作したツールでhtmlに変換しています。といういつもの枕詞を書いたところで。
以前ツールを作ったときは&nbsp;の出し方がわからなかったので、ま、改行が入ってもいいやってことでスペースを出力して妥協していました。ところが業務でJsoupでhtmlを取り扱う必要があって突貫でツールを作ったところでどうしても&nbsp;を出力しなければならなくなったのでググってみました。
以前自力で調べようとしていたときはJsoupのソースの中に"&nbsp;"という文字列が存在しているようなので何らかの方法で出せるはず!という気はしていたのですよね。
で、結局"\u00a0"が"&nbsp;"に変換されるらしいということで、業務で作っていたツールは解決しました。

ところが、このwebページを変換するツールの方は"\u00a0"で"&nbsp;"にならず"&#xa0;"と出力されてしまいます。
業務のツールもプライベートのツールも私が書いたのに・・・なんでや?!
結局、JsoupのDocumentインスタンスでoutputSettings.escapeMode(EscapeMode.base)を設定すればOK。
業務のツールはOutputSettings.Syntaxが多分デフォルトのhtmlだったけれど、プライベートのツールはxmlに設定していたのでEscapeModeも自動的にxhtmlになっていたのだと思います。

んー、ってことは、もしかしてxhtmlの場合は&nbsp;と出力するところを&#xa0;と出力するのが正解なのかな?

	Document doc;
	// この間にいろいろな処理
        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
        doc.outputSettings().escapeMode(EscapeMode.base);

2024年08月12日 プログラミング雑談

Jsoup appendChildとappendElementはわかった気でいても、つい間違う

        Element body = new Element("body");
        body.appendChild(new Element("div").appendElement("div").attr("name", "test")
                .appendText("[Initial Value]")).appendElement("br")
            .appendChild(new Element("b").appendText("[Value Range]"));

ってやると

 <body>
  <div name="1st">
    <div name="2nd">
     [Initial Value]
    </div>
  </div>
  <br><b>[Value Range]</b></br>
 </body>

ってなるかなと思うと

 <body>
  <div name="2nd">
   [Initial Value]
  </div>
  <br><b>[Value Range]</b></br>
 </body>

になる。
最初のbody.appendChild()でbodyの子エレメントになるのはnew Element("div")ではなくて.appendElement("div")の方。だから.appendElement("div")のdivに[Initial Value]がつく。
いつもわかった気になるのだけれど、しばらくJsoup使わないと忘れちゃう。
.appendChild()やattr()は親Element側が戻り値になる。
.appendElement()だけが特殊で、ここで作られた新しいElementが戻り値になる。

2024年08月13日 プログラミング雑談

FTPアップロードのテスト

javaプログラムからftpのアップロードができるか確認です
PCからはアップロードできましたが、ChromeBookからはアップロードできませんでした。

ftp.storeFile("filename", instream);

のところで失敗しているみたい。

試行錯誤の結果、今のところChromeBookのLinuxではcurlでアップロードすることにしました。

curl -T {アップロードファイルパス} -u {ユーザー名}:{パスワード} ftp://{ホスト名}/{任意のアップロード先ディレクトリ}

でできるみたい。

でもこのままだとPC用はFTPでアップロードする実装が入っていて、ChromeBook用は入っていない状態になってしまう。これはコード管理上めんどくさいのでChromeBook上でデバッグするか・・・ってVSCodeベースでjavaビルド環境を作ろうとしていますが、gradleのエクステンションを入れたところでVSCodeが実用に耐えないレベルで重い。どのくらい重いかというとjavaのHello world!がビルド出来ないくらい(笑)
もしかしてjreは入っているけれどjdkが入っていない?

javac App.java

したら、javacが見つからないって言われてしまった。

apt list --installed | grep jdk

したところ、それっぽいのが入っていない・・・。なんてこったい。

sudo apt-get install default-jdk

でjdkを入れて、javacコマンドが通ることを確認してVSCodeを起動しなおし。
VSCodeのブラグインはPCと同じように入れたのだけれど、ビルドはできるけれどVSCode上でjavaプログラムの実行もデバッグも固まってしまってできない感じ。ビルドしてコマンドラインから実行しながらよちよちデバッグしてました。
結局、

ftp.enterLocalPassiveMode();

でパッシブモードに入ったらftpのアップロードができるようになりました。
このwebページの作り始めの時期に手書きのhtmlファイルをftpでコマンドを打ちながらアップロードしていた時代に、パッシブモードにしないと固まることがあったので、このあたりを最初に疑っていました。
FTPクライアントが入っていればこんなに苦労しなかったのだろうけれど・・・
まあ、とりあえず動いてよかった。コードをきれいにして単体テストとかコメント書いたりすれば出来上がりってことになるかな。

2024年08月14日 独り言,プログラミング雑談

FTP書き込み処理をAndroidアプリに入れる

このwebページ生成プログラムはjavaで書いているので理想的にはWrite once, run anywhere.なのだろうけれど、Andropidはセキュリティの制限でファイルアクセス関係が独特で、PC用のコードはそのままでは動かない。
今回はGUIではない、CLIとかCUIとかいうコマンドラインで起動するタイプのアプリなので、Android用に簡単なGUIでボタンを押すと各機能が呼び出されるようなアプリをとりあえず作りました。前回のAndroidアプリ化は6月だったので2か月ぶりにAndroid Studioを起動した感じ。
今回もファイルアクセスがあるのでPC用のコードから変更があることは覚悟していました。PC用アプリならばファイル名がわかれば

Paths.get(ファイル名)

でPathにしたり、

Paths.get(ファイル名).toFile()

でFileにしたり。Javaアプリのファイル操作はPathかFileで何でもできそうな気がするので、ここ何週間かのプログラマ生活でだいぶ慣れた感じがします。
一方でAndroidアプリはちゃんと勉強していないせいでまだ理解できた感が全くありません。
Androidアプリはユーザーが普通にアクセスすることができないアプリ専用の領域ならばファイル名でアクセスできるみたいですけれど、それ以外はURIという謎の名前でアクセスする必要がある模様。
テキストエディタで書いたwebページの原稿をgrepしてから必要なファイルだけ書き換えて・・・とか、どうやればいいのかいまだによくわかりません。とりあえず「扱いたいファイルはこのフォルダにあるからね」とユーザーに選択してもらえば、そのフォルダのファイルは割と自由に扱えるみたいなので、今回は必要なファイルが単一のフォルダに全部あるという前提でAndroid化しました。
それでも最初の操作対象ファイルはURIで指定されるので

String inFileName = DocumentFile.fromSingleUri(this, fileUri).getName();

でUriで指定されたfileUriからinFileNameに(フォルダ名が無い)ファイル名を取り出したり

DocumentFile folder = DocumentFile.fromTreeUri(this, folderUri);

でフォルダ情報をDocumentFile形式にして

ContentResolver cr = getContentResolver();

で取得したContentResolverを使って

InputStream instream = getContentResolver().openInputStream(inFileName.getUri());

でUriで指定されたファイルをInputStreamにして

getFtpClient().storeFile(書き込み先のファイル名, instream);

でFTPでファイルアップロード。

PCでjavaコードを書いていたときはApacheのFTPClientの説明どおりにコードを書けばそのまま動きましたが、Androidの場合はこの処理を

    Executors.newSingleThreadExecutor().execute(() -> {
        ... 何か、FTPでファイルアップロードするコード
    });

みたいに別スレッドに移さなければならない模様。
さらに、私はまだよく理解できていないのだけれど、AndroidのUIに何か表示するためにはその時使っている"Activity"のクラスにUI操作のコードを書く必要があるみたいで、Activityとは別クラスのFTPクラスの処理中にユーザーに何か伝えたくてもやり方がわからない。FTPクラスのアクティビティ用にUIを持ったクラスを作ればいいのかしら。

2024年08月16日 独り言,プログラミング雑談

安いChromeBookでgradleは重すぎたけどmavenはいける

ChromeBookでjavaで書いたFTPの処理がうまく動かない件で、ChromeBookに開発環境入れてみたときに、gradleは重すぎて実用にならないと思いましたがmaven(VSCode + maven)は使えなくもない感じでした。

2024年08月18日 プログラミング雑談

SeleniumはJava8で使えなくなってたのね

htmlファイルのリンクチェックツールを更新しようと思って、SeleniumをJavaで使おうとしたらランタイムエラー。バージョン55が必要なのに52までしか動かない環境だよ、みたいなメッセージ。
htmlファイルのチェックツール、最近作ったときはPlaywright使ってたし、以前Selenium(Selenide)使ったときはJavaではなくてC#(.net)で使ってた気がします。
最近私のPCのJavaをAmazon Correttoに切り替えたばかりなので何か環境に問題があるのかなと思ってあれこれググってたところ、結局SeleniumはJava 11以上のみサポートに変わっていた模様。
Java 11かぁ。もうちょっとJava 8で遊んでいたいので今回はSeleniumは使わないことにします。

別件ですが
Javaって、JDKのフォルダにJREのフォルダもある事には気が付いていたけれど、なんとなくJREをインストールしてからJDKをインストールするものだと思い込んでいました。VisualStudioCode + Amazon Correttoでmavenでのコンパイルに失敗するのでなんでだろうと思ってたけれど、結局JAVA_HOMEがjreの方を指していたのが原因だった模様。うーん、職場のPCにJREをインストールしてたけど、こっちもアンインストールしよう。

2024年10月27日 プログラミング雑談

javaのenumはCのenumとは別物

プログラミングはFORTRANで入門したものの、その後ボードマイコンや低スペックPCを使う中でアセンブラを使い、Cみたいな低レベルのネイティブ開発言語こそが至高!という潜在意識が消えることなく21世紀を迎え、平成末期に仕事の都合でjavaに入門。javaでの開発も良いものだと思い始めたのはここ2年位というプログラミング歴の私。
javaってC++みたいなもの、という気持ちで入門していたのでjavaでC++と同じ用語が使われていると同じ概念かなと勘違いしていたところが多数あります。例えばenum。
これを書く前にちょっとenumでググってみましたが、C界隈ではなんかenumって忌み嫌われた存在のようで、C++的にはenum classを使うのが推奨されるのかな。
javaのenumもCのenumみたいなものかなと思い込んでいましたが、Effecitve Java読んでたらjavaのenumってCのenumやC++のenum classとは全然別の存在であることに今更気が付きました。C++のenum/enum classを私が正しく理解しているという自信もないのですけれど(話す前から言い訳が先行)

Effecitve Java 164ページ(もちろん日本語版)に書かれている「enum型にabstractメソッドを宣言して、各enum定数にそれぞれの振る舞いをoverrideする」コードにちょっと衝撃を受けてます。
そんなことができるの?って感じ。
C/C++気分でenumの列挙値を作っておいて、それぞれの列挙値に対応したKey文字列を使ってMapでValueを管理とかしていたのがもしかしてenumの中だけで完結しちゃうの?
仕事のコードだからコピペで持ってこれないから気分だけ書いてしまうと

public enum Entry {
    FIRST, SECOND, THIRD,
};

public class App {
    public static Map<Entry, String> xxx = new HashMap<>();
    static {
        xxx.put(Entry.FIRST, "一番目の文字列");
        xxx.put(Entry.SECOND, "二番目の文字列");
        xxx.put(Entry.THIRD, "三番目の文字列");
    }
}

みたいなコードは不要で、

public enum Entry {
    FIRST("一番目の文字列"),
    SECOND("二番目の文字列"),
    THIRD("三番目の文字列"),
};

としておけば、

    System.out.println(Entry.FIRST.toString());

でいいし、

public enum Entry {
    FIRST, SECOND, THIRD,
};

public class App {
    public static Map<Entry, Supplier<zzz>> xxx = new HashMap<>();
    static {
        xxx.put(Entry.FIRST, zzzImplFirst::new);
        xxx.put(Entry.SECOND, zzzImplSecond::new);
        xxx.put(Entry.THIRD, zzzImplThird::new);
    }
    public void create(Entry key) {
        Optional.ofNullable(xxx.get(key)).ifPresent(o -> o.get().xxxMethod());
    }
}

も、多分

public enum Entry {
    FIRST{public void xxxMethod() { // do something; } },
    SECOND{public void xxxMethod() { // do something; } },
    THIRD{public void xxxMethod() { // do something; } };
    public abstract void xxxMethod();
};

で処理を振り分けられる。
実際のコーディングではjavaコンパイラに受け入れてもらえるようにちゃんと書かなければなりませんけれど、インターフェースを定義してインプリメントのクラスを作って・・・という回りくどいことが不要になるケースが結構ありそう。

2024年11月10日 プログラミング雑談

jdk17ではjacoco0.8.8以上が必要だった

週末、久しぶりにプログラムを書く気になったので、8月頃に更新したこのwebページ用のhtmlを生成するjavaプログラムをメンテしてました。2か月とか経っているのですっかりどんなコードを書いたか忘れている。
開発環境はWindows 11のPCでjava8でmavenで・・・という感じ。archetype-quickstart-jdk8ってのを使っているのでjunitはjupiter、jacocoとかsurefireとか入ってます。
実行用jarを作るのにmvn installとかするとcheckstyleが口うるさいことを言ってくるけれど都度対応しているし、jacocoもメソッドのサイクロマチック数5以下とか言ってくるのでifとかelse ifとか使っているとあっという間に違反してしまうのでコーディングしたいロジックよりもいかにメソッドのサイクロマチック数を小さくするかにエネルギーを使っていたり。
最近ChromeBookでもjavaの開発環境を作ってみたのでPCからソースファイル一式をChromeBookに持っていってmvn installしてみた。
そしたら単体テスト中にlog4jあたりでExceptionが発生。

java.lang.instrument.IllegalClassFormatException: Error while instrumenting sun/util/resources/cldr/provider/CLDRLocaleDetaMataInfo.
        at org.jacoco.agent.rt.internal_  (以下省略)

って感じ。Exceptionが発生したらスタックトレースを自分の書いたコードまでさかのぼって修正するものだと思っていますが、log4jでgetLoggerするあたりなもので、log4jのとりまわしはよくわからないままチュートリアル的な処理をコピペしてたのであんまり理解できていないのですよね。
javaってWrite once, run anywhere.じゃなかったんかい?とか愚痴りながら、それでも心当たりがありそうなところを手直ししていました。
PCではちゃんとmvn installが正常終了するのになぜ? という観点で見ていましたが、もしかしてPCはjdk8でChromeBookはjdk17なのが悪さしている? ってことで、ググるキーワードにjdkとか追加してましたらやっと原因らしいものにたどり着けました。
結論: jacoco-mavne-pluginのバージョンを0.8.4から0.8.12(最新)にすべし
jacoco 0.8.8で

JaCoCo now officially supports Java 17 and 18 (GitHub #1282, #1198)

ってことだそうです。
pom.xmlに

<jacoco-maven-plugin.version>0.8.4</jacoco-maven-plugin.version>

ってあったので、0.8.12に書き換えたらExceptionが解消。やっと心穏やかにChromebookでjavaのコンパイルができます。

そうそう、去年の春まで使っていたPCのubuntuを22.?から24.?に入れ替えました。こっちでもVisual Studio Code入れてjdk17を入れてコンパイルできるようにしました。まだgitは入れていません。
WindowsではコードのマージとかにWinMergeを使っていますがLinuxに同様のツールは何があるのだろうとググったらmeldというのがヒット。入れてみました。細かい使い勝手は違うけれど、これで十分っぽい。
linuxでブラウザはあるしエディタはあるしjavaの開発もできるし、なんかWindowsが無くてももしからしたらひどく困ることはないのかも。

2024年11月17日 プログラミング雑談

Effective Javaを読み進むごとにjavaの難しさを感じる

今年、2024年2月に書籍Effective Javaを買ってきて、よちよち読んでいますがまだ読み終わりません。
実はEffective Javaをいつ買ったのかなと小遣い帳を見ていたらプログラミング言語Javaは2011年に購入していてちょっとびっくりしていました。てっきり2017年頃買ったのだとばかり思っていたので。Androidのスマホを買ってちょっとAndroid向けのアプリを書いてみたかったのかもしれません
で、Effective Javaの方ですが、やっとページ数で6割くらい読み進んだ感じです。
読めば読むほど自分が幼稚なJavaプログラムを書いていたことを認識してしまいます。まあ仕事ではあまりjavaプログラムを書いていないので主に趣味のコードを書き換えたくなります。
プログラミング言語C++を読んだ時もいろいろショックを受けましたが、あの時は"クラスのあるC"レベルから"C++ 11"に知識をアップデートするのが主目的で、仕事ではC++ 11のコードを書いていないはずなので、自分のプログラミングレベルの幼さよりもC++ 11の新しさに圧倒された感じでした。

で、今週位に読んだ範囲ですと、私は去年の年末あたりからmavenでjacocoあたりにメソッドのサイクロマチック数5とかクラスのサイクロマチック数20とかでmaven installできなくなるので、サイクロマチック数を減らすという観点でstreamなどを使ってjava 1.8の世界に浸っていたわけですが、streamって考えなしに使うなと怒られてしまいました。streamのforEachとかでローカル変数を書き換えてはいけないようです。私ってばコンパイルが通ればあまり気にしていませんでした。
あとはprivate変数でコンテナを持って、コンテナはpublicのgetなんちゃら()というアクセサでアクセス方法を限定して公開していたつもりでしたが、getなんちゃら()でprivateのコンテナをreturnするとコンテナを書き換えられてしまう可能性があるのでセキュアではないとか。

本の最初の方でもequalsやhashCodeのオーバーライドとかComparableの実装とか、クラスを作っても全然意識したことが無かったので目から鱗でしたし。

Effective JavaってJava 9の時代の本なんだよなぁ。ChromeBookに入れられるJDKは既にJava 17だし・・・。Effecitve Javaを読み終わって、Javaが少しわかったような気になったところで世界はもっと先に進んでいるのですよねぇ。全然追いつけません。

2024年11月24日 独り言,プログラミング雑談

JavaのリフレクションでEnumを探す

Javaでプログラムを書いていて、publicクラスAppの中のprivate enumのMakerを引数に持つAppの中のprivateメソッドをテストしたい。
リフレクションを使えばできると聞いてはいるけれど、この手の細かいことは覚えてられないので備忘録的なメモ

// Appクラス
public final class App {
    private enum Maker {
        INDEX("index", Index::new),
        KEYWORD("keyword", Keyword::new);
    }
    private boolean execGen(Maker htmlMode, String inFileName) {
    }
}
// AppTestクラス
class AppTest {
    @Test
    void appTest_execGen() {
        try {
            Class<?> enumAppMaker = null;
            for (Class<?> clazz : App.class.getDeclaredClasses()) {
                if ("Maker".equals(clazz.getSimpleName())) {
                    enumAppMaker = clazz;
                    break;
                }
            }
            Map<String, Enum<?>> makerMember = new HashMap<>();
            for (Object enumConst : enumAppMaker.getEnumConstants()) {
                makerMember.put(((Enum<?>) enumConst).name(), (Enum<?>) enumConst);
            }
            Method method = App.class.getDeclaredMethod("execGen", enumAppMaker, String.class);
            method.setAccessible(true);
            // 入力ファイルがnullの場合はエラーになる
            assertFalse((boolean) method.invoke(app, makerMember.get("INDEX"), ""));

Appクラスの中ではexecGen()もenum Makerもprivateだからいろいろめんどくさい。
execGenの方はメソッドだからググればすぐにヒット。でも引数のenum型の指定方法がわからない。
App.class.getDeclaredClasses()でAppの中のクラス(enumを含む)の配列を取得して、getSimpleName()がenum名、ここでは"Maker"に一致するクラスが探しているenum。
見つけたClass<?>型のenumをgetEnumConstants()するとenum.Values()的な意味合いになる模様。
これを使ってメソッドexecGen()を探したりメソッドexecGen()の引数にしたり。

2024年11月30日 プログラミング雑談

Java mavenで依存jarのクラスパスなどをMANIFESTに書く

2024年5月11日にjava -classpath jarファイル パッケージ名+メインのクラス名 で実行jarが動くとか書いていましたが、maven-jar-pluginを入れれば

java -jar jarファイル

で実行jarが動くそうな。pom.xmlに

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <mainClass>com.example.App</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>

という感じにメインクラス名を書けばいい模様。<mainClass>の中がアプリのメインクラス名、依存jarがある場合は<addClasspath>をtrueにするだけらしい。
Apache Maven Projectの中の説明

2024年12月07日 プログラミング雑談

javaのUIテストについてググってる途中の備忘録

数年前C++でネイティブ開発こそ至高と考えていた時代にjavaのUIってWindowsでいうところのspyとかで見えないからUIテストができないと思っていました。
今年一年javaプログラミングに取り組んでみて、とりあえずswingというライブラリでGUIプログラムを一つ書いてみようかという気分になったところで改めてjavaのUIテストについてググっています。
この手の細かい話はついついtwitterに呟いてしまい、後日見つけられなくて再びググることになるので今回は自分のwebページに呟くことにします。今は簡単に呟けるようにしたし。
Windowsでネイティブアプリと同様にUIテストしたいならば、java access bridgeを有効にすると良いらしい。
Java Access Bridge の有効化とテスト
最近は私のPCはjdkのフォルダにパスが通っているので

jabswitch -enable

でよさそう。
でもspy++で見えるようになった感じがしない・・・

まだいろいろググっている途中だけれど、自分で作るjavaアプリならばjavax.accessibilityを使えば、というか、accessibilityを提供するようにGUIを作ればjavaの中からGUIを疑似的に操作できるような気がしてきました。

2024年12月22日 プログラミング雑談

java swingで画面解像度に合わせて文字サイズを設定する

24インチの4KモニタなどというAmazonで探してもLGから出ている1機種しか出てこないようなモニタを使っていると世間のチュートリアル通りにプログラムを書いていても期待通りに動作しないことがたびたび。
「Java GUIプログラミング」なるSwingを使ったチュートリアル本を読みながらヨチヨチプログラムを書いてみています。
WindowsでいうところのウインドウはJavaのSwingではJFrameに相当するようです。

JFrame frame = new JFrame("ウインドウのタイトル");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウを閉じる処理を登録しておく
JLabel label = new JLabel("Main Text");
frame.add(label);
final int width = 260; // 幅
final int height = 260; // 高さ
frame.setTitle("ListBox"); // ウインドウのタイトル
frame.setSize(width, height); // ウインドウのサイズを設定する
setVisible(true); // 表示する

これだけでウインドウを表示するだけのプログラムになるという事で、30年前にC++でWin32 APIでWindowsアプリのHello world!書いた時よりもずいぶん簡単。多分C#で.netだともっと簡単になっているのでしょうけれど。
で、上記7行のプログラムは24インチ4Kで200%スケーリング表示しているPCだと「200%スケーリング」が反映されていないみたいで表示がちっちゃい。ウインドウの幅と高さを260ピクセルで即値で指定しているのでそれはまあプログラマの私の責任なんだけど、ウインドウの中に表示されるlabelの文字がちっちゃいのが困る。

final Font font = new Font("BIZ UDPゴシック", Font.PLAIN, 12);
label.setFont(font);

ってすれば、Fontのコンストラクタの第3引数は「size - Fontのポイント・サイズ」ですので12ポイントで文字が表示されそうなものですが、文字がちっちゃい。
フォントのポイント・サイズは、本来は1ポイント(1/72)インチのはずですが、画面の解像度に合わせて文字の大きさを制御してくれるわけではなさそう。

private static int ptScale(int point) {
    return point * Toolkit.getDefaultToolkit().getScreenResolution() / 72;
}

とか用意しておいて、

final Font font2 = new Font("BIZ UDPゴシック", Font.PLAIN, ptScale(12));

すると、期待通りの文字サイズで表示されました。
んー、ってことは、Fontのコンストラクタで指定した文字サイズはFontのピクセル・サイズみたいですね。

2024年12月29日 プログラミング雑談

chatGPTとかSQLとか

流行りのchatGPSとかは食わず嫌いであまり使っておらず、また、SQLはSQLiteを我流でSQLiteサイトのチュートリアル見てリファレンスのページを見てなんとなく使っている私です。
先日、「その月までの累積の数値」が入ったテーブルから「その月の数値」を求めたいと思いましたが、SQLの書き方がわからず難儀しました。
そんなのExcelに入れて差分を計算すれば瞬殺じゃん、というのがまあ普通の反応だと思います。でも今回はExcelを使わない縛りでどうしても作ってみたいと思ってしまったのですよね。
1時間くらい考えて、やってみた方法は、即値でWHERE month='先月の日付'で先月の数値のviewを作って、同様の方法で今月の数値のviewを作って、SELECT 今月の数値 - 先月の数値 で差分を作るという方法。
何日か経ってしまったので記憶があいまいですが、確か、"SQLite データ 先月との差"みたいな検索ワードでググってみましたが、日付の計算みたいなのばかりヒットしてデータの差分の出し方にうまくたどり着けなかったのですよね。
そういえば職場でもAI活用って話題が出ていたなぁと思い、chatGPT君に尋ねてみることにしました

sqliteで月ごとの料金が入ったテーブルがあり、前の月との差異を出力したい

すると「SQLiteで月ごとの料金データが入ったテーブルにおいて、前の月との差異を求めるには、自己結合を使用する方法が一般的です。」とのことで、次のコードが出てきました

SELECT 
    t1.month,
    t1.charge AS current_charge,
    t2.charge AS previous_charge,
    (t1.charge - t2.charge) AS difference
FROM 
    monthly_charges t1
LEFT JOIN 
    monthly_charges t2 ON strftime('%Y-%m', t1.month) = strftime('%Y-%m', t2.month, '+1 month')
ORDER BY 
    t1.month;

monthly_chargesがデータの入ったテーブルです。
ここには転記しませんがchatGPT君はクエリの解説までしてくれました
FROMとJOINで同じテーブルを結合するなんて、SQLってそんな使い方ができるの? とか、SELECTの出力対象にmonthly_chargesが無く、後から出てくるFROMやJOINでつけたt1とかt2とかの名前で参照するなんてアリなんだ? とか、目から鱗です。いや、ちゃんとSQL勉強しろよ、って言われた気がしました。
ちなみに、もしかしたら上記SQLは動くかもしれませんが、その時に私が得たSELECTは期待した出力が出てこなかったので、ちょっと手直しして、データを読み込むimportを追加して、次の形になりました

.mode csv
CREATE TABLE monthly_rates (
    id INTEGER PRIMARY KEY,
    month TEXT,  -- YYYY-MM 形式で年月が格納される
    rate INTEGER -- 料金
);
.import rates.csv monthly_rates
SELECT
    current.month AS current_month,
    current.rate AS current_rate,
    previous.rate AS previous_rate,
    (current.rate - previous.rate) AS difference
FROM
    monthly_rates AS current
LEFT JOIN
    monthly_rates AS previous
ON
    strftime('%Y-%m', date(current.month || '-01')) = strftime('%Y-%m', date(previous.month || '-01', '+1 month'))
WHERE
    current.month = strftime('%Y-%m', '2025-02-01');

monthに'YYYY-MM-DD'形式でデータが入っていないので「|| '-01'」を追加しただけだったんですけどね
私がchatGPTに入れた指示が適当だったので完全な答えは得られませんでしたが、chatGPTってスゲーって思いましたね。もう、知らないことをググるよりもchatGPTに聞いてから、それが正しいかどうかググったり自分で実験して検証する という手順の方が良いのかもしれません

この、今日のこの記事を書くにあたり、あらためてchatGPT君に聞いてみました

sqliteで月ごとの料金が入ったテーブルから前の月との差異を出力したい

で、出てきたのは次のSQL

SELECT 
    month,
    fee,
    LAG(fee) OVER (ORDER BY month) AS previous_fee,
    fee - LAG(fee) OVER (ORDER BY month) AS fee_difference
FROM 
    monthly_fees
ORDER BY 
    month;

ちょっと指示が異なるだけなのに全然違うのが出てきちゃったよ
LAGとかORDERって何? この記事を書き終えてから勉強します
chatGPT君のクエリの解説に「前月のデータがない最初の月(データが存在する場合)は、差異が NULL になるので、それを適切に処理したい場合は COALESCE を使って NULL を 0 に変換することができます。」とありました。

SELECT 
    month,
    fee,
    LAG(fee) OVER (ORDER BY month) AS previous_fee,
    COALESCE(fee - LAG(fee) OVER (ORDER BY month), 0) AS fee_difference
FROM 
    monthly_fees
ORDER BY 
    month;

COALESCEって何?(以下略)

ってことで、SQLの本を買ってきました。「初めてのSQL」O'REILLYの翻訳本。ジュンク堂でその他の本もいろいろ手に取りましたが、ほんとのほんとに初歩の初歩から手取り足取りのチュートリアル本が多くて、いや、そのあたりの話は全部すっ飛ばしていいのでもうちょっと実のある話をしてくれないかなぁ? とか思ってこの本になりました。
あと、AIに対するアレルギーを治療するために「ディープラーニングG検定 最強の合格テキスト」(ソフトバンクパブリッシング)ってのも買ってきました。緑本って呼んでほしいみたいです。
2024年は2月にEffective Javaを買ってきて学ばせていただきましたし、今年はSQLとAIの初歩くらい身に着けたいものです。

2025年02月02日 プログラミング雑談

AndroidStudioでjavaを使うまでにいきなり壁

やっと重い腰を上げて小遣い帳アプリを書こうとしているのに、AndroidStudioで入門書を見ながらEmpty Activityを作るとjavaが選択できずkotlin一択になる。
Geminiさんに聞いても「最新のバージョンのAndroidStudioではJavaが使えない場合があります」などと冷たいコメントしかないし。
ググってもなんかアフィリエイト目当てっぽい、どこかのチュートリアルをそのまんま引き写したような古い情報に基づく記載しかないサイトばっかりヒットするし。
結局、qiita内のサイトが一番頼りになりますね。javaを選択するにはEmpty Views Activityを選択する必要がありました。
いきなりこんなところに壁を作らなくてもいいのに。

2025年04月02日 独り言,プログラミング雑談

Androidアプリ 次の壁

前回までののあらすじ
2011年Android 2.3時代に使い始めた小遣い帳アプリがAndroid14のスマホにインストールすらできないことが判明。これでもプログラマーの端くれ(ただし永遠の初心者)、自分で小遣い帳アプリを作ろうとAndroidアプリ開発のチュートリアル本を見ながら作成に着手。
チュートリアル本を見ながら作り始めたものの、Javaで開発したいのにJavaを選択できない! この問題はしばらくググってやっと解決したのであった

ってことで、今日はメニューを表示して今まで使っていたcsvファイルをインポートする処理を作ることにしました
作りたいメニュー
チュートリアル本を見るとoption_menu.xmlとかいうファイル名でres\menuフォルダの下にMenu Itemを配置するだけで良さそう。でもどうしてもメニューを表示するためのボタン(…が縦に並んだやつ)すら表示されません。チュートリアル本を隅から隅まで読んだわけではないので表示方法がどこかに書いてあったのを読み飛ばしたかしら? でも普通にEmpty Views Activityを作ってそこにoption_menu.xml作ってMainActivity.javaにonCreateOptionsMenu()メソッドを置くことしかチュートリアル本に書いていないし・・・。
たかがメニューを表示することで悩むこと2時間とか。結局今回はGeminiさんに聞いてみたらマニフェストに書かれているthemeがNoActionBarになっているのでは? ってことで、マニフェストファイルを開いてそれっぽいところを見たら確かに書いてありました。そんな。私はそんなところいじっていないよ。Empty Views ActivityのデフォルトがNoActionBar設定なのね? そんなところに罠が仕掛けてあるとは・・・。
一応チュートリアル本は一通り斜め読みして大体雰囲気だけはつかんだ気でいましたけれど、ちゃんと最初から最後まで熟読することが結局近道なのでしょうか。急がば回れとも言いますし・・・・
でもチュートリアル本をざっと見た感じだとAndroidアプリ開発はそんなに難しそうに見えなかったのだけどなぁ。
今日はメニューが表示されたところで力尽きました。メニューを選択したら何か動くところまで作りたかったけれど、まだ表示しただけで何も動いていません・・・・
5月連休が終わるまでに自分が使えるレベルにしたかったけれど、こりゃちょっと難しいかなぁ

2025年04月05日 独り言,プログラミング雑談

ysktrt.moneynoteのデータをインポートできた

Android14で動かないysktrt.moneynoteのデータを読み込めて、今後も小遣い帳として使い続けるためのysktrt.moneynoteとデータ互換のある自作アプリの開発中。
Androidアプリはチュートリアル本見ながらHello worldしただけのレベルだからつらい。
やっとysktrt.moneynoteでエクスポートした小遣い帳データを読み込んでDBに入れる処理が書けた。とりあえず動いたってレベルでしかないけれど。
さて、次はデータを表示する処理を書かねば・・・。その次は日々の入出金を入力する処理。過去データを検索する機能も欲しいな。
最初は5月連休中に使い始められるレベルまでもっていきたかったけれど、この調子では今年中に使えるようになる事やら。

2025年04月14日 プログラミング雑談

Valid XHTML 1.0!Valid CSS!