Throwable な Function から、Optional<LocalDate>を生成することでコードを短く

先日、Optional の filter を連結して使う - Oboe吹きプログラマの黙示録 で書いた例は、
DateFormat の日付の妥当性チェック setLenient(true=厳しくない)を行い、これが返却値なしの void メソッドであることから
どうしてもあのようになってしまっていた。

日付入力文字列→チェックを行って日付型への変換は、今まで嫌というほど書いてきたが、
oboe2uran.hatenablog.com

これを使って以下のように書いて少しは短くなる。。。

final TextField<String> dateField = new TextField<>("date", new Model<String>());
queue(dateText);

queue(new Button("submit").add(AjaxFormSubmitBehavior.onSubmit("click", SerialThrowableConsumer.of(t->{
   LocalDate date = Optional.ofNullable(dateField.getModelObject())
   .map(ThrowableFunction.of(s->{
      DateFormat f = new SimpleDateFormat("yyyy/MM/dd");
      f.setLenient(false);
      f.parse(s);
      return s;}))
   .map(e->LocalDate.parse(e, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
   .orElseThrow(()->new RuntimeException("is null Error!!"));
   
   // TODO t=AjaxRequestTarget  
}, (t, x)->{
   // TODO t=AjaxRequestTarget  x=Exception
}))));

RuntimeException であれ java.text.ParseException: Unparseable date: を捕捉できてればいいのである。
これでも、沢山の日付入力があるとやはり書くのがたいへん。。

それで以前、日付のユーティリティクラスを作ったことがあり、そこに static メソッドで Optional
返すメソッドを用意する。

public final class DateUtil{
     private DateUtil(){}
     public static Optional<LocalDate> optionalDate(String string, String pattern){
      return Optional.ofNullable(string)
      .map(ThrowableFunction.of(e->{
         DateFormat f = new SimpleDateFormat(pattern);
         f.setLenient(false);
         f.parse(e);
         return e;}))
      .map(e->LocalDate.parse(e, DateTimeFormatter.ofPattern(pattern)));
   }
}

これで、dateField.getModelObject() での入力した日付文字列の処理は、以下のように書ける。

queue(new Button("submit").add(AjaxFormSubmitBehavior.onSubmit("click", SerialThrowableConsumer.of(t->{
   LocalDate date = DateUtil.optionalDate(dateText.getModelObject(), "yyyy/MM/dd")
   .orElseThrow(()->new RuntimeException("is null Error!!"));
   
   // TODO t=AjaxRequestTarget  
}, (t, x)->{
   // TODO t=AjaxRequestTarget  x=Exception
}))));

StatelessChecker を使う。

Serializable And Throwable Consumer - Oboe吹きプログラマの黙示録
を書いたので、忘れないように本当にステートレス Page として作ったのかチェックするのに
有効な方法を書いておく。

Page クラスに、StatelessComponent アノテーションを付与して WebApplation の init() で
StatelessChecker を beforeペーレンダリングリスナに登録しておく。

@StatelessComponent
public class HomePage extends WebPage{
    //  省略
}

WebApplation の init() で

public class SampleApplication extends WebApplication{
     @Override
    protected void init(){
          getComponentPostOnBeforeRenderListeners().add(new StatelessChecker());
   }

こうしておくとページを描画前に以下のとおり、ページの配置したどの?コンポーネントの組み込みが
ステートレスになっていなかったかを、例外で教えてくれる。

org.apache.wicket.devutils.stateless.StatelessCheckFailureException: 
'[Page class = org.sample.page.HomePage, id = 9, render count = 1]' 
claims to be stateless but isn't. Offending component: [Button [Component id = submit]]
   at org.apache.wicket.devutils.stateless.StatelessChecker.onBeforeRender(StatelessChecker.java:133)
   at org.apache.wicket.application.ComponentOnBeforeRenderListenerCollection$1.notify(ComponentOnBeforeRenderListenerCollection.java:40)
   at org.apache.wicket.application.ComponentOnBeforeRenderListenerCollection$1.notify(ComponentOnBeforeRenderListenerCollection.java:36)
   at org.apache.wicket.util.listener.ListenerCollection.notify(ListenerCollection.java:80)
   at org.apache.wicket.application.ComponentOnBeforeRenderListenerCollection.onBeforeRender(ComponentOnBeforeRenderListenerCollection.java:35)
   at org.apache.wicket.Component.internalBeforeRender(Component.java:949)
   at org.apache.wicket.Component.beforeRender(Component.java:1016)
   at org.apache.wicket.Component.internalPrepareForRender(Component.java:2231)
   at org.apache.wicket.Page.internalPrepareForRender(Page.java:239)
   at org.apache.wicket.Component.render(Component.java:2320)
   at org.apache.wicket.Page.renderPage(Page.java:987)
   at org.apache.wicket.request.handler.render.WebPageRenderer.renderPage(WebPageRenderer.java:124)
   at org.apache.wicket.request.handler.render.WebPageRenderer.respond(WebPageRenderer.java:236)
   at org.apache.wicket.core.request.handler.RenderPageRequestHandler.respond(RenderPageRequestHandler.java:202)
   at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:912)
   at org.apache.wicket.request.RequestHandlerExecutor.execute(RequestHandlerExecutor.java:65)
   at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:283)
   at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:253)
   at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:221)
   at org.apache.wicket.protocol.http.WicketFilter.processRequestCycle(WicketFilter.java:262)
   at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:204)
   at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:286)
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
   at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
   at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:94)
   at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:504)
   at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
   at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
   at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620)
   at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
   at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:502)
   at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1132)
   at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)
   at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1533)
   at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1489)
   at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
   at java.base/java.lang.Thread.run(Thread.java:844)

Serializable And Throwable Consumer

Wicket フォームイベント捕捉した時の処理は、シリアライズ化した関数型インターフェース
github.com
これのおかげで、Wicket 8から、AjaxFormSubmitBehavior の onSubmit でラムダ式を書けるようになった。
しかし、ラムダ式の中で例外捕捉の try~catchブロックを書いていくのも醜い。
そもそもラムダ式に記述する処理に例外が発生するような処理を書くべきではないのだが、
いた仕方ない場面も現実にはある。
→ それなら、Throwable で、Serializable を用意してまおうと考えました。
jdk-serializable-functionalを使います。

import java.util.Objects;
import java.util.function.Consumer;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;
import org.danekja.java.util.function.serializable.SerializableConsumer;
/**
 * Serializable And Throwable Consumer.
 */
public interface SerialThrowableConsumer<T> extends SerializableConsumer<T>{
   
   default SerializableConsumer<T> andThen(Consumer<? super T> after
   , SerializableBiConsumer<T, Exception> onCatch){
      Objects.requireNonNull(after);
      return (T t)->{
         try{
            accept(t);
         }catch(Exception e){
            onCatch.accept(t, e);
         }
         after.accept(t);
      };
   }

   public static <T> SerializableConsumer<T> of(SerialThrowableConsumer<T> consumer
   , SerializableBiConsumer<T, Exception> onCatch){
      return t->{
         try{
            consumer.accept(t);
         }catch(Exception ex){
            onCatch.accept(t, ex);
         }
      };
   }

   public static <T> SerializableConsumer<T> of(SerialThrowableConsumer<T> consumer){
      return t->{
         try{
            consumer.accept(t);
         }catch(Throwable ex){
            throw new RuntimeException(ex);
         }
      };
   }
}

使用例、、、

queue(new Button("send")
.add(AjaxFormSubmitBehavior.onSubmit("click", SerialAndThrowableConsumer.of(t->{
   // t = AjaxRequestTarget
   label.setDefaultModelObject(infield.getValue());
   t.add(response);
}, (t, ex)->{
   t.add(response);
}))));

これを使う候補は、、、
AjaxFormSubmitBehavior.onSubmit
OnChangeAjaxBehavior.onChange
AjaxFormChoiceComponentUpdatingBehavior.onUpdateChoice
AjaxFormComponentUpdatingBehavior.onUpdate
AjaxEventBehavior.onEvent

例外を外側にださない方の of メソッド(以下)しか使わないかもしれない。

public static <T> SerializableConsumer<T> of(SerialThrowableConsumer<T> consumer
   , SerializableBiConsumer<T, Exception> onCatch)

また、ステートレスな Page ということであれば、
Wicket stateless なページ - Oboe吹きプログラマの黙示録
で書いた、CustomStatelessAjaxFormSubmitBehavior  を利用して、

ボタンクリックの振る舞いなどは、、、

queue(new Button("submit").add(CustomStatelessAjaxFormSubmitBehavior.onSubmit("click", SerialThrowableConsumer.of(t->{
     // form 送信 クリック、ステートレスな Pageでの処理
    // t = AjaxRequestTarget
}, (t, x)->{
    // x = 例外Exceprion
}))));

とまとめることができる。

Wicket stateless なページ

Wicket の基本は、ステートフルである。それでもステートレスのページが必要なケースも要件によっては発生するのが
Webアプリ開発の世界。

Form → StatelessForm
Link → StatelessLink か、 BookmarkablePageLink に置き換える

で済ませられれば良いが、Page の中で、

    boolean status = isPageStateless();

をチェックすると、AJAXコンポーネントやビヘビアでは false になってしまう。
wicketstuff-stateless が、これら AJAX におけるステートレス化を提供しているのでこれを使う。

現時点、バージョンが、Wicket本体より遅れてるみたいで、8.0.0-M2 なので、pom.xml は、

<dependency>
    <groupId>org.wicketstuff</groupId>
    <artifactId>wicketstuff-stateless</artifactId>
    <version>8.0.0-M2</version>
</dependency>

wicketstuff-stateless が提供するものは、
コンポーネントでは、、
StatelessAjaxEventBehavior
StatelessAjaxFormComponentUpdatingBehavior
StatelessAjaxFormSubmitBehavior
StatelessOnChangeAjaxBehavior

ビヘビアでは、
StatelessAjaxButton
StatelessAjaxFallbackLink
StatelessAjaxSubmitLink
StatelessIndicatingAjaxButton
StatelessIndicatingAjaxFallbackLink

例えば、、、

queue(new StatelessForm<Void>("form"));
queue(new Button("submit").add(new StatelessAjaxFormSubmitBehavior("click"){
   @Override
   protected void onSubmit(AjaxRequestTarget target){
      // TODO
   }
}));

ということだが、やはり、onSubmit は、Consumer ラムダ式にしたい。

ステートフルの時のように、、

queue(new Button("submit").add(AjaxFormSubmitBehavior.onSubmit("click", t->{
  // TODO
})));

と書きたい。。

しかたなく、StatelessAjaxFormSubmitBehavior を継承したクラスを用意して、、

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.util.lang.Args;
import org.danekja.java.util.function.serializable.SerializableConsumer;
import org.wicketstuff.stateless.behaviors.StatelessAjaxFormSubmitBehavior;

/**
 * CustomStatelessAjaxFormSubmitBehavior.
 */
public class CustomStatelessAjaxFormSubmitBehavior extends StatelessAjaxFormSubmitBehavior{

   public CustomStatelessAjaxFormSubmitBehavior(String event){
      super(event);
   }
   public static CustomStatelessAjaxFormSubmitBehavior onSubmit(String eventName, SerializableConsumer<AjaxRequestTarget> onSubmit){
      Args.notNull(onSubmit, "onSubmit");
      return new CustomStatelessAjaxFormSubmitBehavior(eventName){
         private static final long serialVersionUID = 1L;
         @Override
         protected void onSubmit(AjaxRequestTarget target)   {
            onSubmit.accept(target);
         }
      };
   }
}

ステートレスを保つように、、、以下にする。

queue(new Button("submit").add(CustomStatelessAjaxFormSubmitBehavior.onSubmit("click", t->{
  // TODO
})));

さらに、ステートレス、シリアライズ、Thowable ということであれば、、、
Serializable And Throwable Consumer - Oboe吹きプログラマの黙示録

ZIP 圧縮と展開

先日、tar and gzip 圧縮・展開を書いたので、ZIP圧縮・展開です。これは Apache commons-compress を必要とすることなく
標準ライブラリと、Throwable な Consumerがあれば綺麗に書けます。→ ラムダ式の例外処理を綺麗にする - Oboe吹きプログラマの黙示録

圧縮サンプル ・・・Throwable な Consumer → ThrowableConsumer.of を使います。

List<String> files =  /* 圧縮対象のファイルのエントリPATH のリスト */
try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("out/test.zip")))){
   files.stream().forEach(ThrowableConsumer.of(e->{
      File f = new File(e);
      if (f.exists()){
         zos.putNextEntry(new ZipEntry(f.isDirectory() ? e + "/" : e));
         if (f.isFile()){
            try(FileInputStream fis = new FileInputStream(e); BufferedInputStream bis = new BufferedInputStream(fis)){
               int size = 0;
               byte[] buf = new byte[1024];
               while((size = bis.read(buf)) > 0){
                  zos.write(buf, 0, size);
               }
            }
         }
      }
   }));
}catch(Exception ex){
   ex.printStackTrace();
}

展開サンプル、展開時にエントリ名を標準出力してます。

try(ZipInputStream zin = new ZipInputStream(new FileInputStream("out/test.zip"))){
   ZipEntry entry;
   while((entry = zin.getNextEntry()) != null){
      System.out.println( entry.getName() );
      if (entry.isDirectory()){
         String s = entry.getName();
         new File(dirPath + "/" + s.substring(0, s.length()-1)).mkdir();
      }else{
         String[] d = entry.getName().split("/");
         String dir = dirPath;
         for(int i=0;i < d.length-1;i++){
            dir += "/" + d[i];
            new File(dir).mkdir();
         }
         try(FileOutputStream fos = new FileOutputStream(dirPath+"/"+entry.getName()); BufferedOutputStream bos = new BufferedOutputStream(fos)){
            int size = 0;
            byte[] buf = new byte[1024];
            while((size = zin.read(buf)) > 0){
               bos.write(buf, 0, size);
            }
         }
      }
   }
}catch(Exception ex){
   ex.printStackTrace();
}

tar and gzip のケースのように、
tar and gzip 圧縮・展開を補強 - Oboe吹きプログラマの黙示録

ディレクトリscan を合わせて、
oboe2uran.hatenablog.com

1つのインターフェースとしてまとめておきたくなりました。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.yipuran.function.ThrowableConsumer;

/**
 * ZIP 圧縮/解凍プロセッサ.
 * (規則)
 *     ZIPファイル名拡張子 → .zip
 * (圧縮)
 * Supplier<Collection<FileCollection>> = ファイルコレクション(FileCollection)で指定する対象を
 * Collection<String> compress(String targzPath) で圧縮する。
 * メソッド戻り値は、tarエントリ名 Collection
 * (展開)
 * void decompress(String zipPath, String dirPath) で展開する。
 * @since 1.1
 */
public interface ZipProcessor extends Supplier<Collection<FileCollection>>{

   /**
    * ZIP 圧縮実行.
    * <PRE>
    * Supplier で渡す FileCollection の渡し方で単一か複数か決まる。
    * 例1)
    *    // targetPath配下を圧縮対象にする場合
    *    List<FileCollection> fileCollections =
    *    Arrays.stream(new File(targetPath).listFiles()).map(e->FileCollection.of(e.getAbsolutePath())).collect(Collectors.toList());
    *    ZipProcessor processor = ()->fileCollections;
    *    Collection<String> entries = processor.compress(zipPath);
    *
    * 例2)
    *    // 1つのディレクトリツリーで圧縮
    *    FileCollection fileCollection = FileCollection.of(targetPath);
    *    ZipProcessor processor = ()->Arrays.asList(fileCollection);
    *    Collection<String> entries = processor.compress(zipPath);
    *
    * </PRE>
    * @param zipPath 作成する ZIPファイルパス、 *.zip
    * @return ZIPエントリ名 Collection
    */
   public default Collection<String> compress(String zipPath){
      Collection<String> entries = new ArrayList<>();
      try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(zipPath)))){
         get().forEach(fc->{
            String prefix = fc.getFile().getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
            fc.scan(ThrowableConsumer.of(f->{
               String entryName = f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, "");
               ZipEntry entry = new ZipEntry(f.isDirectory() ? entryName + "/" : entryName);
               entries.add(entry.getName().charAt(0)=='/' ? entry.getName().substring(1) : entry.getName());
               zos.putNextEntry(entry);
               if (f.isFile()){
                  try(FileInputStream fis = new FileInputStream(f); BufferedInputStream bis = new BufferedInputStream(fis)){
                     int size = 0;
                     byte[] buf = new byte[1024];
                     while((size = bis.read(buf)) > 0){
                        zos.write(buf, 0, size);
                     }
                  }
               }
            }));
         });
      }catch(IOException ex){
         throw new RuntimeException(ex);
      }
      return entries;
   }
   /**
    * zip 圧縮実行(対象制限).
    * <PRE>
    * Predicate<File> で、tar作成対象を制限する。任意ディレクトリパスなど制限するために使用する。
    * </PRE>
    * @param zipPath 作成する zip ファイルパス、 *.zip
    * @param p Predicate<File>制限規則の付与
    * @return ZIPエントリ名 Collection
    */
   public default Collection<String> compress(String zipPath, Predicate<File> p){
      Collection<String> entries = new ArrayList<>();
      try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(zipPath)))){
         get().forEach(fc->{
            String prefix = fc.getFile().getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
            fc.scan(p, ThrowableConsumer.of(f->{
               String entryName = f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, "");
               ZipEntry entry = new ZipEntry(f.isDirectory() ? entryName + "/" : entryName);
               entries.add(entry.getName().charAt(0)=='/' ? entry.getName().substring(1) : entry.getName());
               zos.putNextEntry(entry);
               if (f.isFile()){
                  try(FileInputStream fis = new FileInputStream(f); BufferedInputStream bis = new BufferedInputStream(fis)){
                     int size = 0;
                     byte[] buf = new byte[1024];
                     while((size = bis.read(buf)) > 0){
                        zos.write(buf, 0, size);
                     }
                  }
               }
            }));
         });
      }catch(IOException ex){
         throw new RuntimeException(ex);
      }
      return entries;
   }
   /**
    * ZIPファイル展開.
    * @param zipPath ZIPファイルパス
    * @param dirPath 展開先ディレクトリパス
    * @return Set<String> ZIPエントリ名 Collection
    */
   public static Set<String> decompress(String zipPath, String dirPath){
      TreeSet<String> entries = new TreeSet<>();
      // 展開
      try(ZipInputStream zin = new ZipInputStream(new FileInputStream(zipPath))){
         ZipEntry entry;
         while((entry = zin.getNextEntry()) != null){
            entries.add(entry.getName());
            if (entry.isDirectory()){
               String s = entry.getName();
               new File(dirPath + "/" + s.substring(0, s.length()-1)).mkdir();
            }else{
               String[] d = entry.getName().split("/");
               String dir = dirPath;
               for(int i=0;i < d.length-1;i++){
                  dir += "/" + d[i];
                  new File(dir).mkdir();
               }
               try(FileOutputStream fos = new FileOutputStream(dirPath+"/"+entry.getName()); BufferedOutputStream bos = new BufferedOutputStream(fos)){
                  int size = 0;
                  byte[] buf = new byte[1024];
                  while((size = zin.read(buf)) > 0){
                     bos.write(buf, 0, size);
                  }
               }
            }
         }
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
      return entries;
   }
   /**
    * エントリ名コレクション.
    * @param zipPath ZIPファイルパス、 *.zip
    * @return Set<String> ZIPエントリ名 Collection
    */
   public static Set<String> viewPath(String zipPath){
      TreeSet<String> entries = new TreeSet<>();
      try(ZipInputStream zin = new ZipInputStream(new FileInputStream(zipPath))){
         ZipEntry entry;
         while((entry = zin.getNextEntry()) != null){
            entries.add(entry.getName());
         }
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
      return entries;
   }
}

Java8 です。

Java9 module-info 未対応 JAR の Maven 解決方法

先日、初めてJava9 Jigsaw に触れて四苦八苦したが、、、
Java9 Jigsaw モジュール参照側は結局すべてモジュールを引っ張れないとならない?! - Oboe吹きプログラマの黙示録
Java9 Jigsaw モジュール使用プログラムの実行 - Oboe吹きプログラマの黙示録
Java9 Jigsaw と Maven - Oboe吹きプログラマの黙示録
Eclipse WTP プロジェクトで失敗したのは、まだ module-info が作られていない JARファイルを
どう扱うかが、できてないからだ。

以下のようにすれば良い。
例えば、 mybatis Version 3.4.5 を使うとすると pom.xml で、たとえ他のJARで間接参照があって
その pom.xml で書いてあっても改めて pom.xml に書かないと、存在しないモジュール定義のJARでは
だめであった。

<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
</dependency>

コンパイルプラグインで、、--add-modules を書かなくてならず、
ここで指定する名が、存在しないモジュール宣言を強制的に宣言することになり、

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
       <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
       </descriptorRefs>
       <source>9</source>
       <target>9</target>
       <compilerArgs>
            <arg>--add-modules mybatis</arg>
            <arg>--add-modules wasample</arg>
       </compilerArgs>
    </configuration>
</plugin>

wasample は、このWTPプロジェクトでWARとしてつくる artifactId に相当。
作成している WTPプロジェクトの module-info.java で、

module org.wa.wsample{
	requires mybatis;
}

これは 自動でモジュール名を付ける約束として以下の約束で付けていくことになる。
http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-

mybatis.3.4.5.jar を参照するから、requires mybatis なのだ。

これを踏まえて苦労して、SL4J や、Wicket を含めると以下のとおり。。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
       <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
       </descriptorRefs>
       <source>9</source>
       <target>9</target>
       <compilerArgs>
              <arg>--add-modules sl4j-api</arg>
              <arg>--add-modules logback-core</arg>
              <arg>--add-modules logback-classic</arg>
              <arg>--add-modules wicket-core</arg>
              <arg>--add-modules wicket-request</arg>
              <arg>--add-modules wicket-devutils</arg>
              <arg>--add-modules wicket-extensions</arg>
              <arg>--add-modules wicket-guice</arg>
              <arg>--add-modules wicket-ioc</arg>
              <arg>--add-modules wicket-objectsizeof-agent</arg>
              <arg>--add-modules wicket-util</arg>
              <arg>--add-modules wicket-jquery-ui</arg>
              <arg>--add-modules jdk-serializable-functional</arg>
              <arg>--add-modules yipuran-wicketcustom</arg>
              <arg>--add-modules yipuran-wicketguice</arg>
              <arg>--add-modules yipuran-mybatis</arg>
              <arg>--add-modules guice</arg>
              <arg>--add-modules javax.inject</arg>
              <arg>--add-modules openjson</arg>
              <arg>--add-modules mybatis</arg>
              <arg>--add-modules wasample</arg>
       </compilerArgs>
    </configuration>
</plugin>

この pom.xml での dependency の記述にも注意が必要になり、の先頭にないと
コンパイル参照できなかったりする。

<dependencies>
  <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
   </dependency>
   <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
   </dependency>
   <dependency>
       <groupId>org.danekja</groupId>
       <artifactId>jdk-serializable-functional</artifactId>
       <version>1.8.3</version>
   </dependency>
   <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
   </dependency>


依存するJARの artifactId の中の区切り文字が、"-" であるので module-info,java では、"." に置きかえる。

module org.wa.wsample{
	requires slf4j.api;
	requires jdk.serializable.functional;
	requires mybatis;

	requires wicket.core;
	requires wicket.request;
	requires wicket.devutils;
	requires wicket.extensions;
	requires wicket.guice;
	requires wicket.ioc;
	requires wicket.objectsizeof.agent;
	requires wicket.util;
	requires wicket.jquery.ui;
	requires guice;
	requires javax.inject;

	requires yipuran.wicketcustom;
	requires yipuran.wicketguice;
	requires yipuran.mybatis;
}

これでようやく Jigsaw のコンパイル時のモジュール参照が、WTP - maven でもなんとかなるが、
それにしても、コンパイルの所謂、、もっさり感の重さは酷いものだ。

QRコード生成&読込で良く使われてるもの。

Javaでなくても、C++C# でも Zxing を使えば QRコード生成&読込は、事足りる。

github.com


変なものを見つけた。JavaScriptQRコードをデコードするもの。。。
GitHub - colkito/qrcode-decoder-js: Javascript QR code decoder

どうして JavaScript でデコードする必要性があるのか?理解に苦しむ。