Map entrySet() から、GenericBuilder → Fieldsetter Streamで集約してインスタンス

Map<String, Object> に、クラスの属性名と値が格納されているとして、
Map entrySet() → Stream の collect で、Fieldsetter 実行してインスタンスを生成

ここで使用するものは、
https://github.com/yipuran/yipuran-core/wiki#genericbuildert
と、
https://github.com/yipuran/yipuran-core/blob/master/src/main/java/org/yipuran/util/Fieldsetter.java

例)

public class Iteminfo{
	public String a;
	public int b;
	public String c;
	public LocalDate d;
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("a", "A");
map.put("b", 246);
map.put("c", "C");
map.put("d", LocalDate.now());

Map entrySet() → Stream の collect → Supplier で GenericBuilder を提供して、
accumulator 処理 で、GenericBuilder の with でセッターを実行する

Iteminfo info = map.entrySet().stream()
.collect(()->GenericBuilder.of(Iteminfo::new),
      (r, t)->r.with(Fieldsetter.of((p, u)->t.getKey()), t.getValue()), (r, t)->{})
.build();

最後に、build() で生成する。

検証

System.out.println("a = " + info.a );
System.out.println("b = " + info.b );
System.out.println("c = " + info.c );
System.out.println("d = " + info.d );

結果

a = A
b = 246
c = C
d = 2019-09-11

大量に、クラスのフィールドが存在して、フィールド値を格納したインスタンス
生成するのには、有効な手段になりそうだ。

JSON テキストから、Google gson の fromJson を使う場面に近い方法だが、
ストリームの collect で生成のビルダの準備までやる上の方法は、
意外と使える方法かもしれない。

関数型インターフェースで実行する Fieldgetter

昨日、Fieldsettter のことを書いたので、
Fieldsetter は、public でも private でも使用できる - Oboe吹きプログラマの黙示録

それならば、getter も同様に書いてみた。

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.function.Function;
/**
 * Fieldgetter
 */
@FunctionalInterface
public interface Fieldgetter<T, R> extends Serializable{
   String get(T t) throws Exception;

   @SuppressWarnings("unchecked")
   static <T, R> Function<T, R> of(Fieldgetter<T, R> f){
      return t->{
         try{
            Field field = t.getClass().getDeclaredField(f.get(t));
            field.setAccessible(true);
            return (R)field.get(t);
         }catch(Throwable ex){
            throw new RuntimeException(ex);
         }
      };
   }
}

例えば、以下のクラス、、、

public class Foo{
   private String name;
   private String info;
   private LocalDate date;
}
public class FooWrap{
   private Foo foo;
   public Foo getFoo() {
      return foo;
   }
}

インスタンス生成

Foo foo = GenericBuilder.of(Foo::new).with(Fieldsetter.of((t, u)->"name"), "abc")
      .with(Fieldsetter.of((t, u)->"info"), "Information")
      .with(Fieldsetter.of((t, u)->"date"), LocalDate.now())
      .build();
FooWrap fw = GenericBuilder.of(FooWrap::new).with(Fieldsetter.of((t, u)->"foo"), foo).build();

本題、Fieldgetter でクラスのフィールドの値を取り出す。

String info = Optional.ofNullable(fw)
            .map(e->Fieldgetter.of(t->"foo").apply(e))
            .map(e->(String)Fieldgetter.of(t->"info").apply(e))
            .orElse("this is null");

String name = Optional.ofNullable(fw)
            .map(e->Fieldgetter.of(t->"foo").apply(e))
            .map(e->(String)Fieldgetter.of(t->"name").apply(e))
            .orElse("this is null");

LocalDate date = Optional.ofNullable(fw)
               .map(e->Fieldgetter.of(t->"foo").apply(e))
               .map(e->(LocalDate)Fieldgetter.of(t->"date").apply(e))
               .orElse(null);

Type-safety というわけでもないし、こんなの役に立つわけないかも。。。
わざわざこんなもの。。。と思ったけど、いつかどこかで使い道あるかもしれない、、
でも、まだ思いつかない。。。


それに単に、インスタンスに格納されたフィールド値を get するだけなら、
以下の static メソッドでも充分なはずだし。。。

public static <T, R> R getValue(T t, String s){
   try{
      Field f = t.getClass().getDeclaredField(s);
      f.setAccessible(true);
      return (R)f.get(t);
   }catch(Throwable ex){
      throw new RuntimeException(ex);
   }
}

Fieldsetter は、public でも private でも使用できる

yipran-core として作成した GenericBuilder
Wiki に、
 setter が存在しない public フィールドの時、
 と
 setter が存在しない private フィールドの時、
GenericBuilder#with メソッドの書き方の差を書いてしまったが、
実は、org.yipuran.util.Fieldsetter を使う方法では、private / public 両方使用可能だ。

https://github.com/yipuran/yipuran-core/wiki#genericbuildert

例えば、、

public class Foo{
   public String name;
   public String getName() {
      return name;
   }
}
public class FooWrap{
   public Foo foo;
   public Foo getFoo() {
      return foo;
   }
}

以下のように、Fieldsetter . of でセットできる

import org.yipuran.util.Fieldsetter;
import org.yipuran.util.GenericBuilder;
////
Foo foo = GenericBuilder.of(Foo::new).with(Fieldsetter.of((t, u)->"name"), "abc").build();
FooWrap fw = GenericBuilder.of(FooWrap::new).with(Fieldsetter.of((t, u)->"foo"), foo).build();

これは、以下のように確認できる。

String name = Optional.ofNullable(fw).map(e->e.getFoo()).map(e->e.getName()).orElse("not found");

あるいは、、

String name = Optional.ofNullable(fw).map(FooWrap::getFoo).map(Foo::getName).orElse("not found");

Wicket ModalWindow Full size で height も Page の高さサイズに合わせる。

何度も過去、以下を書いたが、Wicket ModalWindow 内の height の調整が抜けていた。。
Wicket full size ModalWindow - Oboe吹きプログラマの黙示録

Wicket ModalWindow full screen - Oboe吹きプログラマの黙示録

heightの調整、先に答えになる JS (jQuery)ソース全体を書くと、、、

var _adjust_full_nocaption =  function(){
   $(".wicket-modal").attr("style", "top:0;left:0;width:100%;position:fixed;visibility:visible;"),
   $(".w_content_container").attr("style", "overflow:auto;height:" + window.innerHeight + "px;");
   $(".w_content_container div div").attr("style", "height:" + window.innerHeight + "px;");
   $("div.w_caption").attr("style", "display:none;"),
   $("div.w_top_1").attr("style", "display:none;"),
   $("div.w_bottom_1").attr("style", "display:none;"),
   $("div.w_right_1").attr("style", "margin:0;"),
   $("div.w_content_1").attr("style", "margin:0;");
}
var _adjust_ful_captionON =  function(){
   $(".wicket-modal").attr("style", "top:0;left:0;width:100%;position:fixed;visibility:visible;"),
   $(".w_content_container").attr("style", "overflow:auto;height:"+ (window.innerHeight - 10) + "px;");
   $(".w_content_container div div").attr("style", "height:" + (window.innerHeight - 10) + "px;");
}
/* caption 無し;ハンドル無し、フル表示 */
var adjustFull = function(msec){
   setTimeout("_adjust_full_nocaption()", msec);
   $(window).off('resize');
   $(window).on('resize', function(){
      _adjust_full_nocaption();
   });
};

/* caption ハンドル有り、フル表示 */
var adjustFullHandle = function(msec){
   setTimeout("_adjust_ful_captionON()", msec);
   $(window).off('resize');
   $(window).on('resize', function(){
      _adjust_ful_captionON();
   });
};

ModlalWindow を開いたら実行する関数
adjustFull ( 10 ) ; ・・・・10 msec 後に描画サイズ調整
または、
adjustFullHandle ( 10 ) ; ・・・・10 msec 後に描画サイズ調整

_adjust_full_nocaption 関数で、
$(".w_content_container div div").attr("style", "height:" + window.innerHeight + "px;");

_adjust_ful_captionON 関数で、
$(".w_content_container div div").attr("style", "height:" + (window.innerHeight - 10) + "px;");

が足りなかった。
これは、ModalWindow が以下のHTML要素で描画されているからだ、
f:id:posturan:20190825113222j:plain

class="wicket-modal" の <div> の中に、
 <div class="w_top_1" >
 <div class="w_left" >
が存在して、
<div class="w_left" > の中に、

<div class="w_right_1" >
 <div class="w_right" >
  <div class="w_content_2" >
   <div class="w_content_3" >
    <div class="w_content" >
     <div class="w_content_container" >

があってようやくこの中に、Panel 生成の div 1個で括って、
Panel として用意する div が生成される。

<wicket:panel>
<div class="modal-panel">
・・・・
</div>
</wicket:panel>

この <div class="modal-panel" > が、上の深い div 階層の中に入る。

Wicket ModalWindowをページいっぱいFull に表示させることで戻りを想定するページ遷移の代わりにする。
→ セッション保持させるものを少なくする設計ができる。

・・・もはや、ModalWindow という名がふさわしくなくなるけど、魅力的な方法だ。

ATOM で、Excel 等、システム規定のアプリで開く

ATOM プロジェクトを開いていて(ATOM起動中)ATOMの上で、Excel / PDF などの拡張子のファイルを
本来のアプリで開くようにするには、次のプラグインをインストールする。


open-unsupported-files


atom.io


対応するファイル拡張子は、デフォルトで

doc,xls,ppt,docx,xlsx,pptx,pdf,rtf,zip,7z,rar,tar,gz,bz2,exe,bat,tps

キー入力の連続

1年前に、標準キー入力させるのに、java.util.Scanner を使用した例を書いた。。。
oboe2uran.hatenablog.com

こんなメソッドを用意して使うのもいいが、

public static String keyIn(String guide){
   System.out.print(guide);
   try(Scanner scan = new Scanner(System.in)){
      return scan.nextLine();
   }
}

これを2回連続 call の実行は、

String s1 = keyIn("s1 --->");
String s2 = keyIn("s2 --->");

Exception in thread "main" java.util.NoSuchElementException: No line found
at java.base/java.util.Scanner.nextLine(Scanner.java:1651)

落ち着いて見直せば当然である。
System.in 標準入力を一旦 CLOSE しているのだから。
では、2回以上連続するキー入力をするには、どうするか?
次のように工夫する。

public static Map<String, String> keyIn(List<String> guides){
   try(Scanner scan = new Scanner(System.in)){
   return guides.stream().collect(()->new HashMap<String, String>()
      ,(r, t)->{
         System.out.print(t + " ---> ");
         r.put(t, scan.nextLine());
      },(r, u)->{});
   }
}

サンプル
キー入力要求ガイドを順に3個並べて、
結果を Map で受け取る。

Map<String, String> map = keyIn(Arrays.asList("s1", "s2", "s3"));
System.out.println(map);

'a', 'b', 'c' を順にキー入力

s1 ---> a
s2 ---> b
s3 ---> c
{s3=c, s1=a, s2=b}

となる。

Apache POI でExcel 日付読込み

久々に JavaExcel を読込む apache POI を使う。

try(InputStream is = new FileInputStream("sample.xlsx");
       XSSFWorkbook book = new XSSFWorkbook(is)){
     XSSFSheet sheet = book.getSheetAt(0);
     XSSFRow row = sheet.getRow(2);
     XSSFCell cell = row.getCell(1);

org.apache.poi.ss.usermodel.CellType は、NUMERIC であり、getNumericCellValue() で値を参照する必要がある。
参照する型は、double である。
だから、

cell.getNumericCellValue()

は、double であり、しかも、Excel の EPOCH 基準日は特殊である。
これを java.util.Date に変換するものが、
apache POI には用意されている。

org.apache.poi.ss.usermodel.DateUtil

getJavaDate(double date)

が、 java.util.Date を返す。
でも、 java.util.Date なんかもう使わないので、 java.time.LocalDate にする。

Date date = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(cell.getNumericCellValue());
LocalDate loaldate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

Excel の EPOCH 基準日が特殊というのは、
(UTC)Unix Epoch Time 1970 年 1月1日 0時0分0秒からを基準ではなく、
1900年 1月1日 0時0分0秒 を Excel が基準にしていることだ。
だから、org.apache.poi.ss.usermodel.DateUtil
変換メソッドが提供されている。

stackoverflow に以下の記事があるが、、
https://stackoverflow.com/questions/19028192/converting-number-representation-of-date-in-excel-to-date-in-java
確かに
1900年1月1日より後ろの日付では、

 LocalDate.of(1899, 12, 30).plusDays((long)cell.getNumericCellValue())

という計算もできるであろうが、1900年 1月1日である double 値 1.0 に対しては使えない。

1899-12-31

になってしまう。