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.File;
019import java.io.InputStream;
020import java.io.FileInputStream;
021import java.io.BufferedInputStream;
022import java.io.FileNotFoundException;
023import java.io.FileOutputStream;
024import java.io.BufferedOutputStream;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.List;
028// import java.util.zip.ZipEntry;
029// import java.util.zip.ZipInputStream;
030// import java.util.zip.ZipOutputStream;
031
032import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
033import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
034import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
035import org.apache.commons.compress.utils.IOUtils;
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         */
078        public static List<File> unCompress( final File targetPath , final File zipFile ) {
079                return unCompress( targetPath,zipFile,"Windows-31J" );
080        }
081
082        /**
083         * エンコードを指定した、ZIPファイルの解凍処理を行います。
084         * 引数にフォルダ(targetPath)に指定されたZIPファイル(zipFile)を解凍します。
085         * 解凍先のファイルが存在する場合でも、上書きされますので注意下さい。
086         *
087         * 解凍途中のエラーは、エラー出力に出力するだけで、処理は止めません。
088         *
089         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
090         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
091         * @og.rev 4.3.3.3 (2008/10/22) mkdirsする前に存在チェック
092         * @og.rev 5.1.9.0 (2010/08/01) 更新時刻の設定
093         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
094         *
095         * @param targetPath 解凍先のフォルダ
096         * @param zipFile 解凍するZIPファイル
097         * @param encording ファイルのエンコード(Windows環境では、"Windows-31J" を指定します)
098         *
099         * @return 解凍されたZIPファイルの一覧
100         */
101        public static List<File> unCompress( final File targetPath, final File zipFile, final String encording ) {
102                List<File> list = new ArrayList<File>();
103
104                // 解凍先フォルダの末尾が'/'又は'\'でなければ区切り文字を挿入
105        //      String tmpPrefix = targetPath;
106        //      if( File.separatorChar != targetPath.charAt( targetPath.length() - 1 ) ) {
107        //              tmpPrefix = tmpPrefix + File.separator;
108        //      }
109
110                ZipArchiveInputStream zis = null;
111                File tmpFile = null;
112        //      String fileName = null;
113
114                try {
115                        zis = new ZipArchiveInputStream( new BufferedInputStream( new FileInputStream( zipFile ) ) ,encording );
116
117                        ZipArchiveEntry entry = null;
118                        while( ( entry = zis.getNextZipEntry() ) != null ) {
119        //                      fileName = tmpPrefix + entry.getName().replace( '/', File.separatorChar );
120                                tmpFile = new File( targetPath,entry.getName() );
121                                list.add( tmpFile );
122
123                                // ディレクトリの場合は、自身を含むディレクトリを作成
124                                if( entry.isDirectory() ) {
125                                        if( !tmpFile.exists() && !tmpFile.mkdirs() ) {
126                                                String errMsg = "ディレクトリ作成に失敗しました。[ファイル名=" + tmpFile + "]";
127                                                System.err.println( errMsg );
128                                                continue;
129                                        }
130                                }
131                                // ファイルの場合は、自身の親となるディレクトリを作成
132                                else {
133                                        // 4.3.3.3 (2008/10/22) 作成する前に存在チェック
134                                        if( !tmpFile.getParentFile().exists() && !tmpFile.getParentFile().mkdirs() ) {
135                                                String errMsg = "親ディレクトリ作成に失敗しました。[ファイル名=" + tmpFile + "]";
136                                                System.err.println( errMsg );
137                                                continue;
138                                        }
139
140                                        BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( tmpFile ) );
141                                        try {
142                                                IOUtils.copy( zis,out );
143                                        }
144                                        catch( IOException zex ) {
145                                                String errMsg = "ZIPファイルの作成(copy)に失敗しました。[ファイル名=" + tmpFile + "]";
146                                                System.err.println( errMsg );
147                                                continue;
148                                        }
149                                        finally {
150                                                Closer.ioClose( out );
151                                        }
152                                }
153                                // 5.1.9.0 (2010/08/01) 更新時刻の設定
154                                long lastTime = entry.getTime();
155                                if( lastTime >= 0 && !tmpFile.setLastModified( lastTime ) ) {
156                                        String errMsg = "ZIP更新時刻の設定に失敗しました。[ファイル名=" + tmpFile + "]";
157                                        System.err.println( errMsg );
158                                }
159                        }
160                }
161                catch( FileNotFoundException ex ) {
162                        String errMsg = "解凍ファイルが作成できません。[ファイル名=" + tmpFile + "]";
163                        throw new RuntimeException( errMsg, ex );
164                }
165                catch( IOException ex ) {
166                        String errMsg = "ZIPファイルの解凍に失敗しました。[ファイル名=" + tmpFile + "]";
167                        throw new RuntimeException( errMsg, ex );
168                }
169                finally {
170                        Closer.ioClose( zis );
171                }
172
173                return list;
174        }
175
176        /**
177         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
178         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
179         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
180         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
181         * 非常に不可がかかる。)
182         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
183         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
184         *
185         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
186         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
187         *
188         * @param files 圧縮対象のファイル配列
189         * @param zipFile ZIPファイル名
190         *
191         * @return ZIPファイルのエントリーファイル名一覧
192         */
193        public static List<File> compress( final File[] files, final File zipFile ) {
194                return compress( files,zipFile,"Windows-31J" );
195        }
196
197        /**
198         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
199         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
200         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
201         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
202         * 非常に不可がかかる。)
203         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
204         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
205         *
206         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
207         *
208         * @param dir 圧縮対象のディレクトリか、ファイル
209         * @param zipFile ZIPファイル名
210         *
211         * @return ZIPファイルのエントリーファイル名一覧
212         */
213        public static List<File> compress( final File dir, final File zipFile ) {
214                File[] files = null;
215                if( dir.isDirectory() ) { files = dir.listFiles(); }
216                else { files = new File[] { dir } ; }                                   // 単独の場合は、配列にして渡します。
217
218                return compress( files,zipFile,"Windows-31J" );
219        }
220
221        /**
222         * 引数に指定されたファイル又はフィルダ内に存在するファイルをZIPファイルに圧縮します。
223         * 圧縮レベルはデフォルトのDEFAULT_COMPRESSIONです。
224         * 圧縮ファイルのエントリー情報として本来は、圧縮前後のファイルサイズ、変更日時、CRCを登録する
225         * 必要がありますが、ここでは高速化のため、設定していません。(特に圧縮後ファイルサイズの取得は、
226         * 非常に不可がかかる。)
227         * このため、一部のアーカイバでは正しく解凍できない可能性があります。
228         * 既にZIPファイルが存在する場合でも、上書きされますので注意下さい。
229         *
230         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
231         * @og.rev 5.7.1.2 (2013/12/20) org.apache.commons.compress パッケージの利用(日本語ファイル名対応)
232         *
233         * @param files 圧縮対象のファイル配列
234         * @param zipFile ZIPファイル名
235         * @param encording ファイルのエンコード(Windows環境では、"Windows-31J" を指定します)
236         *
237         * @return ZIPファイルのエントリーファイル名一覧
238         */
239        public static List<File> compress( final File[] files, final File zipFile, final String encording ) {
240                List<File> list = new ArrayList<File>();
241                ZipArchiveOutputStream zos = null;
242
243                try {
244                        zos = new ZipArchiveOutputStream( new BufferedOutputStream ( new FileOutputStream( zipFile ) ) );
245                        if (encording != null) {
246                                zos.setEncoding( encording );           // "Windows-31J"
247                        }
248
249                        // ZIP圧縮処理を行います
250                        addZipEntry( list, zos, "" , files );   // 開始フォルダは、空文字列とします。
251                }
252                catch( FileNotFoundException ex ) {
253                        String errMsg = "ZIPファイルが見つかりません。[ファイル名=" + zipFile + "]";
254                        throw new RuntimeException( errMsg, ex );
255                }
256                finally {
257                        Closer.ioClose( zos );
258        //              zos.finish();
259        //              zos.flush();
260        //              zos.close();
261                }
262
263                return list;
264        }
265
266        /**
267         * ZIP圧縮処理を行います。
268         * 引数に指定されたFileオブジェクトがディレクトリであれば再帰的に呼び出し、
269         * 下層のファイルをエントリーします。但し、そのディレクトリ自身が空である場合は、
270         * ディレクトリをエントリー情報として設定します。
271         *
272         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
273         * @og.rev 5.1.9.0 (2010/08/01) 更新時刻の設定 、BufferedInputStream のスコープを小さくする。
274         *
275         * @param list ZIPファイルのエントリーファイル名一覧
276         * @param zos ZIP用OutputStream
277         * @param prefix 圧縮時のフォルダ
278         * @param files 圧縮対象のファイル配列
279         * @throws      IOException 入出力エラーが発生した場合
280         */
281        private static void addZipEntry( final List<File> list, final ZipArchiveOutputStream zos, final String prefix, final File[] files ) {
282                File tmpFile = null;
283                try {
284                        for( File fi : files ) {
285                                tmpFile = fi;                           // エラー時のファイル
286                                list.add( fi );
287                                if( fi.isDirectory() ) {
288                                        String entryName = prefix + fi.getName() + "/" ;
289                                        ZipArchiveEntry zae = new ZipArchiveEntry( entryName );
290                                        zos.putArchiveEntry( zae );
291                                        zos.closeArchiveEntry();
292
293                                        addZipEntry( list, zos, entryName, fi.listFiles() );
294                                }
295                                else {
296                                        String entryName = prefix + fi.getName() ;
297                                        ZipArchiveEntry zae = new ZipArchiveEntry( entryName );
298                                        zos.putArchiveEntry( zae );
299                                        InputStream is = new BufferedInputStream( new FileInputStream(fi) );
300                                        IOUtils.copy( is,zos );
301                                        zos.closeArchiveEntry();
302                                        Closer.ioClose( is );
303                                }
304                        }
305                }
306                catch( FileNotFoundException ex ) {
307                        String errMsg = "圧縮対象のファイルが見つかりません。[ファイル名=" + tmpFile + "]";
308                        throw new RuntimeException( errMsg, ex );
309                }
310                catch( IOException ex ) {
311                        String errMsg = "ZIP圧縮に失敗しました。[ファイル名=" + tmpFile + "]";
312                        throw new RuntimeException( errMsg, ex );
313                }
314        }
315
316        /**
317         * ファイルの圧縮または解凍を行います。
318         *
319         * @og.rev 4.1.0.2 (2008/02/01) 新規追加
320         *
321         * Usage: java org.opengion.fukurou.util.ZipArchive comp|uncomp targetPath zipFileName
322         * 第1引数 : comp:圧縮 uncomp:解凍
323         * 第2引数 : ZIPファイル名
324         * 第3引数 : 圧縮時:圧縮対象のファイル又はフォルダ 解凍時:解凍先のフォルダ
325         *
326         * @param args パラメータ
327         */
328        public static void main( final String[] args ) {
329                String usage = "Usage: java org.opengion.fukurou.util.ZipArchive comp|uncomp targetPath zipFileName";
330                if( args.length < 3 ) {
331                        System.out.println( usage );
332                        return;
333                }
334
335                // 開始時間
336                long start = System.currentTimeMillis();
337
338                List<File> list = null;
339                File tgtFile = new File(args[1]);
340                File zipFile = new File(args[2]);
341                if( "comp".equalsIgnoreCase( args[0] ) ) {
342                        list = compress( tgtFile, zipFile );
343                }
344                else if( "uncomp".equalsIgnoreCase( args[0] ) ) {
345                        list = unCompress( tgtFile, zipFile );
346                }
347                else {
348                        System.out.println( usage );
349                        return;
350                }
351
352                if( list != null ) {
353                        // 処理時間を表示
354                        System.out.println( "処理時間 : " + ( System.currentTimeMillis() - start ) + "(ms)" );
355                        // 結果を表示
356                        for( File fileName : list ) {
357                                System.out.println( fileName );
358                        }
359                }
360        }
361}