logback のログ開始を抑制

logback のログ出力で最初に以下の余計なログが出力される場合の抑制

13:29:48,061 |-INFO in ch.qos.logback.classic.LoggerContext[default] - This is logback-classic version 1.4.6
13:29:48,088 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
13:29:48,091 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/D:/pleiades/pleiades_202212/workspace/sample/target/classes/logback.xml]
13:29:48,143 |-WARN in ch.qos.logback.classic.joran.action.LevelAction - <level> element is deprecated. Near [level] on line 19
13:29:48,143 |-WARN in ch.qos.logback.classic.joran.action.LevelAction - Please use "level" attribute within <logger> or <root> elements instead.
13:29:48,176 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - Processing appender named [STDOUT]
13:29:48,176 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
13:29:48,182 |-INFO in ch.qos.logback.core.model.processor.ImplicitModelHandler - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
13:29:48,206 |-WARN in ch.qos.logback.core.model.processor.AppenderModelHandler - Appender named [STDOUT_DEBUG] not referenced. Skipping further processing.
13:29:48,206 |-INFO in ch.qos.logback.classic.model.processor.LevelModelHandler - org.labo level set to DEBUG
13:29:48,206 |-INFO in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Attaching appender named [STDOUT] to Logger[org.labo]
13:29:48,207 |-INFO in ch.qos.logback.core.model.processor.DefaultProcessor@23bb8443 - End of configuration.
13:29:48,207 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@1176dcec - Registering current configuration as safe fallback point

logback のマニュアル:Chapter 3: Logback configuration に書いてあるのですが、
https://logback.qos.ch/manual/configuration.html#logback.statusLC
これを出力しないようにするには statusListener NopStatusListener を割り当てます。
以下のように、logback.xml で、<configuration> に、
 <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
を記述します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
	<Target>System.out</Target>
	<encoder>
		<Pattern>%-23d{yyyy/MM/dd HH:mm:ss.SSS} %-5p [%thread][%class#%method]%m%n</Pattern>
	</encoder>
</appender>
<appender name="STDOUT_DEBUG" class="ch.qos.logback.core.ConsoleAppender">
	<Target>System.out</Target>
	<encoder>
		<Pattern>%-23d{yyyy/MM/dd HH:mm:ss.SSS} %-5p [%thread][%class#%method:%line]%m%n</Pattern>
	</encoder>
</appender>

<logger name="org.sample">
	<level value="debug" />
	<appender-ref ref="STDOUT" />
</logger>

</configuration>

検証した logback 使用のpom.xmldependency

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-core</artifactId>
	<version>1.4.6</version>
</dependency>
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.4.6</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>2.0.7</version>
</dependency>

Java Socket通信の簡単なサンプル

標準キー入力による簡単なサンプル

受信サーバーソケットプログラム
同時複数接続を可能とするために、accept() で接続した Socket は、子スレッドで close する。

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * ReceiverMain
 */
public class ReceiverMain{
    public static void main(String[] args){
        Logger logger = LoggerFactory.getLogger(ReceiverMain.class);
        logger.info("■ 開始");
        try(ServerSocket server = new ServerSocket()){
            server.bind(new InetSocketAddress("localhost", 8091));
            while(true){
                Socket sc = server.accept();
                logger.info("■ 接続 "+sc.getInetAddress().getHostAddress()
                        +" : "+sc.getPort() + " : "+sc.getLocalPort() );
                new Thread(()->{
                    ReceiveProcess process = new ReceiveProcess();
                    process.execute(sc);
                }).start();
            }
        }catch(Exception e){
            logger.error(e.getMessage(), e);
        }finally{
            logger.info("■ 終了");
        }
    }
}

接続した Socket の受信スレッドとして処理する。
"exit" を受信したら終了

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * ReceiveProcess
 */
public class ReceiveProcess{
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    public void execute(Socket sc) {
        try(BufferedReader reader = new BufferedReader(new InputStreamReader(sc.getInputStream()));
              PrintWriter writer = new PrintWriter(sc.getOutputStream(), true)){
            String line = null;
            while(true){
                line = reader.readLine();
                logger.info("■ 受信=" + line);
                writer.println("OK");
                if (line.equals("exit")) {
                    break;
                }
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try{
                sc.close();
            }catch(IOException e){
                logger.error(e.getMessage(), e);
            }
            logger.info("■切断");
        }
    }
}

クライアント側のプログラム
標準キー入力を送信する。"exit" を送信したら終了

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
 * SendMain
 */
public class SendMain{

    public static void main(String[] args){
        try(Scanner scan = new Scanner(System.in)){
            try(Socket socket = new Socket("localhost", 8091);
                PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))
            ){
                    System.out.println("# "+socket.getInetAddress().getHostAddress()
                                                + " : port="+socket.getPort()
                                                + " : Local port="+socket.getLocalPort());
                    while(true){
                        System.out.print("--->");
                        String input = scan.nextLine();
                        writer.println(input);

                        System.out.println("response : " + reader.readLine());
                        if (input.equals("exit")) {
                            break;
                        }
                    }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

writer ではなく、OutputStream のまま使う場合は、flush() ではなくclose() で送信されるので
1回の接続で1回の送信しかできない

mybatis の if 文

良く記述するパターン、
パラメータ変数 String type; に対して、、

WHERE 1=1
   <if test="type == null">
       AND id = #{id}
   </if>
   <if test="type != null">

文字列比較:空文字

   <if test="type == ''">

文字列比較:任意

   <if test="type == 'A012'">

以外にもあまり書かないパターンで、
パラメータ変数の型が boolean なら、
boolean flg であれば、

   <if test="flg">

で、flg = true で then

   <if test="!flg">

で、flg = false で then

パラメータ変数の型が int で以下

   <if test="leng > 0">


AND条件は、&& と書けないので、「and」で書く小文字である

   <if test="leng == 0 and type == null">

OR条件は、「||」または「or」で書く

   <if test="leng == 0 || type == null">

if~else は、choose-when-otherwise の書式を使うしかない。

<choose>
    <when test="type != null and len == 0">
        AND id = #{id}
    </when>
    <when test="name != null and len > 0">
        AND name = #{name}
    </when>
    <otherwise>
        AND id IS NULL
    </otherwise>
</choose>

trim の使い方
https://mybatis.org/mybatis-3/ja/dynamic-sql.html#trim-where-set

prefix 要素内に文字列がある場合に、指定したprefixを先頭に付与する
prefixOverrides 要素内の文字列が、指定したprefixOverridesではじまる場合、その文字列を削除する。
suffix 要素内に文字列がある場合に、指定したsuffixを末尾に付与する
suffixOverrides 要素内の文字列が、指定したsuffixOverridesで終わる場合、その文字列を削除する。

※ prefixOverrides と suffixOverrides は、指定する文字列|(パイプ)で複数指定が可能

固定長文字列フォーマットからクラスオブジェクトを求める

Stream の reduceで文字列を長さで分割 - Oboe吹きプログラマの黙示録
を書いた時、応用すれば長いコードを書かなくても済むのでは?と思いました。
解析対象の固定長フォーマットの文字列、長さ 4,2,6,14 の文字列長の連結

String str = "uranA1   98120230312114807";

これを以下、文字列を解析して取得するJava Object のクラスになるとする。

import java.time.LocalDateTime;

import lombok.Data;
@Data
public class Foo {
    private String name;
    private String rank;
    private int value;
    private LocalDateTime atime;
}

文字列String を substring で取得する Function<T, R> と成果物に対する BiConsumer<T, U> が
成立すれば良いのです。

以前、yipuan-core ライブラリとして、ApplyConsumer という名の関数型インターフェース
https://github.com/yipuran/yipuran-core/wiki#applyconsumert-u
を作っています。
ソースは、、、
https://github.com/yipuran/yipuran-core/blob/master/src/main/java/org/yipuran/function/ApplyConsumer.java

これを使って、以下、列挙型で解析対象のクラスへの変換定義として書きます。

public enum  FooConfig{
    NAME(1, 4, ApplyConsumer.of(s->s, Foo::setName)),
    RANK(2, 2, ApplyConsumer.of(s->s, Foo::setRank)),
    VALUE(3, 6, ApplyConsumer.of(s->Integer.parseInt(s.trim()), Foo::setValue)),
    ATIME(4, 14, ApplyConsumer.of(
s->LocalDateTime.parse(s, DateTimeFormatter.ofPattern("uuuuMMddHHmmss")
 .withResolverStyle(ResolverStyle.STRICT)), Foo::setAtime));

    private int order;
    private int len;
    private ApplyConsumer<String, Foo> ap;
    private FooConfig(int order, int len, ApplyConsumer<String, Foo> ap) {
        this.order = order;
        this.len = len;
        this.ap = ap;
    }    
    public ApplyConsumer<String, Foo> ApplyConsumer(){
        return ap;
    }
    public int getLen() {
        return len;
    }
    public Integer getOrder() {
        return order;
    }
    public static List<FooConfig> list(){
        return Arrays.stream(FooConfig.values())
                .sorted((a, b)->a.getOrder().compareTo(b.getOrder()))
                .collect(Collectors.toList());
    }
}

この列挙型は、
int order; で書式文字列の中の並びの順番を定義してます。
int len; で、各フィールドの文字列長を定義してます。
ApplyConsumer で指定する Function<T, R> も、ここで String の trim() を書いたり、
場合によっては、例外を発生させたり用途に応じた変換を書けるわけです。

使用例は、以下のように書けます。
FooConfig.list() で書式の並び順のFooConfigリストから、String#substring で分割した結果を
一時保存してから、ApplyConsumer を実行する

List<String> datalist = new ArrayList<>();
FooConfig.list().stream().map(e->e.getLen())
.reduce(0, (a, b)->{
    datalist.add(str.substring(a, (a+b)));
    return a+b;
});

Foo foo = FooConfig.list().stream().collect(Foo::new,(r, t)->{
    t.getApplyConsumer().accept(datalist.get(t.getOrder()-1), r);
},(r, t)->{});

String#substring で分割した結果の一時保存をしない場合は、以下のように
reduce の実行だけで済ませる。その代わり求めるオブジェクトはあらかじめ生成しておく。

Foo foo = new Foo();
List<FooConfig> configlist = FooConfig.list();
AtomicInteger count = new AtomicInteger(0);
configlist.stream().map(e->e.getLen())
.reduce(0, (a, b)->{
    FooConfig config = configlist.get(count.getAndIncrement());
    config.getApplyConsumer().accept(str.substring(a, (a+b)), foo);
    return a+b;
});

生成した Foo を標準出力すると、、lombok の toString() で

Foo(name=uran, rank=A1, value=981, atime=2023-03-12T11:48:07)

となります。

Stream の reduceで文字列を長さで分割

今時、電文など固定長文字列でデータ設計するところは少ないであろう。
文字列の長さ、(4, 2, 6, 6) と4個の並びであるとして、18の長さの文字列を分割するのに、
Streamreduce を使うと、わりとスマートに書ける。

List<String> list = new ArrayList<>();
List.of(4, 2, 6, 6).stream().reduce(0, (a, b)->{
    list.add(str.substring(a, (a+b)));
    return a+b;
});

mapMulti

Java16 から使える Stream の mapMulti
mapMulti(BiConsumer> mapper)
flatMap と何が違う、使いどころがすぐに思いつかなかったが、メソッドの説明のサンプルで、
なるほど便利と思うのが書いてある。
ネストされているリストを、1つのStream にする。

 class C {
     static void expandIterable(Object e, Consumer<Object> c) {
         if (e instanceof Iterable<?> elements) {
             for (Object ie : elements) {
                 expandIterable(ie, c);
             }
         } else if (e != null) {
             c.accept(e);
         }
     }

     public static void main(String[] args) {
         var nestedList = List.of(1, List.of(2, List.of(3, 4)), 5);
         Stream expandedStream = nestedList.stream().mapMulti(C::expandIterable);
     }
 }

instanceof Iterable でネストされたList すなわち、Iterable かを確認して、
再帰呼び出しにして、
Consumer として受け入れ処理することで、1つのStream にするのである。

Iterable<?> でしか確認できない点を注意したい。
List<?> と  Iterable<?> は、区別が付けられないのだ。
するとキャストが安全でなくなる。だからエラーになる。

Java16 からの JEP394 Pattern Matching for instanceof

instanceof 後のcast する鬱陶しいのを回避で、よく紹介されますが、

if (obj instanceof String) {
    String s = (String)obj;
    // String s の処理
}
if (obj instanceof String s) {
    // String s の処理
}

次のように、総称型の配列を List に変換するのに使用すれば、
null を渡して
java.lang.NullPointerException: Cannot read the array length because "array" is null
にならずに済ませる処理を簡単に書くことができます。

public static <T> List<T> arrayToList(T...ary){
    return ary instanceof T[] a ? Arrays.stream(a).collect(Collectors.toList()) : new ArrayList<T>();
}

これは、

String[] ary = {"A", "B", "C"};
List<String> = arrayToList(ary);
List<String> = arrayToList("A", "B", "C");
List<String> = arrayToList(null);

で、いずれも例外を起こすことなくList を生成します。

Arrays.stream(ary).collect(Collectors.toList());
だったら、3番目の null を渡すと、
NullPointerException: Cannot read the array length because "array" is null
になってしまいます。