byte[] を IntStream で、、、

byte配列 を IntStream として処理する方法、

int配列 ならば、

    int[] i = { 25, 26, 27, 28 };
    Arrays.stream(i)

は、成立するが、

     byte[] b = { 25, 26, 27, 28 };
     Arrays.stream(b)

というのは成立しない。それでも byte配列 を IntStream として処理したい時はどうするか?
同じ悩みを抱えた人はいたみたいで、以下、stackoverflow に、策が載ってました。
stackoverflow.com
byte配列 なんだから、ByteArrayInputStream で処理するというのも自然と納得はするけど、一手多くて
どうもスッキリしません。

byte[] b = { 25, 26, 27, 28 };

ByteArrayInputStream inst = new ByteArrayInputStream(b);
String result = IntStream.generate(inst::read).limit(inst.available())
                .mapToObj(i->String.format("%02X", i))
                .collect(Collectors.joining(" "));

これで、result は、、

19 1A 1B 1C

にはなります。

やはり1つの実行文で、、、

byte[] b = { 25, 26, 27, 28 };

String result = IntStream.range(0, b.length).mapToObj(i->String.format("%02X", b[i]))
                .collect(Collectors.joining(" "));

で、、、

19 1A 1B 1C

の方が、スマートです。

stackoverflow に書かれたように、つまり、、

    IntStream.range(0, b.length).map(i->b[i])

これに尽きるのですが、map変換の目的によっては、map の中で、そのまま、String.format で変換が無理なので、
mapToObj を使います。

ファイルPATH の階層によるコンパレータ

使わないかもしれないけど。

public final class FilePathComparator implements Comparator<File>{

   private String  splitCharacter = File.separator.equals("\\") ? "\\\\" : "/";
   private OrderBy orderby;

   public enum OrderBy{
      ASC(1),
      DESC(-1);

      private int v;
      private OrderBy(int v){
         this.v = v;
      }
      public int getVector(){
         return v;
      }
   }

   public FilePathComparator(OrderBy orderby){
      this.orderby =orderby;
   }

   @Override
   public int compare(File o1, File o2){
      return new Integer(o1.getPath().split(splitCharacter).length)
      .compareTo(new Integer(o2.getPath().split(splitCharacter).length)) * orderby.getVector();
   }
}

この中の enum OrderBy で、昇順、降順を指定する。
使用例、

List<File> list;

// 省略...

list.stream().sorted(new FilePathComparator(OrderBy.ASC)).forEach(e->{

});

Wicket 作成した Panel の中のListView に置いた Form field の数

ページに、配置する Panel を作り、Panel の中に、ListView を配置して、
ListView で、フォームフィールドのコンポーネントを用意したときで、ListView の行数が可変の場合、
submit した時、行数を求めるのは、どうしたらいいか悩む。

そこで、考えたのが、、、

getRequestCycle() → getPostParameters() → getParameterNames() → Stream で ListView を探して
インデックスの Max を求める。。。

int listvewSize = getRequestCycle().getRequest().getPostParameters().getParameterNames()
.stream()
.filter(e->e.startsWith("panel:listview:"))
.map(e->e.replace("panel:listview:", "").split(":")[0])
.mapToInt(e->new Integer(e))
.max().orElse(-1) + 1;

これで、ListView が増えたり減ったりした後でもサイズを求めて適切に処理できる。

File.separator の処理

ファイルのPATH  Windows でも Linux でも、階層に分けた同じ結果リストを求める。
どういう課題かというと、、、

Windows

c:\a\b\c

Linux

/a/b/c

とある時、

a
a/b
a/b/c

というリストを改行出力した結果を得たい。
Windows 環境の PATH であろうが、Linux環境であろうが、同じ結果が欲しい。
ちょうど、Stream の collect 集計の練習になる。

String path に、格納されているとして、以下のようになる。

List<String> list = Stream.of(path.split(File.separator.equals("\\") ? "\\\\" : "/"))
.filter(s->s.length() > 0 && !s.matches("^[a-zA-Z]:$"))
.collect(()->new ArrayList<String>(), (r, s)->{
   if (r.size() < 1){
      r.add(s);
   }else{
      r.add(r.get(r.size()-1) + "/" + s);
   }
}, (r, t)->r.addAll(t));

もっと短く書けるはずで、、

List<String> list = Stream.of(path.split(File.separator.equals("\\") ? "\\\\" : "/"))
.filter(s->s.length() > 0 && !s.matches("^[a-zA-Z]:$"))
.collect(()->new ArrayList<String>()
, (r, s)->r.add((r.size() < 1 ? "" : r.get(r.size()-1) + "/") + s)
, (r, t)->r.addAll(t));

これで、

list.stream().forEach(e->{
   System.out.println(e);
});
a
a/b
a/b/c

Files.lines を使おう。

Java で 有名なフレームワークを使った Webアプリ開発ばかりやっていると、
ファイルシステム上のテキストファイルを1行ずつ読込み処理するというものを作る機会は
案外、少ないもので、どうするんだっけ。。と思いだすまで数十秒かかってしまい自己嫌悪。。。

たいていは、BufferedReaderを使って、、

try(BufferedReader reader = new BufferedReader(new FileReader("/root/work/foo"))){
   String line;
   while((line = reader.readLine()) != null){
       // 1行分文字列 line を処理
   }
}catch(IOException ex){
   
}

このように書くけど。。。Java8 以降は、以下の書き方もあるんですね。

try{
   Files.lines(FileSystems.getDefault().getPath("", "/root/work/foo"), Charset.forName("UTF-8")).forEach(s->{
      // 1行分文字列 s を処理
   });
}catch(IOException ex){
   
}

1行しか差がないと嘆くことない!
Reader の IO だと、文字セット指定できないけど、Files.lines なら、文字セットを指定できる。

tar and zip の圧縮・展開

昨日書いた FileCollection は、実はこの tar and zip の圧縮・展開 を書く為の伏線です。
oboe2uran.hatenablog.com

Apache commons compress を使用して
https://commons.apache.org/proper/commons-compress/

関数型インターフェースで圧縮・展開を用意した。
圧縮対象、複数のケースも考慮して Supplier で対象コレクションを指定するのである。

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.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 圧縮/解凍プロセッサ.
 */
public interface TarGzipProcessor extends Supplier<Collection<FileCollection>>{
   /**
    * tar and gzip 圧縮実行.
    * @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);
                  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 展開された 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;
   }
   /**
    * 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);
      }
   }
}

Windowsでも使えるように replaceAll が入ってしまった。。


1つのディレクトリを指定し、その配下を tar.gz にする例、

FileCollection fileCollection = FileCollection.of("/home/aaa");
TarGzipProcessor processor = ()->Arrays.asList(fileCollection);
processor.compress("/home/aaa.tar.gz");

複数対象にした圧縮。。。"/home/aaa/" ディレクトリ配下を対象にする場合、
= tarエントリに、親ディクトリ "aaa" を含めさせたくない場合である。

List<FileCollection> fileCollections = Arrays.stream(new File("/home/aaa").listFiles())
.map(e->FileCollection.of(e.getAbsolutePath()))
.collect(Collectors.toList());
TarGzipProcessor processor = ()->fileCollections;
processor.compress(targzipPath);


いきなり、Supplier を使うから、こんな書き方

TarGzipProcessor processor = ()->fileCollections;

で、古くから Java に親しんできた者には、まるで Java のコードでないように一瞬見えてしまいますが、
これが、なんとも簡潔であると気づく。。

FileCollection

昨日の続き、、ディレクトリを走査するもの、名前が良くないので、FileCollection と名付け
さらに、Predicate を走査ではさみたくなった。

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
 * ファイルコレクション.
 */
public final class FileCollection {
   private File file;
   private FileCollection(String path){
      file = new File(path);
   }
   /**
    * インスタンス取得.
    * @param path コレクションするファイルパス
    * @return FileCollection
    */
   public static FileCollection of(String path){
      return new FileCollection(path);
   }
   /**
    * @return file
    */
   public File getFile(){
      return file;
   }
   /**
    * 走査実行→コレクション取得.
    * @return List<File>
    */
   public List<File> scan(){
      return parse(file, new ArrayList<>());
   }
   /**
    * Predicate走査実行→コレクション取得.
    * @param p 検査するPredicate<File>
    * @return List<File>
    */
   public List<File> scan(Predicate<File> p){
      return parse(file, new ArrayList<>(), p);
   }
   /**
    * 走査 Consumer実行.
    * @param c Consumer
    */
   public void scan(Consumer<File> c){
      parse(file, c);
   }
   /**
    * Predicate走査 Consumer実行.
    * @param p 検査するPredicate<File>
    * @param c Consumer
    */
   public void scan(Predicate<File> p, Consumer<File> c){
      parse(file, p, c);
   }
   /**
    * Predicate検査一致の成否.
    * @param p 検査するPredicate<File>
    * @return true=1つ以上一致するものがある。false=一致するものが存在しない。
    */
   public boolean anyMatch(Predicate<File> p){
      return findmatch(file, p);
   }
   private List<File> parse(File file, List<File> list){
      list.add(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, list);
         }
      }
      return list;
   }
   private void parse(File file, Consumer<File> c){
      c.accept(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, c);
         }
      }
   }
   private List<File> parse(File file, List<File> list, Predicate<File> p){
      if (p.test(file)) list.add(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, list, p);
         }
      }
      return list;
   }
   private void parse(File file, Predicate<File> p, Consumer<File> c){
      if (p.test(file)) c.accept(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, p, c);
         }
      }
   }
   private boolean findmatch(File file, Predicate<File> p){
      if (p.test(file)) return true;
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            if (findmatch(f, p)) return true;
         }
      }
      return false;
   }
}

Example : /var 配下、ls -R オプションのように全ての log ファイルを抜き出す。

FileCollection.of("/var").scan(p->p.getName().endsWith(".log"), f->{
   System.out.println(f.getAbsolutePath());
});

Example : /var 配下、1個でも "error" がつくファイル名が存在したら何かする

if (FileCollection.of("/var").anyMatch(f->f.getName().indexOf("error") >= 0)){
   // TODO
}