먼저 소스 코드입니다. 너무 짧은 코드라서 설명할 거리가 별로 없기도 합니다.
전체 소스 코드 보기
배포했다가 버그를 발견하면서 Regular Expression에 대해 좀더 많은 고려를 해야 한다는 것을 느끼게 되었습니다. 다른 글을 통해 Greedy와 Ungreedy에 대한 짧은 글도 썼는데, 정말이지 Regular Expression의 특성은 꼭 알고 가셔야 합니다. 그래야 저처럼 실수 하지 않겠지요.. ^^
제가 작성한 프로그램과 똑같은 일을 수행하는 코드를 Regular Expression을 이용하지 않고 단순히 indexOf 만으로도 충분히 구현이 가능하고 오히려 버그의 개입 여지가 더 없을 수도 있으리라고 생각합니다. 하지만 Regular Expression을 실제 업무에 사용하는 것이 이 글의 목적이기때문에 이에 대한 이해해 주시기 바랍니다.
public static void showUsage()
{
System.out.println("Event Log Remover (build 003)");
System.out.println("Usage:");
System.out.println(" options file file ...\n");
System.out.println("[OPTIONS]");
System.out.println(" -v verbose mode");
System.out.println(" -a add preprocessing statements");
System.out.println(" -d delete event log message");
System.out.println(" -r restore original event log");
System.out.println(" -i apply indentation");
System.out.println("");
System.out.println("2008.12.15\nHyun-Kyu Shin\n");
}
{
System.out.println("Event Log Remover (build 003)");
System.out.println("Usage:");
System.out.println(" options file file ...\n");
System.out.println("[OPTIONS]");
System.out.println(" -v verbose mode");
System.out.println(" -a add preprocessing statements");
System.out.println(" -d delete event log message");
System.out.println(" -r restore original event log");
System.out.println(" -i apply indentation");
System.out.println("");
System.out.println("2008.12.15\nHyun-Kyu Shin\n");
}
위의 코드는 프로그램의 사용법을 나타내주는 부분입니다. 이것을 먼저 보여드리는 이유는 이 프로그램이 무엇을 하는지를 먼저 보여드리기 위해서입니다. 앞의 글에서 설명했듯이 C 프로그램내에 존재하는 event_log에 대해서 원하는 작업을 수행하는 것이 목적입니다. LogRemover는 모두 세개의 모드를 지원합니다. #ifdef~#else~#endif 를 새롭게 적용하는 -a 모드, 모든 이벤트 메시지를 제거하는 -d 모드, #ifdef~#else~#endif 구문을 다시 제거하는 -r 모드. 그리고 -v 는 처리 과정을 표시하고,-i는 들여쓰기를 지원합니다. 처리 대상이 되는 파일은 동시에 여러개를 입력할 수 있고, 디렉토리인 경우는 하위 디렉토리에 대해서 모두 처리를 수행하게 됩니다.
프로그램의 다른 부분은 별도의 설명이 필요없을 정도로 단순하므로 처리의 핵심인 void doJob(File srcFile) 부분에 대해서만 살펴보도록 하겠습니다.
public void doJob(File srcFile)
{
totalFiles ++;
if( isVerbose ) System.out.print("["+totalFiles + "]" + srcFile.getName());
int replaced = 0;
String replaceRegExp = "(event_log\\s*\\(\\s*[a-zA-Z_0-9\\[\\]]+\\s*),.*";
String replaceRegExp2 = "$1\\);";
try{
File tempFile = new File("temp_"+srcFile.getName());
BufferedReader br = new BufferedReader(new FileReader(srcFile));
BufferedWriter bw = new BufferedWriter(new FileWriter(tempFile));
String line;
boolean isIfDefStarted = false;
while( (line = br.readLine()) != null)
{
if( line.matches("^.*#ifdef EVENT_DEBUG.*"))
{
isIfDefStarted = true;
}
boolean isLongEventStatement = line.matches("^\\s*event_log.*,.*;.*");
String indentationStr ="";
if( isLongEventStatement )
{
indentationStr = line.substring(0, line.indexOf("event_log") );
}
if( isIfDefStarted )
{
/*
* 이미 #ifdef EVENT_DEBUG 로 시작된 경우에는
* AddPreStatement 모드인 경우는 그대로 출력하고
* 만약 AddPreStatement 모드가 아닌 경우, 즉
* #ifdef 없이 단순히 EventLog를 삭제하는 경우는
* #ifdef 가 정의된 부분을 함께 삭제하도록 한다.
*/
if( isAddPreStatement )
{
bw.write(line + NEWLINE);
} else {
if( isLongEventStatement )
{
if(isRecoveryMode)
bw.write( line + NEWLINE);
else
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced ++;
}
}
} else { /* IfDef 블럭이 아닌 경우 */
if( isLongEventStatement )
{
if( isAddPreStatement )
{
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#ifdef EVENT_DEBUG" + NEWLINE);
bw. write(line + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#else" + NEWLINE);
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#endif" + NEWLINE);
replaced++;
} else {
if( isRecoveryMode )
{
bw.write(line + NEWLINE);
}else{
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced++;
}
}
} else {
bw.write(line + NEWLINE);
}
}
if( line.matches("^.*#endif.*"))
{
isIfDefStarted = false;
}
}
bw.close();
br.close();
if( replaced > 0 )
{
String destFileName = srcFile.getAbsolutePath();
srcFile.delete();
tempFile.renameTo(new File(destFileName));
} else {
tempFile.delete();
}
if( isVerbose ) System.out.println( " ..." + replaced + " replaced. Success");
}catch(Exception e)
{
e.printStackTrace();
failureFiles++;
if( isVerbose ) System.out.println( "FAILED!!");
}
processedEvents += replaced;
}
{
totalFiles ++;
if( isVerbose ) System.out.print("["+totalFiles + "]" + srcFile.getName());
int replaced = 0;
String replaceRegExp = "(event_log\\s*\\(\\s*[a-zA-Z_0-9\\[\\]]+\\s*),.*";
String replaceRegExp2 = "$1\\);";
try{
File tempFile = new File("temp_"+srcFile.getName());
BufferedReader br = new BufferedReader(new FileReader(srcFile));
BufferedWriter bw = new BufferedWriter(new FileWriter(tempFile));
String line;
boolean isIfDefStarted = false;
while( (line = br.readLine()) != null)
{
if( line.matches("^.*#ifdef EVENT_DEBUG.*"))
{
isIfDefStarted = true;
}
boolean isLongEventStatement = line.matches("^\\s*event_log.*,.*;.*");
String indentationStr ="";
if( isLongEventStatement )
{
indentationStr = line.substring(0, line.indexOf("event_log") );
}
if( isIfDefStarted )
{
/*
* 이미 #ifdef EVENT_DEBUG 로 시작된 경우에는
* AddPreStatement 모드인 경우는 그대로 출력하고
* 만약 AddPreStatement 모드가 아닌 경우, 즉
* #ifdef 없이 단순히 EventLog를 삭제하는 경우는
* #ifdef 가 정의된 부분을 함께 삭제하도록 한다.
*/
if( isAddPreStatement )
{
bw.write(line + NEWLINE);
} else {
if( isLongEventStatement )
{
if(isRecoveryMode)
bw.write( line + NEWLINE);
else
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced ++;
}
}
} else { /* IfDef 블럭이 아닌 경우 */
if( isLongEventStatement )
{
if( isAddPreStatement )
{
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#ifdef EVENT_DEBUG" + NEWLINE);
bw. write(line + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#else" + NEWLINE);
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#endif" + NEWLINE);
replaced++;
} else {
if( isRecoveryMode )
{
bw.write(line + NEWLINE);
}else{
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced++;
}
}
} else {
bw.write(line + NEWLINE);
}
}
if( line.matches("^.*#endif.*"))
{
isIfDefStarted = false;
}
}
bw.close();
br.close();
if( replaced > 0 )
{
String destFileName = srcFile.getAbsolutePath();
srcFile.delete();
tempFile.renameTo(new File(destFileName));
} else {
tempFile.delete();
}
if( isVerbose ) System.out.println( " ..." + replaced + " replaced. Success");
}catch(Exception e)
{
e.printStackTrace();
failureFiles++;
if( isVerbose ) System.out.println( "FAILED!!");
}
processedEvents += replaced;
}
위의 코드에서 가장 문제가 많았던 부분이 바로
String replaceRegExp = "(event_log\\s*\\(\\s*[a-zA-Z_0-9\\[\\]]+\\s*),.*";
String replaceRegExp2 = "$1\\);";
String replaceRegExp2 = "$1\\);";
이 부분입니다. 이벤트 메시지 부분을 제거하고 이벤트 아이디만을 남기로독 하는 부분인데, 이게 메시지 부분에 여러가지 변형사항이 있다보니 이상한 결과들을 내놓는 경우가 있었습니다. 위의 코드에서 replaceRegExp 는 검색을 하는 데 사용되고, replaceRegExp2는 그 결과를 이용해서 치환할 때 사용되는 식입니다. $1 는 replaceRegExp를 통해 검색된 내용 중 ,(comma)의 앞부분을 의미하게 됩니다.
만약 처리하는 파일에 다음과 같은 라인이 있다고 하면,
event_log ( EVENT_01 , "Hello Event" );
이에 대한 처리 결과는
event_log( EVENT_01 );
이 되도록 하는 것입니다.
그런데, Regular Expression을 잘못 쓰게 되면
event_log( EVENT_02 , "Hello, Sir");
은
event_log( EVENT_02, "Hello );
와 같은 현상이 나타나기도 했습니다.
몇 번의 수정을 통해 위와 같은 Regular Expression을 쓰게 되었고, 다행히 지금까지는 별다른 에러 케이스를 찾지 못했습니다. ^^
if( isIfDefStarted )
{
/*
* 이미 #ifdef EVENT_DEBUG 로 시작된 경우에는
* AddPreStatement 모드인 경우는 그대로 출력하고
* 만약 AddPreStatement 모드가 아닌 경우, 즉
* #ifdef 없이 단순히 EventLog를 삭제하는 경우는
* #ifdef 가 정의된 부분을 함께 삭제하도록 한다.
*/
if( isAddPreStatement )
{
bw.write(line + NEWLINE);
} else {
if( isLongEventStatement )
{
if(isRecoveryMode)
bw.write( line + NEWLINE);
else
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced ++;
}
}
}
{
/*
* 이미 #ifdef EVENT_DEBUG 로 시작된 경우에는
* AddPreStatement 모드인 경우는 그대로 출력하고
* 만약 AddPreStatement 모드가 아닌 경우, 즉
* #ifdef 없이 단순히 EventLog를 삭제하는 경우는
* #ifdef 가 정의된 부분을 함께 삭제하도록 한다.
*/
if( isAddPreStatement )
{
bw.write(line + NEWLINE);
} else {
if( isLongEventStatement )
{
if(isRecoveryMode)
bw.write( line + NEWLINE);
else
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced ++;
}
}
}
이 부분은 #ifdef EVENT_DEBUG 블럭 안쪽일 경우의 처리를 나타냅니다. 만약 -a 모드인 경우는 있는 그대로 표현하면 됩니다. 어차피 하고자 하는 일이 #ifdef ~ #else ~ #endif 를 추가하는 것이기 때문에 이미 있는 부분은 그대로 유지하고자 하는 것입니다. 만약 -a 모드가 아니라면 기본적으로는 해당 부분을 삭제하게 됩니다. 다만 이벤트 메시지를 포함한 부분인 경우에 대해서만 추가적으로 처리하게 되는데, -r 모드인 경우는 이벤트 메시지를 포함한 전체 문장으로, -d 모드인 경우는 이벤트 메시지가 삭제된 형태로 출력하는 것입니다.
} else { /* IfDef 블럭이 아닌 경우 */
if( isLongEventStatement )
{
if( isAddPreStatement )
{
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#ifdef EVENT_DEBUG" + NEWLINE);
bw.write(line + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#else" + NEWLINE);
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#endif" + NEWLINE);
replaced++;
} else {
if( isRecoveryMode )
{
bw.write(line + NEWLINE);
}else{
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced++;
}
}
} else {
bw.write(line + NEWLINE);
}
}
if( isLongEventStatement )
{
if( isAddPreStatement )
{
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#ifdef EVENT_DEBUG" + NEWLINE);
bw.write(line + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#else" + NEWLINE);
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
if(isApplyIndentation) bw.write(indentationStr);
bw.write("#endif" + NEWLINE);
replaced++;
} else {
if( isRecoveryMode )
{
bw.write(line + NEWLINE);
}else{
bw.write( line.replaceFirst(replaceRegExp,replaceRegExp2) + NEWLINE);
replaced++;
}
}
} else {
bw.write(line + NEWLINE);
}
}
#ifdef EVENT_DEBUG 블럭이 아닌 경우에는 이벤트 메시지를 포함하는 문장이 나오는 경우에 대해서만 추가 처리를 해주면 됩니다. -a 모드라면 #ifdef~#else~#endif 를 적용하고, -r 모드라면 원본 문장 그대로, -d 모드의 경우에는 이벤트 메시지를 삭제한 형태가 출력됩니다.
설명하고 나니 무척이나 간단합니다. 이처럼 간단한 프로그램에서 왜 그리 실수가 많았는지 모르겠네요 ^^
소스 코드에 대한 조언은 언제든지 환영입니다. 오히려 시간 내어 봐주시는 분들께 제가 더 큰 감사를 드려야 겠지요. (근데, 여기 오셔서 보시는 분들이 몇분이나 될 지는 의문입니다 ^^)
좋은 하루 되세요.
LogRemover.java
댓글을 달아 주세요
패턴 검색 부분을 아래와 같이 변경하였습니다.
2008/12/15 11:12그동안 참 엉뚱한 짓을 했군요. ^^
String replaceRegExp = "(event_log[^,]+),.*";