JUnit static メソッドのmock

以前、書いていたが、、
JUnit4 でのモック - Oboe吹きプログラマの黙示録

static メソッドのモック は、mockito-core だけでなく、mockito-inline も必要

<dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-core</artifactId>
   <version>4.4.0</version>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-inline</artifactId>
   <version>4.4.0</version>
   <scope>test</scope>
</dependency>

バージョンが上がっている

PushbackInputStream を使用する

以前、Excel 拡張子 (xls) (xlsx) 両方に対応した操作 Apache POI として、
oboe2uran.hatenablog.com
を書いた。

しかし、ファイルシステムによっては、Excel WorkBook の認識に、
Your InputStream was neither an OLE2 stream, nor an OOXML stream
or you haven't provide the poi-ooxml*.jar in the classpath/modulepath - FileMagic:

と、エラーになってしまうことがある。

どうも、POI の先読みの方法とPushBackInputStream の使い方が悪いように見える。
そこで、前のコンストラクタで InputStream を渡して Book を生成する部分を以下のように修正する。

public class ExcelWorker implements Closeable{
    private Workbook book;

    private static final int MAX_PATTERN_LENGTH = 44;

    public ExcelWorker(InputStream inst) throws EncryptedDocumentException, IOException {
        PushbackInputStream inp = new PushbackInputStream(inst, MAX_PATTERN_LENGTH);
        byte[] data = new byte[MAX_PATTERN_LENGTH];
        inp.read(data, 0, MAX_PATTERN_LENGTH);
        inp.unread(data);
        FileMagic fm = FileMagic.valueOf(data);
        if (FileMagic.OOXML==fm){
            book = new XSSFWorkbook(inp);
        }else if(FileMagic.OLE2==fm){
            book = new HSSFWorkbook(inp);
        }else {
            book = WorkbookFactory.create(inp);
        }
    }

クォート文字で囲ってないJSONキー

JSONの仕様は、フィールド名:キー は、(") ダブルクォーテーションで囲むと
されているが、RFC8259 にそんなこと書いてあるか?
JavaScript 表記のことを考えると、キーをダブルクォーテーションで囲まないケースで
読込みエラーになるのは、不都合なのではないか?

以下のように書いたJSON この時点でこれはJSONでないという方もいるだろうが、

{
   id : 1207,
   name : "Foo",
   flg : true,
   values : [ 11, 21, 34 ]
}

これを、Java Jackson で普通に読もうとすると、
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('i' (code 105)):
 was expecting double-quote to start field name

となる。
Jackson には、これを回避して読込む方法がある。
ObjectMapper のコンフィグレーションとして、JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES を指定する。

ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES.mappedFeature(), true);
Map<String, Object> map = mapper.readValue(jsontext, new TypeReference<LinkedHashMap<String, Object>>(){});

では、シングルクォーテーション文字で囲んだ場合はどうだろうか?

{
   'id' : 1207,
   'name' : "Foo",
   'flg' : true,
   'values' : [ 11, 21, 34 ]
}

この場合は、普通の ObjectMapper で読むと
com.fasterxml.jackson.core.JsonParseException: Unexpected character (''' (code 39)):
  was expecting double-quote to start field name

と怒られる。
シングルクォーテーションで囲んだ場合を読み込ませるようにするには、、
JsonReadFeature.ALLOW_SINGLE_QUOTES を指定する。

ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES.mappedFeature(), true);
Map<String, Object> map = mapper.readValue(jsontext, new TypeReference<LinkedHashMap<String, Object>>(){});

シングルクォーテーション、ダブルクォーテーション、
囲み文字無しの混在を検証する。

JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES を指定した時、

double quote と quote無し が混在 OK
double quote と single quote が混じっている NG

JsonReadFeature.ALLOW_SINGLE_QUOTES を指定した時、

single quote と double quote が混じっている OK
single quote と quote無し が混じっている NG

Iterator → Stream

稀に書くことがある Iterator から Stream への変換メモ

Spliterators.spliteratorUnknownSize で、イテレータSpliterator を作って、
StreamSupport で Stream生成

Iterator<T> iterator =list.iterator();

サイズが解らない場合ケースが多いので、

Stream<T> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator , Spliterator.ORDERED) , false);

パラレルStream にする必要性もないので、StreamSupport.stream の第2引数は false、

リスト要素の前方を参照する処理(3)

oboe2uran.hatenablog.com

これを、先頭だけ Consumer にして、BiConsumer if 文を書く必要をなくす。

public static <T> void eachPrevious(List<T> list, Consumer<T> first, BiConsumer<T, T> consumer){
    ListIterator<T> it = list.listIterator();
    first.accept(it.next());
    while(it.hasNext()) {
        T pre = it.previous();
        it.next();
        consumer.accept(it.next(), pre);
    }
}
List<String> list = List.of("A", "B", "C", "D");
eachPrevious(list, t->{
    System.out.println("first : "+t);
}, (t, u)->{
    System.out.println("cur="+t+ "  pre="+u);
});

結果は、、

first : A
cur=B  pre=A
cur=C  pre=B
cur=D  pre=C

リスト要素の前方を参照する処理(2)

前回の投稿、
oboe2uran.hatenablog.com

さらに、BiConsumer の処理にしてみます。

public static <T> void eachPrevious(List<T> list, BiConsumer<T, T> consumer){
    for(ListIterator<T> it = list.listIterator(); it.hasNext();) {
        T pre = null;
        if (it.hasPrevious()) {
            pre = it.previous();
            it.next();
        }
        consumer.accept(it.next(), pre);
    }
}

サンプル

List<String> list = List.of("A", "B", "C", "D");

eachPrevious(list, (t, u)->{
     // TODO リスト前の要素 u と、処理する
     System.out.println("cur="+t+ "  pre="+u);
});

結果

cur=A  pre=null
cur=B  pre=A
cur=C  pre=B
cur=D  pre=C

目的の処理が、BiConsumer で書きやすくなります。