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.process;
017
018import org.opengion.fukurou.system.OgRuntimeException ;                         // 6.4.2.0 (2016/01/29)
019import org.opengion.fukurou.system.OgCharacterException ;                       // 6.5.0.1 (2016/10/21)
020import org.opengion.fukurou.system.DateSet;                                                     // 6.4.2.0 (2016/01/29)
021import org.opengion.fukurou.system.Closer;
022import org.opengion.fukurou.util.FileUtil;
023import org.opengion.fukurou.util.CommentLineParser;
024import org.opengion.fukurou.util.FileInfo;                                                      // 6.4.0.2 (2015/12/11)
025import org.opengion.fukurou.security.HybsCryptography ;                         // 5.7.2.1 (2014/01/17)
026import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.3.1.0 (2015/06/28)
027
028import java.io.File;
029import java.io.BufferedReader;
030import java.io.PrintWriter;
031import java.io.IOException;
032import java.nio.charset.CharacterCodingException;                                       // 6.3.1.0 (2015/06/28)
033
034/**
035 * FileLineModel は、LineModel を継承した ファイルリスト専用の
036 * LineModel の実装クラスです。
037 *
038 * FileLineModel オブジェクトには、ファイル属性(Level,File,Length,Modify,LineCnt,Biko,MD5)
039 * が設定されます。
040 * オプションで、FILEPATH,ADDRESS,FILENAME 属性を文字列で準備できます。(6.3.1.0 (2015/06/28))
041 * ADDRESS は、指定ファイルの親フォルダ。FILENAME はファイル名。FILEPATH は、ファイル名を含む
042 * 完全なファイルパスになります。
043 * ※ 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
044 *    この、新しい属性に、値をセットする場合は、useFilePath="true" をセットしてください。
045 * ※ 6.3.1.1 (2015/07/10) Modify のフォーマット(modifyForm)を、指定可能にします。
046 *    これは、Date型のまま、扱いたい所だが、文字列化しています。
047 *    初期値は、"yyyy/MM/dd HH:mm:ss" です。
048 *
049 * LineCnt と、MD5 は、それぞれ、計算するかどうかのフラグを設定する必要があります。
050 *
051 * ※ useLineCnt=false の場合のLength(文字数)は、File#length() メソッドで求めます。
052 *    一方、useLineCnt=true にすると、行単位に、String#length() を加算するため、
053 *    先のLength(文字数)値とは異なりますのでご注意ください。
054 *
055 * omitCmnt=true にすると、コメント部分を削除した行数と文字数を求めます。
056 * これは、/* から */ の間、// から改行までです。
057 * ただし、"(二重引用符)で囲まれた文字列は、コメントとみなしません。
058 *
059 * 8.1.0.4 (2022/01/28)
060 *  さらに、各行の空行はカウントに含めないことにします。
061 *
062 * データの1行分を FileLineModel に割り当てます。
063 * カラム番号は、0 から始まります。カラム名よりカラム番号を求める場合に、
064 * 存在しない場合は、-1 を返します。
065 * カラム番号が -1 の場合は、処理を行いません。
066 *
067 * 注意:このクラスは、同期処理されていません。
068 *
069 * @version  4.0
070 * @author   Kazuhiko Hasegawa
071 * @since    JDK5.0,
072 */
073public class FileLineModel extends LineModel {
074        // 5.7.2.1 (2014/01/17) MD5 項目追加
075        // 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
076        private static final String[] KEYS = { "Level","File","Length","Modify","LineCnt","Biko","MD5","FILEPATH","ADDRESS","FILENAME" };
077
078        private static final int LEVEL          = 0;
079        private static final int FILE           = 1;
080        private static final int LENGTH         = 2;
081        private static final int MODIFY         = 3;
082        private static final int LINECNT        = 4;
083        private static final int BIKO           = 5;
084        private static final int MD5            = 6;            // 5.7.2.1 (2014/01/17)
085        private static final int FILEPATH       = 7;            // 6.3.1.0 (2015/06/28)
086        private static final int ADDRESS        = 8;            // 6.3.1.0 (2015/06/28)
087        private static final int FILENAME       = 9;            // 6.3.1.0 (2015/06/28)
088
089        private final boolean useLineCnt ;
090        private final boolean useMD5 ;                          // 5.7.2.1 (2014/01/17) MD5 項目追加
091        private final boolean omitCmnt ;                        // 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)
092        private final boolean useFilePath ;                     // 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性を使う場合は、true
093        private String encode = "JISAutoDetect";        // 5.7.4.0 (2014/03/07) コメント削除時の文字数計算で利用するファイルのエンコード
094        private String modifyForm = "yyyy/MM/dd HH:mm:ss" ;     // 6.3.1.1 (2015/07/10)
095
096        /**
097         * コンストラクターです。
098         * useLineCnt=false , useMD5=false , omitCmnt=false で初期化されます。
099         *
100         * @og.rev 5.7.2.1 (2014/01/17) MD5対応
101         * @og.rev 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)対応
102         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
103         *
104         */
105        public FileLineModel() {
106                this( false,false,false,false );                // 6.3.1.0 (2015/06/28)
107        }
108
109        /**
110         * ラインカウントの有無を指定した、コンストラクターです。
111         * useMD5=false , omitCmnt=false で初期化されます。
112         *
113         * @og.rev 4.2.2.0 (2008/05/10) 行数カウントの使用有無
114         * @og.rev 5.7.2.1 (2014/01/17) MD5対応
115         * @og.rev 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)対応
116         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
117         *
118         * @param       isLineCnt       行数カウントの使用有無
119         */
120        public FileLineModel( final boolean isLineCnt ) {
121                this( isLineCnt,false,false,false );            // 6.3.1.0 (2015/06/28)
122        }
123
124        /**
125         * ラインカウントの有無と、MD5計算の有無を指定した、コンストラクターです。
126         * omitCmnt=false で初期化されます。
127         *
128         * @og.rev 5.7.2.1 (2014/01/17) 新規追加(MD5対応)
129         * @og.rev 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)対応
130         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加。
131         *
132         * @param       isLineCnt       行数カウントの使用有無
133         * @param       isMD5           ファイルのMD5の使用有無
134         */
135        public FileLineModel( final boolean isLineCnt,final boolean isMD5 ) {
136                this( isLineCnt,isMD5,false,false );            // 6.3.1.0 (2015/06/28)
137        }
138
139        /**
140         * ラインカウントの有無と、MD5計算の有無と、コメント除外の可否を指定した、コンストラクターです。
141         *
142         * @og.rev 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)
143         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
144         *
145         * @param       isLineCnt       行数カウントの使用有無
146         * @param       isMD5           ファイルのMD5の使用有無
147         * @param       isOmit          コメント除外の可否(true:除外する)
148         */
149        public FileLineModel( final boolean isLineCnt,final boolean isMD5,final boolean isOmit ) {
150                this( isLineCnt,isMD5,isOmit,false );   // 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
151        }
152
153        /**
154         * ラインカウントの有無と、MD5計算の有無と、コメント除外の可否と、追加属性可否を指定した、コンストラクターです。
155         *
156         * @og.rev 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)
157         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
158         * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
159         *
160         * @param       isLineCnt       行数カウントの使用有無
161         * @param       isMD5           ファイルのMD5の使用有無
162         * @param       isOmit          コメント除外の可否(true:除外する)
163         * @param       isPath          FILEPATH,ADDRESS,FILENAME 属性の可否(true:使用する)
164         */
165        public FileLineModel( final boolean isLineCnt,final boolean isMD5,final boolean isOmit,final boolean isPath ) {
166                super();
167                // 4.3.4.4 (2009/01/01)
168                useLineCnt      = isLineCnt;
169                useMD5          = isMD5;                                // 5.7.2.1 (2014/01/17)
170                omitCmnt        = isOmit;                               // 5.7.4.0 (2014/03/07)
171                useFilePath     = isPath;                               // 5.7.4.0 (2014/03/07)
172                init( KEYS );
173        }
174
175        /**
176         * LineModel を元に、FileLineModel を構築します。
177         * これは、一旦ファイル等にセーブされた FileLineModel 形式を
178         * 元に戻す簡易コンストラクタです。
179         *
180         * @og.rev 4.2.3.0 (2008/05/26) 新規追加
181         * @og.rev 5.7.2.1 (2014/01/17) MD5の設定処理追加
182         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
183         * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
184         *
185         * @param       model   元のLineModel
186         */
187        public FileLineModel( final LineModel model ) {
188                super();
189                // 4.3.4.4 (2009/01/01)
190                init( model.getNames() );
191
192                final Object[] obj = model.getValues();
193
194                setValue( LEVEL   ,Integer.valueOf( (String)obj[LEVEL] ) );
195                setValue( FILE    ,new File((String)obj[FILE]) );
196                setValue( LENGTH  ,Long.valueOf( (String)obj[LENGTH] ) );
197                setValue( MODIFY  ,(String)obj[MODIFY] );
198
199                final String cnt = (String)obj[LINECNT] ;
200                useLineCnt = cnt != null && cnt.length() > 0 && ! "null".equalsIgnoreCase( cnt ) ;
201                if( useLineCnt ) { setValue( LINECNT ,cnt ); }
202
203                setValue( BIKO  ,(String)obj[BIKO] );
204
205                // 5.7.2.1 (2014/01/17)
206                final String md5Data = (String)obj[MD5] ;
207                useMD5 = md5Data != null && md5Data.length() > 0 && ! "null".equalsIgnoreCase( md5Data ) ;
208                if( useMD5 ) { setValue( MD5 ,md5Data ); }
209
210                omitCmnt   = false;                     // 5.7.4.0 (2014/03/07) 既存の LineModel から取得できないので、強制設定します。
211
212                // 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
213                // 念のため、配列数をチェックしながら処理します。
214                if( obj.length > FILEPATH ) {
215                        final String path = (String)obj[FILEPATH] ;
216                        useFilePath = path != null && path.length() > 0 && ! "null".equalsIgnoreCase( path ) ;
217                        if( useFilePath ) {
218                                setValue( FILEPATH ,path );
219                                if( obj.length > ADDRESS  ) { setValue( ADDRESS  ,(String)obj[ADDRESS]  ); }
220                                if( obj.length > FILENAME ) { setValue( FILENAME ,(String)obj[FILENAME] ); }
221                        }
222                }
223                else {
224                        useFilePath = false;
225                }
226        }
227
228        /**
229         * File属性値をセットします。
230         * LEVEL,FILE,LENGTH,MODIFY,LINECNT,MD5 の各属性を設定します。
231         *
232         * @og.rev 4.2.2.0 (2008/05/10) 行数カウントの使用有無
233         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
234         * @og.rev 5.7.2.1 (2014/01/17) MD5計算処理の追加
235         * @og.rev 5.7.4.0 (2014/03/07) コメント除外の可否(true:除外する)対応
236         * @og.rev 5.7.7.1 (2014/06/13) omitCmnt=true(コメント除外する) and useMD5=true(MD5計算する) 場合の処理
237         * @og.rev 6.2.1.0 (2015/03/13) ファイルの削除に失敗するため、削除しない。
238         * @og.rev 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
239         *
240         * @param       level   ファイルのディレクトリ階層
241         * @param       file    ファイルオブジェクト
242         */
243        public void setFileVals( final int level, final File file ) {
244                setValue( LEVEL  ,Integer.valueOf( level ) );
245                setValue( FILE   ,file );
246                setValue( MODIFY ,DateSet.getDate( file.lastModified(),modifyForm ) );                                  // 6.3.1.1 (2015/07/10)
247
248                // 5.7.7.1 (2014/06/13) omitCmnt=true(コメント除外する) and useMD5=true(MD5計算する) 場合の処理
249                // 別にコメント除去されたファイルを作成して、それの MD5 を求める。
250                File ocFile = null;
251                if( omitCmnt && useMD5 ) {
252                        try {
253                                ocFile = File.createTempFile( "temp",".tmp" );
254                                ocFile.deleteOnExit();                                                          // 一応、このメソッド内で削除しますが、念のため。
255                        }
256                        catch( final IOException ex ) {
257                                final String errMsg = "コメント除外のMD5計算用 temp ファイルの作成に失敗しました。" + ex.getMessage() ;
258                                throw new OgRuntimeException( errMsg,ex );
259                        }
260                }
261
262                if( useLineCnt || omitCmnt ) {
263                        final long[] cntVals = getLineCnt( file,ocFile );                       // 5.7.7.1 (2014/06/13) 出力ファイルを渡します。
264                        setValue( LINECNT ,String.valueOf( cntVals[0] ) );
265                        setValue( LENGTH  ,Long.valueOf(   cntVals[1] ) );
266                }
267                else {
268                        setValue( LENGTH  ,Long.valueOf( file.length() ) );
269                }
270
271                // 5.7.2.1 (2014/01/17) MD5計算がtrue で、かつ、ファイルの場合、MD5 計算を行います。
272                if( useMD5 && file.isFile() ) {
273                        // 5.7.7.1 (2014/06/13) omitCmnt を考慮したMD5計算
274                        if( ocFile == null ) {
275                                setValue( MD5 ,HybsCryptography.getMD5( file ) );
276                        }
277                        else {
278                                setValue( MD5 ,HybsCryptography.getMD5( ocFile ) );
279                                // 6.0.2.4 (2014/10/17) RV  java.io.File.delete() の例外的戻り値を無視しています。
280                                // 6.2.1.0 (2015/03/13) ファイルの削除に失敗するため、削除しない。
281                        }
282                }
283
284                // 6.3.1.0 (2015/06/28) FILEPATH,ADDRESS,FILENAME 属性追加
285                if( useFilePath ) {
286                        // FILEPATH は、正規のパス名文字列 を求めるが、エラー時は、絶対パス名文字列 にする。
287                        try {
288                                setValue( FILEPATH      , file.getCanonicalPath() );    // 正規のパス名文字列
289                        }
290                        catch( final IOException ex ) {
291                                setValue( FILEPATH      , file.getAbsolutePath() );             // 絶対パス名文字列
292                        }
293
294                        // ADDRESS は、親の名前なので、直フォルダ名になる。
295                        final File parent = file.getParentFile();
296                        if( parent != null ) {
297                                setValue( ADDRESS       ,parent.getName() );
298                        }
299
300                        setValue( FILENAME      ,file.getName() );
301                }
302        }
303
304        /**
305         * コメント削除時の文字数計算で利用するファイルのエンコードをセットします。
306         * 初期値:JISAutoDetect
307         *
308         * @og.rev 5.7.4.0 (2014/03/07) 新規追加
309         *
310         * @param       encode  コメント削除時の文字数計算で利用するファイルのエンコード
311         */
312        public void setEncode( final String encode ) {
313                this.encode = encode;
314        }
315
316        /**
317         * File属性値をセットします。
318         *
319         * @param       file    ファイルオブジェクト
320         */
321        public void setFile( final File file ) {
322                setValue( FILE,file );
323        }
324
325        /**
326         * ファイルを取得します。
327         *
328         * @return      ファイル
329         */
330        public File getFile() {
331                return (File)getValue( FILE );
332        }
333
334        /**
335         * 備考情報属性値をセットします。
336         *
337         * @og.rev 4.2.2.0 (2008/05/10) 行数カウントの使用有無
338         *
339         * @param       biko    備考情報
340         */
341        public void setBiko( final String biko ) {
342                setValue( BIKO,biko );
343        }
344
345        /**
346         * レベル File属性値を取得します。
347         *
348         * @return      ファイルのディレクトリ階層
349         */
350        public int getLevel() {
351                return ((Integer)getValue( LEVEL )).intValue();
352        }
353
354        /**
355         * ファイルサイズ File属性値を取得します。
356         *
357         * @return      ファイルサイズ
358         */
359        public long getLength() {
360                return ((Long)getValue( LENGTH )).longValue();
361        }
362
363        /**
364         * 更新日時(Modify) のフォーマットを、指定します。
365         *
366         * ここで指定しない場合は、"yyyy/MM/dd HH:mm:ss" になります。
367         * Date型で変換できないようなフォームを指定した場合は、実行時に
368         * エラーになりますので、ご注意ください。
369         *
370         * @og.rev 6.3.1.1 (2015/07/10) Modify のフォーマットを、指定可能にします。
371         *
372         * @param       form 更新日時のフォーマット
373         * @see         java.text.SimpleDateFormat
374         */
375        public void setModifyForm( final String form ) {
376                if( form != null && !form.isEmpty() ) {
377                        modifyForm = form;
378                }
379        }
380
381        /**
382         * 更新日時 File属性値を取得します。
383         *
384         * @return      更新日時(yyyy/MM/dd HH:mm:ss)
385         */
386        public String getModify() {
387                return (String)getValue( MODIFY );
388        }
389
390        /**
391         * MD5 File属性値を取得します。
392         * ただし、useMD5 が true でないと値は返しません。
393         *
394         * @og.rev 5.7.2.1 (2014/01/17) 新規追加(MD5対応)
395         *
396         * @return      MD5の値
397         */
398        public String getMD5() {
399                return (String)getValue( MD5 );
400        }
401
402        /**
403         * 行数と文字数を取得します。
404         * 行数カウントとファイルの文字数カウント(バイト数ではありません)を行います。
405         * ※ useLineCnt=false の場合のLength(文字数)は、File#length() メソッドで求めます。
406         *    一方、useLineCnt=true にすると、行単位に、String#length() を加算するため、
407         *    先のLength(文字数)値とは異なりますのでご注意ください。
408         *
409         * 結果は、long型の配列で返します。[0]が行数で、[1]が文字数です。
410         * omitCmnt 属性を使用した場合は、コメント部分を削除した行数と文字数を求めます。
411         * これは、/* から */ の間、// から改行までです。
412         * ただし、"(二重引用符)で囲まれた文字列は、コメントとみなしません。
413         *
414         * @og.rev 5.7.4.0 (2014/03/07) 行数カウントとファイルの文字数カウントを行う。
415         * @og.rev 5.7.7.1 (2014/06/13) omitCmnt=true(コメント除外する) and useMD5=true(MD5計算する) 場合の処理
416         * @og.rev 6.2.1.0 (2015/03/13) ディレクトリ以外からファイルのみに対象を変更。
417         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
418         * @og.rev 6.4.0.2 (2015/12/11) CommentLineParser 改造。
419         * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。
420         * @og.rev 8.1.0.4 (2022/01/28) 各行の空行はカウントに含めないことにする。
421         *
422         * @param       file    行数を数えるファイルオブジェクト
423         * @param       ocFile  omitCmnt=trueの場合に、MD5計算する時の、仮出力ファイル(nullの場合は、無視)
424         *
425         * @return  long型の配列([0]が行数で、[1]が文字数)
426         * @og.rtnNotNull
427         */
428        private long[] getLineCnt( final File file,final File ocFile ) {
429                long lineCnt = 0L;              // 行数
430                long charCnt = 0L;              // 文字数
431
432                final BufferedReader reader = FileUtil.getBufferedReader( file,encode );
433
434                // 5.7.7.1 (2014/06/13) omitCmnt=true(コメント除外する) and useMD5=true(MD5計算する) 場合の処理
435                PrintWriter writer = null;
436                if( ocFile != null ) { writer = FileUtil.getPrintWriter( ocFile ,encode ); }
437
438                // 6.4.0.2 (2015/12/11) CommentLineParser 改造
439                final CommentLineParser clp = omitCmnt ? new CommentLineParser( FileInfo.getSUFIX( file ) ) : null;
440                try {
441                        // 6.2.1.0 (2015/03/13) ディレクトリ以外からファイルのみに対象を変更。
442                        if( file.isFile() ) {
443                                String line ;
444                                while((line = reader.readLine()) != null) {
445                                        if( omitCmnt ) {
446                                                line = clp.line( line );
447                                                // 8.1.0.4 (2022/01/28) 各行の空行はカウントに含めないことにする。
448//                                              if( line == null ) { continue; }        // 戻り値が null の場合は、行として不成立
449                                                if( line == null || line.isEmpty() ) { continue; }      // 戻り値が nullか 空行の場合は、含めない
450                                                if( writer != null ) { writer.println( line ); }        // 5.7.7.1 (2014/06/13)
451                                        }
452
453                                        lineCnt++;
454                                        charCnt += line.length();
455                                }
456                        }
457                }
458                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
459                catch( final CharacterCodingException ex ) {
460                        final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
461                                                                +       "  ファイルのエンコードが指定のエンコードと異なります。" + CR
462                                                                +       " [" + file.getPath() + "] , Encode=[" + encode + "]" ;
463                        throw new OgCharacterException( errMsg,ex );    // 6.5.0.1 (2016/10/21)
464                }
465                catch( final IOException ex ) {
466                        final String errMsg = "ファイルカウント中に例外が発生しました。" + CR
467                                                                +       " [" + file.getPath() + "] , Encode=[" + encode + "]" ;
468                        throw new OgRuntimeException( errMsg,ex );
469                }
470                finally {
471                        Closer.ioClose( reader ) ;
472                        Closer.ioClose( writer ) ;              // 5.7.7.1 (2014/06/13) ioClose は、引数が null なら無視します。
473                }
474
475                return new long[] { lineCnt,charCnt };
476        }
477}