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                                try {
228                                        in = new BufferedInputStream( new FileInputStream( target.getAbsolutePath() ) );
229                                        byte[] buf = new byte[BUF_SIZE];
230                                        int count;
231                                        while( ( count = in.read( buf, 0, BUF_SIZE ) ) != -1 ) {
232                                                zos.write( buf, 0, count );
233                                        }
234                                }
235                                finally {
236//                                      in.close();
237                                        Closer.ioClose( in );
238                                }
239                                zos.closeEntry();
240                        }
241                }
242                catch( FileNotFoundException ex ) {
243                        String errMsg = "圧縮対象のファイルが見つかりません。[ファイル名=" + target.getName() + "]";
244                        throw new RuntimeException( errMsg, ex );
245                }
246                catch( IOException ex ) {
247                        String errMsg = "ZIP圧縮に失敗しました。[ファイル名=" + target.getName() + "]";
248                        throw new RuntimeException( errMsg, ex );
249                }
250//              finally {
251//                      Closer.ioClose( in );
252//              }
253        }
254
255        /**
256         * ファイルの圧縮または解凍を行います。
257         *
258         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
259         *
260         * 使用方法 : java [comp or uncomp] [targetPath] [zipFileName]
261         * 第1引数 : comp:圧縮 uncomp:解凍
262         * 第2引数 : 圧縮時:圧縮対象のファイル又はフォルダ 解凍時:解凍先のフォルダ
263         * 第3引数 : ZIPファイル名
264         *
265         * @param args パラメータ
266         */
267        public static void main( final String[] args ) {
268                String usage = "Usage: java [comp or uncomp] [targetPath] [zipFileName]";
269                if( args.length < 3 ) {
270                        System.out.println( usage );
271                        return;
272                }
273
274                // 開始時間
275                long start = System.currentTimeMillis();
276
277                List<String> list = null;
278                if( "comp".equals( args[0] ) ) {
279                        list = compress( args[1], args[2] );
280                }
281                else if( "uncomp".equals( args[0] ) ) {
282                        list = unCompress( args[1], args[2] );
283                }
284                else {
285                        System.out.println( usage );
286                        return;
287                }
288
289                if( list != null ) {
290                        // 結果を表示
291                        for( String fileName : list ) {
292                                System.out.println( fileName );
293                        }
294                        // 処理時間を表示
295//                      System.out.println( "処理時間 : " + String.valueOf( System.currentTimeMillis() - start ) + "(ms)" );
296                        System.out.println( "処理時間 : " + ( System.currentTimeMillis() - start ) + "(ms)" );
297                }
298        }
299}