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.xml;
017
018import java.sql.Connection;
019import java.sql.SQLException;
020
021// import java.io.Reader;
022import java.io.BufferedReader;
023import java.io.InputStreamReader;
024// import java.io.FileInputStream;
025import java.io.InputStream;
026import java.io.IOException;
027import java.io.File;
028import java.io.UnsupportedEncodingException;
029import java.io.Writer;                                                                                          // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。
030
031import java.util.Arrays;
032import java.util.List;
033import java.util.Map;
034import java.util.ArrayList;
035import java.util.Enumeration;
036import java.util.jar.JarFile;
037import java.util.jar.JarEntry;
038import java.net.URL;
039
040import org.opengion.fukurou.system.OgRuntimeException;                          // 6.4.2.0 (2016/01/29)
041import org.opengion.fukurou.system.ThrowUtil;                                           // 6.4.2.0 (2016/01/29)
042import org.opengion.fukurou.util.FileUtil;                                                      // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。
043import org.opengion.fukurou.system.Closer;
044import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
045
046/**
047 * ORACLE XDK 形式のXMLファイルを読み取って、データベースに登録します。
048 *
049 * これは、Ver5の時は、org.opengion.hayabusa.common.InitFileLoader として
050 * 使用されていたクラスを改造したものです。
051 * InitFileLoader は、Ver6 では廃止されていますので、ご注意ください。
052 *
053 * 登録の実行有無の判断は、ファイルの更新時刻より判断します。(useTimeStamp=true の場合)
054 * これは、読み取りファイルの更新時刻が、0でない場合、読み取りを行います。
055 * 読み取りが完了した場合は、更新時刻を 0 に設定します。
056 * 読み取るファイルは、クラスローダーのリソースや、指定のフォルダ以下のファイル、そして、
057 * zip 圧縮されたファイルの中から、拡張子が xml で、UTF-8でエンコードされている
058 * 必要があります。通常は、ファイル名がテーブル名と同一にしておく必要がありますが、
059 * ROWSETのtable属性にテーブル名をセットしておくことも可能です。
060 * ファイルの登録順は、原則、クラスローダーの検索順に、見つかった全てのファイルを
061 * 登録します。データそのものは、INSERT のみ対応していますので、原則登録順は無視されます。
062 * ただし、拡張XDK 形式で、EXEC_SQL タグを使用した場合は、登録順が影響する可能性があります。
063 * 例:GE12.xml GE12 テーブルに登録するXMLファイル
064 * 登録時に、既存のデータの破棄が必要な場合は、拡張XDK 形式のXMLファイルを
065 * 作成してください。これは、EXEC_SQL タグに書き込んだSQL文を実行します。
066 * 詳細は、{@link org.opengion.fukurou.xml.HybsXMLHandler HybsXMLHandler} クラスを参照してください。
067 *
068 *   <ROWSET tableName="XX" >
069 *       <EXEC_SQL>                    最初に記載して、初期処理(データクリア等)を実行させる。
070 *           delete from GEXX where YYYYY
071 *       </EXEC_SQL>
072 *       <ROW num="1">
073 *           <カラム1>値1</カラム1>
074 *             ・・・
075 *           <カラムn>値n</カラムn>
076 *       </ROW>
077 *        ・・・
078 *       <ROW num="n">
079 *          ・・・
080 *       </ROW>
081 *       <EXEC_SQL>                    最後に記載して、項目の設定(整合性登録)を行う。
082 *           update GEXX set AA='XX' , BB='XX' where YYYYY
083 *       </EXEC_SQL>
084 *   <ROWSET>
085 *
086 * @og.rev 4.0.0.0 (2004/12/31) 新規作成(org.opengion.hayabusa.common.InitFileLoader)
087 * @og.rev 6.0.0.0 (2014/04/11) パッケージ、クラスファイル変更
088 *
089 * @version     6.0
090 * @author      Kazuhiko Hasegawa
091 * @since       JDK7.0,
092 */
093public final class XMLFileLoader {
094        private static final String ENCODE = "UTF-8";                   // 8.0.0.1 (2021/10/08) cloud対応
095
096        private final Connection connection ;
097        private final boolean useTimeStamp ;
098
099        /** 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。 */
100        private Writer log = FileUtil.getLogWriter( "System.out" );
101
102        /** 6.0.0.0 (2014/04/11) タイムスタンプのゼロクリア対象のファイルを管理するリスト */
103        private final List<File> fileList = new ArrayList<>();
104
105        // 6.0.0.0 (2014/04/11) setAfterMap メソッドの対応
106        /** 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。 */
107        private Map<String,String> afterMap     ;
108
109        /** 6.0.0.0 (2014/04/11) 追加,更新,削除,実行 の各実行時のカウントの総数 */
110        // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseShortArrayInitializer
111//      private final int[] crudCnt = new int[] { 0,0,0,0 };    // CRUD カウントですが、配列の順番は追加,更新,削除,実行
112        private final int[] crudCnt = { 0,0,0,0 };                              // CRUD カウントですが、配列の順番は追加,更新,削除,実行
113
114        /** getCRUDCount() で返される カウント数の配列番号 {@value} */
115        public static final int INS = 0;
116        /** getCRUDCount() で返される カウント数の配列番号 {@value} */
117        public static final int DEL = 1;
118        /** getCRUDCount() で返される カウント数の配列番号 {@value} */
119        public static final int UPD = 2;
120        /** getCRUDCount() で返される カウント数の配列番号 {@value} */
121        public static final int DDL = 3;
122
123        /**
124         * コネクションを引数にする、コンストラクターです。
125         * classPath="resource" で初期化された XMLFileLoader を作成します。
126         * useTimeStamp 属性を true に設定すると、このファイルを読み取る都度
127         * タイムスタンプを、クリアします。
128         * また、タイムスタンプがクリアされたファイルは読み込みませんので、機能的に
129         * 一度しか読み込まないという事になります。
130         *
131         * @param       conn                    登録用コネクション
132         * @param       useTimeStamp    タイムスタンプの管理を行うかどうか[true:行う/false:行わない]
133         */
134        public XMLFileLoader( final Connection conn , final boolean useTimeStamp ) {
135                connection                      = conn ;
136                this.useTimeStamp       = useTimeStamp;
137        }
138
139        /**
140         * ログ出力を行う 内部ログ(Writer) を指定します。
141         *
142         * 内部ログ(Writer) の初期値は、null とします。
143         * 内部ログ(Writer)が null の場合は、なにもしません。
144         *
145         * @og.rev 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。
146         *
147         * @param       log     Writerオブジェクト
148         */
149        public void setLogWriter( final Writer log ) {
150                this.log = log ;
151        }
152
153        /**
154         * XMLファイルを読み取った後で指定するカラムと値のペア(マップ)情報をセットします。
155         *
156         * このカラムと値のペアのマップは、オブジェクト構築後に設定される為、
157         * XMLファイルのキーの存在に関係なく、Mapのキーと値が使用されます。(Map優先)
158         * null を設定した場合は、なにも処理されません。
159         *
160         * @og.rev 6.0.0.0 (2014/04/11) 新規追加
161         *
162         * @param       map     後設定するカラムデータマップ
163         */
164        public void setAfterMap( final Map<String,String> map ) {
165                afterMap = map;
166        }
167
168        /**
169         * XMLファイルを登録後の 追加,更新,削除,実行 のカウント配列を返します。
170         *
171         * 簡易的に処理したいために、配列に設定しています。
172         * 順番に、追加,更新,削除,実行 のカウント値になります。
173         *
174         * @og.rev 6.0.0.0 (2014/04/11) 新規追加
175         *
176         * @return      追加,更新,削除,実行 のカウント配列
177         * @og.rtnNotNull
178         */
179        public int[] getCRUDCount() {
180                // 6.0.2.5 (2014/10/31) refactoring
181                return crudCnt.clone();
182        }
183
184        /**
185         * 対象となるファイル群を ClassLoader の指定パスから、検索します。
186         *
187         * 対象ファイルは、指定フォルダに テーブル名.xml 形式で格納しておきます。
188         * このフォルダのファイルをピックアップします。
189         * useTimeStamp 属性を true に設定すると、このファイルを読み取る都度
190         * タイムスタンプを、クリアします。
191         * また、タイムスタンプがクリアされたファイルは読み込みませんので、機能的に
192         * 一度しか読み込まないという事になります。
193         *
194         * @og.rev 8.5.0.0 (2023/04/21) 対象外の resource を読込んでいた為、判定条件を変更
195         *
196         * @param       path    対象となるファイル群を検索するクラスパス
197         */
198        public void loadClassPathFiles( final String path ) {
199                loadClassPathFiles( path, "" );
200        }
201
202        /**
203         * 対象となるファイル群を ClassLoader の指定パスから、検索します。
204         *
205         * 対象ファイルは、指定フォルダに テーブル名.xml 形式で格納しておきます。
206         * このフォルダのファイルをピックアップします。
207         * useTimeStamp 属性を true に設定すると、このファイルを読み取る都度
208         * タイムスタンプを、クリアします。
209         * また、タイムスタンプがクリアされたファイルは読み込みませんので、機能的に
210         * 一度しか読み込まないという事になります。
211         *
212         * @og.rev 6.0.0.0 (2014/04/11) 新規追加
213         * @og.rev 6.4.0.4 (2015/12/26) Writer(ログ)のCloseは、ここでは行わない。
214         * @og.rev 6.8.5.1 (2018/01/15) ファイル名は、##バージョン番号を変換しておく必要がある。
215         * @og.rev 8.5.0.0 (2023/04/21) 対象外の resource を読込んでいた為、判定条件を変更
216         *
217         * @param       path    対象となるファイル群を検索するクラスパス
218         * @param       keyword 検索対象ファイルのキーワード
219         */
220//      public void loadClassPathFiles( final String path ) {
221        public void loadClassPathFiles( final String path,final String keyword ) {      // 8.5.0.0 (2023/04/21) Modify
222                try {
223                        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
224                        final Enumeration<URL> enume = loader.getResources( path );
225                        while( enume.hasMoreElements() ) {
226                                final URL url = enume.nextElement();
227                                // jar:file:/実ディレクトリ または、file:/実ディレクトリ
228//                              final String dir = url.getFile();
229                                final String dir = url.getFile().replaceAll( "%23%23","##" );   // 6.8.5.1 (2018/01/15)
230
231                                if( keyword == null || keyword.isEmpty() || dir.contains( keyword ) ){  // 8.5.0.0 (2023/04/21) Add
232                                        println( "      XMLFileLoader Scan:[ " + url + " ]" );                          // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理
233
234                                        if( "jar".equals( url.getProtocol() ) ) {
235                                                // dir = file:/C:/webapps/gf/WEB-INF/lib/resource2.jar!/resource 形式です。
236                                                final String jar = dir.substring(dir.indexOf( ':' )+1,dir.lastIndexOf( '!' ));
237                                                // jar = /C:/webapps/gf/WEB-INF/lib/resource2.jar 形式に切り出します。
238
239                                                // 5.6.6.1 (2013/07/12) jarファイルも、タイムスタンプ管理の対象
240                                                loadJarFile( new File( jar ) );
241                                        }
242                                        else {
243                                                // 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加
244                                                // dir = /C:/webapps/gf/WEB-INF/classes/resource/ 形式です。
245                                                loadXMLDir( new File( dir ) );
246                                        }
247                                }                                                                                                                                               // 8.5.0.0 (2023/04/21) Add
248                        }
249                        Closer.commit( connection );
250                }
251                catch( final SQLException ex ) {
252                        final String errMsg = "SQL実行時にエラーが発生しました。"
253                                        + CR + ex.getMessage();
254                        Closer.rollback( connection );
255                        throw new OgRuntimeException( errMsg,ex );
256                }
257                catch( final IOException ex ) {
258                        final String errMsg = "XMLファイル読み取り時にエラーが発生しました。"
259                                        + CR + ex.getMessage();
260                        throw new OgRuntimeException( errMsg,ex );
261                }
262                finally {
263                        // 6.4.0.4 (2015/12/26) Writer(ログ)のCloseは、ここでは行わない。
264                        setZeroTimeStamp();                             // 6.0.0.0 (2014/04/11) タイムスタンプの書き換えをメソッド化
265                }
266        }
267
268        /**
269         * 対象となるファイル群を ファイル単体、フォルダ階層以下、ZIPファイル から、検索します。
270         *
271         * 対象ファイルは、テーブル名.xml 形式で格納しておきます。
272         * この処理では、ファイル単体(*.xml)、フォルダ階層以下、ZIPファイル(*.jar , *.zip) は混在できません。
273         * 最初に判定した形式で、個々の処理に振り分けています。
274         *
275         * @og.rev 6.0.0.0 (2014/04/11) 新規追加
276         * @og.rev 6.4.0.4 (2015/12/26) Writer(ログ)のCloseは、ここでは行わない。
277         *
278         * @param       fileObj 読取元のファイルオブジェクト
279         * @see         #loadClassPathFiles( String )
280         */
281        public void loadXMLFiles( final File fileObj ) {
282                try {
283                        // nullでなく、ファイル/フォルダが存在することが前提
284                        if( fileObj != null && fileObj.exists() ) {
285                                println( "      XMLFileLoader Scan:[ " + fileObj + " ]" );      // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理
286                                loadXMLDir( fileObj );                  // ファイルかディレクトリ
287                        }
288                        Closer.commit( connection );
289                }
290                catch( final SQLException ex ) {
291                        final String errMsg = "SQL実行時にエラーが発生しました。"
292                                        + CR + ex.getMessage();
293                        Closer.rollback( connection );
294                        throw new OgRuntimeException( errMsg,ex );
295                }
296                catch( final IOException ex ) {
297                        final String errMsg = "XMLファイル読み取り時にエラーが発生しました。"
298                                        + CR + ex.getMessage();
299                        throw new OgRuntimeException( errMsg,ex );
300                }
301                finally {
302                        // 6.4.0.4 (2015/12/26) Writer(ログ)のCloseは、ここでは行わない。
303                        setZeroTimeStamp();                             // 6.0.0.0 (2014/04/11) タイムスタンプの書き換えをメソッド化
304                }
305        }
306
307        /**
308         * XMLフォルダ/ファイルを読み取り、データベースに追加(INSERT)するメソッドをコールします。
309         *
310         * ここでは、フォルダ階層を下るための再起処理を行っています。
311         * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、
312         * XMLファイルをデータベースに登録することが可能です。
313         * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave}
314         * を参照願います。
315         *
316         * @og.rev 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加
317         * @og.rev 8.0.0.1 (2021/10/08) cloud対応
318         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
319         *
320         * @param       jarObj  読取元のJarファイルオブジェクト
321         * @throws      SQLException    データベースアクセスエラー
322         * @throws      IOException             データ入出力エラー
323         */
324        private void loadJarFile( final File jarObj ) throws SQLException,IOException {
325                if( ! useTimeStamp || jarObj.lastModified() > 0 ) {
326                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
327//                      JarFile jarFile = null;
328//                      try {
329//                              jarFile = new JarFile( jarObj );
330                        try ( JarFile jarFile = new JarFile( jarObj ) ) {
331                                final Enumeration<JarEntry> flEnum = jarFile.entries() ;                // 4.3.3.6 (2008/11/15) Generics警告対応
332                                while( flEnum.hasMoreElements() ) {
333                                        final JarEntry ent = flEnum.nextElement();                                      // 4.3.3.6 (2008/11/15) Generics警告対応
334                                        final String file = ent.getName();
335                                        if( ! ent.isDirectory() && file.endsWith( ".xml" ) ) {
336                                                // 5.6.6.1 (2013/07/12) jarファイルの中身のタイムスタンプは見ない。
337                                                final String table = file.substring( file.lastIndexOf('/')+1,file.lastIndexOf('.') );
338                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
339//                                              InputStream stream = null;
340//                                              try {
341//                                                      stream = jarFile.getInputStream( ent ) ;
342                                                        // 8.0.0.1 (2021/10/08) cloud対応
343//                                                      loadXML( stream,table,file );
344//                                                      final BufferedReader reader = new BufferedReader( new InputStreamReader( stream,ENCODE ) );
345                                                try ( InputStream stream = jarFile.getInputStream( ent ) ;
346                                                          BufferedReader reader = new BufferedReader( new InputStreamReader( stream,ENCODE ) ) ) {
347                                                                loadXML( reader,table,file );
348                                                }
349//                                              finally {
350//                                                      Closer.ioClose( stream );
351//                                              }
352                                        }
353                                }
354                                fileList.add( jarObj );                 // 5.6.6.1 (2013/07/12) jarファイルも、タイムスタンプ管理の対象
355                        }
356//                      finally {
357//                              Closer.zipClose( jarFile );             // 5.5.2.6 (2012/05/25) findbugs対応
358//                      }
359                }
360        }
361
362        /**
363         * XMLフォルダ/ファイルを読み取り、データベースに追加(INSERT)するメソッドをコールします。
364         *
365         * ここでは、フォルダ階層を下るための再起処理を行っています。
366         * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、
367         * XMLファイルをデータベースに登録することが可能です。
368         * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave}
369         * を参照願います。
370         *
371         * @og.rev 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加
372         * @og.rev 8.0.0.1 (2021/10/08) cloud対応
373         *
374         * @param       fileObj 読取元のファイルオブジェクト
375         * @throws      SQLException    データベースアクセスエラー
376         * @throws      IOException             データ入出力エラー
377         */
378        private void loadXMLDir( final File fileObj ) throws SQLException,IOException {
379                if( fileObj.isDirectory() ) {
380                        final File[] list = fileObj.listFiles();
381                        // 6.3.9.0 (2015/11/06) null になっている可能性がある(findbugs)
382                        if( list != null ) {
383                                Arrays.sort( list );
384                                for( final File file : list ) {
385                                        loadXMLDir( file );
386                                }
387                        }
388                }
389                else if( ! useTimeStamp || fileObj.lastModified() > 0 ) {
390                        final String name = fileObj.getName() ;
391                        if( name.endsWith( ".xml" ) ) {
392                                final String table = name.substring( name.lastIndexOf('/')+1,name.lastIndexOf('.') );
393        //                      InputStream stream = null;
394        //                      try {
395                                        println( "        " + fileObj );                // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理
396                                        // 8.0.0.1 (2021/10/08) cloud対応
397//                                      stream = new FileInputStream( fileObj ) ;
398//                                      loadXML( stream,table,fileObj.getPath() );
399                                        final BufferedReader reader = FileUtil.getBufferedReader( fileObj,ENCODE );
400                                        loadXML( reader,table,fileObj.getPath() );
401                                        fileList.add( fileObj );                                // 正常に処理が終われば、リストに追加します。
402        //                      }
403        //                      finally {
404        //                              Closer.ioClose( stream );
405        //                      }
406                        }
407                        else if( name.endsWith( ".zip" ) || name.endsWith( ".jar" ) ) {
408                                loadJarFile( fileObj );
409                        }
410                }
411        }
412
413        /**
414         * XMLファイルを読み取り、データベースに追加(INSERT)します。
415         *
416         * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、
417         * XMLファイルをデータベースに登録することが可能です。
418         * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave}
419         * を参照願います。
420         *
421         * @og.rev 5.6.6.1 (2013/07/12) 更新カウント数も取得します。
422         * @og.rev 5.6.7.0 (2013/07/27) HybsXMLSave の DDL(データ定義言語:Data Definition Language)の処理件数追加
423         * @og.rev 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行しない。
424         * @og.rev 6.2.0.0 (2015/02/27) try ~ finally 構文
425         * @og.rev 7.3.2.0 (2021/03/19) isExecErr でfalseを指定した場合に、エラー内容の文字列を取り出します。
426         * @og.rev 8.0.0.1 (2021/10/08) cloud対応
427         *
428//       * @param       stream  XMLファイルを読み取るInputStream
429         * @param       reader  XMLファイルを読み取るBufferedReader(cloud対応)
430         * @param       table   テーブル名(ROWSETタグのtable属性が未設定時に使用)
431         * @param       file    ログ出力用のファイル名
432         * @see org.opengion.fukurou.xml.HybsXMLSave
433         * @throws SQLException SQL実行時エラー
434         * @throws UnsupportedEncodingException  エンコードエラー
435         */
436//      private void loadXML( final InputStream stream, final String table, final String file )
437        private void loadXML( final BufferedReader reader, final String table, final String file )
438                                                                        throws SQLException,UnsupportedEncodingException {
439                // 6.2.0.0 (2015/02/27) try ~ finally 構文
440//              Reader reader = null;
441                try {
442//                      // InputStream より、XMLファイルを読み取り、table に追加(INSERT)します。
443//                      reader = new BufferedReader( new InputStreamReader( stream,"UTF-8" ) );
444                        final HybsXMLSave save = new HybsXMLSave( connection,table );
445                        save.onExecErrException( false );               // 5.6.9.2 (2013/10/18) falseで、EXEC_SQL のエラー時に Exception を発行しない。
446                        save.setAfterMap( afterMap );                   // 6.0.0.0 (2014/04/11) 新規追加
447                        save.insertXML( reader );
448
449                        final int insCnt = save.getInsertCount();
450                        final int delCnt = save.getDeleteCount();
451                        final int updCnt = save.getUpdateCount();               // 5.6.6.1 (2013/07/12) 更新カウント数も取得
452                        final int ddlCnt = save.getDDLCount();                  // 5.6.7.0 (2013/07/27) DDL処理件数追加
453
454                        crudCnt[INS] += insCnt ;
455                        crudCnt[DEL] += delCnt ;
456                        crudCnt[UPD] += updCnt ;
457                        crudCnt[DDL] += ddlCnt ;
458
459                        final String tableName = save.getTableName() ;
460
461                        // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理
462                        println( "          File=[" + file + "] TABLE=[" + tableName + "] DEL=["+ delCnt +"] INS=[" + insCnt + "] UPD=[" + updCnt + "] DDL=[" + ddlCnt + "]" );
463
464                        // 7.3.2.0 (2021/03/19) isExecErr でfalseを指定した場合に、エラー内容の文字列を取り出します。
465                        final String errMsg = save.getErrorMessage() ;
466                        if( !errMsg.isEmpty() ) {
467                                println( errMsg );
468                        }
469                }
470                finally {
471                        Closer.ioClose( reader );
472                }
473        }
474
475        /**
476         * 指定のリストのファイルのタイムスタンプをゼロに設定します。
477         * useTimeStamp=true の時に、XMLファイルのロードに成功したファイルの
478         * タイムスタンプをゼロに設定することで、2回目の処理が避けられます。
479         *
480         * @og.rev 6.0.0.0 (2014/04/11) 新規追加
481         */
482        private void setZeroTimeStamp() {
483                if( useTimeStamp ) {
484                        for( final File file : fileList ) {
485                                if( !file.setLastModified( 0L ) ) {
486                                        final String errMsg = "タイムスタンプの書き換えに失敗しました。"
487                                                                        + "file=" + file ;
488                                        System.err.println( errMsg );
489                                }
490                        }
491                }
492        }
493
494        /**
495         * 登録されている ログ(Writer) に、メッセージを書き込みます。
496         * メッセージの最後に改行を挿入します。
497         *
498         * 内部ログ(Writer)が null の場合は、なにもしません。
499         * 内部ログ(Writer) の初期値は、標準出力(System.out) から作成された Writerです。
500         *
501         * @og.rev 6.0.0.0 (2014/04/11) ログ関係を Writer で管理
502         * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。
503         *
504         * @param       msg     書き出すメッセージ
505         */
506        private void println( final String msg ) {
507                if( log != null ) {
508                        try {
509                                log.write( msg );
510                                log.write( CR );
511                        }
512                        catch( final IOException ex ) {
513                                // ファイルが書き込めなかった場合
514                                final String errMsg = msg + " が、書き込めませんでした。"
515                                                                        + ex.getMessage() ;
516                                System.err.println( errMsg );
517                                System.err.println( ThrowUtil.ogStackTrace( ex ) );                             // 6.4.2.0 (2016/01/29)
518                        }
519                }
520        }
521}