    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.util;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import java.io.File;
    020import java.io.InputStream;
    021import java.io.FileInputStream;
    022import java.io.BufferedInputStream;
    023import java.io.FileNotFoundException;
    024import java.io.FileOutputStream;
    025import java.io.BufferedOutputStream;
    026import java.io.IOException;
    027import java.util.ArrayList;
    028import java.util.List;
    029
    030import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
    031import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
    032import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
    033import org.apache.commons.compress.utils.IOUtils;
    034
    035import org.opengion.fukurou.system.Closer;                                                      // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
    036
    037/**
    038 * ZipArchive.java は、ZIPファイルの解凍・圧縮を行うためのUtilクラスです。
    039 *
    040 * zipファイルで、圧縮時のファイルのエンコードを指定できるようにします。
    041 * ファイルをZIPにするには、java.util.zipパッケージ を利用するのが一般的です。
    042 * ところが、ファイル名にUTF-8文字エンコーディングを利用する為、Windowsの世界では
    043 * これを取り扱うアプリケーションも少ないため、文字化けして見えてしまいます。
    044 * これを解決するには、エンコードが指定できるアーカイバを使用する必要があります。
    045 * 有名どころでは、ant.jar に含まれる、org.apache.tools.zip と、Apache Commons の
    046 * org.apache.commons.compress です。
    047 * org.apache.tools.zip は、java.util.zip とほぼ同じ扱い方、クラス名が使えるので
    048 * 既存のアプリケーションを作り変えるには、最適です。
    049 * openGion では、アーカイバ専用ということで、org.apache.commons.compress を
    050 * 採用します。
    051 *
    052 * @og.group ユーティリティ
    053 * @og.rev 6.0.0.0 (2014/04/11) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
    054 *
    055 * @version  6.0
    056 * @author   Kazuhiko Hasegawa
    057 * @since    JDK5.0,
    058 */
    059public final class ZipArchive {
    060
    061        /**
    062         * 全てスタティックメソッドのためインスタンスの作成を禁止します。
    063         */
    064        private ZipArchive() {};
    065
    066        /**
    067         * エンコードに、Windows-31J を指定した、ZIPファイルの解凍処理を行います。
    068         * 引数にフォルダ(targetPath)に指定されたZIPファイル(zipFile)を解凍します。
    069         * 解凍先のファイルが存在する場合でも、上書きされますので注意下さい。
    070         *
    071         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
    072         *
    073         * @param targetPath 解凍先のフォルダ
    074         * @param zipFile 解凍するZIPファイル
    075         *
    076         * @return 解凍されたZIPファイルの一覧
    077         * @og.rtnNotNull
    078         */
    079        public static List<File> unCompress( final File targetPath , final File zipFile ) {
    080                return unCompress( targetPath,zipFile,"Windows-31J" );
    081        }
    082
    083        /**
    084         * エンコードを指定した、ZIPファイルの解凍処理を行います。
    085         * 引数にフォルダ(targetPath)に指定されたZIPファイル(zipFile)を解凍します。
    086         * 解凍先のファイルが存在する場合でも、上書きされますので注意下さい。
    087         *
    088         * 解凍途中のエラーは、エラー出力に出力するだけで、処理は止めません。
    089         *
    090         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    091         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
    092         * @og.rev 4.3.3.3 (2008/10/22) mkdirsする前に存在チェック
    093         * @og.rev 5.1.9.0 (2010/08/01) 更新時刻の設定
    094         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
    095         *
    096         * @param targetPath 解凍先のフォルダ
    097         * @param zipFile 解凍するZIPファイル
    098         * @param encording ファイルのエンコード(Windows環境では、"Windows-31J" を指定します)
    099         *
    100         * @return 解凍されたZIPファイルの一覧
    101         * @og.rtnNotNull
    102         */
    103        public static List<File> unCompress( final File targetPath, final File zipFile, final String encording ) {
    104                final List<File> list = new ArrayList<>();
    105
    106                // 解凍先フォルダの末尾が'/'又は'\'でなければ区切り文字を挿入
    107        //      String tmpPrefix = targetPath;
    108        //      if( File.separatorChar != targetPath.charAt( targetPath.length() - 1 ) ) {
    109        //              tmpPrefix = tmpPrefix + File.separator;
    110        //      }
    111
    112                ZipArchiveInputStream zis = null;
    113                File tmpFile = null;
    114        //      String fileName = null;
    115
    116                try {
    117                        zis = new ZipArchiveInputStream( new BufferedInputStream( new FileInputStream( zipFile ) ) ,encording );
    118
    119                        ZipArchiveEntry entry = null;
    120                        while( ( entry = zis.getNextZipEntry() ) != null ) {
    121        //                      fileName = tmpPrefix + entry.getName().replace( '/', File.separatorChar );
    122                                tmpFile = new File( targetPath,entry.getName() );
    123                                list.add( tmpFile );
    124
    125                                // ディレクトリの場合は、自身を含むディレクトリを作成
    126                                if( entry.isDirectory() ) {
    127                                        if( !tmpFile.exists() && !tmpFile.mkdirs() ) {
    128                                                final String errMsg = "ディレクトリ作成に失敗しました。[ファイル名=" + tmpFile + "]";
    129                                                System.err.println( errMsg );
    130                                                continue;
    131                                        }
    132                                }
    133                                // ファイルの場合は、自身の親となるディレクトリを作成
    134                                else {
    135                                        // 4.3.3.3 (2008/10/22) 作成する前に存在チェック
    136                                        if( !tmpFile.getParentFile().exists() && !tmpFile.getParentFile().mkdirs() ) {
    137                                                final String errMsg = "親ディレクトリ作成に失敗しました。[ファイル名=" + tmpFile + "]";
    138                                                System.err.println( errMsg );
    139                                                continue;
    140                                        }
    141
    142                                        final BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( tmpFile ) );
    143                                        try {
    144                                                IOUtils.copy( zis,out );
    145                                        }
    146                                        catch( final IOException zex ) {
    147                                                final String errMsg = "ZIPファイルの作成(copy)に失敗しました。[ファイル名=" + tmpFile + "]";
    148                                                System.err.println( errMsg );
    149                                                continue;
    150                                        }
    151                                        finally {
    152                                                Closer.ioClose( out );
    153                                        }
    154                                }
    155                                // 5.1.9.0 (2010/08/01) 更新時刻の設定
    156                                final long lastTime = entry.getTime();
    157                                if( lastTime >= 0 && !tmpFile.setLastModified( lastTime ) ) {
    158                                        final String errMsg = "ZIP更新時刻の設定に失敗しました。[ファイル名=" + tmpFile + "]";
    159                                        System.err.println( errMsg );
    160                                }
    161                        }
    162                }
    163                catch( final FileNotFoundException ex ) {
    164                        final String errMsg = "解凍ファイルが作成できません。[ファイル名=" + tmpFile + "]";
    165                        throw new OgRuntimeException( errMsg, ex );
    166                }
    167                catch( final IOException ex ) {
    168                        final String errMsg = "ZIPファイルの解凍に失敗しました。[ファイル名=" + tmpFile + "]";
    169                        throw new OgRuntimeException( errMsg, ex );
    170                }
    171                finally {
    172                        Closer.ioClose( zis );
    173                }
    174
    175                return list;
    176        }
    177
    178        /**
    179         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
    180         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
    181         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
    182         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
    183         * 非常に不可がかかる。)
    184         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
    185         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
    186         *
    187         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    188         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
    189         *
    190         * @param files 圧縮対象のファイル配列
    191         * @param zipFile ZIPファイル名
    192         *
    193         * @return ZIPファイルのエントリーファイル名一覧
    194         * @og.rtnNotNull
    195         */
    196        public static List<File> compress( final File[] files, final File zipFile ) {
    197                return compress( files,zipFile,"Windows-31J" );
    198        }
    199
    200        /**
    201         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
    202         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
    203         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
    204         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
    205         * 非常に不可がかかる。)
    206         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
    207         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
    208         *
    209         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
    210         * @og.rev 6.3.9.0 (2015/11/06) 1行にまとめる。
    211         *
    212         * @param dir 圧縮対象のディレクトリか、ファイル
    213         * @param zipFile ZIPファイル名
    214         *
    215         * @return ZIPファイルのエントリーファイル名一覧
    216         * @og.rtnNotNull
    217         */
    218        public static List<File> compress( final File dir, final File zipFile ) {
    219
    220                final File[] files = dir.isDirectory() ? dir.listFiles() : new File[] { dir } ;         // ※ files は null もありうる。
    221                return compress( files,zipFile,"Windows-31J" );
    222        }
    223
    224        /**
    225         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
    226         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
    227         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
    228         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
    229         * 非常に不可がかかる。)
    230         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
    231         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
    232         *
    233         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    234         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
    235         * @og.rev 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
    236         *
    237         * @param files 圧縮対象のファイル配列
    238         * @param zipFile ZIPファイル名
    239         * @param encording ファイルのエンコード(Windows環境では、"Windows-31J" を指定します)
    240         *
    241         * @return ZIPファイルのエントリーファイル名一覧
    242         * @og.rtnNotNull
    243         */
    244        public static List<File> compress( final File[] files, final File zipFile, final String encording ) {
    245                final List<File> list = new ArrayList<>();
    246
    247                if( files != null && files.length > 0 && zipFile != null ) {
    248                        ZipArchiveOutputStream zos = null;
    249
    250                        try {
    251                                zos = new ZipArchiveOutputStream( new BufferedOutputStream ( new FileOutputStream( zipFile ) ) );
    252                                if( encording != null ) {
    253                                        zos.setEncoding( encording );           // "Windows-31J"
    254                                }
    255
    256                                // ZIP圧縮処理を行います
    257                                addZipEntry( list, zos, "" , files );   // 開始フォルダは、空文字列とします。
    258                        }
    259                        catch( final FileNotFoundException ex ) {
    260                                final String errMsg = "ZIPファイルが見つかりません。[ファイル名=" + zipFile + "]";
    261                                throw new OgRuntimeException( errMsg, ex );
    262                        }
    263                        finally {
    264                                Closer.ioClose( zos );
    265                //              zos.finish();
    266                //              zos.flush();
    267                //              zos.close();
    268                        }
    269                }
    270
    271                return list;
    272        }
    273
    274        /**
    275         * ZIP圧縮処理を行います。
    276         * 引数に指定されたFileオブジェクトがディレクトリであれば再帰的に呼び出し、
    277         * 下層のファイルをエントリーします。但し、そのディレクトリ自身が空である場合は、
    278         * ディレクトリをエントリー情報として設定します。
    279         *
    280         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    281         * @og.rev 5.1.9.0 (2010/08/01) 更新時刻の設定 、BufferedInputStream のスコープを小さくする。
    282         * @og.rev 6.8.0.0 (2017/06/02) アーカイブするファイルのタイムスタンプをセットする。
    283         * @og.rev 7.2.9.5 (2020/11/28) 削除してすぐに実行すると、存在しないファイルリストが渡される場合がある。
    284         *
    285         * @param list ZIPファイルのエントリーファイル名一覧
    286         * @param zos ZIP用OutputStream
    287         * @param prefix 圧縮時のフォルダ
    288         * @param files 圧縮対象のファイル配列(可変長引数)
    289         * @throws      IOException 入出力エラーが発生した場合
    290         */
    291        private static void addZipEntry( final List<File> list, final ZipArchiveOutputStream zos, final String prefix, final File... files ) {
    292                File tmpFile = null;
    293                try {
    294                        for( final File fi : files ) {
    295                                tmpFile = fi;                           // エラー時のファイル
    296                                if( !fi.exists() ) { continue; }                        // 7.2.9.5 (2020/11/28)
    297
    298                                list.add( fi );
    299                                if( fi.isDirectory() ) {
    300                                        final String entryName = prefix + fi.getName() + "/" ;
    301                                        final ZipArchiveEntry zae = new ZipArchiveEntry( entryName );
    302                                        zae.setTime( fi.lastModified() );                                                                       // 6.8.0.0 (2017/06/02) ※ 効いてなさそう
    303                                        zos.putArchiveEntry( zae );
    304                                        zos.closeArchiveEntry();
    305
    306                                        addZipEntry( list, zos, entryName, fi.listFiles() );
    307                                }
    308                                else {
    309                                        final String entryName = prefix + fi.getName() ;
    310                                        final ZipArchiveEntry zae = new ZipArchiveEntry( entryName );
    311                                        zae.setTime( fi.lastModified() );                                                                       // 6.8.0.0 (2017/06/02)
    312                                        zos.putArchiveEntry( zae );
    313                                        final InputStream is = new BufferedInputStream( new FileInputStream(fi) );
    314                                        IOUtils.copy( is,zos );
    315                                        zos.closeArchiveEntry();
    316                                        Closer.ioClose( is );
    317                                }
    318                        }
    319                }
    320                catch( final FileNotFoundException ex ) {
    321                        final String errMsg = "圧縮対象のファイルが見つかりません。[ファイル名=" + tmpFile + "]";
    322                        throw new OgRuntimeException( errMsg, ex );
    323                }
    324                catch( final IOException ex ) {
    325                        final String errMsg = "ZIP圧縮に失敗しました。[ファイル名=" + tmpFile + "]";
    326                        throw new OgRuntimeException( errMsg, ex );
    327                }
    328        }
    329
    330        /**
    331         * ファイルの圧縮または解凍を行います。
    332         *
    333         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    334         * @og.rev 5.9.21.1 (2017/06/12) 結果出力追加
    335         *
    336         * Usage: java org.opengion.fukurou.util.ZipArchive comp|uncomp targetPath zipFileName
    337         * 第1引数 : comp:圧縮 uncomp:解凍
    338         * 第2引数 : ZIPファイル名
    339         * 第3引数 : 圧縮時:圧縮対象のファイル又はフォルダ 解凍時:解凍先のフォルダ
    340         *
    341         * @param args パラメータ
    342         */
    343        public static void main( final String[] args ) {
    344                final String usage = "Usage: java org.opengion.fukurou.util.ZipArchive comp|uncomp targetPath zipFileName";
    345
    346                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
    347                if( args.length < 3 || !"comp".equalsIgnoreCase( args[0] ) && !"uncomp".equalsIgnoreCase( args[0] ) ) {
    348                        System.out.println( usage );
    349                        return;
    350                }
    351
    352                // 開始時間
    353                final long start = System.currentTimeMillis();
    354
    355                List<File> list = null;
    356                final File tgtFile = new File(args[1]);
    357                final File zipFile = new File(args[2]);
    358                if( "comp".equalsIgnoreCase( args[0] ) ) {
    359                        list = compress( tgtFile, zipFile );
    360                }
    361                else if( "uncomp".equalsIgnoreCase( args[0] ) ) {
    362                        list = unCompress( tgtFile, zipFile );
    363                }
    364
    365                if( list != null ) {
    366                        // 処理時間を表示
    367                        System.out.println( "処理時間 : " + ( System.currentTimeMillis() - start ) + "(ms)" );
    368                        // 結果を表示
    369                        for( final File fileName : list ) {
    370                                System.out.println( fileName );
    371                        }
    372                }
    373                else{
    374                        System.out.println( "list is null." );          // 5.9.21.1 (2017/06/16)
    375                }
    376        }
    377}