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.hayabusa.servlet; 017 018import java.io.File; 019import java.io.IOException; 020// import java.io.FileNotFoundException; // 6.9.0.1 (2018/02/05) 021import java.util.Map; 022import java.util.concurrent.ConcurrentSkipListMap; // 6.4.3.1 (2016/02/12) refactoring 023import java.util.List; 024import java.util.ArrayList; 025import java.util.Set; 026// import java.util.Random ; 027import java.util.concurrent.atomic.AtomicInteger; // 5.5.2.6 (2012/05/25) findbugs対応 028 029import jakarta.servlet.http.HttpServletRequest; 030 031import org.opengion.fukurou.system.FileOperation; // 8.5.6.0 (2024/02/29) package変更 fukurou.model → fukurou.system 032import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 033import org.opengion.fukurou.util.ZipArchive; // 6.0.0.0 (2014/04/11) zip 対応 034import org.opengion.hayabusa.io.HybsFileOperationFactory; // 8.0.0.0 (2021/09/30) 035import org.opengion.hayabusa.common.HybsSystem; 036import org.opengion.hayabusa.servlet.multipart.MultipartParser; 037import org.opengion.hayabusa.servlet.multipart.Part; 038import org.opengion.hayabusa.servlet.multipart.FilePart; 039import org.opengion.hayabusa.servlet.multipart.ParamPart; 040 041/** 042 * ファイルをサーバーにアップロードする場合に使用されるマルチパート処理サーブレットです。 043 * 044 * 通常のファイルアップロード時の、form で使用する、enctype="multipart/form-data" 045 * を指定した場合の、他のリクエスト情報も、取り出すことが可能です。 046 * 047 * ファイルをアップロード後に、指定のファイル名に変更する機能があります。 048 * file 登録ダイアログで指定した name に、"_NEW" という名称を付けたリクエスト値を 049 * ファイルのアップロードと同時に送信することで、この名前にファイルを付け替えます。 050 * また、アップロード後のファイル名は、name 指定の名称で、取り出せます。 051 * クライアントから登録したオリジナルのファイル名は、name に、"_ORG" という名称 052 * で取り出すことが可能です。 053 * 054 * maxPostSize : 最大転送サイズ(Byte)を指定します。 0,またはマイナスで無制限です。 055 * useBackup : ファイルアップロード時に、すでに同名のファイルが存在した場合に、 056 * バックアップ処理(renameTo)するかどうか[true/false]を指定します(初期値:false) 057 * 058 * ファイルアップロード時に、アップロード先に、同名のファイルが存在した場合は、既存機能は、そのまま 059 * 置き換えていましたが、簡易バージョンアップ機能として、useBackup="true" を指定すると、既存のファイルを 060 * リネームして、バックアップファイルを作成します。 061 * バックアップファイルは、アップロードフォルダを基準として、_backup/ファイル名.拡張子_処理時刻のlong値.拡張子 になります。 062 * オリジナルのファイル名(拡張子付)を残したまま、"_処理時刻のlong値" を追加し、さらに、オリジナルの拡張子を追加します。 063 * バックアップファイルの形式は指定できません。 064 * 065 * 5.7.1.2 (2013/12/20) zip 対応 066 * filename 属性に、".zip" の拡張子のファイル名を指定した場合は、アップロードされた一連のファイルを 067 * ZIP圧縮します。これは、アップロード後の処理になります。 068 * ZIP圧縮のオリジナルファイルは、そのまま残ります。 069 * なお、ZIPファイルは、useBackup属性を true に設定しても、無関係に、上書きされます。 070 * 071 * 8.0.1.0 (2021/10/29) storageType → storage 、bucketName → bucket に変更 072 * × storage (初期値:システムリソースのCLOUD_TARGET) → 廃止 073 * × bucket (初期値:システムリソースのCLOUD_BUCKET) → 廃止 074 * useLocal (初期値:false) 075 * 076 * @og.rev 5.10.9.0 (2019/03/01) oota クラウドストレージ対応を追加。(Fileクラスを拡張) 077 * @og.group その他機能 078 * 079 * @version 4.0 080 * @author Kazuhiko Hasegawa 081 * @since JDK5.0, 082 */ 083public final class MultipartRequest { 084 private static AtomicInteger dumyNewFileCnt = new AtomicInteger(1); // 5.5.2.6 (2012/05/25) findbugs対応 085 086 /** 5.6.5.3 (2013/06/28) アップロード時のダミーファイル名をもう少しだけランダムにする。 */ 087// // 6.3.9.0 (2015/11/06) Variables should start with a lowercase character(PMD) 088// private static final String RANDOM_KEY = new Random().nextInt( Integer.MAX_VALUE ) + "_" ; 089// 8.5.5.1 (2024/02/29) spotbugs DMI_RANDOM_USED_ONLY_ONCE (Random は使う必要がないので廃止) 090 private static final String RANDOM_KEY = String.valueOf( System.currentTimeMillis() ).substring( 5 ) + "_" ; 091 092 /** 6.4.3.1 (2016/02/12) PMD refactoring. TreeMap → ConcurrentSkipListMap に置き換え。 */ 093 private final Map<String,List<String>> paramMap = new ConcurrentSkipListMap<>(); // 6.4.3.1 (2016/02/12) ソートします。 094 095 /** 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 */ 096 private final List<UploadedFile> files = new ArrayList<>(); // 5.7.1.1 (2013/12/13) HTML5対応 097 098 /** 099 * MultipartRequest オブジェクトを構築します。 100 * 101 * 引数として、ファイルアップロード時の保存フォルダ、最大サイズ、エンコード、 102 * 新しいファイル名などを指定できます。新しいファイル名は、アップロードされる 103 * ファイルが一つだけの場合に使用できます。複数のファイルを同時に変更したい 104 * 場合は、アップロードルールにのっとり、リクエストパラメータで指定してください。 105 * 106 * HTML5 では、ファイルアップロード時に、multiple 属性(inputタグのtype="file")を 107 * 付ける事で、ファイルを複数選択できます。 108 * その場合は、inputのname属性は、一つなので、_NEW による名前の書き換えはできません。 109 * 110 * @og.rev 3.8.1.3A (2006/01/30) 新ファイル名にオリジナルファイル名の拡張子をセットします 111 * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。 112 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応。staticフィールドへの書き込みに、AtomicInteger を利用します。 113 * @og.rev 5.6.5.3 (2013/06/28) useBackup引数追加 114 * @og.rev 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 115 * @og.rev 5.7.1.2 (2013/12/20) zip 対応 116 * @og.rev 5.7.4.3 (2014/03/28) zip 対応復活。inputFilename のリクエスト変数処理追加 117 * @og.rev 6.0.2.4 (2014/10/17) useBackup 修正。_PFX(接頭辞) , _SFX(接尾辞) 機能を追加。ファイル名にフォルダ指定可 118 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 119 * @og.rev 5.9.25.0 (2017/10/06) クラウドストレージ利用処理追加 120 * @og.rev 6.9.0.1 (2018/02/05) ファイルをセーブするディレクトリは、必要な場合のみ、作成します。 121 * @og.rev 5.10.9.0 (2019/03/01) クラウドストレージ対応を追加。 122 * @og.rev 8.0.0.2 (2021/10/15) ローカルファイルとクラウドファイル間の移動 123 * @og.rev 8.0.1.0 (2021/10/29) useLocal 属性を追加。storageType , bucketName 削除 124 * 125 * @param request HttpServletRequestオブジェクト 126 * @param saveDirectory ファイルアップロードがあった場合の保存フォルダ名 127 * @param maxPostSize ファイルアップロード時の最大ファイルサイズ(Byte)0,またはマイナスで無制限 128 * @param encoding ファイルのエンコード 129 * @param inputFilename アップロードされたファイルの新しい名前 130 * @param useBackup ファイルアップロード時に、バックアップ処理するかどうか[true/false/rename]を指定 131 * @param fileURL クラウドストレージ用のURL 132// * @param storage クラウドプラグイン名(ローカルファイルを強制する場合は、LOCAL を指定する) 133// * @param bucket バケット名(ローカルファイルを強制する場合は、LOCAL を指定する) 134 * @param useLocal 強制的にローカルファイルを使用する場合、true にセットします。 135 * 136 * @throws IOException 入出力エラーが発生したとき 137 * @throws IllegalArgumentException セーブディレクトリ に関係するエラー 138 */ 139 public MultipartRequest(final HttpServletRequest request, 140 final String saveDirectory, 141 final int maxPostSize, 142 final String encoding, 143 final String inputFilename, 144 final String useBackup, // 6.0.2.4 (2014/10/17) true/false/rename 145// final String fileURL) throws IOException,IllegalArgumentException { // (2017/10/06) 追加 146 final String fileURL, 147// final String storage, 148// final String bucket ) throws IOException,IllegalArgumentException { // 5.10.9.0 (2019/03/01) ADD 149// final boolean useLocal ) throws IOException,IllegalArgumentException { // 8.0.1.0 (2021/10/29) 150 final boolean useLocal ) throws IOException { // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidUncheckedExceptionsInSignatures 151 152 if( request == null ) { 153 throw new IllegalArgumentException("request cannot be null"); 154 } 155 156// // 6.9.0.1 (2018/02/05) ファイルをセーブするディレクトリは、必要な場合のみ、作成します。 157// if( saveDirectory == null ) { 158// throw new IllegalArgumentException("saveDirectory cannot be null"); 159// } 160// // 5.5.2.6 (2012/05/25) 0,またはマイナスで無制限 161// // Save the dir 162// final File dir = new File(saveDirectory); 163// 164// // Check saveDirectory is truly a directory 165// if( !dir.isDirectory() ) { 166// throw new IllegalArgumentException("Not a directory: " + saveDirectory); 167// } 168// 169// // Check saveDirectory is writable 170// if( !dir.canWrite() ) { 171// throw new IllegalArgumentException("Not writable: " + saveDirectory); 172// } 173 174 // Parse the incoming multipart, storing files in the dir provided, 175 // and populate the meta objects which describe what we found 176 final MultipartParser parser = new MultipartParser(request, maxPostSize); 177 if( encoding != null ) { 178 parser.setEncoding(encoding); 179 } 180 181// // 2017/10/06 ADD システムリソースにクラウドストレージ利用が登録されている場合は、クラウドストレージを利用する 182// final String storage = HybsSystem.sys( "CLOUD_TARGET"); 183// final boolean useStorage = storage != null && storage.length() > 0 ; 184 185// File dir = null; 186 // Save the dir 187 // 5.10.9.0 (2019/03/01) クラウドストレージ対応 oota tmp 188 // File dir = new File(saveDirectory); 189 // 8.0.0.2 (2021/10/15) ローカルファイルとクラウドファイル間の移動 190 // 8.0.1.0 (2021/10/29) storageType , bucketName 削除 191// final FileOperation dir = HybsFileOperationFactory.create(storageType, bucketName, saveDirectory); 192// final FileOperation dir = HybsFileOperationFactory.createDir(storage, bucket, saveDirectory); 193 final FileOperation dir = HybsFileOperationFactory.createDir(useLocal, saveDirectory); 194 // 5.10.9.0 (2019/03/01) if条件を追加。チェックはローカルストレージの場合のみ行います。 oota tmp 195// if(dir.isLocal()) { 196// if( !dir.isCloud() ) { 197// // セーブディレクトリ 作成 198// if( ! dir.exists() && ! dir.mkdirs() ) { 199// throw new IllegalArgumentException( "Not make directory: " + saveDirectory ); 200// } 201// 202// // Check saveDirectory is truly a directory 203// if(!dir.isDirectory()) { 204// throw new IllegalArgumentException("Not a directory: " + saveDirectory); 205// } 206// 207// // Check saveDirectory is writable 208// if(!dir.canWrite()) { 209// throw new IllegalArgumentException("Not writable: " + saveDirectory); 210// } 211// } 212 213 // 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 214 Part part; 215 while( (part = parser.readNextPart()) != null ) { 216 final String name = part.getName(); 217 if( part.isParam() && part instanceof ParamPart ) { 218 final ParamPart paramPart = (ParamPart)part; 219 final String value = paramPart.getStringValue(); 220 // 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。 221 List<String> existingValues = paramMap.get(name); 222 if( existingValues == null ) { 223 existingValues = new ArrayList<>(); 224 paramMap.put(name, existingValues); 225 } 226 existingValues.add(value); 227 } 228 else if( part.isFile() && part instanceof FilePart ) { 229 final FilePart filePart = (FilePart)part; 230 final String orgName = filePart.getFilename(); // 5.7.1.1 (2013/12/13) 判りやすいように変数名変更 231 if( orgName != null ) { 232 // 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 233 // 同一 name で、複数ファイルを扱う必要があります。 234 // 3.8.1.2 (2005/12/19) 仮ファイルでセーブする。 235 // 5.6.5.3 (2013/06/28) アップロード時のダミーファイル名をもう少しだけランダムにする。 236 final String uniqKey = RANDOM_KEY + dumyNewFileCnt.getAndIncrement() ; 237 filePart.setFilename( uniqKey ); 238 // 標準のファイル書き込み 2017/10/06 DELETE クラウドストレージ利用判定を追加 239 240 // ファイル書き込み 241 // 5.10.9.0 (2019/03/01) クラウドストレージ対応。oota tmp 242 // 8.0.1.0 (2021/10/29) storageType , bucketName 削除 243// filePart.writeTo(dir); 244// filePart.writeTo(dir, storage, bucket); 245 filePart.writeTo(dir, useLocal); 246 247// if( useStorage ){ 248// // クラウドストレージにアップロード 249// filePart.writeToCloud( storage, fileURL, request.getSession(true) ); 250// }else{ 251// // if( dir == null ) { dir = makeDirs( saveDirectory ); } // 6.9.0.1 (2018/02/05) 252// // 標準のファイル書き込み 253// filePart.writeTo(dir); 254// } 255 256 // 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 257 files.add( new UploadedFile( 258 uniqKey, // 5.7.1.1 (2013/12/13) 順番変更 259 dir.toString(), 260 name, // 5.7.1.1 (2013/12/13) 項目追加 261 orgName, 262 filePart.getContentType())); 263 } 264 } 265 else { 266 final String errMsg = "Partオブジェクトが、ParamPartでもFilePartでもありません。" 267 + " class=[" + part.getClass() + "]"; 268 throw new OgRuntimeException( errMsg ); 269 } 270 } 271 272 // 5.7.4.3 (2014/03/28) inputFilename は、リクエスト変数が使えるようにします。 273 final String filename = getReqParamFileName( inputFilename ) ; 274 275 // 3.5.6.5 (2004/08/09) 登録後にファイルをリネームします。 276 // 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 277 final int size = files.size(); 278 279 // 5.7.1.2 (2013/12/20) zip 対応 280 // 5.9.25.0 (2017/10/06) FileをString型に変更 281 final String[] tgtFiles = new String[size]; 282 final boolean isZip = filename != null && filename.endsWith( ".zip" ) ; 283 284 for( int i=0; i<size; i++ ) { 285 final UploadedFile upFile = files.get(i); 286 final String name = upFile.getName(); // 5.7.1.1 (2013/12/13) 287 288 String newName = isZip ? null : filename ; 289 String prefix = null ; // 6.0.2.4 (2014/10/17) _PFX(接頭辞) , _SFX(接尾辞) 機能を追加 290 String sufix = null ; // 6.0.2.4 (2014/10/17) _PFX(接頭辞) , _SFX(接尾辞) 機能を追加 291 if( newName == null && name != null ) { 292 final int adrs = name.lastIndexOf( HybsSystem.JOINT_STRING ); // カラム__行番号 の __ の位置 293 // 6.0.2.4 (2014/10/17) _PFX(接頭辞) , _SFX(接尾辞) 機能を追加 294 if( adrs < 0 ) { 295 newName = getParameter( name + "_NEW" ); 296 prefix = getParameter( name + "_PFX" ); 297 sufix = getParameter( name + "_SFX" ); 298 } 299 else { 300 final String name1 = name.substring( 0,adrs ); 301 final String name2 = name.substring( adrs ); 302 newName = getParameter( name1 + "_NEW" + name2 ); 303 prefix = getParameter( name1 + "_PFX" + name2 ); 304 sufix = getParameter( name1 + "_SFX" + name2 ); 305 } 306 } 307 308 // 5.7.1.1 (2013/12/13) UploadedFile 内で処理するように変更 309 // 5.9.25.0 (2017/10/06) MODIFY fileURLとsessionを追加 310 // 5.10.9.0 (2019/03/01) クラウドストレージ対応。sessionは不要になったため除去。 ootat tmp 311 // 8.0.1.0 (2021/10/29) storageType , bucketName 削除 312// tgtFiles[i] = upFile.renameTo( newName,prefix,sufix,useBackup,fileURL,request.getSession(true) ); 313// tgtFiles[i] = upFile.renameTo( newName,prefix,sufix,useBackup,fileURL, storage, bucket); 314 tgtFiles[i] = upFile.renameTo( newName,prefix,sufix,useBackup,fileURL, useLocal); 315 } 316 // 5.7.1.2 (2013/12/20) zip 対応 317 // 6.0.0.0 (2014/04/11) 一旦保留にしていましたが、復活します。 318// if( isZip && !useStorage ) { 319 if( isZip && (useLocal || !dir.isCloud()) ) { 320 final File zipFile = new File( saveDirectory,filename ); 321 // 5.9.25.0 (2017/10/06) tgtFiles が、String型に変更されたため 322 final File[] files = new File[tgtFiles.length]; 323 for( int i=0; i<tgtFiles.length; i++ ) { 324 files[i] = new File( tgtFiles[i] ); 325 } 326 ZipArchive.compress( files,zipFile ); 327 } 328 } 329 330 /** 331 * リクエストパラメータの名前配列を取得します。 332 * 333 * @return リクエストパラメータの名前配列 334 * @og.rtnNotNull 335 */ 336 public String[] getParameterNames() { 337 final Set<String> keyset = paramMap.keySet(); 338// return keyset.toArray( new String[keyset.size()] ); 339 return keyset.toArray( new String[0] ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応 340 } 341 342 /** 343 * ファイルアップロードされたファイル群のファイル配列を取得します。 344 * 345 * @og.rev 5.7.1.1 (2013/12/13) HTML5 ファイルアップロードの複数選択(multiple)対応 346 * 347 * @return アップロードされたファイル群 348 * @og.rtnNotNull 349 */ 350 public UploadedFile[] getUploadedFile() { 351// return files.toArray( new UploadedFile[files.size()] ); 352 return files.toArray( new UploadedFile[0] ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応 353 } 354 355 /** 356 * 指定の名前のリクエストパラメータの値を取得します。 357 * 358 * 複数存在する場合は、一番最後の値を返します。 359 * 360 * @param name リクエストパラメータ名 361 * 362 * @return パラメータの値 363 */ 364 public String getParameter( final String name ) { 365 final List<String> values = paramMap.get(name); 366 367 // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method 368 return values == null || values.isEmpty() ? null : values.get( values.size() - 1 ); 369 } 370 371 /** 372 * 指定の名前のリクエストパラメータの値を配列型式で取得します。 373 * 374 * @og.rev 5.3.2.0 (2011/02/01) 新規作成 375 * @og.rev 6.3.9.1 (2015/11/27) null ではなく長さが0の配列を返すことを検討する(findbugs)。 376 * 377 * @param name リクエストパラメータ名 378 * 379 * @return パラメータの値配列(存在しない場合は、長さ0の配列を返します) 380 * @og.rtnNotNull 381 */ 382 public String[] getParameters( final String name ) { 383 final List<String> values = paramMap.get(name); 384 return values == null || values.isEmpty() 385 ? new String[0] 386// : values.toArray( new String[values.size()] ); 387 : values.toArray( new String[0] ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応 388 } 389 390 /** 391 * 指定の名前のリクエストパラメータの値を配列(int)型式で取得します。 392 * 393 * @og.rev 5.3.2.0 (2011/02/01) 新規作成 394 * @og.rev 5.3.6.0 (2011/06/01) 配列値が""の場合にNumberFormatExceptionが発生するバグを修正 395 * @og.rev 6.3.9.1 (2015/11/27) null ではなく長さが0の配列を返すことを検討する(findbugs)。 396 * 397 * @param name リクエストパラメータ名 398 * 399 * @return パラメータの値配列(存在しない場合は、長さ0の配列を返します) 400 * @og.rtnNotNull 401 */ 402 public int[] getIntParameters( final String name ) { 403 final List<String> values = paramMap.get(name); 404 405 return values == null || values.isEmpty() 406 ? new int[0] 407 : values.stream() 408 .filter( str -> str != null && !str.isEmpty() ) // 条件 409 .mapToInt( Integer::parseInt ) // 変換 String → int 410 .toArray(); // int[] 配列 411 } 412 413 /** 414 * 指定の名前の ファイル名のリクエスト変数処理を行います。 415 * 416 * filename 属性のみ、{@XXXX} のリクエスト変数が使えるようにします。 417 * 418 * @og.rev 5.7.4.3 (2014/03/28) 新規追加 419 * 420 * @param fname ファイル名 421 * @return リクエスト変数を処理したファイル名 422 */ 423 private String getReqParamFileName( final String fname ) { 424 425 String rtn = fname ; 426 if( fname != null ) { 427 final StringBuilder filename = new StringBuilder( fname ) ; 428 int st = filename.indexOf( "{@" ); 429 while( st >= 0 ) { 430 final int ed = filename.indexOf( "}",st ); 431 if( ed < 0 ) { 432 final String errMsg = "{@XXXX} の対応関係が取れていません。" 433 + " filename=[" + fname + "]"; 434 throw new OgRuntimeException( errMsg ); 435 } 436 final String key = filename.substring( st+2,ed ); // "}" は切り出し対象外にする。 437 final String val = getParameter( key ); 438 filename.replace( st,ed+1,val ); // "}" を含めて置換したいので、ed+1 439 // 次の "{@" を探す。開始は置換文字数が不明なので、st から始める。 440 st = filename.indexOf( "{@",st ); 441 } 442 rtn = filename.toString(); 443 } 444 return rtn ; 445 } 446 447// /** 448// * 指定のディレクトリが無ければ作成します。 449// * 450// * @og.rev 6.9.0.1 (2018/02/05) ファイルをセーブするディレクトリは、必要な場合のみ、作成します。 451// * @og.rev 8.0.0.0 (2021/09/30) クラウド対応のため、 452// * 453// * @param saveDir ディレクトリ名 454// * @return セーブ可能なディレクトリ 455// * @throws IllegalArgumentException セーブディレクトリ に関係するエラー(無理から) 456// */ 457// private File makeDirs( final String saveDir ) throws IllegalArgumentException { 458// // セーブディレクトリの名前チェック 459// if( saveDir == null ) { 460// throw new IllegalArgumentException( "saveDir cannot be null" ); 461// } 462// 463// // セーブディレクトリのオブジェクト 464// final File dir = new File( saveDir ); 465// 466// // セーブディレクトリ 作成 467// if( ! dir.exists() && ! dir.mkdirs() ) { 468// throw new IllegalArgumentException( "Not make directory: " + saveDir ); 469// } 470// 471// // ディレクトリでなければ、エラー 472// if( !dir.isDirectory() ) { 473// throw new IllegalArgumentException( "Not a directory: " + saveDir ); 474// } 475// 476// // 書込みできなければ、エラー 477// if( !dir.canWrite() ) { 478// throw new IllegalArgumentException( "Not writable: " + saveDir ); 479// } 480// 481// return dir; 482// } 483}