    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 java.io.BufferedInputStream;
    019import java.io.BufferedOutputStream;
    020import java.io.File;
    021import java.io.FileInputStream;
    022import java.io.FileNotFoundException;
    023import java.io.FileOutputStream;
    024import java.io.IOException;
    025import java.util.ArrayList;
    026import java.util.List;
    027import java.util.zip.ZipEntry;
    028import java.util.zip.ZipInputStream;
    029import java.util.zip.ZipOutputStream;
    030
    031/**
    032 * ZipFileUtil.java は、ZIPファイルの解凍・圧縮を行うためのUtilクラスです。
    033 *
    034 * @og.group ユーティリティ
    035 *
    036 * @version  4.1
    037 * @author       Hiroki Nakamura
    038 * @since    JDK5.0,
    039 */
    040public final class ZipFileUtil {
    041
    042        /** ファイル読み込み時のバッファサイズ */
    043        private static final int        BUF_SIZE        = 1024;
    044
    045        /**
    046         * 全てスタティックメソッドのためインスタンスの作成を禁止します。
    047         */
    048        private ZipFileUtil() {};
    049
    050        /**
    051         * 引数に指定されたZIPファイルをフォルダに解凍します。
    052         * 解凍先のファイルが存在する場合でも、上書きされますので注意下さい。
    053         *
    054         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    055         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
    056         * @og.rev 4.3.3.3 (2008/10/22) mkdirsする前に存在チェック
    057         * @og.rev 5.1.9.0 (2010/08/01) 更新時刻の設定
    058         *
    059         * @param targetPath 解凍先のフォルダ
    060         * @param zipFileName 解凍するZIPファイル
    061         *
    062         * @return 解凍されたZIPファイルの一覧
    063         */
    064        public static List<String> unCompress( final String targetPath, final String zipFileName ) {
    065                // 解凍先フォルダの末尾が'/'又は'\'でなければ区切り文字を挿入
    066                String tmpPrefix = targetPath;
    067                if( File.separatorChar != targetPath.charAt( targetPath.length() - 1 ) ) {
    068                        tmpPrefix = tmpPrefix + File.separator;
    069                }
    070
    071                List<String> list = new ArrayList<String>();
    072                ZipInputStream zis = null;
    073                ZipEntry entry = null;
    074                BufferedOutputStream out = null;
    075                String fileName = null;
    076                File tmpFile = null;
    077                try {
    078                        zis = new ZipInputStream( new BufferedInputStream( new FileInputStream( zipFileName ) ) );
    079
    080                        while( ( entry = zis.getNextEntry() ) != null ) {
    081                                fileName = tmpPrefix + entry.getName().replace( '/', File.separatorChar );
    082                                list.add( fileName );
    083
    084                                boolean flag = true;    // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
    085                                tmpFile = new File( fileName );
    086                                // ディレクトリの場合は、自身を含むディレクトリを作成
    087                                if( entry.isDirectory() ) {
    088                                        // 4.3.3.3 (2008/10/22) 作成する前に存在チェック
    089                                        if( !tmpFile.exists() ) {
    090                                                flag = tmpFile.mkdirs();
    091                                        }
    092                                }
    093                                // ディレクトリの場合は、自身の親となるディレクトリを作成
    094                                else {
    095                                        // 4.3.3.3 (2008/10/22) 作成する前に存在チェック
    096                                        if( !tmpFile.getParentFile().exists() ) {
    097                                                flag = new File( fileName ).getParentFile().mkdirs();
    098                                        }
    099
    100                                        out = new BufferedOutputStream( new FileOutputStream( fileName ) );
    101                                        byte[] buf = new byte[BUF_SIZE];
    102                                        int count = 0;
    103                                        while( ( count = zis.read( buf ) ) != -1 ) {
    104                                                out.write( buf, 0, count );
    105                                        }
    106                                        out.close();
    107                                }
    108                                // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
    109                                if( ! flag ) { System.err.println( fileName + " の ディレクトリ作成に失敗しました。" ); }
    110                                // 5.1.9.0 (2010/08/01) 更新時刻の設定
    111                                long lastTime = entry.getTime();
    112                                if( lastTime >= 0 ) {
    113                                        flag = tmpFile.setLastModified( lastTime );
    114                                }
    115                                if( ! flag ) { System.err.println( fileName + " の 更新時刻の設定に失敗しました。" ); }
    116                        }
    117                }
    118                catch( FileNotFoundException ex ) {
    119                        String errMsg = "解凍ファイルが作成できません。[ファイル名=" + fileName + "]";
    120                        throw new RuntimeException( errMsg, ex );
    121                }
    122                catch( IOException ex ) {
    123                        String errMsg = "ZIPファイルの解凍に失敗しました。[ファイル名=" + fileName + "]";
    124                        throw new RuntimeException( errMsg, ex );
    125                }
    126                finally {
    127                        Closer.ioClose( zis );
    128                        Closer.ioClose( out );
    129                }
    130
    131                return list;
    132        }
    133
    134        /**
    135         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
    136         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
    137         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
    138         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
    139         * 非常に不可がかかる。)
    140         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
    141         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
    142         *
    143         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    144         *
    145         * @param targetPath 圧縮対象のファイル又はフォルダ
    146         * @param zipFileName ZIPファイル名
    147         *
    148         * @return ZIPファイルのエントリーファイル名一覧
    149         */
    150        public static List<String> compress( final String targetPath, final String zipFileName ) {
    151                List<String> list = new ArrayList<String>();
    152                ZipOutputStream zos = null;
    153
    154                try {
    155                        zos = new ZipOutputStream( new BufferedOutputStream ( new FileOutputStream( zipFileName ) ) );
    156                        File target = new File( targetPath );
    157
    158                        // ZIP圧縮処理を行います
    159                        addZipEntry( zos, list, target, "", 0 );
    160
    161                        zos.close();
    162                }
    163                catch( FileNotFoundException ex ) {
    164                        String errMsg = "ZIPファイルが見つかりません。[ファイル名=" + zipFileName + "]";
    165                        throw new RuntimeException( errMsg, ex );
    166                }
    167                catch( IOException ex ) {
    168                        String errMsg = "ZIP圧縮に失敗しました。[ファイル名=" + zipFileName + "]";
    169                        throw new RuntimeException( errMsg, ex );
    170                }
    171                finally {
    172                        Closer.ioClose( zos );
    173                }
    174
    175                return list;
    176        }
    177
    178        /**
    179         * ZIP圧縮処理を行います。
    180         * 引数に指定されたFileオブジェクトがディレクトリであれば再帰的に呼び出し、
    181         * 下層のファイルをエントリーします。但し、そのディレクトリ自身が空である場合は、
    182         * ディレクトリをエントリー情報として設定します。
    183         *
    184         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    185         * @og.rev 5.1.9.0 (2010/08/01) 更新時刻の設定 、BufferedInputStream のスコープを小さくする。
    186         *
    187         * @param zos ZIP用OutputStream
    188         * @param list ZIPファイルのエントリーファイル名一覧
    189         * @param target 圧縮対象のファイルオブジェクト
    190         * @param prefix 処理中のディレクトリ
    191         * @param depth 階層
    192         */
    193        private static void addZipEntry( final ZipOutputStream zos, final List<String> list, final File target, final String prefix, final int depth ) {
    194//              BufferedInputStream in = null;
    195
    196                try {
    197                        // ターゲットがディレクトリの場合は、ファイルが含まれているかを
    198                        // チェックし、空ならばそのディレクトリをエントリーに追加する。(最後に'/'が必要)
    199                        // 空じゃなければ、再起呼び出し
    200                        if( target.isDirectory() ) {
    201                                File[] fileList = target.listFiles();
    202                                if( fileList.length == 0 ) {
    203                                        list.add( prefix + target.getName() );
    204                                        ZipEntry entry = new ZipEntry( prefix + target.getName() + '/' );
    205                                        zos.putNextEntry( entry );
    206                                        zos.closeEntry(); // ディレクトリのエントリーは空で作成する必要がある。但し、FindBugsはエラー
    207                                }
    208                                else {
    209                                        for( int i = 0; i < fileList.length; i++ ) {
    210                                                // 再起呼び出しを行う際、圧縮対象にフォルダが指定された場合、
    211                                                // 最初の再起処理時は、エントリーにフォルダのパスを含めないようにする。
    212                                                String nextPrefix = "";
    213                                                if( depth > 0 ) {
    214                                                        nextPrefix = prefix + target.getName() + '/';
    215                                                }
    216                                                addZipEntry( zos, list, fileList[i], nextPrefix, depth + 1 );
    217                                        }
    218                                }
    219                        }
    220                        // ターゲットがファイルの場合
    221                        else {
    222                                list.add( prefix + target.getName() );
    223                                ZipEntry entry = new ZipEntry( prefix + target.getName() );
    224                                entry.setTime( target.lastModified() );                 // 5.1.9.0 (2010/08/01) 更新時刻の設定
    225                                zos.putNextEntry( entry );
    226                                BufferedInputStream in = null;
    227
    228                                try {
    229                                        in = new BufferedInputStream( new FileInputStream( target.getAbsolutePath() ) );
    230                                        byte[] buf = new byte[BUF_SIZE];
    231                                        int count;
    232                                        while( ( count = in.read( buf, 0, BUF_SIZE ) ) != -1 ) {
    233                                                zos.write( buf, 0, count );
    234                                        }
    235                                }
    236                                finally {
    237//                                      in.close();
    238                                        Closer.ioClose( in );
    239                                }
    240                                zos.closeEntry();
    241                        }
    242                }
    243                catch( FileNotFoundException ex ) {
    244                        String errMsg = "圧縮対象のファイルが見つかりません。[ファイル名=" + target.getName() + "]";
    245                        throw new RuntimeException( errMsg, ex );
    246                }
    247                catch( IOException ex ) {
    248                        String errMsg = "ZIP圧縮に失敗しました。[ファイル名=" + target.getName() + "]";
    249                        throw new RuntimeException( errMsg, ex );
    250                }
    251//              finally {
    252//                      Closer.ioClose( in );
    253//              }
    254        }
    255
    256        /**
    257         * ファイルの圧縮または解凍を行います。
    258         *
    259         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
    260         * @og.rev 5.9.21.1 (2017/06/12) 結果出力追加
    261         *
    262         * 使用方法 : java [comp or uncomp] [targetPath] [zipFileName]
    263         * 第1引数 : comp:圧縮 uncomp:解凍
    264         * 第2引数 : 圧縮時:圧縮対象のファイル又はフォルダ 解凍時:解凍先のフォルダ
    265         * 第3引数 : ZIPファイル名
    266         *
    267         * @param args パラメータ
    268         */
    269        public static void main( final String[] args ) {
    270                String usage = "Usage: java [comp or uncomp] [targetPath] [zipFileName]";
    271                if( args.length < 3 ) {
    272                        System.out.println( usage );
    273                        return;
    274                }
    275
    276                // 開始時間
    277                long start = System.currentTimeMillis();
    278
    279                List<String> list = null;
    280                if( "comp".equals( args[0] ) ) {
    281                        list = compress( args[1], args[2] );
    282                }
    283                else if( "uncomp".equals( args[0] ) ) {
    284                        list = unCompress( args[1], args[2] );
    285                }
    286                else {
    287                        System.out.println( usage );
    288                        return;
    289                }
    290
    291                if( list != null ) {
    292                        // 結果を表示
    293                        for( String fileName : list ) {
    294                                System.out.println( fileName );
    295                        }
    296                        // 処理時間を表示
    297//                      System.out.println( "処理時間 : " + String.valueOf( System.currentTimeMillis() - start ) + "(ms)" );
    298                        System.out.println( "処理時間 : " + ( System.currentTimeMillis() - start ) + "(ms)" );
    299                }
    300                else{
    301                        System.out.println( "list is null." ); // 5.9.21.1 (2017/06/16)
    302                }
    303        }
    304}