tar and gzip 圧縮・展開を補強

Tar GZIP 圧縮を先日書いた
oboe2uran.hatenablog.com

これに、圧縮する時に対象を制限したり展開せずにエントリを抽出するものを追加した。
長いけど以下のとおり。

ただし、→ FileCollection - Oboe吹きプログラマの黙示録 が必要

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.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;

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

  /**
   * tar and gzip 圧縮実行.
   * <PRE>
   * Supplier で渡す FileCollection の渡し方で単一か複数か決まる。
   * 例1)
   *    // targetPath配下を圧縮対象にする場合
   *    List<FileCollection> fileCollections =
   *    Arrays.stream(new File(targetPath).listFiles()).map(e->FileCollection.of(e.getAbsolutePath())).collect(Collectors.toList());
   *    TarGzipProcessor processor = ()->fileCollections;
   *    Collection<String> entries = processor.compress(targzipPath);
   *
   * 例2)
   *    // 1つのディレクトリツリーで圧縮
   *    FileCollection fileCollection = FileCollection.of(targetPath);
   *    TarGzipProcessor processor = ()->Arrays.asList(fileCollection);
   *    Collection<String> entries = processor.compress(targzipPath);
   *
   * </PRE>
   * @param targzPath 作成する tar and gzip ファイルパス、 *.tar.gz
   * @return tarエントリ名 Collection
   */
  public default Collection<String> compress(String targzPath){
    Collection<String> entries = new ArrayList<>();
    String tarpath = targzPath.replaceAll("\\.tar\\.gz$", ".tar");
    // tar 生成
    try(FileOutputStream out = new FileOutputStream(tarpath);TarArchiveOutputStream taos = new TarArchiveOutputStream(out)){
      get().forEach(fc->{
        String prefix = fc.getFile().getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
        fc.scan(f->{
          try{
            if (f.isDirectory()){
              TarArchiveEntry entry = new TarArchiveEntry(fc.getFile(), f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
              taos.putArchiveEntry(entry);
              taos.closeArchiveEntry();
              entries.add(entry.getName());
              return;
            }
            TarArchiveEntry entry = new TarArchiveEntry(f, f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
            taos.putArchiveEntry(entry);
            //taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
            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){
                taos.write(buf, 0, size);
              }
            }
            taos.closeArchiveEntry();
            entries.add(entry.getName());
          }catch(IOException ex){
            throw new RuntimeException(ex);
          }
        });
      });
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    // gzip 生成
    try(FileInputStream fis = new FileInputStream(tarpath); BufferedInputStream bis = new BufferedInputStream(fis);
      FileOutputStream fos = new FileOutputStream(targzPath); GzipCompressorOutputStream gout = new GzipCompressorOutputStream(fos)
    ){
      int size = 0;
      byte[] buf = new byte[1024];
      while((size = bis.read(buf)) > 0){
        gout.write(buf, 0, size);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    new File(tarpath).delete();
    return entries;
  }

  /**
   * tar and gzip 圧縮実行(対象制限).
   * <PRE>
   * Predicate<File> で、tar作成対象を制限する。任意ディレクトリパスなど制限するために使用する。
   * </PRE>
   * @param targzPath 作成する tar and gzip ファイルパス、 *.tar.gz
   * @param p Predicate<File>制限規則の付与
   * @return tarエントリ名 Collection
   */
  public default Collection<String> compress(String targzPath, Predicate<File> p){
    Collection<String> entries = new ArrayList<>();
    String tarpath = targzPath.replaceAll("\\.tar\\.gz$", ".tar");
    // tar 生成
    try(FileOutputStream out = new FileOutputStream(tarpath);TarArchiveOutputStream taos = new TarArchiveOutputStream(out)){
      get().forEach(fc->{
        String prefix = fc.getFile().getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
        fc.scan(p, f->{
          try{
            if (f.isDirectory()){
              TarArchiveEntry entry = new TarArchiveEntry(fc.getFile(), f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
              taos.putArchiveEntry(entry);
              taos.closeArchiveEntry();
              entries.add(entry.getName());
              return;
            }
            TarArchiveEntry entry = new TarArchiveEntry(f, f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
            taos.putArchiveEntry(entry);
            //taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
            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){
                taos.write(buf, 0, size);
              }
            }
            taos.closeArchiveEntry();
            entries.add(entry.getName());
          }catch(IOException ex){
            throw new RuntimeException(ex);
          }
        });
      });
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    // gzip 生成
    try(FileInputStream fis = new FileInputStream(tarpath); BufferedInputStream bis = new BufferedInputStream(fis);
      FileOutputStream fos = new FileOutputStream(targzPath); GzipCompressorOutputStream gout = new GzipCompressorOutputStream(fos)
    ){
      int size = 0;
      byte[] buf = new byte[1024];
      while((size = bis.read(buf)) > 0){
        gout.write(buf, 0, size);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    new File(tarpath).delete();
    return entries;
  }


  /**
   * tar and gzip 展開.
   * @param targzPath tar and gzip ファイルパス、 *.tar.gz
   * @param dirPath 展開先ディレクトリPATH
   * @return Collection<String> 展開された tar エントリ名
   */
  public static Collection<String> decompress(String targzPath, String dirPath){
    Collection<String> entries = new ArrayList<>();
    String tarname = targzPath.substring(targzPath.lastIndexOf("/") + 1).replaceAll("\\.gz$", "");
    // gzip 解凍
    try(FileInputStream fis = new FileInputStream(targzPath);GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
      FileOutputStream  fos = new FileOutputStream(dirPath + "/" + tarname)
    ){
      int size = 0;
      byte[] buf = new byte[1024];
      while((size = gin.read(buf)) > 0){
        fos.write(buf, 0, size);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    // tar展開
    try(FileInputStream fis = new FileInputStream(dirPath + "/" + tarname);  TarArchiveInputStream tais = new TarArchiveInputStream(fis)){
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        File file = new File(dirPath + "/" + entry.getName());
        if (entry.isDirectory()){
          file.mkdirs();
          entries.add(entry.getName());
          continue;
        }
        if (!file.getParentFile().exists()){ file.getParentFile().mkdirs(); }
        try(FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos)){
          int size = 0;
          byte[] buf = new byte[1024];
          while((size = tais.read(buf)) > 0){
            bos.write(buf, 0, size);
          }
          entries.add(entry.getName());
        }
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    new File(dirPath + "/" + tarname).delete();
    return entries;
  }

  /**
   * tar and gzip エントリ名コレクション.
   * @param targzPath tar and gzip ファイルパス、 *.tar.gz
   * @return Collection<String>
   */
  public static Collection<String> viewPath(String targzPath){
    Collection<String> entries = new ArrayList<>();
    try(  FileInputStream fis = new FileInputStream(targzPath);
        GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream();
        TarArchiveInputStream tais = new TarArchiveInputStream(pin)
    ){
      pin.connect(pos);
      new Thread(()->{
        try{
          int size = 0;
          byte[] buf = new byte[1024];
          while((size = gin.read(buf)) >= 0){
            pos.write(buf, 0, size);
            pos.flush();
          }
        }catch(IOException e){
          throw new RuntimeException(e.getMessage(), e);
        }
      }).start();
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        entries.add(entry.getName());
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
  /**
   * ArchiveEntryコレクション.
   * @param targzPath tar and gzip ファイルパス、 *.tar.gz
   * @return Collection<ArchiveEntry>
   */
  public static Collection<ArchiveEntry> entries(String targzPath){
    Collection<ArchiveEntry> entries = new ArrayList<>();
    try(  FileInputStream fis = new FileInputStream(targzPath);
        GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream();
        TarArchiveInputStream tais = new TarArchiveInputStream(pin)
    ){
      pin.connect(pos);
      new Thread(()->{
        try{
          int size = 0;
          byte[] buf = new byte[1024];
          while((size = gin.read(buf)) >= 0){
            pos.write(buf, 0, size);
            pos.flush();
          }
        }catch(IOException e){
          throw new RuntimeException(e.getMessage(), e);
        }
      }).start();
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        entries.add(entry);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
  /**
   * Predicate→ArchiveEntryコレクション.
   * @param targzPath tar and gzip ファイルパス、 *.tar.gz
   * @param p Predicate<ArchiveEntry> ファイルのArchiveEntry の Predicate
   * @return Collection<ArchiveEntry>
   */
  public static Collection<ArchiveEntry> entries(String targzPath, Predicate<ArchiveEntry> p){
    Collection<ArchiveEntry> entries = new ArrayList<>();
    try(  FileInputStream fis = new FileInputStream(targzPath);
        GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream();
        TarArchiveInputStream tais = new TarArchiveInputStream(pin)
    ){
      pin.connect(pos);
      try{
        Thread th = new Thread(()->{
          try{
            int size = 0;
            byte[] buf = new byte[1024];
            while((size = gin.read(buf)) >= 0){
              pos.write(buf, 0, size);
              pos.flush();
            }
          }catch(NullPointerException e){
            throw new RuntimeException(e.getMessage(), e);
          }catch(IOException e){
            throw new RuntimeException(e.getMessage(), e);
          }
        });
        th.start();
        th.join();
      }catch(InterruptedException e1){
      }
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        if (p.equals(entry)) entries.add(entry);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
  /**
   * Predicateファイル展開.
   * @param targzPath targzPath tar and gzip ファイルパス、 *.tar.gz
   * @param dirPath 展開先パス
   * @param p Predicate<ArchiveEntry> 展開するファイルのArchiveEntry の Predicate
   */
  public static void predicateOpen(String targzPath, String dirPath, Predicate<ArchiveEntry> p){
    try(  FileInputStream fis = new FileInputStream(targzPath);
        GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream();
        TarArchiveInputStream tais = new TarArchiveInputStream(pin)
    ){
      pin.connect(pos);
      try{
        Thread th = new Thread(()->{
          try{
            int size = 0;
            byte[] buf = new byte[1024];
            while((size = gin.read(buf)) >= 0){
              pos.write(buf, 0, size);
              pos.flush();
            }
          }catch(NullPointerException e){
            throw new RuntimeException(e.getMessage(), e);
          }catch(IOException e){
            throw new RuntimeException(e.getMessage(), e);
          }
        });
        th.start();
        th.join();
      }catch(InterruptedException e1){
      }
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        if (p.test(entry) && !entry.isDirectory()){
          String[] names = entry.getName().split("/");
          try(FileOutputStream fos = new FileOutputStream(dirPath + "/" + names[names.length-1]);
            BufferedOutputStream bos = new BufferedOutputStream(fos)){
            int size = 0;
            byte[] buf = new byte[1024];
            while((size = tais.read(buf)) > 0){
              bos.write(buf, 0, size);
            }
          }catch(IOException e){
            throw new RuntimeException(e.getMessage(), e);
          }
        }
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
  }
  /**
   * GZIP解凍実行.
   * @param gzipPath gzip ファイルPATH   *.gz
   * @param dirPath 展開先ディレクトリPATH
   */
  public static void openGz(String gzipPath, String dirPath){
    String tarname = gzipPath.substring(gzipPath.lastIndexOf("/") + 1).replaceAll("\\.gz$", "");
    try(FileInputStream fis = new FileInputStream(gzipPath);GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
      FileOutputStream  fos = new FileOutputStream(dirPath + "/" + tarname)
    ){
      int size = 0;
      byte[] buf = new byte[1024];
      while((size = gin.read(buf)) > 0){
        fos.write(buf, 0, size);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
  }
}

単純に tar だけだったら、、

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.function.Predicate;
import java.util.function.Supplier;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

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

  /**
   * tar 圧縮実行.
   * <PRE>
   * Supplier で渡す FileCollection の渡し方で単一か複数か決まる。
   * 例1)
   *    // targetPath配下を圧縮対象にする場合
   *    List<FileCollection> fileCollections =
   *    Arrays.stream(new File(targetPath).listFiles()).map(e->FileCollection.of(e.getAbsolutePath())).collect(Collectors.toList());
   *    TarProcessor processor = ()->fileCollections;
   *    Collection<String> entries = processor.compress(tarPath);
   *
   * 例2)
   *    // 1つのディレクトリツリーで圧縮
   *    FileCollection fileCollection = FileCollection.of(targetPath);
   *    TarProcessor processor = ()->Arrays.asList(fileCollection);
   *    Collection<String> entries = processor.compress(tarPath);
   *
   * </PRE>
   * @param tarPath 作成する tar ファイルパス、 *.tar
   * @return tarエントリ名 Collection
   */
  public default Collection<String> compress(String tarPath){
    Collection<String> entries = new ArrayList<>();
    // tar 生成
    try(FileOutputStream out = new FileOutputStream(tarPath);TarArchiveOutputStream taos = new TarArchiveOutputStream(out)){
      get().forEach(fc->{
        String prefix = fc.getFile().getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
        fc.scan(f->{
          try{
            if (f.isDirectory()){
              TarArchiveEntry entry = new TarArchiveEntry(fc.getFile(), f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
              taos.putArchiveEntry(entry);
              taos.closeArchiveEntry();
              entries.add(entry.getName());
              return;
            }
            TarArchiveEntry entry = new TarArchiveEntry(f, f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
            taos.putArchiveEntry(entry);
            //taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
            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){
                taos.write(buf, 0, size);
              }
            }
            taos.closeArchiveEntry();
            entries.add(entry.getName());
          }catch(IOException ex){
            throw new RuntimeException(ex);
          }
        });
      });
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }

  /**
   * tar ファイル展開.
   * @param tarPath tar ファイルパス、 *.tar
   * @param dirPath 展開先ディレクトリPATH
   * @return 展開された tar エントリ名
   */
  public static Collection<String> decompress(String tarPath, String dirPath){
    Collection<String> entries = new ArrayList<>();
    // tar 展開
    try(FileInputStream fis = new FileInputStream(tarPath); TarArchiveInputStream tais = new TarArchiveInputStream(fis)){
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        File file = new File(dirPath + "/" + entry.getName());
        if (entry.isDirectory()){
          file.mkdirs();
          entries.add(entry.getName());
          continue;
        }
        if (!file.getParentFile().exists()){ file.getParentFile().mkdirs(); }
        try(FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos)){
          int size = 0;
          byte[] buf = new byte[1024];
          while((size = tais.read(buf)) > 0){
            bos.write(buf, 0, size);
          }
        }
        entries.add(entry.getName());
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
  /**
   * エントリ名コレクション.
   * @param tarPath tar ファイルパス、 *.tar
   * @return Collection<String>
   */
  public static Collection<String> viewPath(String tarPath){
    Collection<String> entries = new ArrayList<>();
    try(FileInputStream fis = new FileInputStream(tarPath); TarArchiveInputStream tais = new TarArchiveInputStream(fis)){
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        entries.add(entry.getName());
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
  /**
   * ArchiveEntryコレクション.
   * @param tarPath tar ファイルパス、 *.tar
   * @return Collection<ArchiveEntry>
   */
  public static Collection<ArchiveEntry> entries(String tarPath){
    Collection<ArchiveEntry> entries = new ArrayList<>();
    try(FileInputStream fis = new FileInputStream(tarPath); TarArchiveInputStream tais = new TarArchiveInputStream(fis)){
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        entries.add(entry);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
  /**
   * Predicate→ArchiveEntryコレクション.
   * @param tarPath tarPath tar ファイルパス、 *.tar
   * @param p ファイルのArchiveEntry の Predicate
   * @return Collection<ArchiveEntry>
   */
  public static Collection<ArchiveEntry> entries(String tarPath, Predicate<ArchiveEntry> p){
    Collection<ArchiveEntry> entries = new ArrayList<>();
    try(FileInputStream fis = new FileInputStream(tarPath); TarArchiveInputStream tais = new TarArchiveInputStream(fis)){
      ArchiveEntry entry = null;
      while((entry = tais.getNextEntry()) != null){
        if (p.equals(entry)) entries.add(entry);
      }
    }catch(IOException ex){
      throw new RuntimeException(ex.getMessage(), ex);
    }
    return entries;
  }
}

Webページ入力フィールド制限、ToolTip

今まで過去から何度も書いたものをまとめる。
過去。。。
入力フィールドでよく使いそうな jQuery 処理のメモ - Oboe吹きプログラマの黙示録
input タグ type="number" のスピンボタンを非表示 - Oboe吹きプログラマの黙示録
chromeで0以上の整数入力に限定する - Oboe吹きプログラマの黙示録
chrome で、何がなんでも全角入力させない - Oboe吹きプログラマの黙示録
chrome で、何がなんでも全角入力させない - Oboe吹きプログラマの黙示録
全角英数字→半角英数字 - Oboe吹きプログラマの黙示録

chrome限定ではなく、Edge 、IE11 でも有効で、尚且つ、全角モードやNumLockキーなど ToolTip で警告

CSSとして以下を用意 input-support.css

@CHARSET "UTF-8";
/*--- type="number" のスピンボタンを非表示 --------*/
input[type="number"]{
  -moz-appearance:textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button{
  -webkit-appearance: none;
  margin: 0;
}
/*---- for ToolTip --------------------------------*/
.ui-tooltip {
  font-size: 14px;
  border-radius: 6px;
  color: #000000;
  background: #ffffe0;
  z-index: 20002;
  opacity: 0.85;
  filter: alpha(opacity=85);
  -ms-filter: "alpha(opacity=85)";
}

jQuery ソース

/**
 * input-support.js
 */
/* for 全角数字入力→ 半角数字 */
var numberConvert = function(value){
  var str = new String(value);
  var han = str.replace(/[0-9]/g, function(s){ return String.fromCharCode(s.charCodeAt(0)-0xFEE0); });
  return han.replace(/[\-|ー|―|-]/, function(s){ return '-'; }).replace(/[。.]/g, ".");
};
/* 全角英数字→半角英数字 */
var zenkakuTohan = function(value){
  var str = new String(value);
  var han = str.replace(/[A-Za-z0-9]/g, function(s){ return String.fromCharCode(s.charCodeAt(0)-0xFEE0); });
  return han.replace(/[\-|ー|―|-]/g, "-").replace(/[。.]/g, ".");
};
/* 数値→3桁区切り : 逆は、.replace(/,/g, ''); を使う */
var digitFormat = function(str){
   var num = new String(str).replace(/,/g, "");
   while(num != (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
   return num;
};
/* 全角数字→半角数字 */
var zenkakuNumToHan = function(txt){
  return txt.replace(/[0-9]/g, function(s){ return String.fromCharCode(s.charCodeAt(0)-0xFEE0); });
};
/* ひらがな→カタカナ */
var hirakanaTokatakana = function(txt){
   return txt.replace(/[ぁ-ん]/g, function(s){ return String.fromCharCode(s.charCodeAt(0)+0x0060); });
}
/* カタカナ→ひらがな */
var hirakanaTokatakana = function(txt){
   return txt.replace(/[ァ-ン]/g, function(s){ return String.fromCharCode(s.charCodeAt(0)-0x0060); });
}
/* 日付入力チェック */
var isValidDate = function(s){
   if (!s.match(/^\d{4}\/(0{0,1}[1-9]|1[012])\/(0{0,1}[1-9]|[12][0-9]|3[01])$/)){
      return true;
   }
   var ary = s.split(/\//);
   var y = ary[0];
   var m = parseInt(ary[1], 10);
   var d = parseInt(ary[2], 10);
   if(m < 1 || m > 12 || d < 1 || d > 31) {
      return true;
   }
   var dt = new Date(y, m - 1, d, 0, 0, 0, 0);
   if(dt.getFullYear() != y || dt.getMonth() != m - 1 || dt.getDate() != d){
      return true;
   }
   return false;
};
/**
 * Tooltip 実行
 */
var callTooltip = function(input, message){
  $(input).prop("title", "");
  $(input).tooltip({
    content: message,
    show: { effect: "slideDown" },
    hide: { effect: "slideDown" },
  });
  $(input).tooltip("open");
  setTimeout("$('" + input + "').tooltip('close');$('" + input + "').tooltip('destroy');", 1000);
};

/**
 * 強制入力セット関数。
 *     class="ank-text" class="num-text" 、id が必須
 * 半角英数字(コピーペースト不可) : ank-text
 * 半角英数字(コピーペースト可能) : ank-pastable-text
 * 半角数字                         : num-text
 * 時刻 HH:mm:ss                    : time-text
 * 金額入力(半角)                 : money-text
 */
var inputHankakuForce = function(){
  // 半角英数
  $('input.ank-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(zenkakuTohan($(this).val()));
  }).bind("paste",function(){
    return false;
  }).keyup(function(eo){
    $(this).val(zenkakuTohan($(this).val()));
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40 
       || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });
  // 半角英数(コピーペースト可能)
  $('input.ank-pastable-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(zenkakuTohan($(this).val()));
  }).keyup(function(eo){
    $(this).val(zenkakuTohan($(this).val()));
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40
         || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });
  // 半角数字
  $('input.num-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(numberConvert($(this).val()));
  }).bind("paste",function(){
    return false;
  }).keyup(function(eo){
    $(this).val(numberConvert($(this).val()).replace(/[^\-0-9]/g, ''));
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40
        || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });
  // 時刻 HH:mm:ss
  $('input.time-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(numberConvert($(this).val()));
    if (!$(this).val().match(/^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])$/)){
      callTooltip("#"+$(this).prop("id"), "HH:mm:ss 形式でありません");
    }
  }).bind("paste",function(){
    return false;
  }).keyup(function(eo){
    $(this).val(numberConvert($(this).val()).replace(/:/g, ':').replace(/[^0-9:]/g, ''));
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40
         || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });
  // 日付入力 yyyy/MM/dd
  $('input.date-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(numberConvert($(this).val()));
    if (isValidDate($(this).val())){
      callTooltip("#"+$(this).prop("id"), "日付エラー yyyy/MM/dd");
    }
  }).bind("paste",function(){
    return false;
  }).keyup(function(eo){
    $(this).val(numberConvert($(this).val()).replace(///g, '/').replace(/[^0-9\/]/g, ''));
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40
          || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });

  // 金額入力(半角)
  $('input.money-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(numberConvert($(this).val()));
  }).bind("paste",function(){
    return false;
  }).keyup(function(eo){
    var value = numberConvert($(this).val()).replace(/[^\-0-9]/g, '');
    if (value.match(/^(|-)[0-9]+$/)){
      $(this).val(digitFormat(value));
    }
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40
          || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });
  // IPアドレス
  $('input.ip-text').focus(function(eo){
    $(this).prop('type','tel');
  }).blur(function(eo){
    $(this).prop('type','text');
    $(this).val(numberConvert($(this).val()));
    var a = $(this).val().split(/\./);
    if (a.length != 4){
      callTooltip("#"+$(this).prop("id"), "IP address エラー");
    }
    var error = 0;
    $.each(a, function(i, t){
      if (t < 0 || 255 < t) error = 1;
    });
    if (error==1){
      callTooltip("#"+$(this).prop("id"), "IP address エラー");
    }
  }).bind("paste",function(){
    return false;
  }).keyup(function(eo){
    $(this).val(numberConvert($(this).val()).replace(/[^0-9\.]/g, ''));
    if (eo.keyCode==229){return false;  }
  }).keydown(function(eo){
    if ((eo.keyCode >= 33 && eo.keyCode <= 39)|| eo.keyCode==40
         || eo.keyCode==45 || eo.keyCode==46 || eo.keyCode==12){
      if (event.getModifierState("NumLock")===false){
        callTooltip("#"+$(this).prop("id"), "NumLock OFF(消灯) になってます");
      }
    }
    if (eo.keyCode==229){
      callTooltip("#"+$(this).prop("id"), "全角入力になってます");
      return false;
    }
  });
};

HTML

<link rel="stylesheet" type="text/css" href="../css/jquery-ui-1.12.1.min.css">
<script type="text/javascript" src="../js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="../js/jquery-ui-1.12.1.min.js"></script>

<link rel="stylesheet" type="text/css" href="input-support.css">
<script type="text/javascript" src="input-support.js"></script>

<script type="text/javascript">
$(function(){
  inputHankakuForce();
});
</script>
<style type="text/css">
table{ white-space: nowrap; }
</style>
</head>
<body>
<form>
<table>
  <tr>
    <td>半角英数字 only(コピーペースト不可)</td>
    <td><input id="a1" type="text" class="ank-text"></td>
  </tr>
  <tr>
    <td>半角英数字 only(コピーペースト可能)</td>
    <td><input id="a2" type="text" class="ank-pastable-text"></td>
  </tr>
  <tr>
    <td>半角数字 only(全角数字→半角自動変換)</td>
    <td><input id="n1" type="text" class="num-text"></td>
  </tr>
  <tr>
    <td>時刻(hh:mm:ss)</td>
    <td><input id="t1" type="text" class="time-text" maxlength="8"></td>
  </tr>
  <tr>
    <td>日付(yyyy/MM/dd)</td>
    <td><input id="t1" type="text" class="date-text" maxlength="10"></td>
  </tr>
  <tr>
    <td>金額(3桁区切り)</td>
    <td><input id="y1" type="text" class="money-text"></td>
  </tr>
  <tr>
    <td>IP-address</td>
    <td><input id="y1" type="text" class="ip-text" maxlength="15"></td>
  </tr>
</table>

Java9 の HttpClient を試す

Java9 HttpClient インキュベーターなので、この先どうなるか判らないが、
Apatch HTTPClient を使わなくて済むようになるのか?
とりあえず試してみる。
モジュール使用宣言を用意する必要があり、module-info..java を次のように用意する。

module sample{
    requires jdk.incubator.httpclient;
}

パッケージは、jdk.incubator.http だが、requires で書くのは、jdk.incubator.httpclient
import するもの

import java.net.URI;
import java.nio.charset.StandardCharsets;
import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;

まずは、同期型

try{
   HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.google.com"))
   .GET()
   .build();
   HttpResponse<String> res = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
   .followRedirects(HttpClient.Redirect.SAME_PROTOCOL).build()
   .send(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8));
   // HttpResponse を参照
   res.headers().map().entrySet().forEach(e->{
      System.out.println(e.getKey() + "→" + e.getValue() );
   });
   System.out.println(res.body());
}catch(Exception e){
   e.printStackTrace();
} 

非同期型は、sendAsync を使う、HttpResponse.BodyHandlerを処理することになる。

HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.google.com"))
.GET()
.build();

CompletableFuture<HttpResponse<String>> future
= HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.SAME_PROTOCOL).build()
.sendAsync(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8));

// CompletableFuture<HttpResponse<String>> を join する。
future.handle((r, t)->{
   System.out.println(Thread.currentThread().getName());
   r.headers().map().entrySet().forEach(e->{
      System.out.println(e + "→" + e.getValue() );
   });
   System.out.println(r.body());
   return r;
}).join();

ExecutorService  handleAsync で、、、

final ExecutorService service = Executors.newFixedThreadPool(4);
HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.google.com"))
.GET()
.build();

HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.SAME_PROTOCOL)
.executor(service)
.build()
.sendAsync(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8))
.handleAsync((r, t)->{
   System.out.println(Thread.currentThread().getName());
   r.headers().map().entrySet().forEach(e->{
      System.out.println(e + "→" + e.getValue() );
   });
   System.out.println(r.body());
   return r;
}).join();
service.shutdown();

同じリクエスト、ExecutorService  whenCompleteAsync で、例外発生時も捕捉する場合

final ExecutorService service = Executors.newFixedThreadPool(4);
HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.google.com"))
.GET()
.build();

HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.SAME_PROTOCOL)
.executor(service)
.build()
.sendAsync(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8))
.whenCompleteAsync((r, x)->{
   r.headers().map().entrySet().forEach(e->{
      System.out.println(e + "→" + e.getValue() );
   });
   System.out.println(r.body());
   Optional.ofNullable(x).ifPresent(t->t.printStackTrace());
}).join();

これは、java.util.concurrent.CompletionException でラップされた例外を捕捉することになる。
URL が誤ってれば、java.nio.channels.UnresolvedAddressExceptionなどがラップされて捕捉される。

サンプルとしてもっと簡単に、返されるレスポンス BODYだけを標準出力するなら、、、

HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.SAME_PROTOCOL)
.executor(service)
.build()
.sendAsync(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8))
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();

結果 body をファイル出力するなら。 HttpResponse.BodyHandler.asFile を使う。

HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.SAME_PROTOCOL)
.executor(service)
.build()
.sendAsync(request, HttpResponse.BodyHandler.asFile(FileSystems.getDefault().getPath("c:/work/test")))
.thenApply(HttpResponse::body)
//.thenAccept(System.out::println) /* ファイル出力した時の path を標準出力 */ 
.join();

↑ //.thenAccept(System.out::println)  は、ファイル出力もするけど、標準出力もするようにもできる。

Java9 Jigsaw モジュール参照側は結局すべてモジュールを引っ張れないとならない?!

Java9 Jigsaw モジュール使用プログラムの実行 - Oboe吹きプログラマの黙示録

に書いたが、Elipse Maven で、WTP 書いてコンパイルができないのは、

依存する JAR が export を宣言した module-info を用意したJAR を使ってないからだ。

でも、開発するWebアプリで使用する JARなどは、要件によって多岐にわたる。

全て用意されていればそのバージョンを使うことになる。

ログ出力で有名な SL4J は新しくないと module-info が入っていない。

  slf4j-api  1.7.xx ではだめで、1.8.0-beta0 ならmodule-info が入っている。

ひどいのは、javax.servlet-api でまだ入っておらず、Mavenセントラルリポジトリ

さがしても、module-info が入っている JAR が見つからない。

Webアプリなどは、javax.servlet-api を使う場面は結構あるはずだ。

これでは、片手落ちである。。

Java9 Jigsaw が浸透するのは、かなり時間かかりそうだ。

Java7, 8 で Jigsaw が登場するのが見送られて、あれだけ時間かかって、、

あれだけ騒いで、ようやくJava9 で出てもこの状況はあんまりです。

マップの比較、MapDiff

1年近く前に、リストの比較を書いた。
リストの比較、ListDiff - Oboe吹きプログラマの黙示録

それなら2つのマップの比較も普遍的なものが書けるであろう。

import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;

/**
 * MapDiff
 */
public final class MapDiff<K, V>{
   private BiFunction<V, V, Boolean> matchFunction;
   private Consumer<K> leftonly;
   private Consumer<K> rightonly;
   private Consumer<K> diffConsumer;
   private Consumer<K> matchConsumer;

   private MapDiff(BiFunction<V, V, Boolean> matchFunction){
      this.matchFunction = matchFunction;
   }
   /**
    * MapDiffインスタンス生成.
    * @param matchFunction 比較する2つのMapのValue要素を等しいと判定する BiFunction で Boolean(true=等しい) を返す。
    * @return ListDiff<T>{
    */
   public static <K, V> MapDiff<K, V> of(BiFunction<V, V, Boolean> matchFunction){
       return new MapDiff<>(matchFunction);
   }
   /**
    * 左側(1番目指定)Mapだけに所属するkeyの Consumerを登録.
    * @param leftonly Consumer<K>
    */
   public void leftOnly(Consumer<K> leftonly){
      this.leftonly = leftonly;
   }
   /**
    * 右側(2番目指定)Mapだけに所属するkeyの Consumerを登録.
    * @param rightonly Consumer<K>
    */
   public void rightOnly(Consumer<K> rightonly){
      this.rightonly = rightonly;
   }
   /**
    * MapのValue値が異なるkeyの Consumerを登録.
    * @param biConsumer Consumer<K>
    */
   public void unmatch(Consumer<K> diffConsumer){
      this.diffConsumer = diffConsumer;
   }
   /**
    * MapのKeyとValue値が同じkeyの Consumerを登録.
    * @param biConsumer Consumer<K>
    */
   public void match(Consumer<K> matchConsumer){
      this.matchConsumer = matchConsumer;
   }
   /**
    * Mapの比較.
    * @param leftMap
    * @param rightMap
    */
   public void diff(Map<K, V> leftMap, Map<K, V> rightMap){
      leftMap.entrySet().stream().forEach(e->{
         K k = e.getKey();
         if (rightMap.containsKey(k)){
            if (matchFunction.apply(e.getValue(), rightMap.get(k))){
               if (matchConsumer != null) matchConsumer.accept(k);
            }else{
               if (diffConsumer != null) diffConsumer.accept(k);
            }
         }else{
            if (leftonly != null) leftonly.accept(k);
         }
      });
      if (rightonly != null){
         rightMap.keySet().forEach(key->{
            if (!leftMap.containsKey(key)) rightonly.accept(key);
         });
      }
   }
   /**
    * Mapの比較.
    * @param map1 マップ1
    * @param map2 マップ2
    * @param match 値が一致するかを返す BiFunction<V, V, Boolean>
    * @param leftOnly マップ1に対してマップ2で削除されたキーのConsumer<K>
    * @param rightOnly マップ1に対してマップ2で追加されたキーのConsumer<K>
    * @param mc マップ1に対してマップ2で変更されたキーのConsumer<K>
    */
   public static <K, V> void diffMap(Map<K, V> map1, Map<K, V> map2, BiFunction<V, V, Boolean> match
   , Consumer<K> leftOnly, Consumer<K> rightOnly, Consumer<K> mc){
      map1.entrySet().stream().forEach(e->{
         K k = e.getKey();
         if (map2.containsKey(k)){
            if (!match.apply(e.getValue(), map2.get(k))) mc.accept(k);;
         }else{
            leftOnly.accept(k);
         }
      });
      map2.keySet().forEach(key->{
         if (!map1.containsKey(key)) rightOnly.accept(key);
      });
   }
}

だいたいこんなかんじで使う

MapDiff<Integer, String> m = MapDiff.of((v1, v2)->v1.equals(v2));
m.leftOnly(k->leftMap.put(k, map1.get(k)));
m.rightOnly(k->rightMap.put(k, map2.get(k)));
m.unmatch(k->dMap.put(k, map1.get(k) + "→" + map2.get(k)));

m.diff(map1, map2);

Java9 Jigsaw モジュール使用プログラムの実行

モジュール参照のJavaプログラムを実行するにはモジュール exportしている JARの置かれたパスを指定して
読み込めるように指定オプションと参照側のクラスを指定しないとならない。

java  -m {モジュール検索先PATH}  -p {実行するモジュール名}/クラス名

{モジュール検索先PATH}複数の場合、Windowsでは、";" セミコロン区切り、Linux なら ":"コロン区切り
Eclipse で実行する時は、実行構成の VM引数にこれを書かなくてはならず、めんどくさい。

実際、Maven プロジェクトで実行テストするケースが多いので、参照実行側の module-info を見つけられるように、
モジュール検索先PATH には、target/classes も付けないとならない。

-p target/classes;C:\Users\yipuran\.m2\repository\org\wa\model\models\1.0 -m org.wa.note/org.wa.note.NodeTest

これはPCのローカルリポジトリに models-1.0.jar という参照されるモジュールJARが存在して
org.wa.note パッケージで作成した module-info.class が、models-1.0.jarの モジュールに依存している場合だ。

手数がかかる。。

java -jar で実行する為に1つのJAR に纏めてしまえば、こんな手間は要らないが、Jigsawの目的である
依存によるプログラムの肥大化を防ぐということには反することになる。
でも、プログラム開発時、module-info 定義で、見せたくない~直接使用させたくない public を制限抑止するのには
モジュールの使用は有効だ。

pom.xml の記述

<build>

  <plugins>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
         <version>1.2.1</version>
         <executions>
            <execution>
               <phase>package</phase>
               <goals>
                  <goal>shade</goal>
               </goals>
               <configuration>
                  <finalName>note</finalName>
                  <transformers>
                     <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.wa.note.NodeTest</mainClass>
                     </transformer>
                     </transformers>
                  </configuration>
            </execution>
         </executions>
      </plugin>
      <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>
         </configuration>
      </plugin>
   </plugins>

</build>

これで、

java -jar note.jar

で実行できる。

しかし、Webアプリとしてはどうだろうか?
Eclipse WTP で用意して実行してみるがダメだった。

参照側ーWebアプリ側で、module-info.java を書かなければ、参照するものはきちんと参照もできて
module-info export 記述されてないパッケージへは使用できないようにコンパイルエラーにはなる。
半分目的は達成されているのだが、参照側で module-info を Webアプリではどこにどう書けばよいのか?
まだ、明確に理解していない。