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 のコードでないように一瞬見えてしまいますが、
これが、なんとも簡潔であると気づく。。