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}