グルーピングした時にソートもする。

グルーピングしながら、ソートした結果リストを求めることを
いざコーディングしようとすると、即時、思いつかないのが残念でメモ。

Collectors.collectingAndThen を使うのが重要

サンプル、以下、文字列の key と value があるクラスオブジェクト

public class Foo{
   public String key;
   public String value;
   public Foo(String key, String value){
      this.key = key;
      this.value = value;
   }
}

あえて、value には、数値の文字列が入る約束として、この value で、グルーピング結果のコレクションがソート
されるようにする。

List の Map を生成

Map<String, List<Foo>> map =
list.stream().collect(Collectors.groupingBy(e->e.key, Collectors.mapping(t->t,
   Collectors.collectingAndThen(Collectors.toCollection(ArrayList::new)
         , t->t.stream().sorted((o1, o2)->new Integer(o1.value).compareTo(new Integer(o2.value)))
               .collect(Collectors.toList())
   )
)));

Set の Map を生成、 toList() を toSet() に返るだけですが。

Map<String, Set<Foo>> map =
list.stream().collect(Collectors.groupingBy(e->e.key, Collectors.mapping(t->t,
   Collectors.collectingAndThen(Collectors.toCollection(ArrayList::new)
         , t->t.stream().sorted((o1, o2)->new Integer(o1.value).compareTo(new Integer(o2.value)))
               .collect(Collectors.toSet())
   )
)));

chromeブラウザ左下に出るURL表示を出ないようにする

方法があった。
stackoverflow で見つけました。

$(function(){
   $("body").on('mouseover', 'a', function(e){
      var $link = $(this),
      href = $link.attr('href') || $link.data("href");
      $link.off('click.chrome');
      $link.on('click.chrome', function(){
         /* window.location.href = href; */
         this.location.href = href;
      })
      .attr('data-href', href)
      .css({ cursor: 'pointer' })
      .removeAttr('href');
   });
});

注意しなければならないのは、必ず、<a> タグの href属性を書いておくこと。
mouseover のイベントで、href 属性を data属性である data-href に置き換えて書込み
ブラウザ左下に URLが出るのを抑止、
再びマウスが上にきてクリックされても、data-hrefの残されてた URLを
location.href へのセットでページ遷移させる方法だ。

stackoverflow.com

DropDownChoice の IChoiceRenderer

2年近く前、Wicket6 → 7 になった時、DropDownChoice の為の IChoiceRenderer が、
 public T getObject(String id, IModel> choices)
が増えて、choices のgetModelObject() で取ってくるものをこのメソッドで返さなくてはならなくなり、
当時、以下のように、optionタグの value属性値を返せるようにするインターフェースを用意して
更に、IChoiceRenderer継承と、DropDownChoice がそれを使うように継承したものを作った。。
IChoiceRenderer ラムダ化 - Oboe吹きプログラマの黙示録

でも、やはりDropDownChoice の継承と、選択要素にoptionタグの value属性値を返せるようにするインターフェース
を付けるなんて方法は、選択要素の構造に手を加えるので良くない。
かと言って、IChoiceRenderer の無名インナークラスのコーディングは、長くなって嫌だ。

option タグの value を返すもの。bodyを返すもの。選択されたものを判断して返すもの。
これらの処理関数を埋め込めるラムダ式になっていれば良い。

import java.util.List;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.model.IModel;
import org.danekja.java.util.function.serializable.SerializableBiPredicate;
import org.danekja.java.util.function.serializable.SerializableFunction;
/**
 * IChoiceRenderer を提供
 */
public class ChoiceRender{
   private ChoiceRender(){}

   public static <T> IChoiceRenderer<T> of(SerializableFunction<T, String> idfunction
         , SerializableFunction<T, String> displayfunction
         , SerializableBiPredicate<T, String> bipredicate
   ){
      return new IChoiceRenderer<T>(){
         @Override
         public Object getDisplayValue(T t){
            return displayfunction.apply(t);
         }
         @Override
         public String getIdValue(T t, int index){
            return idfunction.apply(t);
         }
         @Override
         public T getObject(String id, IModel<? extends List<? extends T>> choices){
            return choices.getObject().stream().filter(e->bipredicate.test(e, id)).findFirst().orElse(null);
         }
      };
   }
}


例)
以下のような対象クラスがあったとする。

public class Foo implements Serializable{
   public int val;
   public String name;
   public Foo(int val, String name){
      this.val = val;
      this.name = name;
   }
}
final DropDownChoice<Foo> selectFoo =
new DropDownChoice<Foo>("select1", new Model<>(), Arrays.asList(new Foo(1,"A"), new Foo(2,"B"), new Foo(3,"C") )
      , ChoiceRender.of(t->Integer.toString(t.val), t->t.name, (t,u)->Integer.toString(t.val).equals(u)) ) ;
queue(selectFoo);

そして、optionタグの value も body も同じものとして生成することも簡単に書ける。

例えば、"a","b","c"

final DropDownChoice<String> selectChar =
new DropDownChoice<String>("select2", new Model<>(), Arrays.asList("a","b","c")
   , ChoiceRender.of(t->t, t->t, (t,u)->t.equals(u)) ) ;
queue(selectChar);

フォーム送信されて受け取るところ。。

Optional.ofNullable(selectChar.getModelObject()).ifPresent(s->{
  // 
});

実行中のクラス名やメソッド名の取得

Thread.currentThread() を参照することで、

スレッド名 → Thread.currentThread().getName()
クラス名 → Thread.currentThread().getStackTrace()[1].getClassName()
メソッド名 → Thread.currentThread().getStackTrace()[1].getMethodName()

で取得できるので、以下のように staticメソッドのユーティリティクラスを用意すれば、、、

   public static String getClassName(){
      return Thread.currentThread().getStackTrace()[2].getClassName();
   }
   public static String getMethodName(){
      return Thread.currentThread().getStackTrace()[2].getMethodName();
   }

と思うが、落とし穴があって、ラムダ式の中で実行すると、

lambda$1 のように、メソッド名が取得できても役に立たない場合がある。
それなら、ラムダの場合は、コード上の行番号を付加したものを返す約束にすれば使えそうだ。

工夫して作ったのが以下のとおり。

public final class CurrentUtil{
   private CurrentUtil(){}

   /**
    * @return 実行中スレッド名
    */
   public static String getThreadName(){
      return Thread.currentThread().getName();
   }

   /**
    * @return 実行中クラス名
    */
   public static String getClassName(){
      return Thread.currentThread().getStackTrace()[2].getClassName();
   }

   /**
    * 実行中メソッド名取得.
    * @return 実行中メソッド名
    */
   public static String getMethodName(){
      return Optional.of(Thread.currentThread().getStackTrace()[2])
      .map(e->e.getMethodName().startsWith("lambda$")
         ? e.getMethodName() + ":" + e.getLineNumber() : e.getMethodName())
      .get();
   }
}

startsWithでラムダなんて判断しないで、以下の様にと考えもしたが、

Optional.of(Thread.currentThread().getStackTrace()[2])
.map(e->e.getClassName()
+ "." + e.getMethodName()
+ ":" + e.getLineNumber())
.get();

実際のラムダ式が使用されてるメソッドまできちんと出すには、getStackTrace()の配列をきちんと
順に解析しないとならない。
それよりも、getLineNumber() が返す行番号の方が役に立つ

Pattern to Stream

前から常に思っていたのですが、Java8 には、java.util.regex.Pattern に、splitAsStream があるのに、
matchAsStream に相当するマッチしたものをストリームで返すメソッドが提供されてないんだと不満でした。
Java9 なら、Matcher に、 Stream を取得できる
public Stream results()
が提供される予定のようです。もうすぐ Java9 が出るだろうが実運用がすぐに移行させてはもらえないでしょうし。

そこで代替えを探していたらネットにはたくさん溢れていたので、その中で納得のいくもの1つに手を
加えたものと1、もう1つを紹介

パターンマッチ → Spliterator → StreamSupport → Stream という目的の為のイテレータ

import java.util.Spliterators.AbstractSpliterator;
import java.util.function.Consumer;
import java.util.regex.Matcher;
/**
 * MatcherItrator
 */
public final class MatcherItrator extends AbstractSpliterator<CharSequence>{
   private final Matcher matcher;

   public MatcherItrator(Matcher m){
      super(m.regionEnd() - m.regionStart(), ORDERED|NONNULL);
      matcher = m;
   }
   @Override
   public boolean tryAdvance(Consumer<? super CharSequence> c){
      if (!matcher.find()) return false;
      c.accept(matcher.group());
      return true;
   }
}

この super の呼び出し、super(Long.MAX_VALUE, ORDERED|NONNULL); でも良いのではないかと考えるが。。。

サンプル

String string = " abc_def_ghi_..";

StreamSupport.stream(new MatcherItrator(Pattern.compile("[a-z]+").matcher(string)), false)
.forEach(e->{
  System.out.println(e);
});


Pattern と 検査対象を渡して、マッチ結果文字列Stream あるいは、MatchResultのStream を求める
ユーティリティクラス

import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator;
import java.util.function.Consumer;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
 * MatcherStream
 */
public final class MatcherStream{
   private MatcherStream(){}

   public static Stream<String> find(Pattern pattern, CharSequence input){
      return findMatches(pattern, input).map(MatchResult::group);
   }

   public static Stream<MatchResult> findMatches(Pattern pattern, CharSequence input){
      Matcher matcher = pattern.matcher(input);
      Spliterator<MatchResult> spliterator
= new AbstractSpliterator<MatchResult>(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL){
         @Override
         public boolean tryAdvance(Consumer<? super MatchResult> action){
            if (!matcher.find()) return false;
            action.accept(matcher.toMatchResult());
            return true;
         }
      };
       return StreamSupport.stream(spliterator, false);
   }
}

Stream を求める

MatcherStream.find(Pattern.compile("[a-z]+"), string)
.forEach(e->{
  System.out.println(e);
});

java.util.regex.MatchResult の Stream を求める

MatcherStream.findMatches(Pattern.compile("[a-z]+"), string)
.forEach(r->{
   System.out.println(r.group()+" "+r.start());
}

Stream でListの中で特定要素があるインデックスを求める

完璧=普遍的とは言えないが、、
例)

List<String> list = Arrays.asList("a", "b", "c", "d", "e");
int findIndex =
IntStream.range(0, list.size()).map(i->list.get(i).equals("c") ? i : -1).max().orElse(-1);;

でも、2つ以上マッチする場合は、最後のインデックスを取得することになる。
それでなら、

int findIndex =
IntStream.range(0, list.size()).map(i->list.get(i).equals("c") ? i : -1)
.filter(i->i >= 0).min().getAsInt();

これで最初に見つかるインデックスであり、全てのインデックスは、

IntStream.range(0, list.size()).map(i->list.get(i).equals("c") ? i : -1).filter(i->i >= 0)

PaxHeaders.X が作られないように圧縮ファイルを作成する

apache commons compress で圧縮ファイル tar.gz を作成していたら、
階層が深かったり長すぎるファイル名で圧縮後、解凍を行ったときに、
PaxHeaders.X という名のディレクトリエントリ、中に展開したときに不完全に解凍されたファイルのパスが
作られてしまった。

tar の仕様の問題であるが、Apache commons compress で圧縮するときの回避として、

TarArchiveOutputStream の設定メソッドで、

setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);

ではなく、

setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);

で設定する。

以下も修正

tar and zip の圧縮・展開 - Oboe吹きプログラマの黙示録