キー入力の連続

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

になってしまう。

Wicket TextField と type="search"

昔、Wicket 1.5 の頃は、
<input type="search" に対して、TextField<String> の代わりに、TextArea<String> を使用することで対応できた。
今の Wickrt8 ではこれはダメだ。

TextField<String> で、<input type="search" は、
  must be applied to a tag with [type] attribute matching any of [text], not [search]
のエラーになる。

Chrome でも、IE や Edge のように、<input type="search" > を使用したい。
つまり、
フォーカスが当たって、
f:id:posturan:20190808213026j:plain
キー入力したら、、
f:id:posturan:20190808213051j:plain
と入力文字をクリアする「×」が出てくるいわゆる type="search" のフィールドにしたいのだ。

しかたなく、jQuery で対応する。

$("input[type='text']").focus(function(eo){
    $(this).prop('type','search');
}).blur(function(eo){
    $(this).prop('type','text');
});

これで無理やりフォーカスが当たったら、type="search" にして
フォーカスがはずれたら元に戻す。

<input wicket:id="item" id="item" type="text">

なら、

$("#item").focus(function(eo){
    $(this).prop('type','search');
}).blur(function(eo){
    $(this).prop('type','text');
});

そういえば昔、
oboe2uran.hatenablog.com

なんて書いていたな。。。。

依存のJARがどのJARファイルか調べる。

使用しているクラスやインターフェースがどの JARファイルを使用(依存)なのか、
きちんとバージョンなど把握したい場合が、開発作業中に時々ある。

Eclipse を使っているのであれば、次のように参照、見つけることができる。

対象のクラスやインターフェースをクリックしてフォーカスを充てる。
f:id:posturan:20190807200028j:plain

F3キー押下して、宣言を開く、
開いたら、アウトライン ビューを表示する。
アウトライン ビューで対象を右クリック→「表示」→パッケージエクスプローラをクリック

f:id:posturan:20190807200354j:plain

すると、パッケージエクスプローラで、JARファイルを参照することができる。
f:id:posturan:20190807200732j:plain

ATOM インストールしたら入れておきたいパッケージ

メモ:ATOM インストールしたら入れておきたいパッケージをリストアップ

https://atom.io/packages/japanese-menu

https://atom.io/packages/markdown-preview-enhanced

https://atom.io/packages/minimap-bookmarks

https://atom.io/packages/minimap-find-and-replace

https://atom.io/packages/plantuml-preview
https://atom.io/packages/plantuml-generator
https://atom.io/packages/language-plantuml


そして、markdown-preview の設定として、、
oboe2uran.hatenablog.com

oboe2uran.hatenablog.com

Stream で処理する正規表現

Java8 でも使えるように書いてみた。。

昨日の、 正規表現マッチのストリーム処理を考察 - Oboe吹きプログラマの黙示録
に続いて、思いついたので、まとめてみた。

import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.Spliterators.AbstractSpliterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
 * RegExpress.java
 */
public final class RegExpress{
   private RegExpress(){
   }

   public static Stream<String> matchToStream(String regex, CharSequence input){
      Matcher m = Pattern.compile(regex).matcher(input);
      return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<String>(){
         @Override
         public boolean hasNext(){
            return m.find();
         }
         @Override
         public String next(){
            return m.group();
         }
      }, Spliterator.ORDERED), false);
   }

   public static Stream<MatchResult> findMatches(String regex, CharSequence input){
      Matcher matcher = Pattern.compile(regex).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);
   }

   public static void results(String regex, String input, Consumer<String> c){
      Matcher m = Pattern.compile(regex).matcher(input);
      while(m.find()){
         c.accept(m.group());
      }
   }

   public static void resultMatch(String regex, String input, Consumer<MatchResult> c){
      Matcher m = Pattern.compile(regex).matcher(input);
      while(m.find()){
         c.accept(m.toMatchResult());
      }
   }

   public static void resultMatch(String regex, String input, BiConsumer<MatchResult, Integer> c){
      Matcher m = Pattern.compile(regex).matcher(input);
      AtomicInteger i = new AtomicInteger(0);
      while(m.find()){
         c.accept(m.toMatchResult(), i.getAndIncrement());
      }
   }

   public static String replace(String regex, String string, BiFunction<String, Integer, String> f){
      Matcher m = Pattern.compile(regex).matcher(string);
      AtomicInteger i = new AtomicInteger(0);
      AtomicInteger x = new AtomicInteger(0);
      return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<String>(){
         @Override
         public boolean hasNext(){
            return m.find();
         }
         @Override
         public String next(){
            return string.substring(i.getAndSet(m.end()), m.start())
                    + f.apply(m.group(), x.getAndIncrement());
         }
      }, Spliterator.ORDERED), false).collect(Collectors.joining()) + string.substring(i.get());
   }
}

使用例

RegExpress.matchToStream("[0-9]+", string).forEach(e->System.out.println("["+e+"]"));

RegExpress.findMatches("[0-9]+", string)
.forEach(m->System.out.println("["+m.group()+"] start="+m.start()+" end="+m.end()));

RegExpress.resultMatch("[0-9]+", string, m->{
   System.out.println("["+m.group()+"] start="+m.start()+" end="+m.end());
});

RegExpress.resultMatch("[0-9]+", string, (m, i)->{
   System.out.println(i+":["+m.group()+"] start="+m.start()+" end="+m.end());
});

String res = RegExpress.replace("[0-9]+", string, (e, i)->"{"+i+":"+e+"}");

正規表現マッチのストリーム処理を考察

Java8 では、正規表現で分割 split したストリームは取得できるけど、
  java.util.regex.Pattern の splitAsStream(CharSequence input) ⇒ Stream<String>
マッチした文字列のストリームは標準では提供されていない。

自然に Matcher find() 実行ループをメソッドにすれば、

public static void results(String regex, String input, Consumer<String> c){
   Matcher m = Pattern.compile(regex).matcher(input);
   while(m.find()){
      c.accept(m.group());
   }
}

Consumer<String> にマッチした文字列を順番に処理するラムダは書ける。

Stream 生成するには、↑をリストに一旦格納するよりも、、
Java8 では、java.util.regex.Matcher から、spliterator を生成するなどの方法ぐらいしか
良い方法はない。

public static Stream<String> matchToStream(Pattern pattern, CharSequence target){
   Matcher m = pattern.matcher(target);
   return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<String>(){
      @Override
      public boolean hasNext(){
         return m.find();
      }
      @Override
      public String next(){
         return m.group();
      }
   }, Spliterator.ORDERED), false);
}

Java9 以上なら、java.util.regex.Matcher に、
 String replaceAll(Function<MatchResult, String> replacer) や、
 Stream<MatchResult>を取得する results()
があって、
Function で置換する為のストリームがあって便利だが、
MatchResult で、start() end() group() は参照できても
何番目のマッチなのか?までは、AtomicInteger など外にカウンタを置かないと
認識できない。

Java8 でもStream<MatchResult>を取得するものを
yipuran-core の中に用意はした。。。
 org.yipuran.regex.MatcherStream#findMatches(Pattern pattern, CharSequence input)
けど、何番目のマッチなのか?までは、相変わらずこのままでは取得できない。
Stream<MatchResult>を取得するだけなのだから当然である。
Stream<MatchResult>から collect(Collector.toList()) でリストにすれば順番が確定はするが、
その後で、再び Stream の処理をするのはナンセンスである。

BiConsumer でカウンタ付きの Consumer

public void resultMatch(String regex, String target, BiConsumer<MatchResult, Integer> b){
   AtomicInteger i = new AtomicInteger(0);
   Pattern.compile(regex).matcher(target).results().forEach(m->b.accept(m, i.getAndIncrement()));
}

なんか、メソッドを作るほどの事でもない。。。

でも、何番目にマッチするかを認識しながら、置換を行う場合、、、

public static String replace(String regex, String string, BiFunction<String, Integer, String> f){
   Matcher m = Pattern.compile(regex).matcher(string);
   AtomicInteger i = new AtomicInteger(0);
   AtomicInteger x = new AtomicInteger(0);
   return StreamSupport.stream(   Spliterators.spliteratorUnknownSize(new Iterator<String>(){
      @Override
      public boolean hasNext(){
         return m.find();
      }
      @Override
      public String next(){
         return string.substring(i.getAndSet(m.end()), m.start()) + f.apply(m.group(), x.getAndIncrement());
      }
   }, Spliterator.ORDERED), false)
   .collect(Collectors.joining()) + string.substring(i.get());
}

あるいは、yipuran-core の MatcherStream.findMatches を使用して、、

public static String replace(String regex, String string, BiFunction<MatchResult, Integer, String> f) {
   AtomicInteger i = new AtomicInteger(0);
   AtomicInteger x = new AtomicInteger(0);
   return Stream.concat( MatcherStream.findMatches(Pattern.compile(regex), string)
   .collect(()->new ArrayList<String>(), (r, t)->{
      r.add( string.substring(i.getAndSet(t.end()), t.start()) + f.apply(t, x.getAndIncrement())  );
   },(r, u)->{}).stream(), Stream.of(string.substring(i.get())))
   .collect(Collectors.joining());
}

これだと、Stream.concat で Stream を2つ作って結合であまり良くないかも。