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