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.BufferedReader;
021import java.io.BufferedWriter;
022import java.io.File;
023import java.io.InputStream;
024import java.io.FileInputStream;
025import java.io.FileNotFoundException;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStreamReader;
029import java.io.OutputStream;
030import java.io.OutputStreamWriter;
031import java.io.PrintWriter;
032import java.io.UnsupportedEncodingException;
033import java.io.Writer;
034import java.util.Collections;
035import java.util.List;
036
037import java.nio.channels.FileChannel;
038
039/**
040 * FileUtil.java は、共通的に使用される File関連メソッドを集約した、クラスです。
041 *
042 * 全変数は、public static final 宣言されており、全メソッドは、public static synchronized 宣言されています。
043 *
044 * @og.group ユーティリティ
045 *
046 * @version  4.0
047 * @author       Kazuhiko Hasegawa
048 * @since    JDK5.0,
049 */
050public final class FileUtil {
051        private static final NonClosePrintWriter outWriter = new NonClosePrintWriter( System.out );
052        private static final NonClosePrintWriter errWriter = new NonClosePrintWriter( System.err );
053
054        /** システム依存の改行記号をセットします。 */
055        private static final String CR = System.getProperty("line.separator");
056
057        /** 5.6.1.2 (2013/02/22) UNIX系のファイル名を表すセパレータ文字  */
058        private static final char UNIX_SEPARATOR = '/';
059
060        /** 5.6.1.2 (2013/02/22) Windwos系のファイル名を表すセパレータ文字       */
061        private static final char WINDOWS_SEPARATOR = '\\';
062
063        /** 5.6.1.2 (2013/02/22) ファイルの拡張子の区切りを表す文字      */
064        public static final char EXTENSION_SEPARATOR = '.';
065
066        private static final byte B_CR = (byte)0x0d ;   // '\r'
067        private static final byte B_LF = (byte)0x0a ;   // '\n'
068        private static final int  BUFSIZE = 8192 ;              // 5.1.6.0 (2010/05/01)
069
070        /**
071         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
072         *
073         */
074        private FileUtil() {}
075
076        /**
077         * Fileオブジェクトとエンコードより PrintWriterオブジェクトを作成します。
078         *
079         * @param       file    出力するファイルオブジェクト
080         * @param       encode  ファイルのエンコード
081         *
082         * @return      PrintWriterオブジェクト
083         * @throws RuntimeException 何らかのエラーが発生した場合
084         */
085        public static PrintWriter getPrintWriter( final File file,final String encode ) {
086                return getPrintWriter( file,encode,false );
087        }
088
089        /**
090         * Fileオブジェクトとエンコードより PrintWriterオブジェクトを作成します。
091         *
092         * @param       file    出力するファイルオブジェクト
093         * @param       encode  ファイルのエンコード
094         * @param       append  ファイルを追加モード(true)にするかどうか
095         *
096         * @return      PrintWriterオブジェクト
097         * @throws RuntimeException 何らかのエラーが発生した場合
098         */
099        public static PrintWriter getPrintWriter( final File file,final String encode,final boolean append ) {
100                final PrintWriter writer ;
101
102                try {
103                        writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
104                                        new FileOutputStream(file,append) ,encode )));
105                }
106                catch( UnsupportedEncodingException ex ) {
107                        String errMsg = "指定されたエンコーディングがサポートされていません。" + CR
108                                                        + ex.getMessage() + CR
109                                                        + "File=[" + file + " , encode=[" + encode + "]" ;
110                        throw new RuntimeException( errMsg,ex );
111                }
112                catch( FileNotFoundException ex ) {             // 3.6.1.0 (2005/01/05)
113                        String errMsg = "ファイル名がオープン出来ませんでした。" + CR
114                                                        + ex.getMessage() + CR
115                                                        + "File=[" + file + " , encode=[" + encode + "]" ;
116                        throw new RuntimeException( errMsg,ex );
117                }
118
119                return writer ;
120        }
121
122        /**
123         * ファイル名より、PrintWriterオブジェクトを作成する簡易メソッドです。
124         *
125         * これは、ファイル名は、フルパスで、追加モードで、UTF-8 エンコードの
126         * ログファイルを出力する場合に使用します。
127         * また、ファイル名に、"System.out" と、"System.err" を指定できます。
128         * その場合は、標準出力、または、標準エラー出力に出力されます。
129         * "System.out" と、"System.err" を指定した場合は、NonClosePrintWriter
130         * オブジェクトが返されます。これは、close() 処理が呼ばれても、何もしない
131         * クラスです。また、常に内部キャッシュの同じオブジェクトが返されます。
132         *
133         * @param       file    出力するファイル名
134         *
135         * @return      PrintWriterオブジェクト
136         * @throws RuntimeException 何らかのエラーが発生した場合
137         * @throws IllegalArgumentException ファイル名が null の場合
138         */
139        public static PrintWriter getLogWriter( final String file ) {
140                if( file == null ) {
141                        String errMsg = "ファイル名に、null は指定できません。";
142                        throw new IllegalArgumentException( errMsg );
143                }
144
145                final PrintWriter writer ;
146                if( "System.out".equalsIgnoreCase( file ) ) {
147                        writer = outWriter ;
148                }
149                else if( "System.err".equalsIgnoreCase( file ) ) {
150                        writer = errWriter ;
151                }
152                else {
153                        writer = getPrintWriter( new File( file ),"UTF-8",true );
154                }
155
156                return writer ;
157        }
158
159        /**
160         * OutputStreamとエンコードより PrintWriterオブジェクトを作成します。
161         *
162         * @og.rev 5.5.2.0 (2012/05/01) 新規追加
163         *
164         * @param       os              利用するOutputStream
165         * @param       encode  ファイルのエンコード
166         *
167         * @return      PrintWriterオブジェクト
168         * @throws RuntimeException 何らかのエラーが発生した場合
169         */
170        public static PrintWriter getPrintWriter( final OutputStream os,final String encode ) {
171                final PrintWriter writer ;
172
173                try {
174                        writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
175                                        os ,encode )));
176                }
177                catch( UnsupportedEncodingException ex ) {
178                        String errMsg = "指定されたエンコーディングがサポートされていません。" + CR
179                                                        + ex.getMessage() + CR
180                                                        + "encode=[" + encode + "]" ;
181                        throw new RuntimeException( errMsg,ex );
182                }
183                return writer ;
184        }
185
186        /**
187         * PrintWriter を継承した、JspWriterなどの Writer 用のクラスを定義します。
188         *
189         * 例えば、JspWriterなどの JSP/Servlet等のフレームワークで使用される
190         * Writer では、flush や close 処理は、フレームワーク内で行われます。
191         * その場合、通常のファイルと同じ用に、flush や close をアプリケーション側で
192         * 行うと、内部処理的に不整合が発生したり、最悪の場合エラーになります。
193         * このクラスは、NonFlushPrintWriter クラスのオブジェクトを返します。
194         * これは、通常の、new PrintWriter( Writer ) で、求めるのと、ほとんど同様の
195         * 処理を行いますが、close() と flush() メソッドが呼ばれても、何もしません。
196         *
197         * @param       writer  出力するWriteオブジェクト(NonFlushPrintWriterクラス)
198         *
199         * @return      PrintWriterオブジェクト
200         */
201        public static PrintWriter getNonFlushPrintWriter( final Writer writer ) {
202                return new NonFlushPrintWriter( writer );
203        }
204
205        /**
206         * Fileオブジェクトとエンコードより BufferedReaderオブジェクトを作成します。
207         *
208         * @param       file    入力するファイルオブジェクト
209         * @param       encode  ファイルのエンコード
210         *
211         * @return      BufferedReaderオブジェクト
212         * @throws RuntimeException 何らかのエラーが発生した場合
213         */
214        public static BufferedReader getBufferedReader( final File file,final String encode ) {
215                final BufferedReader reader ;
216
217                try {
218                        reader = new BufferedReader(new InputStreamReader(
219                                                        new FileInputStream( file ) ,encode ));
220                }
221                catch( UnsupportedEncodingException ex ) {
222                        String errMsg = "指定されたエンコーディングがサポートされていません。" + CR
223                                                        + ex.getMessage() + CR
224                                                        + "FIle=[" + file + " , encode=[" + encode + "]" ;
225                        throw new RuntimeException( errMsg,ex );
226                }
227                catch( FileNotFoundException ex ) {
228                        String errMsg = "ファイル名がオープン出来ませんでした。" + CR
229                                                        + ex.getMessage() + CR
230                                                        + "FIle=[" + file + " , encode=[" + encode + "]" ;
231                        throw new RuntimeException( errMsg,ex );
232                }
233
234                return reader ;
235        }
236
237        /**
238         * 指定のファイル名が、実際に存在しているかどうかをチェックします。
239         * 存在しない場合は、2秒毎に、3回確認します。
240         * それでも存在しない場合は、エラーを返します。
241         * return されるFileオブジェクトは、正規の形式(CanonicalFile)です。
242         *
243         * @param       dir                     フォルダ名
244         * @param       filename        ファイル名
245         *
246         * @return      存在チェック(なければ null/あれば、CanonicalFile)
247         */
248        public static File checkFile( final String dir, final String filename ) {
249                return checkFile( dir,filename,3 );
250        }
251
252        /**
253         * 指定のファイル名が、実際に存在しているかどうかをチェックします。
254         * 存在しない場合は、2秒毎に、指定の回数分確認します。
255         * それでも存在しない場合は、エラーを返します。
256         * return されるFileオブジェクトは、正規の形式(CanonicalFile)です。
257         *
258         * @param       dir                     フォルダ名
259         * @param       filename        ファイル名
260         * @param       count           回数指定
261         *
262         * @return      存在チェック(なければ null/あれば、CanonicalFile)
263         */
264        public static File checkFile( final String dir, final String filename,final int count ) {
265                File file = null;
266
267                int cnt = count;
268                while( cnt > 0 ) {
269                        file = new File( dir,filename );
270                        if( file.exists() ) { break; }
271                        else {
272                                if( cnt == 1 ) { return null; }         // 残り1回の場合は、2秒待機せずに即抜ける。
273                                try { Thread.sleep( 2000 );     }       // 2秒待機
274                                catch ( InterruptedException ex ) {
275                                        System.out.println( "InterruptedException" );
276                                }
277                                System.out.println();
278                                System.out.print( "CHECK File Error! CNT=" + cnt );
279                                System.out.print( " File=" + file.getAbsolutePath() );
280                        }
281                        cnt--;
282                }
283
284                // ファイルの正式パス名の取得
285                try {
286                        return file.getCanonicalFile() ;
287                }
288                catch( IOException ex ) {
289                        String errMsg = "ファイルの正式パス名が取得できません。[" + file.getAbsolutePath() + "]";
290                        throw new RuntimeException( errMsg,ex );
291                }
292        }
293
294        /**
295         * ファイルのバイナリコピーを行います。
296         *
297         * copy( File,File,false ) を呼び出します。
298         *
299         * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
300         *
301         * @param       fromFile        コピー元ファイル名
302         * @param       toFile          コピー先ファイル名
303         *
304         * @return      バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
305         * @see         #copy( File,File,boolean )
306         */
307        public static boolean copy( final String fromFile,final String toFile ) {
308                return copy( new File( fromFile ), new File( toFile ), false );
309        }
310
311        /**
312         * ファイルのバイナリコピーを行います。
313         *
314         * copy( File,File,boolean ) を呼び出します。
315         * 第3引数の、keepTimeStamp=true で、コピー元のファイルのタイムスタンプを、
316         * コピー先にもセットします。
317         *
318         * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
319         *
320         * @param       fromFile        コピー元ファイル名
321         * @param       toFile          コピー先ファイル名
322         * @param       keepTimeStamp   タイムスタンプ維持[true/false]
323         *
324         * @return      バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
325         * @see         #copy( File,File,boolean )
326         */
327        public static boolean copy( final String fromFile,final String toFile,final boolean keepTimeStamp ) {
328                return copy( new File( fromFile ), new File( toFile ), keepTimeStamp );
329        }
330
331        /**
332         * ファイルのバイナリコピーを行います。
333         *
334         * copy( File,File,false ) を呼び出します。
335         *
336         * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
337         *
338         * @param       fromFile        コピー元ファイル
339         * @param       toFile          コピー先ファイル
340         *
341         * @return      バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
342         * @see         #copy( File,File,boolean )
343         */
344        public static boolean copy( final File fromFile,final File toFile ) {
345                return copy( fromFile, toFile, false );
346        }
347
348        /**
349         * ファイルのバイナリコピーを行います。
350         *
351         * 第3引数の、keepTimeStamp=true で、コピー元のファイルのタイムスタンプを、
352         * コピー先にもセットします。
353         * toFile が、ディレクトリの場合は、fromFile のファイル名をそのままコピーします。
354         * fromFile がディレクトリの場合は、エラーにします。
355         * copyDirectry( File,Fileboolean )を使用してください。(自動処理はしていません)
356         *
357         * @og.rev 5.1.6.0 (2010/05/01) 新規追加
358         * @og.rev 5.6.5.2 (2013/06/21) ByteBufferを利用した方式から、transferTo を使用する方式に変更
359         * @og.rev 5.7.1.2 (2013/12/20) copy先(toFile)のフォルダが存在しなければ、作成します。
360         *
361         * @param       fromFile        コピー元ファイル
362         * @param       toFile          コピー先ファイル
363         * @param       keepTimeStamp タイムスタンプ維持[true/false]
364         *
365         * @return      バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
366         * @see         #copyDirectry( File,File,boolean )
367         */
368        public static boolean copy( final File fromFile,final File toFile,final boolean keepTimeStamp ) {
369                FileInputStream  inFile  = null;
370                FileOutputStream outFile = null;
371                FileChannel  fin  = null;
372                FileChannel  fout = null;
373
374                File tempToFile = toFile ;
375                try {
376                        // fromFileが、ディレクトリの場合は、エラー
377                        if( fromFile.isDirectory() ) {
378                                System.err.println( fromFile + " がディレクトリのため、処理できません。" );
379                                return false;
380                        }
381                        // toFileが、ディレクトリの場合は、そのパスでファイル名をfromFileから取り出す。
382                        if( toFile.isDirectory() ) {
383                                tempToFile = new File( toFile,fromFile.getName() );
384                        }
385
386                        // 5.7.1.2 (2013/12/20) copy先(toFile)のフォルダが存在しなければ、作成します。
387                        File parent = tempToFile.getParentFile();
388                        if( !parent.exists() && !parent.mkdirs() ) {
389                                // ディレクトリを作成する
390                                System.err.println( parent + " の ディレクトリ作成に失敗しました。" );
391                                return false;
392                        }
393
394                        inFile  = new FileInputStream( fromFile );
395                        outFile = new FileOutputStream( tempToFile );
396
397                        fin  = inFile.getChannel();
398                        fout = outFile.getChannel();
399
400                        // 5.6.5.2 (2013/06/21) ByteBufferを利用した方式から、transferTo を使用する方式に変更
401//                      ByteBuffer buffer = ByteBuffer.allocateDirect( BUFSIZE );
402//                      while ( (fin.read(buffer) != -1) || buffer.position() > 0) {
403//                              buffer.flip();
404//                              fout.write( buffer );
405//                              buffer.compact();
406//                      }
407
408                        fin.transferTo(0, fin.size(), fout );
409
410                }
411                catch ( IOException ex ) {
412                        System.out.println(ex.getMessage());
413                        return false;
414                }
415                finally {
416                        Closer.ioClose( inFile  ) ;
417                        Closer.ioClose( outFile );
418                        Closer.ioClose( fin  ) ;
419                        Closer.ioClose( fout );
420                }
421
422                if( keepTimeStamp ) {
423                        return tempToFile.setLastModified( fromFile.lastModified() );
424                }
425
426                return true;
427        }
428
429        /**
430         * ファイルのバイナリコピーを行います。
431         *
432         * このファイルコピーは、バイナリファイルの 改行コードを
433         * CR+LF に統一します。また、UTF-8 の BOM(0xef,0xbb,0xbf) があれば、
434         * 取り除きます。
435         *
436         * @og.rev 5.1.6.0 (2010/05/01) 新規追加
437         *
438         * @param       fromFile        コピー元ファイル
439         * @param       toFile          コピー先ファイル
440         *
441         * @return      バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
442         */
443        public static boolean changeCrLfcopy( final File fromFile,final File toFile ) {
444                BufferedInputStream     fromStream = null;
445                BufferedOutputStream    toStream   = null;
446                File tempToFile = toFile ;
447                try {
448                        // ディレクトリの場合は、そのパスでファイル名をfromFileから取り出す。
449                        if( toFile.isDirectory() ) {
450                                tempToFile = new File( toFile,fromFile.getName() );
451                        }
452                        fromStream = new BufferedInputStream( new FileInputStream( fromFile ) );
453                        toStream   = new BufferedOutputStream( new FileOutputStream( tempToFile ) );
454
455                        byte[] buf = new byte[BUFSIZE];
456                        int len ;
457                        // 4.2.3.0 (2008/05/26) changeCrLf 属性対応
458
459                        boolean bomCheck = true;        // 最初の一回だけ、BOMチェックを行う。
460                        byte    bt = (byte)0x00;        // バッファの最後と最初の比較時に使用
461                        while( (len = fromStream.read(buf,0,BUFSIZE)) != -1 ) {
462                                int st = 0;
463                                if( bomCheck && len >= 3 &&
464                                        buf[0] == (byte)0xef &&
465                                        buf[1] == (byte)0xbb &&
466                                        buf[2] == (byte)0xbf  ) {
467                                                st = 3;
468                                }
469                                else {
470                                        // バッファの最後が CR で、先頭が LF の場合、LF をパスします。
471                                        if( bt == B_CR && buf[0] == B_LF ) {
472                                                st = 1 ;
473                                        }
474                                }
475                                bomCheck = false;
476
477                                for( int i=st;i<len;i++ ) {
478                                        bt = buf[i] ;
479                                        if( bt == B_CR || bt == B_LF ) {
480                                                toStream.write( (int)B_CR );            // CR
481                                                toStream.write( (int)B_LF );            // LF
482                                                // CR+LF の場合
483                                                if( bt == B_CR && i+1 < len && buf[i+1] == B_LF ) {
484                                                        i++;
485                                                        bt = buf[i] ;
486                                                }
487                                        }
488                                        else {
489                                                toStream.write( (int)bt );
490                                        }
491                                }
492                        }
493                        // 最後が改行コードでなければ、改行コードを追加します。
494                        // テキストコピーとの互換性のため
495                        if( bt != B_CR && bt != B_LF ) {
496                                toStream.write( (int)B_CR );            // CR
497                                toStream.write( (int)B_LF );            // LF
498                        }
499                }
500                catch ( IOException ex ) {
501                        System.out.println(ex.getMessage());
502                        return false;
503                }
504                finally {
505                        Closer.ioClose( fromStream ) ;
506                        Closer.ioClose( toStream ) ;
507                }
508
509                return true;
510        }
511
512        /**
513         * 入出力ストリーム間でデータの転送を行います。
514         *
515         * ここでは、すでに作成されたストリームに基づき、データの入出力を行います。
516         * よって、先にフォルダ作成や、存在チェック、ファイルの削除などの必要な処理は
517         * 済まして置いてください。
518         * また、このメソッド内で、ストリームのクロース処理は行っていません。
519         *
520         * @og.rev 5.1.6.0 (2010/05/01) 新規追加
521         *
522         * @param       input   入力ストリーム
523         * @param       output  出力ストリーム
524         *
525         * @return      データ転送が正常に終了したかどうか[true:成功/false:失敗]
526         */
527        public static boolean copy( final InputStream input,final OutputStream output ) {
528                if( input == null ) {
529                        System.err.println( "入力ストリームが 作成されていません。" );
530                        return false;
531                }
532
533                if( output == null ) {
534                        System.err.println( "出力ストリームが 作成されていません。" );
535                        return false;
536                }
537
538                try {
539                        byte[] buf = new byte[BUFSIZE];
540                        int len;
541                        while((len = input.read(buf)) != -1) {
542                                output.write(buf, 0, len);
543                        }
544                }
545                catch ( IOException ex ) {
546                        System.out.println( ex.getMessage() );
547                        return false;
548                }
549        //      finally {
550        //              Closer.ioClose( input );
551        //              Closer.ioClose( output );
552        //      }
553                return true ;
554        }
555
556        /**
557         * 再帰処理でディレクトリのコピーを行います。
558         *
559         * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは falseを返します。
560         *
561         * @og.rev 4.3.0.0 (2008/07/24) 追加
562         * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
563         *
564         * @param       fromDir コピー元ディレクトリ名
565         * @param       toDir   コピー先ディレクトリ名
566         *
567         * @return      ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗]
568         */
569        public static boolean copyDirectry( final String fromDir, final String toDir ) {
570                return copyDirectry( new File( fromDir ), new File( toDir ),false );
571        }
572
573        /**
574         * 再帰処理でディレクトリをコピーします。
575         *
576         * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは falseを返します。
577         *
578         * @og.rev 4.3.0.0 (2008/07/24) 追加
579         * @og.rev 5.1.6.0 (2010/05/01) 内部処理を若干変更します。
580         *
581         * @param       fromDir   コピー元ディレクトリ
582         * @param       toDir     コピー先ディレクトリ
583         *
584         * @return      ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗]
585         */
586        public static boolean copyDirectry( final File fromDir, final File toDir ) {
587                return copyDirectry( fromDir, toDir, false );
588        }
589
590        /**
591         * 再帰処理でディレクトリをコピーします。
592         *
593         * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは falseを返します。
594         *
595         * @og.rev 4.3.0.0 (2008/07/24) 追加
596         * @og.rev 5.1.6.0 (2010/05/01) 内部処理を若干変更します。
597         * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。
598         *
599         * @param       fromDir コピー元ディレクトリ
600         * @param       toDir   コピー先ディレクトリ
601         * @param       keepTimeStamp タイムスタンプ維持[true/false]
602         *
603         * @return      ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗]
604         */
605        public static boolean copyDirectry( final File fromDir, final File toDir, final boolean keepTimeStamp ) {
606                // コピー元がディレクトリでない場合はfalseを返す
607                // 4.3.4.4 (2009/01/01)
608                if( !fromDir.exists() || !fromDir.isDirectory() ) {
609                        System.err.println( fromDir + " が ディレクトリでないか、存在しません。" );
610                        return false;
611                }
612
613                // 4.3.4.4 (2009/01/01) ディレクトリを作成する
614                // 6.0.0.1 (2014/04/25) These nested if statements could be combined
615                if( !toDir.exists() && !toDir.mkdirs() ) {
616                        System.err.println( toDir + " の ディレクトリ作成に失敗しました。" );
617                        return false;
618                }
619
620                // ディレクトリ内のファイルをすべて取得する
621                File[] files = fromDir.listFiles();
622
623                // 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラー
624                if( files == null ) {
625                        System.err.println( fromDir + " はアクセスできません。" );
626                        return false;
627                }
628
629                // ディレクトリ内のファイルに対しコピー処理を行う
630                boolean flag = true;
631                for( int i = 0; files.length>i; i++ ){
632                        if( files[i].isDirectory() ){ // ディレクトリだった場合は再帰呼び出しを行う
633                                flag = copyDirectry( files[i], new File( toDir, files[i].getName()),keepTimeStamp );
634                        }
635                        else{ // ファイルだった場合はファイルコピー処理を行う
636                                flag = copy( files[i], new File( toDir, files[i].getName()),keepTimeStamp );
637                        }
638                        if( !flag ) { return false; }
639                }
640                return true;
641        }
642
643        /**
644         * 指定されたファイル及びディレクトを削除します。
645         * ディレクトリの場合はサブフォルダ及びファイルも削除します。
646         * 1つでもファイルの削除に失敗した場合、その時点で処理を中断しfalseを返します。
647         *
648         * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。
649         *
650         * @param       file 削除ファイル/ディレクトリ
651         *
652         * @return      ファイル/ディレクトリの削除に終了したかどうか[true:成功/false:失敗]
653         */
654        public static boolean deleteFiles( final File file ) {
655                if( file.exists() ) {
656                        if( file.isDirectory() ) {
657                                File[] list = file.listFiles();
658
659                                // 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラー
660                                if( list == null ) {
661                                        System.err.println( file + " はアクセスできません。" );
662                                        return false;
663                                }
664
665                                for( int i=0; i<list.length; i++ ) {
666                                        deleteFiles( list[i] );
667                                }
668                        }
669                        if( !file.delete() ) { return false; }
670                }
671                return true;
672        }
673
674        /**
675         * 指定されたディレクトリを基点としたファイル名(パスを含む)の一覧を返します。
676         *
677         * @og.rev 4.3.6.6 (2009/05/15) 新規作成
678         * @og.rev 5.4.3.2 (2012/01/06) 引数isCopy追加
679         *
680         * @param dir 基点となるディレクトリ
681         * @param sort ファイル名でソートするか
682         * @param list ファイル名一覧を格納するList
683         * @param isCopy コピー中ファイルを除外するか [true:含む/false:除外]
684         */
685        public static void getFileList( final File dir, final boolean sort, final List<String> list, boolean isCopy ) {
686                if( list == null ) { return; }
687                if( dir.isFile() ) {
688                        // コピー中判定はrenameで行う
689                        if( !isCopy && !dir.renameTo( dir ) ){
690                                return;
691                        }
692                        else{
693                                list.add( dir.getAbsolutePath() );
694                        }
695                }
696                else if( dir.isDirectory() ) {
697                        File[] files = dir.listFiles();
698                        for( int i=0; i<files.length; i++ ) {
699                                getFileList( files[i], sort, list, isCopy );
700                        }
701                }
702                if( sort ) {
703                        Collections.sort( list );
704                }
705        }
706
707        /**
708         * 指定されたディレクトリを基点としたファイル名(パスを含む)の一覧を返します。
709         * 互換性のため、コピー中ファイルも含みます。
710         *
711         * @og.rev 5.4.3.2 (2012/01/06) コピー中対応のため引数4つを作成する
712         *
713         * @param dir 基点となるディレクトリ
714         * @param sort ファイル名でソートするか
715         * @param list ファイル名一覧を格納するList
716         */
717        public static void getFileList( final File dir, final boolean sort, final List<String> list ) {
718                        getFileList( dir, sort, list, true );
719        }
720
721        /**
722         * 指定されたファイル名(パスを含む)から、パスも拡張子もないファイル名を返します。
723         *
724         * @og.rev 5.6.1.2 (2013/02/22) 新規作成
725         *
726         * @param filename ファイル名(パスを含む)
727         * @return パスも、拡張子もないファイル名
728         */
729        public static String getBaseName( final String filename ) {
730
731                if (filename == null) {
732                        return null;
733                }
734
735                // セパレータの位置を取得。
736                int lastUnixPos    = filename.lastIndexOf(UNIX_SEPARATOR);
737                int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
738                int lastSepPos = Math.max( lastUnixPos , lastWindowsPos );
739
740                // 拡張子の位置を取得。
741                int extPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
742                if( lastSepPos > extPos ) { extPos = -1; }      // 念のため、最後のセパレータより前にある拡張子の区切り文字は無効。
743
744                if( extPos < 0 ) {
745                        // SEPARATOR がなければ、lastSepPos + 1 = 0 となり、先頭から取得できる。
746                        return filename.substring( lastSepPos + 1 );
747                } else {
748                        return filename.substring( lastSepPos + 1 , extPos );
749                }
750        }
751
752        /**
753         * ファイルをリネームを行います。
754         * 引数のuseBackup属性を true にすると、toFile が存在した場合、toFile の直下に "_backup" フォルダを
755         * 作成して、toFile + "_" + (現在時刻のLONG値) + "." + (toFileの拡張子) に名前変更します。
756         * useBackup属性を false にすると、toFile が存在した場合、toFile を削除します。
757         *
758         * @og.rev 5.7.1.2 (2013/12/20) 新規追加
759         *
760         * @param       fromFile        名前変更する元のファイル
761         * @param       toFile          名前変更後のファイル
762         * @param       useBackup       バックアップを作成するかどうか(true:作成する/false:作成しない)
763         * @return      true:正常処理/false:異常処理
764         */
765        public static boolean renameTo( final File fromFile , final File toFile , final boolean useBackup ) {
766                if( fromFile == null || toFile == null ) {
767                        String errMsg = "入力ファイルが null です。" ;
768                        System.err.println( errMsg );
769                        return false;
770                }
771
772                // 変更先のファイルが存在した場合の処理。
773                if( toFile.exists() ) {
774                        // バックアップ作成する場合
775                        if( useBackup ) {
776                                File parent = toFile.getParentFile();                   // バックアップすべきファイルのフォルダ
777                                File backup = new File( parent , "_backup" );   // その直下に、"_backup" フォルダを作成
778                                if( !backup.exists() && !backup.mkdirs() ) {
779                                        String errMsg = "バックアップ処理でbackupフォルダの作成に失敗しました。[" + backup + "]";
780                                        System.err.println( errMsg );
781                                        return false;
782                                }
783                                // バックアップファイル名は、元のファイル名(拡張子含む) + "_" + 現在時刻のlong値 + "." + 元のファイルの拡張子
784                                String bkupName = toFile.getName();
785                                File toFile2  = new File( parent,bkupName );    // オリジナルの toFile をrename するとまずいので、同名のFileオブジェクトを作成
786
787                                bkupName = bkupName + "_" + System.currentTimeMillis() + "."  + getExtension( bkupName ) ;
788                                File bkupFile = new File( backup,bkupName );
789
790                                if( !toFile2.renameTo( bkupFile ) ) {
791                                        String errMsg = "バックアップ処理でバックアップファイルをリネームできませんでした。" +CR
792                                                                                 + "  [" + toFile + "] ⇒ [" + bkupFile + "]" ;
793                                        System.err.println( errMsg );
794                                        return false;
795                                }
796                        }
797                        // バックアップ作成しない場合は、削除します。
798                        else if( !toFile.delete() ) {
799                                String errMsg = "既存のファイル[" + toFile + "]が削除できませんでした。";
800                                System.err.println( errMsg );
801                                return false;
802                        }
803                }
804
805                if( !fromFile.renameTo( toFile ) ) {
806                        String errMsg = "所定のファイルをリネームできませんでした。" + CR
807                                                                + "  [" + fromFile + "] ⇒ [" + toFile + "]" ;
808                                System.err.println( errMsg );
809                                return false;
810                }
811                return true;
812        }
813
814        /**
815         * ファイル名から 拡張子を取得します。
816         *
817         * 一番最後に見つかったピリオドから後ろを切り取って返します。
818         * 拡張子の区切り文字(".")がなければ、空文字列を返します。
819         *
820         * @og.rev 5.7.1.2 (2013/12/20) UploadedFileからに移動。若干のロジック変更
821         *
822         * @param       fileName        ファイル名
823         * @return      拡張子
824         */
825        public static String getExtension( final String fileName ) {
826                int extPos = fileName.lastIndexOf( EXTENSION_SEPARATOR );
827                if( extPos >= 0 ) {
828                        return fileName.substring( extPos + 1 );
829                }
830                return "";
831        }
832
833        /**
834         * PrintWriter を継承した、System.out/System.err 用のクラスを定義します。
835         *
836         * 通常の、new PrintWriter( OutputStream ) で、求めるのと、ほとんど同様の
837         * 処理を行います。
838         * ただ、close() メソッドが呼ばれても、何もしません。
839         *
840         */
841        private static final class NonClosePrintWriter extends PrintWriter {
842                /**
843                 * コンストラクター
844                 *
845                 * new PrintWriter( OutputStream ) を行います。
846                 *
847                 * @param out OutputStream
848                 */
849                public NonClosePrintWriter( final OutputStream out ) {
850                        super( out );
851                }
852
853                /**
854                 * close() メソッドをオーバーライドします。
855                 *
856                 * 何もしません。
857                 *
858                 */
859                public void close() {
860                        // ここでは処理を行いません。
861                }
862        }
863
864        /**
865         * PrintWriter を継承した、JspWriterなどの Writer 用のクラスを定義します。
866         *
867         * 例えば、JspWriterなどの JSP/Servlet等のフレームワークで使用される
868         * Writer では、flush や close 処理は、フレームワーク内で行われます。
869         * その場合、通常のファイルと同じ用に、flush や close をアプリケーション側で
870         * 行うと、内部処理的に不整合が発生したり、最悪の場合エラーになります。
871         * このクラスは、単に、通常の、new PrintWriter( Writer ) で、求めるのと、
872         * ほとんど同様の処理を行います。
873         * ただ、close() と flush() メソッドが呼ばれても、何もしません。
874         *
875         */
876        private static final class NonFlushPrintWriter extends PrintWriter {
877                /**
878                 * コンストラクター
879                 *
880                 * new PrintWriter( Writer ) を行います。
881                 *
882                 * @param writer Writer
883                 */
884                public NonFlushPrintWriter( final Writer writer ) {
885                        super( writer );
886                }
887
888                /**
889                 * close() メソッドをオーバーライドします。
890                 *
891                 * 何もしません。
892                 *
893                 */
894                public void close() {
895                        // ここでは処理を行いません。
896                }
897
898                /**
899                 * flush() メソッドをオーバーライドします。
900                 *
901                 * 何もしません。
902                 *
903                 */
904                public void flush() {
905                        // ここでは処理を行いません。
906                }
907        }
908
909        /**
910         * ファイルをコピーします。
911         *
912         * 引数に &lt;file1&gt; &lt;file2&gt; [&lt;encode1&gt; &lt;encode2&gt;] を指定します。
913         * file1 を読み込み、file2 にコピーします。コピー前に、file2 は、file2_backup にコピーします。
914         * file1 が、ディレクトリの場合は、ディレクトリごとコピーします。
915         * encode1、encode2 を指定すると、エンコード変換しながらコピーになります。
916         * この場合は、ファイル同士のコピーのみになります。
917         *
918         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
919         * @og.rev 5.1.6.0 (2010/05/01) 引数の並び順、処理を変更します。
920         *
921         * @param       args 引数配列  file1 file2 [encode1 encode2]
922         * @throws Throwable なんらかのエラーが発生した場合。
923         */
924        public static void main( final String[] args ) throws Throwable {
925                if( args.length != 2 && args.length != 4 ) {
926                        LogWriter.log("Usage: java org.opengion.fukurou.util.FileUtil <file1> <file2> [<encode1> <encode2>]" );
927                        return ;
928                }
929
930                File file1 = new File( args[0] );
931                File file2 = new File( args[1] );
932
933                File tempFile = new File( args[1] + "_backup" );
934
935                if( args.length < 3 ) {
936                        if( file1.isDirectory() ) {
937                                FileUtil.copyDirectry( file1, file2, true );
938                        }
939                        else {
940                                FileUtil.copy( file2,tempFile );
941                                FileUtil.copy( file1,file2, true );
942                        }
943                }
944                else {
945                        String encode1 = args[2];
946                        String encode2 = args[3];
947
948                        FileUtil.copy( file2,tempFile );
949
950                        BufferedReader reader = FileUtil.getBufferedReader( file1 ,encode1 );
951                        PrintWriter    writer = FileUtil.getPrintWriter(    file2 ,encode2 );
952
953                        try {
954                                String line1;
955                                while((line1 = reader.readLine()) != null) {
956                                        writer.println( line1 );
957                                }
958                        }
959                        finally {
960                                Closer.ioClose( reader ) ;
961                                Closer.ioClose( writer ) ;
962                        }
963                }
964        }
965}