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.BufferedReader; 019import java.io.PrintWriter; 020import java.io.File; 021import java.io.IOException; 022import java.util.List; // 6.3.1.1 (2015/07/10) 023import java.util.Arrays; // 6.3.1.1 (2015/07/10) 024import java.nio.charset.CharacterCodingException; // 6.3.1.0 (2015/06/28) 025import java.util.Locale; // 6.4.0.2 (2015/12/11) 026 027import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 028import org.opengion.fukurou.system.OgCharacterException ; // 6.5.0.1 (2016/10/21) 029import org.opengion.fukurou.system.Closer; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 030 031import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 032 033/** 034 * CommentLineParser.java は、ファイルを行単位に処理して、コメントを除去するクラスです。 035 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 036 * 037 * ブロックコメントの状態や、コメント除外の状態を管理しています。 038 * オブジェクト作成後、line( String ) メソッドに、ファイルから読み取った1行分の文字列を渡せば、 039 * コメントが除外された形で返されます。 040 * 041 * コメントが除去された行は、rTrim しますが、行の削除は行いません。 042 * これは、Grep等で、文字列を発見した場合に、ファイルの行番号がずれるのを防ぐためです。 043 * 逆に、Diff等で、複数のコメント行は、1行の空行にしたい場合や、空行自体をなくして 044 * 比較したい場合は、戻ってきた行が、空行かどうかで判定して呼び出し元で処理してください。 045 * 046 * 引数の行文字列が、null の場合は、null を返します。(読み取り行がなくなった場合) 047 * 048 * 文字列くくり指定 は、例えば、ラインコメント(//) が、文字列指定("//") や、"http://xxxx" などの 049 * プログラム本文で使用する場合のエスケープ処理になります。 050 * つまり、文字列くくり指定についても、IN-OUT があり、その範囲内は、コメント判定外になります。 051 * 052 * ※ 6.3.1.1 (2015/07/10) 053 * コメントセットを、add で、追加していく機能を用意します。 054 * 現状では、Java,ORACLE,HTML のコメントを意識せず処理したいので、すべてを 055 * 処理することを前提に考えておきます。 056 * 057 * ※ 6.4.0.2 (2015/12/11) 058 * 行コメントが先頭行のみだったのを修正します。 059 * og:comment タグを除外できるようにします。そのため、 060 * 終了タグに、OR 条件を加味する必要があるため、CommentSet クラスを見直します。 061 * 可変長配列を使うため、文字列くくり指定を前に持ってきます。 062 * 063 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 064 * @og.rev 6.3.1.1 (2015/07/10) 内部構造大幅変更 065 * @og.group ユーティリティ 066 * 067 * @version 6.0 068 * @author Kazuhiko Hasegawa 069 * @since JDK7.0, 070 */ 071public class CommentLineParser { 072 private final List<CommentSet> cmntSetList ; 073 074 /** 075 * 処理するコメントの種類を拡張子で指定するコンストラクターです。 076 * これは、ORACLE系のラインコメント(--)が、Java系の演算子(i--;など)と 077 * 判定されるため、ひとまとめに処理できません。 078 * ここで指定する拡張子に応じて、CommentSet を割り当てます。 079 * 080 * ・sql , tri , spc は、ORACLE系を使用。 081 * ・xml , htm , html , は、Java,C,JavaScript系 + HTML,XML系を使用。 082 * ・jsp は、 Java,C,JavaScript系 + HTML,XML系 + ORACLE系 + openGion JSP系 を使用。 083 * ・それ以外は、Java,C,JavaScript系を使用。 084 * css は、それ以外になりますが、//(ラインコメント)はありませんが、コメントアウトされます。 085 * 086 * @og.rev 6.4.0.2 (2015/12/11) sufix によるコメント処理方法の変更。 087 * @og.rev 6.4.1.0 (2016/01/09) comment="***"のコメント処理方法の追加。 088 * @og.rev 6.4.1.1 (2016/01/16) sufixを小文字化。 089 * @og.rev 6.8.1.7 (2017/10/13) COMMENT ON で始まる行(大文字限定)は、コメントとして扱う 090 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 091 * 092 * @param sufix 拡張子 093 */ 094 public CommentLineParser( final String sufix ) { 095 final String type = sufix == null ? "null" : sufix.toLowerCase( Locale.JAPAN ); 096 097 if( "sql , tri , spc".contains( type ) ) { 098 cmntSetList = Arrays.asList( 099 new CommentSet( "--" , "/*" , "*/" ) // ORACLE系 100 , new CommentSet( "COMMENT ON" , null , (String)null ) // 大文字のみ除外する。 101 ); 102 } 103 else if( "xml , htm , html".contains( type ) ) { 104 cmntSetList = Arrays.asList( 105 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 106 , new CommentSet( null , "<!--" , "-->" ) // HTML,XML系 107 ); 108 } 109 else if( "jsp".contains( type ) ) { 110 cmntSetList = Arrays.asList( 111 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 112 , new CommentSet( null , "<!--" , "-->" ) // HTML,XML系 113 , new CommentSet( "--" , "/*" , "*/" ) // ORACLE系 114 , new CommentSet( null , "<og:comment" , "/>" , "</og:comment>" ) // openGion JSP系 XML なので、このまま。 115 , new CommentSet( null , "comment=\"" , "\"" ) // openGion comment="***" 6.4.1.0 (2016/01/09) 116 ); 117 } 118 else { 119 cmntSetList = Arrays.asList( 120 // 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 121// new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 122 new CommentSet( "//" , "/*" , "*/" ).useEsc() // Java,C,JavaScript系 123 ); 124 } 125 } 126 127 /** 128 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 129 * 行として存在しない場合は、null を返します。 130 * 131 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 132 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 133 * 134 * @param inLine 1行の文字列 135 * @return コメント削除後の1行の文字列 136 */ 137 public String line( final String inLine ) { 138 139 String outLine = inLine ; 140 for( final CommentSet cmntSet : cmntSetList ) { 141 outLine = line( outLine,cmntSet ); 142 } 143 return outLine ; 144 } 145 146 /** 147 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 148 * 行として存在しない場合は、null を返します。 149 * 150 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 151 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 152 * 153 * @param inLine 1行の文字列 154 * @param cmntSet コメントを管理するオブジェクト 155 * @return コメント削除後の1行の文字列 156 */ 157 private String line( final String inLine , final CommentSet cmntSet ) { 158 if( inLine == null ) { return null; } 159 160 final int size = inLine.length(); 161 162 final StringBuilder buf = new StringBuilder( size ); 163 164 for( int st=0; st<size; st++ ) { 165 final char ch = inLine.charAt(st); 166 167 if( !cmntSet.checkEsc( ch ) ) { // エスケープ文字でないなら、判定処理を進める 168 // ブロックコメント継続中か、先頭がブロックコメント 169 if( cmntSet.isBlockIn( inLine,st ) ) { 170 final int ed = cmntSet.blockOut( inLine,st ) ; // 終了を見つける 171 if( ed >= 0 ) { // 終了があれば、そこまで進める。 172 st = ed; 173 continue; // ブロックコメント脱出。再読み込み 174 } 175 break; // ブロックコメント継続中。次の行へ 176 } 177 178 // ラインコメント発見。次の行へ 179 if( cmntSet.isLineCmnt( inLine,st ) ) { break; } 180 } 181 182 // 通常の文字なので、追加する。 183 buf.append( ch ); 184 } 185 186 // rTrim() と同等の処理 187 int len = buf.length(); 188 while( 0 < len && buf.charAt(len-1) <= ' ' ) { 189 len--; 190 } 191 buf.setLength( len ); 192 193 return buf.toString() ; 194 } 195 196 /** 197 * コメントセットを管理する内部クラスです。 198 * 199 * コメントの種類を指定します。 200 * 201 * Java,C,JavaScript系、// , /* , */ 202 * HTML,XML系、 // , <!-- , --> 203 * ORACLE系 -- , /* , */ 204 * openGion JSP系 null , <og:comment , /> ,</og:comment> 205 * 206 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 207 * @og.rev 6.4.0.2 (2015/12/11) CommentSet の見直し。 208 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 209 */ 210 private static final class CommentSet { 211 private final String LINE_CMNT ; // ラインコメント 212 private final String BLOCK_CMNT1 ; // ブロックコメントの開始 213 private final String[] BLOCK_CMNT2 ; // ブロックコメントの終了 214 215 216 private static final char ESC_CHAR1 = '"'; ; // コメント判定除外("") 217 private static final char ESC_CHAR2 = '\'' ; // コメント判定除外('') 218 private static final char CHAR_ESC = '\\' ; // エスケープ文字('\\') 219 220 private boolean useCharEsc ; // 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 221 222 private boolean escIn1 ; // コメント判定除外中かどうか("") 223 private boolean escIn2 ; // コメント判定除外中かどうか('') 224 private boolean chEsc ; // コメント除外のエスケープ処理中かどうか 225 226 private boolean isBlkIn ; // ブロックコメントが継続しているかどうか 6.4.1.1 (2016/01/16) refactoring isBlockIn → isBlkIn 227 228 /** 229 * コメントの種類を指定するコンストラクタです。 230 * 231 * Java,C,JavaScript系、// , /* , */ 232 * HTML,XML系、 // , <!-- , --> 233 * ORACLE系 -- , /* , */ 234 * openGion JSP系 null , <og:comment , /> ,</og:comment> 235 * 236 * @param lineCmnt ラインコメント 237 * @param blockCmnt1 ブロックコメントの開始 238 * @param blockCmnt2 ブロックコメントの終了(可変長配列) 239 */ 240 CommentSet( final String lineCmnt,final String blockCmnt1,final String... blockCmnt2 ) { 241 LINE_CMNT = lineCmnt ; // ラインコメント 242 BLOCK_CMNT1 = blockCmnt1 ; // ブロックコメントの開始 243 BLOCK_CMNT2 = blockCmnt2 ; // ブロックコメントの終了 244 } 245 246 /** 247 * エスケープ文字を使用する設定を行います。 248 * 249 * 本来は、コンストラクタで、指示すべきですが、String... 定義を使っているため、 250 * 最後に、引数を追加できません。かといって、途中や先頭に入れるのも、嫌です。 251 * とりあえず、メソッドを作成して、自分自身を返せば、List にセットする処理は、 252 * そのまま使用できるため、このような形にしています。 253 * 254 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 255 * 256 * @return 自分自身 257 */ 258 /* default */ CommentSet useEsc() { 259 useCharEsc = true; 260 return this; 261 } 262 263 /** 264 * ブロック外で、エスケープ文字の場合は、内外反転します。 265 * 266 * @og.rev 6.4.1.1 (2016/01/16) Avoid if (x != y) ..; else ..; refactoring 267 * @og.rev 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 268 * 269 * @param ch チェックするコメント除外char 270 * @return エスケープ文字中の場合は、true 271 */ 272 /* default */ boolean checkEsc( final char ch ) { 273 if( isBlkIn || chEsc ) { 274 chEsc = false; 275 } 276 else { 277// chEsc = CHAR_ESC == ch; 278 chEsc = useCharEsc && CHAR_ESC == ch; // 6.9.8.0 (2018/05/28) エスケープ文字の使用可否 279 if( !escIn2 && ESC_CHAR1 == ch ) { escIn1 = !escIn1 ; } // escIn2 でない場合に、escIn1 の判定を行う。 280 if( !escIn1 && ESC_CHAR2 == ch ) { escIn2 = !escIn2 ; } // 同様にその逆 281 } 282 return escIn1 || escIn2; // どちらかで、エスケープ中 283 } 284 285 /** 286 * ブロックコメント中かどうかの判定を行います。 287 * 288 * @og.rev 6.4.5.1 (2016/04/28) ブロックコメントを指定しないケースに対応。 289 * 290 * @param line チェックする行データ 291 * @param st チェック開始文字数 292 * @return ブロックコメント中の場合は、true を返します。 293 */ 294 /* default */ boolean isBlockIn( final String line , final int st ) { 295 if( !isBlkIn && BLOCK_CMNT1 != null ) { isBlkIn = line.startsWith( BLOCK_CMNT1,st ); } 296 297 return isBlkIn ; 298 } 299 300 /** 301 * ラインコメントかどうかの判定を行います。 302 * 303 * @param line チェックする行データ 304 * @param st チェック開始文字数 305 * @return ラインコメントの場合は、true を返します。 306 */ 307 /* default */ boolean isLineCmnt( final String line , final int st ) { 308 return LINE_CMNT != null && line.startsWith( LINE_CMNT,st ) ; 309 } 310 311 /** 312 * ブロックコメントの終了を見つけます。 313 * 終了は、複数指定でき、それらのもっとも最初に現れる方が有効です。 314 * 例:XMLタグで、BODYが、あるなしで、終了条件が異なるケースなど。 315 * この処理では、ブロックコメントが継続中かどうかの判定は行っていないため、 316 * 外部(呼び出し元)で、判定処理してください。 317 * 318 * ※ このメソッドは、ブロックコメント中にしか呼ばれないため、 319 * ブロックコメントを指定しないケース(BLOCK_CMNT1==null)では呼ばれません。 320 * 321 * @param line チェックする行データ 322 * @param st チェック開始文字数 323 * @return ブロックコメントの終了の位置。なけらば、-1 324 */ 325 /* default */ int blockOut( final String line , final int st ) { 326 int ed = line.length(); 327 for( final String key : BLOCK_CMNT2 ) { 328 final int tmp = line.indexOf( key,st + BLOCK_CMNT1.length() ); // 6.4.1.0 (2016/01/09) 開始位置の計算ミス 329 if( tmp >= 0 && tmp < ed ) { // 存在して、かつ小さい方を選ぶ。 330 ed = tmp + key.length(); // アドレスは、終了コメント記号の後ろまで。 331 isBlkIn = false; // ブロックコメントから抜ける。 332 } 333 } 334 335 return isBlkIn ? -1 : ed ; // 見つからない場合は、-1 を返す。 336 } 337 } 338 339 /** 340 * このクラスの動作確認用の、main メソッドです。 341 * 342 * Usage: java org.opengion.fukurou.util.CommentLineParser inFile outFile [encode] [-rowTrim] 343 * 344 * -rowTrim を指定すると、空行も削除します。これは、コメントの増減は、ソースレベルで比較する場合に 345 * 関係ないためです。デフォルトは、空行は削除しません。grep 等で検索した場合、オリジナルの 346 * ソースの行数と一致させるためです。 347 * 348 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 349 * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。 350 * 351 * @param args コマンド引数配列 352 */ 353 public static void main( final String[] args ) { 354 if( args.length < 2 ) { 355 System.out.println( "Usage: java org.opengion.fukurou.util.CommentLineParser inFile outFile [encode] [-rowTrim]" ); 356 } 357 358 final File inFile = new File( args[0] ); 359 final File outFile = new File( args[1] ); 360 String encode = "UTF-8" ; 361 boolean rowTrim = false; 362 for( int i=2; i<args.length; i++ ) { 363 if( "-rowTrim".equalsIgnoreCase( args[i] ) ) { rowTrim = true; } 364 else { encode = args[i]; } 365 } 366 367 final BufferedReader reader = FileUtil.getBufferedReader( inFile ,encode ); 368 final PrintWriter writer = FileUtil.getPrintWriter( outFile ,encode ); 369 370 final CommentLineParser clp = new CommentLineParser( FileInfo.getSUFIX( inFile ) ); 371 372 try { 373 String line1; 374 while((line1 = reader.readLine()) != null) { 375 line1 = clp.line( line1 ); 376 if( !rowTrim || !line1.isEmpty() ) { 377 writer.println( line1 ); 378 } 379 } 380 } 381 // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 382 catch( final CharacterCodingException ex ) { 383 final String errMsg = "文字のエンコード・エラーが発生しました。" + CR 384 + " ファイルのエンコードが指定のエンコードと異なります。" + CR 385 + " [" + inFile.getPath() + "] , Encode=[" + encode + "]" ; 386 throw new OgCharacterException( errMsg,ex ); // 6.5.0.1 (2016/10/21) 387 } 388 catch( final IOException ex ) { 389 final String errMsg = "ファイルコピー中に例外が発生しました。\n" 390 + " inFile=[" + inFile + "] , outFile=[" + outFile + "]\n" ; 391 throw new OgRuntimeException( errMsg,ex ); 392 } 393 finally { 394 Closer.ioClose( reader ) ; 395 Closer.ioClose( writer ) ; 396 } 397 } 398}