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 * 091 * @param sufix 拡張子 092 */ 093 public CommentLineParser( final String sufix ) { 094 final String type = sufix == null ? "null" : sufix.toLowerCase( Locale.JAPAN ); 095 096 if( "sql , tri , spc".contains( type ) ) { 097 cmntSetList = Arrays.asList( 098 new CommentSet( "--" , "/*" , "*/" ) // ORACLE系 099 , new CommentSet( "COMMENT ON" , null , (String)null ) // 大文字のみ除外する。 100 ); 101 } 102 else if( "xml , htm , html".contains( type ) ) { 103 cmntSetList = Arrays.asList( 104 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 105 , new CommentSet( null , "<!--" , "-->" ) // HTML,XML系 106 ); 107 } 108 else if( "jsp".contains( type ) ) { 109 cmntSetList = Arrays.asList( 110 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 111 , new CommentSet( null , "<!--" , "-->" ) // HTML,XML系 112 , new CommentSet( "--" , "/*" , "*/" ) // ORACLE系 113 , new CommentSet( null , "<og:comment" , "/>" , "</og:comment>" ) // openGion JSP系 114 , new CommentSet( null , "comment=\"" , "\"" ) // openGion comment="***" 6.4.1.0 (2016/01/09) 115 ); 116 } 117 else { 118 cmntSetList = Arrays.asList( 119 new CommentSet( "//" , "/*" , "*/" ) // Java,C,JavaScript系 120 ); 121 } 122 } 123 124 /** 125 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 126 * 行として存在しない場合は、null を返します。 127 * 128 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 129 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 130 * 131 * @param inLine 1行の文字列 132 * @return コメント削除後の1行の文字列 133 */ 134 public String line( final String inLine ) { 135 136 String outLine = inLine ; 137 for( final CommentSet cmntSet : cmntSetList ) { 138 outLine = line( outLine,cmntSet ); 139 } 140 return outLine ; 141 } 142 143 /** 144 * 1行分の文字列を読み取って、コメント部分を削除した文字列を返します。 145 * 行として存在しない場合は、null を返します。 146 * 147 * @og.rev 5.7.4.0 (2014/03/07) 新規追加 148 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 149 * 150 * @param inLine 1行の文字列 151 * @param cmntSet コメントを管理するオブジェクト 152 * @return コメント削除後の1行の文字列 153 */ 154 private String line( final String inLine , final CommentSet cmntSet ) { 155 if( inLine == null ) { return null; } 156 157 final int size = inLine.length(); 158 159 final StringBuilder buf = new StringBuilder( size ); 160 161 for( int st=0; st<size; st++ ) { 162 final char ch = inLine.charAt(st); 163 164 if( !cmntSet.checkEsc( ch ) ) { // エスケープ文字でないなら、判定処理を進める 165 // ブロックコメント継続中か、先頭がブロックコメント 166 if( cmntSet.isBlockIn( inLine,st ) ) { 167 final int ed = cmntSet.blockOut( inLine,st ) ; // 終了を見つける 168 if( ed >= 0 ) { // 終了があれば、そこまで進める。 169 st = ed; 170 continue; // ブロックコメント脱出。再読み込み 171 } 172 break; // ブロックコメント継続中。次の行へ 173 } 174 175 // ラインコメント発見。次の行へ 176 if( cmntSet.isLineCmnt( inLine,st ) ) { break; } 177 } 178 179 // 通常の文字なので、追加する。 180 buf.append( ch ); 181 } 182 183 // rTrim() と同等の処理 184 int len = buf.length(); 185 while( 0 < len && buf.charAt(len-1) <= ' ' ) { 186 len--; 187 } 188 buf.setLength( len ); 189 190 return buf.toString() ; 191 } 192 193 /** 194 * コメントセットを管理する内部クラスです。 195 * 196 * コメントの種類を指定します。 197 * 198 * Java,C,JavaScript系、// , /* , */ 199 * HTML,XML系、 // , <!-- , --> 200 * ORACLE系 -- , /* , */ 201 * openGion JSP系 null , <og:comment , /> ,</og:comment> 202 * 203 * @og.rev 6.3.1.1 (2015/07/10) CommentSet で管理します。 204 * @og.rev 6.4.0.2 (2015/12/11) CommentSet の見直し。 205 */ 206 private static final class CommentSet { 207 private final String LINE_CMNT ; // ラインコメント 208 private final String BLOCK_CMNT1 ; // ブロックコメントの開始 209 private final String[] BLOCK_CMNT2 ; // ブロックコメントの終了 210 211 private static final char ESC_CHAR1 = '"'; ; // コメント判定除外("") 212 private static final char ESC_CHAR2 = '\'' ; // コメント判定除外('') 213 private static final char CHAR_ESC = '\\' ; // エスケープ文字('\\') 214 215 private boolean escIn1 ; // コメント判定除外中かどうか("") 216 private boolean escIn2 ; // コメント判定除外中かどうか('') 217 private boolean chEsc ; // コメント除外のエスケープ処理中かどうか 218 219 private boolean isBlkIn ; // ブロックコメントが継続しているかどうか 6.4.1.1 (2016/01/16) refactoring isBlockIn → isBlkIn 220 221 /** 222 * コメントの種類を指定するコンストラクタです。 223 * 224 * Java,C,JavaScript系、// , /* , */ 225 * HTML,XML系、 // , <!-- , --> 226 * ORACLE系 -- , /* , */ 227 * openGion JSP系 null , <og:comment , /> ,</og:comment> 228 * 229 * @param lineCmnt ラインコメント 230 * @param blockCmnt1 ブロックコメントの開始 231 * @param blockCmnt2 ブロックコメントの終了(可変長配列) 232 */ 233 CommentSet( final String lineCmnt,final String blockCmnt1,final String... blockCmnt2 ) { 234 LINE_CMNT = lineCmnt ; // ラインコメント 235 BLOCK_CMNT1 = blockCmnt1 ; // ブロックコメントの開始 236 BLOCK_CMNT2 = blockCmnt2 ; // ブロックコメントの終了 237 } 238 239 /** 240 * ブロック外で、エスケープ文字の場合は、内外反転します。 241 * 242 * @og.rev 6.4.1.1 (2016/01/16) Avoid if (x != y) ..; else ..; refactoring 243 * 244 * @param ch チェックするコメント除外char 245 * @return エスケープ文字中の場合は、true 246 */ 247 /* default */ boolean checkEsc( final char ch ) { 248 if( isBlkIn || chEsc ) { 249 chEsc = false; 250 } 251 else { 252 chEsc = CHAR_ESC == ch; 253 if( !escIn2 && ESC_CHAR1 == ch ) { escIn1 = !escIn1 ; } // escIn2 でない場合に、escIn1 の判定を行う。 254 if( !escIn1 && ESC_CHAR2 == ch ) { escIn2 = !escIn2 ; } // 同様にその逆 255 } 256 return escIn1 || escIn2; // どちらかで、エスケープ中 257 } 258 259 /** 260 * ブロックコメント中かどうかの判定を行います。 261 * 262 * @og.rev 6.4.5.1 (2016/04/28) ブロックコメントを指定しないケースに対応。 263 * 264 * @param line チェックする行データ 265 * @param st チェック開始文字数 266 * @return ブロックコメント中の場合は、true を返します。 267 */ 268 /* default */ boolean isBlockIn( final String line , final int st ) { 269 if( !isBlkIn && BLOCK_CMNT1 != null ) { isBlkIn = line.startsWith( BLOCK_CMNT1,st ); } 270 271 return isBlkIn ; 272 } 273 274 /** 275 * ラインコメントかどうかの判定を行います。 276 * 277 * @param line チェックする行データ 278 * @param st チェック開始文字数 279 * @return ラインコメントの場合は、true を返します。 280 */ 281 /* default */ boolean isLineCmnt( final String line , final int st ) { 282 return LINE_CMNT != null && line.startsWith( LINE_CMNT,st ) ; 283 } 284 285 /** 286 * ブロックコメントの終了を見つけます。 287 * 終了は、複数指定でき、それらのもっとも最初に現れる方が有効です。 288 * 例:XMLタグで、BODYが、あるなしで、終了条件が異なるケースなど。 289 * この処理では、ブロックコメントが継続中かどうかの判定は行っていないため、 290 * 外部(呼び出し元)で、判定処理してください。 291 * 292 * ※ このメソッドは、ブロックコメント中にしか呼ばれないため、 293 * ブロックコメントを指定しないケース(BLOCK_CMNT1==null)では呼ばれません。 294 * 295 * @param line チェックする行データ 296 * @param st チェック開始文字数 297 * @return ブロックコメントの終了の位置。なけらば、-1 298 */ 299 /* default */ int blockOut( final String line , final int st ) { 300 int ed = line.length(); 301 for( final String key : BLOCK_CMNT2 ) { 302 final int tmp = line.indexOf( key,st + BLOCK_CMNT1.length() ); // 6.4.1.0 (2016/01/09) 開始位置の計算ミス 303 if( tmp >= 0 && tmp < ed ) { // 存在して、かつ小さい方を選ぶ。 304 ed = tmp + key.length(); // アドレスは、終了コメント記号の後ろまで。 305 isBlkIn = false; // ブロックコメントから抜ける。 306 } 307 } 308 309 return isBlkIn ? -1 : ed ; // 見つからない場合は、-1 を返す。 310 } 311 } 312 313 /** 314 * このクラスの動作確認用の、main メソッドです。 315 * 316 * Usage: java org.opengion.fukurou.util.CommentLineParser inFile outFile [encode] [-rowTrim] 317 * 318 * -rowTrim を指定すると、空行も削除します。これは、コメントの増減は、ソースレベルで比較する場合に 319 * 関係ないためです。デフォルトは、空行は削除しません。grep 等で検索した場合、オリジナルの 320 * ソースの行数と一致させるためです。 321 * 322 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 323 * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。 324 * 325 * @param args コマンド引数配列 326 */ 327 public static void main( final String[] args ) { 328 if( args.length < 2 ) { 329 System.out.println( "Usage: java org.opengion.fukurou.util.CommentLineParser inFile outFile [encode] [-rowTrim]" ); 330 } 331 332 final File inFile = new File( args[0] ); 333 final File outFile = new File( args[1] ); 334 String encode = "UTF-8" ; 335 boolean rowTrim = false; 336 for( int i=2; i<args.length; i++ ) { 337 if( "-rowTrim".equalsIgnoreCase( args[i] ) ) { rowTrim = true; } 338 else { encode = args[i]; } 339 } 340 341 final BufferedReader reader = FileUtil.getBufferedReader( inFile ,encode ); 342 final PrintWriter writer = FileUtil.getPrintWriter( outFile ,encode ); 343 344 final CommentLineParser clp = new CommentLineParser( FileInfo.getSUFIX( inFile ) ); 345 346 try { 347 String line1; 348 while((line1 = reader.readLine()) != null) { 349 line1 = clp.line( line1 ); 350 if( !rowTrim || !line1.isEmpty() ) { 351 writer.println( line1 ); 352 } 353 } 354 } 355 // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 356 catch( final CharacterCodingException ex ) { 357 final String errMsg = "文字のエンコード・エラーが発生しました。" + CR 358 + " ファイルのエンコードが指定のエンコードと異なります。" + CR 359 + " [" + inFile.getPath() + "] , Encode=[" + encode + "]" ; 360 throw new OgCharacterException( errMsg,ex ); // 6.5.0.1 (2016/10/21) 361 } 362 catch( final IOException ex ) { 363 final String errMsg = "ファイルコピー中に例外が発生しました。\n" 364 + " inFile=[" + inFile + "] , outFile=[" + outFile + "]\n" ; 365 throw new OgRuntimeException( errMsg,ex ); 366 } 367 finally { 368 Closer.ioClose( reader ) ; 369 Closer.ioClose( writer ) ; 370 } 371 } 372}