Shinnara's Blog
Talking with Shinnara :: NaraTalk.com


새로운 막강(?) PC가 제 손에 들어옴에 따라 새롭게 개발 환경을 꾸몄습니다.
윈도우 재 설치(조립 업체에서 이건 해줘서...), 오피스 등등의 프로그램 깔고,
C 개발환경 설치하고 등등.. 며칠의 시간이 흘렀습니다.

그리고 사이드 웍으로 하고 있는 자바 어플리케이션 개발을 위해 Eclipse를 깔고, Subversive plugin 과 Connector를 설치했는데도 Connector가 없다면서 실행이 안되더군요.

구글 신의 도움을 받아 업데이트 사이트를 변경해보았습니다.

http://www.polarion.org/projects/subversive/download/eclipse/2.0/update-site/

이 사이트에서 최신의 Connector와  SVNKit을 설치하니 정상적으로 동작...^^

이것때문에 하루동안이나 고생했네요.


이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/313 관련글 쓰기

댓글을 달아 주세요


JXL(JExcel API) is a very useful library for handling Microsoft Excel files on Java envrionment. I like JXL because of its simple and intuitive usage. And also it works very well.

During works with JXL, developers want to specify column width. ColumnView is very helpful for this.

JExcel API's FAQ also describes too.

If you want that 1st column has 15 characters width, you can use like this:

sheet.setColumnView(0, 15);


You can also make auto sizable to the 2nd column:

CellView autoSizeCellView = new CellView();
autoSizeCellView.setAutosize(true);
       
sheet.setColumnView(1, autoSizeCellView);


"Auto Size" works well, but the result is not always so good. In some cases, you can face to the undesirable output.



이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/310 관련글 쓰기

댓글을 달아 주세요


Java is a very nice and popular programming language. Groovy is a more graceful one. Groovy makes developers feel free. Even I've been on groovy since just about 1 month ago, Groovy makes me very happy and gives many insperations.

Today, I'll show a simple example of "Java and Groovy Integration". Groovy has a "Groovy script engine", so on runtime, SW could behave more dynamically.  On my point, I'll apply this feature to support the analysis SW. I make the framework to maintain SW and File In/Out. Other developer and researcher compose the analysis routine using Groovy script.

Today's purpose is getting summation from 1 to the designated number using groovy script and adding sum value from Java Class.

Here are source codes:

[MyClass.java]
public class MyClass {

        public int value;
}



[MyScript.groovy]
sum = 0;

1.upto(number) {
    each ->
        sum += each   
}

sum += myclass.value


[ScriptTest.java]
import groovy.lang.*;
import groovy.util.*;
import java.util.*;

public class ScriptTest {

    public static void main(String[] args)throws Exception {

        MyClass myclass = new MyClass();
        myclass.value = 10;
       
        Hashtable<String, Object> variables = new Hashtable<String, Object>();
        variables.put("number", new Integer(10));
        variables.put("myclass", myclass );
       
        GroovyScriptEngine engine = new GroovyScriptEngine(".");   
        Binding binding = new Binding(variables);
        engine.run("src/MyScript.groovy", binding);
       
        System.out.println("Result "+ binding.getProperty("sum"));
       
           
    }

}



Using above, the result is:

Result 65


All works are done successfully.


이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/309 관련글 쓰기

댓글을 달아 주세요


 Java를 좋아하는 저에게 Groovy는 꽤나 관심이 가는 놈이었습니다. 하지만 여러가지 사정상 제대로 알기가 어려웠지요. 그러던 차에 "Groovy In Action"이라는 책이 인사이트에서 번역서("프로그래밍 그루비")로 출간이 되었습니다. 새해가 되면서 결심한 것 중의 하나가 "매년 새로운 언어에 대해 알아보자" 였기 때문에 책을 사게 되었습니다.

 짬이 나는 시간에 조금 씩 읽기 때문에 진도가 그리 빠르지는 못합니다. 그래도 제대로된 지도자(?)가 옆에 버티고 있으니 예전보다는 보다 체계적으로 알아간다는 느낌입니다.

 프로그래밍 그루비를 읽다가 클로저와 관련된 부분에서 조금 이상한 부분이 있어서, 책을 번역하신 역자이신 박제권 님과 연락이 되었습니다. 주요 내용은 "클로저 내에서 명시적으로 변수 선언을 해야 하는가"였습니다.

논의 되었던 내용을 간단히 정리하자면,

명시적인 클래스 밖에서는 변수 선언을 생략할 수 있다.


프로그래밍 그루비의 예제 2.5에는 클로저를 이용하여 "짠" 소리가 나는 횟수를 세는 코드가 제시되어 있습니다. 여기서 제가 궁금했던 것은 클로저 내에서 변수 선언 없이 변수를 사용하고 있었는데, 그게 가능한 것인가였습니다.

예를 들어 다음과 같은 코드가 있다고 합시다.

1.upto(10) { number ->
 mynumber = number
 println mynumber
}

위 코드를 shownum.groovy 파일에 저장하여 groovy로 실행하거나  groovysh을 이용하게 되면 정상 동작을 합니다.

그러나 아래와 같이,


class MyClosure{

 public void printNumber(){

   1.upto(10) { number ->
    mynumber = number
    println mynumber
   }
 }

}

(new MyClosure()).printNumber()

라고 하게되면 이는 mynumber가 정의되지 않았다며 에러를 발생하게 됩니다.

반면 다음의 코드는 정상  동작을 합니다.


class MyClosure{

 public void printNumber(){

   1.upto(10) { number ->
    def mynumber = number
    println mynumber
   }
 }

}

(new MyClosure()).printNumber()

1.upto(10) { number ->
 mynumber = number
 println mynumber
}

클래스 내부에서는 def를 이용하여 선언하고, 클래스 밖에서는 def 없이 사용을 하였습니다.

즉, 클래스 내부에서 클로저를 사용할 경우 명시적인 변수 선언이 필요하나, 클래스 밖에서는 그렇지 않아도 된다는 것입니다.

아래와 같이

[getsum.groovy]
total = 0

1.upto(100) { number ->
   total += number
}

println "1+ ... + 100 = $total"

명시적인 변수 선언이 없더라도 1부터 100까지의 합인 5050을 정확하게 표시합니다.

그루비에서 클래스로 감싸여 있지 않은 코드에 대해서는 암묵적으로 파일 이름에 해당하는 클래스를 생성하게 되는 데, 이 과정에서 변수 선언이 없이 사용된 변수에 대한 처리도 이루어지는 것으로 보입니다. (보다 자세한 사항은 Groovy Spec을 찾아봐야 겠습니다.)

결과적으로 프로그래밍 그루비의 예제 2.5에 있는 코드는 스크립트 형태로 실행(명시적인 클래스안에 있지 않은 경우)하는 경우, 올바른 코드입니다.

이올린에 북마크하기(0) 이올린에 추천하기(0)
1 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/302 관련글 쓰기

  1. Subject: 클로저에서의 변수 선언

    Tracked from 그루비 groovy  삭제

    한번 당한 적이 있는 문제였는데도 독자의 질문에 제대로 답하지 못했었다. "Shinnara" 님의 블로그에 "def" 의 헷갈리는 부분에 대해 글이 올라왔다. def 는 어떤 경우에는 꼭 필요하고, 어떤 경우에는 생략할 수 있다. http://naratalk.com/302 을 읽어보시라.

    2009/04/28 17:02

댓글을 달아 주세요


 전에 C 코드를 분석해서 그 안에 있는 내용을 추출해내려 한다는 이야기와 함께 몇가지 시도를 했다는 것을 올린 적이 있습니다. Perl을 이용해서 함수 호출 및 상수 추출 등을 구현해 보았는데요, 어찌보면 정식 방법은 아니죠. 보다 정확하게 하려면 C의 문법을 이용해서 처리를 해야 하는데, 생각보다 쉽지는 않네요. 물론 제대로된 노력을 해보지 않은 탓도 있겠지만요.

 한동안은 C 코드 분석과 관련된 내용을 생각치 않고 있다가, 어제부터 다시 시작하게 되었습니다. 이번에는 좀더 개선된 내용으로 만들어 볼까 하는데, 이번에도 잘 될지는 의문입니다.

 이번에 시작하면서 가장 먼저 하는 것은 코드 상에 존재하는 주석을 제거하는 일입니다. 여타의 컴파일러가 하듯이 말이죠. Perl의 Parse::RecDescent 모듈을 보면 주석을 제거 하는 부분이 들어가 있음을 알 수 있습니다. C 언어의 문법을 활용하는 방법입니다.

 하지만 여기서는 좀더 단순히 주석만을 제거한 새로운 C 코드를 얻는 것을 일차적인 목표로 합니다. 주석이 제거 되고 나면 그 다음에 문법을 활용해서 파싱을 하든, 예전처럼 꽁수를 부려 원하는 정보를 얻어 내든 할 수 있을 테니까요.

 주석을 제거하기 위해 처음에는 라인 단위로 읽어서 처리하려고 했습니다. 그러다보니 예외 케이스가 많이 있더군요. 그래서 적용한 방법은 한문자씩 읽어서 주석의 시작과 끝을 판단, 주석이라고 판단되면 출력하지 않는 것입니다. C 코드를 작성할 때 단순히 ASCII 코드만 있으면 좀더 처리가 편리하겠지만, 요즘은 UniCode를 지원해야겠죠? 그래서 아래와 같은 코드를 삽입하게 됩니다.

    private String getStringChar(int unicode)
    {
        char[] codeUnits = new char[2];
        int count = Character.toChars(unicode, codeUnits, 0);
        return new String(codeUnits, 0, count);
    }

 위의 코드는 Supplementary Characters in the Java Platform에서 참고하였습니다. 코드 내용은 unicode 코드의 값을 String으로 변환해주는 것입니다.

 주석을 판단하여 제거하는 부분은 다음과 같습니다.

    public void removeComments(File srcFile, File tgtFile) throws Exception
    {
        BufferedReader br = new BufferedReader(new FileReader(srcFile));
        BufferedWriter bw = new BufferedWriter(new FileWriter(tgtFile));
       
        StringBuffer buffer = new StringBuffer();
        int unicodeChar;
        int prevChar = -1;
       
        boolean comments = false;
       
        while( (unicodeChar = br.read()) >= 0 )
        {           
            String str = getStringChar(unicodeChar);
           
            if( !comments && unicodeChar == '/')
            {
                int nextChar = br.read();
                if( nextChar == '*' )
                {
                    comments = true;
                    bw.write(buffer.toString());
                    buffer = new StringBuffer();                   
                } else                   
                    str += getStringChar(nextChar);                   
            }
                       
            if( !comments )                               
                buffer.append(str);           
           
            if( comments && unicodeChar == '/' && prevChar == '*')
                comments = false;       
           
            prevChar = unicodeChar;
           
        }
       
        if( buffer.length() > 0 ) bw.write(buffer.toString());
       
        bw.close();
        br.close();       
    }

 원본 파일과 주석이 제거된 내용을 저장할 파일을 받아서 처리를 하게 됩니다. C 에서는 /* ~ */ 형태의 주석만을 사용하기 때문에 이러한 패턴만을 찾아서 제거합니다.

아직 개선의 여지가 있을 수 있는 코드이기는 하지만, 처음에 생각했던 것 보다는 코드가 꽤나 단순해 보이네요.

이제 이 결과물을 이용해서 원하는 정보를 뽑아내는 과정을 고심해봐야겠습니다.








이올린에 북마크하기(0) 이올린에 추천하기(0)
1 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/277 관련글 쓰기

  1. Subject: 주석 제거 프로그램(CommentRemover) 배포

    Tracked from Adobe AIR Devpia.  삭제

    Adobe AIR Application Installer Page Download Application now. This application requires Adobe&#174; AIR&#8482; to be installed for Mac OS or Windows. ↑Install Now 버튼(배지)을 클릭하세요. 프로그래밍 작업을 완료한 후 주석을 제거해 본 적 있으신가요? 타 업체에 소스 코드를 넘길 때..또는 CSS, 자바스크립트를 웹에 올..

    2009/08/29 02:36

댓글을 달아 주세요

Regular Expression을 실제 작업에 이용하기에 이은 두번째 글입니다. 이번 글에서는 지난 글에서 소개하지 못했던 자바 소스에 대해서 간략히 설명을 할까 합니다. 그리고는 고수님들의 조언을 받아서 리팩토링도 하고, 또 다른 언어로 바꾸어볼까도 생각합니다. 지난 글에서 밝혔듯이 처음에 작성하고 섣불리 팀내에 배포했다가 몇가지 패턴을 인식못하고  또 잘못 바꾸어주는 덕에 테스트의 중요성을 실감한 사건이기도 했습니다. 정말이지 테스트는 무지 중요합니다. ^^


먼저 소스 코드입니다. 너무 짧은 코드라서 설명할 거리가 별로 없기도 합니다.

전체 소스 코드 보기


 배포했다가 버그를 발견하면서 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");       
    }

위의 코드는 프로그램의 사용법을 나타내주는 부분입니다. 이것을 먼저 보여드리는 이유는 이 프로그램이 무엇을 하는지를 먼저 보여드리기 위해서입니다.  앞의 글에서 설명했듯이 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;
    }

위의 코드에서 가장 문제가 많았던 부분이 바로

        String replaceRegExp = "(event_log\\s*\\(\\s*[a-zA-Z_0-9\\[\\]]+\\s*),.*";
        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  블럭 안쪽일 경우의 처리를 나타냅니다. 만약 -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);
                    }                   
                }


#ifdef EVENT_DEBUG 블럭이 아닌 경우에는 이벤트 메시지를 포함하는 문장이 나오는 경우에 대해서만 추가 처리를 해주면 됩니다. -a 모드라면 #ifdef~#else~#endif 를 적용하고, -r 모드라면 원본 문장 그대로, -d 모드의 경우에는 이벤트 메시지를 삭제한 형태가 출력됩니다.

설명하고 나니 무척이나 간단합니다. 이처럼 간단한 프로그램에서 왜 그리 실수가 많았는지 모르겠네요 ^^

소스 코드에 대한 조언은 언제든지 환영입니다. 오히려 시간 내어 봐주시는 분들께 제가 더 큰 감사를 드려야 겠지요. (근데, 여기 오셔서 보시는 분들이 몇분이나 될 지는 의문입니다 ^^)

좋은 하루 되세요.

이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 1 Comment

TRACKBACK :: http://naratalk.com/trackback/271 관련글 쓰기

댓글을 달아 주세요

  1. BlogIcon Shinnara  댓글주소  수정/삭제  댓글쓰기

    패턴 검색 부분을 아래와 같이 변경하였습니다.
    그동안 참 엉뚱한 짓을 했군요. ^^

    String replaceRegExp = "(event_log[^,]+),.*";

    2008/12/15 11:12


 요즘 간혹 올리는 글들이 대체로는 Regular Expression(정규 표현식)에 대한 것들이네요. C 코드 분석이나 Perl 이야기나 모두.. 몇번에 걸쳐 올리던 C 코드 분석은 현재로서는 잠정 중단입니다. 대신에 회사 업무 도중 Regexp 를 사용할 수 있는 좋은 기회가 생겼지요.

 아시다시피 제가 속한 부서는 임베디드 소프트웨어를 만들고 있습니다. C 를 이용해서 프로그램을 만들지요. 비슷한 기능들을 모아 CSC라 분류하고 각 개발자는 몇개의 CSC를 맡아서 개발 및 테스트를 진행합니다. 보통 CSC는 디렉토리별로 나뉘게 되고, 이 디렉토리 아래에 .c 파일이 위치하게 되는 형태이지요.

 프로그램을 작성하다보면 소프트웨어의 작동시 발생되는 여러 이벤트들을 외부에 알려야 하는 경우가 생깁니다. 이를 위해 event_log 라는 함수를 사용하게 되는데요, event_log 함수는 다음과 같이 사용됩니다.

event_log( EVENT_ID , "detail desc");

 개발 과정에서는 뒤의 detail description을 이용해서 보다 알기 쉽게 이벤트의 내용을 표시해주게 됩니다. 하지만 정식 릴리즈가 가까워지면 수행 시간이나 이미지 크기 등의 문제로 인해, 뒤의 "detail desc"를 삭제해 주어야 합니다. 즉 아래와 같은 형태로 바꾸어야 하지요.

event_log( EVENT_ID );

 여기서 문제가 발생합니다. event_log 함수가 전체 프로그램 내에서 무척이나 많이 사용되고 있기 때문에 "detail desc"을 삭제하는 일이 만만치 않은 작업이 되는 것이죠. 또한 정식 릴리즈 버전이 아니라 테스트를 위해서는 자세한 이벤트의 내용을 알기를 원하기 때문에 아래와 같은 형태로 변경하기로 하였습니다.

#ifdef EVENT_DEBUG
event_log( EVENT_ID, "detail desc");
#else
event_log( EVENT_ID);
#endif

즉, 컴파일 시점에서 테스트용 이미지와 정식 빌드용 이미지를 EVENT_DEBUG를 이용해서 분리해내고자 하는 것입니다. 이러한 기법은 C/C++에서는 너무도 많이 쓰이는 방법입니다.

 소스 코드 전체에 대해서 단순히 뒤의 자세한 설명만 없애는 것을 예상했을 때는 UltraEdit, AcroEdit 등 개발용 텍스트 에디터등을 이용해 정규 표현식으로 치환하려고 했습니다. 하지만 새롭게 제안된 내용은 단순한 치환으로는 안되더군요. 해당 에디터 들에서 줄바꿈을 만들어 내기가 어려웠습니다. 물론 매크로등을 이용하면 되긴 했지만, 뭔가 찜찜한 기분에 아예 변환 도구를 만들자고 생각했습니다. 

 어떤 언어로 만들까 잠시 고민을 해보았습니다. 최근에 관심을 가지고 있는 Perl을 이용해서 간단히 작업을 해보았는데 꽤나 쉽게 처리할 수 있었습니다. 이 글을 작성하는 곳이 회사가 아니라 집이다보니 해당 소스를 완벽하게 첨부를 하지는 못하지만, 대략 아래와 같은 형태입니다.

 foreach  $aLine (<fileHandle> )
 {   
   
   if( $aLine =~ /^\s*(event_log.*),.*;$/ ) {
       print "#ifdef EVENT_DEBUG\n";
       print "$aLine\n";
       print "#else\n";
       print "$1);\n";
       print "#endif\n";
    } else {
       print "$aLine";
    }
 }
 close( fileHandle );

 앞서 소개했던 Perl을 이용한 C 소스 코드 분석 에 나온 코드 형태와 유사합니다.  일단 주어진 하나의 파일에 대해 처리하는 것은 어렵지 않더군요. 하지만 코드를 작성하다보니 점점 하고 싶어지는 게 많아집니다. #ifdef 을 붙였다가 다시 떼고 싶을 땐 어떻게 하지? 이렇게 했다가 다시 이벤트 아이디만 표시하는 깔끔한 (?) 코드로 변경하고자 한다면? 디렉토리를 입력으로 주면 하위 디렉토리에 있는 모든 .c 파일에 대해서 같은 처리를 하면 편리하지 않을까? 등등등...

 그러다 보니 아직 서툰 Perl로 작성하기에는 어려움이 많더군요. 손에 익숙치 않다보니.. 그래서 일단 Java로 짜기로 했습니다. Perl이야 말로 정규 표현식을 이용한 문자열 처리의 최고봉이긴 하지만, Java도 정규 표현식을 지원하니 비슷한 형태가 가능합니다.

오늘 회사에서 관련된 툴을 작성하여 팀 내에 배포를 하였는데, 자잘한 버그들이 존재하는 바람에 두번이나 더 배포를 해야 했습니다. 아직 한번 더 배포할 것이 남아있기도 하구요. 일단 원하는 기능을 잘 동작하는 것 같습니다. 자세한 소스는 다음에 올리기로 하고, 앞으로는 오늘 작성한 소스에 대한 공개적인 리팩토링 및 이를 Perl 등의 다른 언어로 표현하는 것에 대해서 다루어볼까 합니다. Perl을 배우는 좋은 기회가 될 것 같기도 하구요.

모두 좋은 밤, 그리고 즐거운 주말 되세요.

p.s. 펄 관련 유용한 사이트 링크 하나..
http://gypark.pe.kr/wiki/Perl/%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D

이올린에 북마크하기(0) 이올린에 추천하기(0)
1 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/269 관련글 쓰기

  1. Subject: Regular Expression 을 실제 작업에 이용하기 (2)

    Tracked from Talking with Shinnara :: NaraTalk.com  삭제

    Regular Expression을 실제 작업에 이용하기에 이은 두번째 글입니다. 이번 글에서는 지난 글에서 소개하지 못했던 자바 소스에 대해서 간략히 설명을 할까 합니다. 그리고는 고수님들의 조언을 받아서 리팩토링도 하고, 또 다른 언어로 바꾸어볼까도 생각합니다. 지난 글에서 밝혔듯이 처음에 작성하고 섣불리 팀내에 배포했다가 몇가지 패턴을 인식못하고 또 잘못 바꾸어주는 덕에 테스트의 중요성을 실감한 사건이기도 했습니다. 정말이지 테스트는 무지 중요..

    2008/12/15 10:03

댓글을 달아 주세요


Solaris에서 serial 통신을 테스트 하기 위해 SerialTest.java를 작성한 후 컴파일을 하였습니다. 컴파일은 문제없이 수행되었고, 이제 실행을 해보려고 하니 제목과 같은 에러 메시지가 나오면서 실행이 안되더군요. 그래서 구글을 통해 찾아보니, 64비트임을 알려주어야 한다고 되어 있더군요. 그래서 다음과 같이 실행하였습니다.

java -d64 SerialTest

이렇게 하니 해당 에러 메시지는 사라졌답니다. 문제는 libgcc_s.so.1 가 없다면서 또 에러가 났다는 점..

이제 또 다시 그 에러 해결을 하러 가봐야겠습니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
TAG 64, Elf, java, solaris
0 Trackback, 1 Comment

TRACKBACK :: http://naratalk.com/trackback/261 관련글 쓰기

댓글을 달아 주세요

  1. BlogIcon Shinnara  댓글주소  수정/삭제  댓글쓰기

    LD_LIBRARY_PATH에 /usr/sfw/lib 와 /usr/sfw/lib/sparcv9 을 추가해주니 잘 되네요..

    2008/10/31 15:36


Google 이 Android 의 SDK를 발표한 것은 다들 아시는 일일텐데요, 저도 비슷한 분야에서 일하고 있는지라 관심을 가지고 지켜보고 있었습니다. SDK 가 발표되자 다운을 받고 샘플 프로그램도 짜보고 했는데, 기존의 J2ME에 비해서는 많이 좋아진것 같다는 느낌입니다. 자세한 것은 더 써보면서 이야기하기로 하구요.

오늘은 SDK에 포함된 샘플프로그램 중에 Lunar Lander 를 컴파일해서 돌려봤습니다. 달착륙선을 정해진 위치에 잘 착륙시키는 게임인데요, 매우 간단한 게임이지만 은근히 중독성이 있네요. 처음에는 어떻게 하는 지 몰라서 그냥 추락하기만 했는데, 요령이 생기니 재밌네요.

방향키의 가운데 버튼이 로켓(? 부스터?)입니다. 버튼을 누르고 있으면 추진력이 생기는데, 이때 연료가 줄어들게 되고, 남은 연료는 왼쪽 상단에 표시됩니다. 그리고 속도가 화면 가운데 위쪽의 게이지로 표시되는데 녹색으로만 되어야 하고 그것보다 높은 상태에서 땅에 닿게되면 속도가 너무 빠르다면서 실패하게 됩니다.

사용자 삽입 이미지


그리고 정확한 위치에 착륙해야 하는데, 착륙지점이 아래쪽에 녹색으로 표시되고 그 선위에 정확히 착륙시키는 것입니다.

몇번을 해보다보니 요령이 생겨서 드디어 성공했답니다~~

사용자 삽입 이미지

Android SDK 받으신 분들 한번 해보세요~ 아직 설치 안해보신 분들도 한번 해보시구요. 설치도 간단하답니다

Google Android


update::

몇번 더 해보니 3연속으로 성공하기도 하네요^^

사용자 삽입 이미지

이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/222 관련글 쓰기

댓글을 달아 주세요

사용자 삽입 이미지
Google 은 Google Code 를 통해 Google 의 여러 서비스에 접근할 수 있는 API 를 제공하고 있습니다. 오늘은 이중에서 SpreadSheet 의 내용을 얻어 오는 방법을 간략하게 소개합니다. 자세한 내용은 Google Spreadsheets Data API Developer's Guide: Java 를 참고하시면 됩니다. 이때 Java Library 가 필요하며 여기서 다운받으실 수 있습니다.

먼저 서비스를 만들고 로그인 정보를 입력합니다.


            SpreadsheetService service = new SpreadsheetService("Naratalk-SpreadSheet-1");
            service.setUserCredentials(<사용자아이디>, <패스워드>);


서비스 이름은 아직까지 별로 중요하지는 않은것 같습니다. 무엇을 입력해도 상관없더군요. (이부분은 자세한 정보 필요). 그리고 사용자 아이디와 패스워드를 입력하는데 사용자 아이디는 gmail 계정 아이디가 아니라 전체 이메일 주소를 모두 입력하셔야 합니다. 만약 Google Apps 등을 이용해서 자신만의 도메인을 쓰고 있다면 그 이메일 전체를 입력하시면 됩니다.


            URL metafeedUrl = new URL("http://spreadsheets.google.com/feeds/spreadsheets/private/full");
            SpreadsheetFeed feed = service.getFeed(metafeedUrl, SpreadsheetFeed.class);
            List<SpreadsheetEntry> spreadsheets = feed.getEntries();


서비스 URL 입니다. 별로 고칠 부분은 없어보입니다.  feed.getEntries() 를 통해 등록되어 있는 모든 SpreadSheet 의 Entry 를 List 로 반환합니다. 이제 이 List 를 차례로 보면서 해당 내용을 확인해봅니다.

            for (int i = 0; i < spreadsheets.size(); i++) {
              System.out.println("Entry>>");
              SpreadsheetEntry entry = spreadsheets.get(i);
              System.out.println("\t" + entry.getTitle().getPlainText());
            }


 전체적인 구조는 SpreadSheetEntry -> WorksheetEntry -> CellEntry 의 형태를 가지며, 동일한 방법에 의해 접근이 가능합니다.  자세한 내용은 API 문서를 참고하시면 됩니다.

전체 프로그램 소스는 아래와 같습니다.

import com.google.gdata.client.spreadsheet.*;
import java.net.URL;
import com.google.gdata.data.spreadsheet.*;
import java.util.*;

public class GdataTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
       
        try{
            SpreadsheetService service = new SpreadsheetService("Naratalk-SpreadSheet-1");
            service.setUserCredentials("", "");
           
            URL metafeedUrl = new       URL("http://spreadsheets.google.com/feeds/spreadsheets/private/full");
            SpreadsheetFeed feed = service.getFeed(metafeedUrl, SpreadsheetFeed.class);
            List<SpreadsheetEntry> spreadsheets = feed.getEntries();
            for (int i = 0; i < spreadsheets.size(); i++) {
              System.out.println("Entry>>");
              SpreadsheetEntry entry = spreadsheets.get(i);
              System.out.println("\t" + entry.getTitle().getPlainText());
             
              List<WorksheetEntry> worksheets = entry.getWorksheets();
              for(int k=0;k< worksheets.size();k++)
              {
                  WorksheetEntry worksheet = worksheets.get(k);
                  System.out.println("\t\t"+ worksheet.getTitle().getPlainText());
                 
                  URL cellFeedUrl = worksheet.getCellFeedUrl();
                  CellFeed cellFeed = service.getFeed(cellFeedUrl, CellFeed.class);
                  for (CellEntry cell : cellFeed.getEntries()) {
                    System.out.println(cell.getTitle().getPlainText());
                    String shortId = cell.getId().substring(cell.getId().lastIndexOf('/') + 1);
                    System.out.println(" -- Cell(" + shortId + "/" + cell.getTitle().getPlainText()
                        + ") formula(" + cell.getCell().getInputValue() + ") numeric("
                        + cell.getCell().getNumericValue() + ") value("
                        + cell.getCell().getValue() + ")");
                  }
              }
              
            }
           
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/178 관련글 쓰기

댓글을 달아 주세요

데스크탑에서 돌아가는 소프트웨어를 만들다 보면 시스템 트레이에 등록해야 하는 경우가 생깁니다. Java 의 경우, JDK 1.6 이전에서는 별도의 라이브러리를 사용해야 했으나, 1.6 부터는 기본적으로 SystemTray 라는 클래스를 지원하고 있습니다. 따라서 매우 간편하게 시스템 트레이를 사용할 수 있습니다.

오늘의 이야기는 바로 시스템 트레이에 아이콘을 등록하고, 메뉴 작동에 대한 부분입니다.  JFrame 에 JTextArea 와 JButton 을 설치하고 오른쪽 위의 "X" 버튼을 누르면 창이 사라지면서 시스템 트레이에 트레이 아이콘을 등록합니다. 트레이 아이콘은 "restore" 와 "exit" 라는 메뉴를 가지며 각각 창을 다시 보여주는 기능과 종료하는 기능을 수행합니다. 먼저 오늘 만들게 될 프로그램의 모습입니다.

사용자 삽입 이미지

보시는 바와 같이 매우 간단합니다.  Layout 은 BorderLayout 을 썼고, JTextArea 를 BorderLayout.CENTER 로 JButton 을 BorderLayout.SOUTH 로 설정하였습니다. Exit 버튼을 누르면 프로그램이 종료하며, 우측 상단의 'X' 를 누르면 창이 닫히면서 트레이 아이콘이 등록됩니다.

사용자 삽입 이미지

시스템 트레이에 등록된 모습입니다. 녹색 바탕에 흰색으로 1이라는 숫자가 있는 아이콘이 트레이 아이콘이며, 풍선도움말에 "System Tray Test"라는 문구가 나옵니다.

사용자 삽입 이미지
아이콘을 오른쪽 클릭하였을 때 나오는 팝업 메뉴입니다. Restore 와 Exit 가 보입니다.

시스템 트레이에 아이콘을 등록하기 위해서는 TrayIcon 과 여기서 작동될 팝업메뉴가 필요하고 이를 SystemTray 를 이용해서 등록해주면 됩니다.

먼저 팝업 메뉴를 만드는 코드입니다. 팝업 메뉴를 만들고 여기에 ActionListener 를 만들수 있는 방법은 매우 다양하지만 저는 간단히 아래와 같이 했습니다.


        // 메뉴 만들기
        PopupMenu popup = new PopupMenu();
        MenuItem mi1 = new MenuItem("Restore");
        mi1.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e)
            {
                restore();
            }
        });
        popup.add(mi1);
        MenuItem mi2 = new MenuItem("Exit");
        mi2.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e)
            {
                exit();
            }
        });
        popup.add(mi2);
  

다음은 TrayIcon 을 만드는 과정입니다.

사용자 삽입 이미지
시스템 트레이에 등록될 아이콘입니다.

        // TrayIcon 만들기
        BufferedImage bi = null;
        try{           
            bi = ImageIO.read(new File("res/o1.gif"));               
        }catch(Exception e)
        {
            e.printStackTrace();
        }   
       
        if( bi != null)    trayIcon = new TrayIcon(bi,"System Tray Test", popup);

이미지를 얻기 위해 ImageIO를 사용했습니다. 다른 예외 처리가 필요할 수도 있지만 여기서는 단순하게 ^^

그리고 마지막으로 시스템 트레이에 TrayIcon을 등록하는 과정입니다.


 
       if( SystemTray.isSupported() && trayIcon != null){       
           
            SystemTray tray = SystemTray.getSystemTray();
            try{
                tray.add(trayIcon);
            }catch(Exception e){
                e.printStackTrace();
            }
        }

시스템 트레이에서 아이콘을 제거하기 위해서는 remove 메소드를 사용하면 됩니다.

전체 소스 파일의 내용을 보시려면 아래 버튼을 눌러주세요

전체 소스



SystemTray 는 1.6 이상에서만 동작하니, 최신 J2SE SDK 를 받아서 사용하시기 바랍니다.





이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/174 관련글 쓰기

댓글을 달아 주세요


Nokia 의 N95 에 들어갈 이미지 뷰어를 Midlet 으로 만드는데 이상하게 N95 에서 찍은 사진을 로드하게 되면 Unhandled Exception 을 내며 죽는 것입니다. 네트워크를 통해 받는 다른 이미지들은 제대로 보여지는데, N95로 찍은 사진만 그런 문제가 발생하길래, N95 가 Jpeg 를 만들어 낼  때, 약간의 문제가 있지않은가하는 생각을 했었는데,오늘 구글 검색을 통해본 결과 OutOfMemory 에 의한 문제로 밝혀졌네요.

http://discussion.forum.nokia.com/forum/showthread.php?t=108918

위의 글을 보면, 동일한 Exception 때문에 고민한 흔적이 역력하다. J2ME 개발을 꽤 했다고 생각했는데 메모리때문일거라는 생각을 못하다니 창피한 일이 아닐 수 없습니다.

참고로,Java VM 의 memory 크기를 알고 싶은 경우 다음과 같은 메소드를 활용하면 됩니다.

java.lang
Class Runtime


freeMemory

public long freeMemory()
Returns the amount of free memory in the system. Calling the gc method may result in increasing the value returned by freeMemory.
Returns:
an approximation to the total amount of memory currently available for future allocated objects, measured in bytes.

totalMemory

public long totalMemory()
Returns the total amount of memory in the Java Virtual Machine. The value returned by this method may vary over time, depending on the host environment.

Note that the amount of memory required to hold an object of any given type may be implementation-dependent.

Returns:
the total amount of memory currently available for current and future objects, measured in bytes.

Runtime class 에 totalMemory() 와 freeMemory() 가 존재하므로 이를 활용하여 메모리를 많이 사용하는 어플리케이션의 작동 상태를 조절할 수 있겠지요? 저의 경우는 반복적인 이미지 로드를 막기위해 프로그램에서 캐싱을 구현해놨었는데, 이부분을 메모리 사이즈와 관련해서 조절해봐야겠네요.


이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/169 관련글 쓰기

댓글을 달아 주세요


 Java 로 Desktop 에서 돌아가는 어플리케이션을 개발을 하다보면 자주 접하는 문제 중에 하나가 배포를 어떻게 할까입니다. 특히 Windows 에서 어떻게 하면 사용자가 편리하게 실행시킬 수 있을까를 고민하게 됩니다.
가장 기본적인 것으로는 배치 파일을 만들어서 그 파일 안에 "java MainForm" 과 같은 실행 스크립트를 적는 것이겠지요. 물론 매우 열악한 해결방법이긴 하지만요..
 
좀더 나아간다면 JNI 를 이용해서 Wrapper 를 제작하는 방법입니다. 즉 해당 플랫폼에 맞는 실행화일을 만들어서 이 실행화일이 Java 프로그램을 띄우게 되는 구조입니다. Eclipse 와 같은 프로그램들이 이와 같은 방법을 사용합니다. 이 경우, 별도의 설치 없이 파일을 특정 디렉토리에 설치하는 것만으로 실행할 수 있습니다. 하지만 프로그램이 실행되는데 필요한 조건들이 선결되어야 한다는 가정이 있어야 겠지요. 예를 들어 JRE 가 설치되어 있지 않다면 Wrapper 가 Java VM 을 부르지 못하니까요.

한단계 더 나아간다면 인스톨러를 활용하는 것입니다. 일반적인 어플리케이션의 설치과정처럼 인스톨러가 있어서 환경에 대한 체크도 해주고, 실행 화일도 만들어주는 형태입니다. 사용자에게 가장 친숙하게 다가설 수 있는 방법입니다.

 그외에도 JNLP(java network launching protocol) 를 이용한 Java Web Start 도 대안이 될 수 있을 것입니다. 이는 네트워크를 통하여 어플리케이션을 설치하고 실행시키는 방법입니다.

위의 내용과 관련된 자세한 자료는 다음을 참고하세요. 기본적인 설명과 다양한 툴을 소개하고 있습니다.

http://www.excelsior-usa.com/articles/java-to-exe.html

오늘 소개할 내용은 위의 문서에도 나오는 Launch4j 입니다.


사용자 삽입 이미지
Launch4j 는 Wrapper 를 통하여 exe 를 만드는 방법입니다. 전에는 JSmooth 라는 툴을 잘 써왔는데, 이상하게도 JSmooth 에서는 classpath 를 제대로 찾지 못하는 문제가 종종 발생해서 관련된 클래스를 모두 하나의 jar 에 묶어 embed 하여 exe 를 만들어야 했습니다. 제가 잘 못해서 그런지는 모르겠지만 여하튼 그것때문에 예전에 배포한 한 프로그램은 실행화일의 크기가 4MB 가 넘어갔습니다.  그리고 수정이 있을때마다 패키징을 다시해야하는 문제까지 생겼습니다. 그래서 다른 툴을 알아보게 되었는데, 오늘 테스트해본 툴이 바로 Launch4j 입니다.

Launch4j 의 홈페이지에 보면 2.x와 3.x 의 두가지 버전을 다운로드 할 수 있게 하고 있는데, 3.x 를 추천합니다. stable 버젼은 2.x 이지만 , 받아서 설치해보니 Classpath 와 관련된 내용을 설정할 수 없도록 되어 있더군요. 3.x 에서는 제대로 동작합니다.

제가 설치한 버젼은 3.0.0-pre2 입니다. 제일 궁금한 것이 classpath 에 있는 라이브러리를 제대로 참조하여 실행 시킬수 있는가 하는 것입니다. 그래야 Wrapper 를 한번 만들면 그 다음부터는 관련된 클래스화일만 변경해도 프로그램이 변경될 수 있으니까요.

그래서 간단한 프로그램을 작성해보았습니다. JFrame 안에 두개의 JTextField 를 위 아래에 놓고, 위에는 현재 시간을, 아래에는 이 내용을 Base64 로 인코딩한 정보를 보여주는 간단한 어플리케이션입니다. Base64 를 쓰는 이유는 별도의 라이브러리인  commons-codec을 제대로 참조하는지 테스트하기 위함입니다.

소스 파일은 두개로 나누어보았습니다.

[Main.java]

package com.naratalk.test;
import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;
import java.util.*;


public class Main extends JFrame{

    JTextField tfDate,tfEncoded;
    UserLib ul;
    public Main()
    {
        super("Main Application");
        ul = new UserLib();
        initUI();
        addUpdateTask();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
   
    private void initUI()
    {
        this.setLayout(new BorderLayout());
        tfDate = new JTextField();       
        tfDate.setBorder(new TitledBorder("Current Date & Time"));       
       
        this.add(tfDate, BorderLayout.NORTH);       
       
        tfEncoded = new JTextField();       
        tfEncoded.setBorder(new TitledBorder("Encoded"));       
       
        this.add(tfEncoded, BorderLayout.SOUTH);
    }
   
    private void addUpdateTask()
    {
        java.util.Timer timer = new java.util.Timer();
        timer.schedule( new TimerTask() {
            public void run()
            {
                tfDate.setText(ul.getSimpleDate());
                tfEncoded.setText(ul.encodeBase64(tfDate.getText()));
                addUpdateTask();
            }
        }, 1000);
    }
   
   
    public static void main(String[] args)
    {
        Main mf = new Main();
        mf.setSize(500, 500);
        mf.setVisible(true);
    }
}


[UserLib.java]

package com.naratalk.test;

import java.text.*;
import java.util.*;
import org.apache.commons.codec.binary.*;
public class UserLib {

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   
    public String getSimpleDate()
    {
        return sdf.format(new Date());
    }
   
    public String encodeBase64(String str)
    {
        Base64 base64 = new Base64();
        return new String(base64.encode(str.getBytes()));       
    }
}


그리고 Ant 의 build 파일은 전에 설명한 것처럼 dist 디렉토리에 jar 로 만들어놓습니다.

[build.xml]

<project name="InstallerTest" default="main" basedir=".">
    <target name="init">
        <property name="build" value="bin" />
        <property name="home" value="." />
        <property name="src" value="src" />
        <property name="dist" value="dist" />
        <property name="jarname" value="installertest.jar" />
        <property name="mainclass" value="com.naratalk.test.Main" />
    </target>

    <target name="compile" depends="init">
        <mkdir dir="${build}" />
        <javac srcdir="${src}"  destdir="${build}" >
            <classpath>
                    <fileset dir="lib">
                        <include name="**/*.jar" />
                    </fileset>
                </classpath>
       </javac>
    </target>

    <target name="makejar" depends="compile">
        <mkdir dir="${dist}" />
        <jar destfile="${dist}/${jarname}"
             basedir="${build}" >
             <manifest>
                <attribute name="Main-Class" value="${mainclass}" />
             </manifest>
        </jar>
    </target>

    <target name="main" depends="compile" />
   
</project>

그리고 dist 디렉토리에 이번에 쓰인 라이브러리인 commons-codec-1.3.jar 를 옮겨 놓습니다. 빌드를 수행하고나면 dist 디렉토리에 installertest.jar 와 commons-codec-1.3.jar 가 있게 됩니다.

이제 Launch4j 를 사용할 차례입니다.

뭐 특별한 내용은 없습니다. 워낙 심플한 것이라서요. 이번 글에서 테스트하고 싶은게 결국 classpath 를 제대로 인식하느냐의 문제이므로 이부분에 대해 적어보겠습니다.

사용자 삽입 이미지

Classpath 탭에서 Custom classpath 를 체크하고,  MainClass 에는 폴더버튼을 눌러 main class 가 있는 jar 를 선택하고, Edit Item 에 commons-codec-1.3.jar 라고 입력하고 Accept 버튼을 누르면 Classpath 에 추가가 됩니다.


사용자 삽입 이미지

그리고 JRE 탭에서는 Minimun JRE 버젼은 1.x.x 형태로 입력하시면 됩니다. 저의 경우는 Don't user private JREs 를 선택했습니다.

사용자 삽입 이미지
이렇게 하고 톱니모양의 버튼을 누르면 exe 파일이 생성되는데, 제 경우는 24KB 정도되는 크기입니다. 이 exe 파일은 Main-Class 를 불러주는 역할만을 하므로 Main-Class 의 이름이 바뀌지 않는다면 프로그램이 변경되더라도 exe 자체를 변경할 필요가 없습니다. 물론 classpath 에 추가할 내용이 있다면 다시 해야겠지만요. 오른쪽의 이미지는 이 프로그램을 실행한 화면입니다.



Launch4j 는 Ant 를 이용해서 작업을 진행할 수도 있습니다. 이와 관련해서는 다음번에 다시 한번 글을 올리도록하겠습니다.






이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/159 관련글 쓰기

댓글을 달아 주세요


Ant 를 쓰니 커맨드 라인에서의 작업이 무척이나 간편해집니다.

오늘은 뭐 별건 아니지만, 제가 쓰는 build.xml 화일을 소개해볼까 합니다. build.xml 화일이야 프로젝트에 맞도록 많이 바뀌겠지만, 오늘 이야기 하고자 하는 것은 가장 기본이 되는 부분입니다.

먼저 코드를 보시면..

<project name="DesKeyServerVer1.0" default="main" basedir=".">
        <target name="init">
                <property name="build" value="bin" />
                <property name="home" value="." />
                <property name="src" value="src" />
                <property name="dist" value="dist" />
                <property name="jarname" value="deskeyserver-1.0.jar" />
                <property name="mainclass" value="com.shinnara.deskeyserver.DesKeyServer" />
        </target>

        <target name="compile" depends="init">
            <mkdir dir="${build}" />
            <javac srcdir="${src}"  destdir="${build}" >
                <classpath>
                    <fileset dir="lib">
                        <include name="**/*.jar" />
                    </fileset>
                </classpath>
            </javac>
        </target>

    <target name="makejar" depends="compile">
        <mkdir dir="${dist}" />
        <jar destfile="${dist}/${jarname}"
             basedir="${build}" >
             <manifest>
                <attribute name="Main-Class" value="${mainclass}" />
             </manifest>
        </jar>
    </target>

    <target name="run" depends="compile">
        <java  classname="${mainclass}">
            <classpath>
                <pathelement path="${build}" />
                <fileset dir="lib">
                    <include name="**/jar" />
                </fileset>
            </classpath>
        </java>
    </target>

        <target name="main" depends="compile" />

        <target name="clean" depends="init">
                <delete dir="${build}" />
        </target>

        <target name="clean-bulid" depends="clean,compile" />

</project>


내용은 뭐 특별히 소개할만한게 없을지도 모르겠습니다만, ant 를 처음 사용하시는 분들을 위해 소개해봅니다.

제 작업 디렉토리를 보면 다음과 같습니다.

[shinnara ~/java/DesKeyServer/ver1.0]$ ls
CVS/       bin/       build.xml  dist/      lib/       src/

CVS 를 쓰다보니 CVS 라는 디렉토리가 있네요. 이부분은 그냥 넘어가도록 하고, 소스가 있는 src/, 컴파일된 클래스 파일이 있는 bin/ , jar 파일이 위치하는 dist/ , 프로그램에서 참조하는 라이브러리가 있는 lib/  그리고 위에 소개한 build.xml  입니다. bin/ 과 dist/ 는 ant 를 통해 자동으로 생성되는 디렉토리입니다. 물론 만들어놓아도 상관은 없습니다.

소스를 작성한 후, 컴파일을 하기 위해서는 현재 디렉토리에서 ant 를 입력합니다. ant 뒤에 target 을 지정할 수 있습니다. 지정하지 않을 경우 project 에서 default 로 명시한 target 을 수행하게 됩니다.

위의 빌드 파일을 보면

<project name="DesKeyServerVer1.0" default="main" basedir=".">


기본으로 설정된 target 이  main 임을 알 수 있습니다. 따라서 명령행에서 ant 라고 치거나 ant main 이라고 치는 것은 동일한 명령이 됩니다.

ant 에서 target 은 일련의 작업의 모음입니다. 작업은 task 라고 불리는데 디렉토리를 만들거나, 컴파일을 하거나, jar 로 묶는 등 ant 가 제공하는 기능을 의미합니다. ant 에서는 core task 와 optional task 의 두 부류의 task 를 지원하며, optional task 의 경우 별도의 라이브러리가 필요할 수 있습니다.

이름이 main 인 target 을 살펴볼까요?

<target name="main" depends="compile" />

별 내용없이 depends="compile" 이라고 되어 있네요. depends 는 선행조건을 의미합니다. 즉 main 이 실행되기 위해서는 compile 이 되어 있어야 한다는 것이지요.

그럼 이제 compile 을 살펴볼까요?

        <target name="compile" depends="init">
            <mkdir dir="${build}" />
            <javac srcdir="${src}"  destdir="${build}" >
                <classpath>
                    <fileset dir="lib">
                        <include name="**/*.jar" />
                    </fileset>
                </classpath>
           </javac>
        </target>

depends 에 init 가 있는 걸 보니, init가 뭘 하는지도 알아야겠군요. 위의 소스를 보면 여러 property들을 정의하고 있는 것을 찾아볼 수 있을 것입니다. ant 에서 property 를 참조하는 방법은 ${property_name} 입니다. 위의 소스를 보면 ${build} 라는 것을 볼 수 있는데, init 에서 build 에 대해 bin 으로 정의하고 있으므로

<mkdir dir="${build}" />




<mkdir dir="bin" />


과 같은 내용이라 할 수 있습니다.

javac 를 이용해서 컴파일을 하기 전에 먼저 디렉토리를 만드는 군요. 그리고나서 javac 를 이용하여 컴파일을 합니다. 커맨드라인에서 컴파일을 할 때 가장 귀찮은 것이 클래스 패스를 설정하는 것인데 ant 를 사용하면 무척이나 쉽게 할 수 있습니다. 위의 코드에서 fileset 부분은 lib 라는 디렉토리 밑에 있는 모든 .jar 파일을 클래스패스에 추가하라는 의미입니다.

그 외에도 컴파일러 옵션을 추가 할 수 도 있습니다.

<compilerarg value="-Xlint" />



이제 컴파일 부분이 끝났습니다. 컴파일을 했으면 실행을 시켜봐야 겠지요?


    <target name="run" depends="compile">
        <java  classname="${mainclass}">
            <classpath>
                <pathelement path="${build}" />
                <fileset dir="lib">
                    <include name="**/jar" />
                </fileset>
            </classpath>
        </java>
    </target>


compile 부분과 별로 다른게 없군요..

다음은 makejar 입니다.


    <target name="makejar" depends="compile">
        <mkdir dir="${dist}" />
        <jar destfile="${dist}/${jarname}"
             basedir="${build}" >
             <manifest>
                <attribute name="Main-Class" value="${mainclass}" />
             </manifest>
        </jar>
    </target>

여기서 주의 깊게 볼만한 내용은 manifest 부분입니다. jar 로 묶을 때 Main-Class 를 지정할 수 있습니다.

그외의 작업으로 clean, clean-build 가 있지만 너무 쉬운 내용이라 그냥 넘어가도록 하겠습니다.

즐거운 프로그래밍하세요~ ^^







이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/143 관련글 쓰기

댓글을 달아 주세요


Triple DES 는 DES 알고리즘을 세번 적용한 것으로 DES 알고리즘에 비해 보안성이 뛰어납니다. Java 에서는 기본적(1.4부터인가요?)으로 Triple DES 를 지원하고 있습니다. 여기서는 Triple DES 를 이용하여 암호화 하고 복호화 하는 방법에 대해 간략히 알아보고자 합니다.

암호화의 방법에는 크게 대칭키 방식과 비대칭키 방식이 있음을 알고 계실 것입니다. 오늘 보고자하는 Triple DES 는 대칭키 방식으로 암호화할 때나 복호화할 때 모두 같은 키를 사용하는 방법입니다. 따라서 키를 생성해야 하는데, 키를 생성하는 방법은 다음과 같습니다.

        KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
        keyGenerator.init(168);  
        Key key = keyGenerator.generateKey();


KeyGenerator 는 javax.crypto 패키지 안에 있습니다.  init(168)은 168bit 의 크기를 갖는 키를 생성하겠다는 의미이며, 112 를 사용할 수 도 있습니다. 이 때 만들어지는 키의 내용을 확인해 보려면 다음과 같은 코드를 사용할 수 있습니다.

        for (int i = 0; i < key.getEncoded().length ; i++)    {
            System.out.print((key.getEncoded())[i] + " ");
        }


키의 크기를 168로 할 경우, 위의 코드는 24개의 값을 보여주게 됩니다.

키가 만들어 졌으니 이 키를 이용해서 암호화를 수행해야 합니다.


       
        Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);


Cipher 를 통해 암호화/복호화를 수행할 수 있으며 DESede/ECB/PKCS5Padding 은 각각 algorithm/mode/padding 을 의미합니다.  init()를 통해 동작 형태를 지정할 수 있으며, 위의 코드는 암호화를 의미합니다.

실제 암호화는 doFinal() 메소드를 통해서 수행하게 됩니다.

       
        byte[] plainText = "Welcome to Shinnara's World".getBytes("UTF8");
        byte [] cipherText = cipher.doFinal(plainText);


다음 코드를 통해 암호화된 내용을 확인해 보실 수 있습니다.


       
        for (int i = 0; i < cipherText.length ; i++)    {
            System.out.print(cipherText[i] + " ");
        }


이제 암호화된 내용을 다시 복호화하는 방법입니다. 키의 내용이 byte[] encodedkey 로 전달되었다고 가정합니다.


       
SecretKeyFactory sfk = SecretKeyFactory.getInstance("DESede");
        SecretKey sk = sfk.generateSecret(new DESedeKeySpec( encodedkey));



복호화 모드로 Cipher 를 만듭니다.

     
cipher.init(Cipher.DECRYPT_MODE, sk);


복호화 하는 것 역시 doFinal 메소드를 사용합니다.

     
byte [] decryptedText = cipher.doFinal(cipherText);


내용을 확인해보면, 암호화 하기 전의 내용이 제대로 표현되었음을 알 수 있습니다.

너무 쉽죠? ^^







이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/142 관련글 쓰기

댓글을 달아 주세요


유닉스나 리눅스에서 서버를 돌릴 경우, 백그라운드로 실행을 하는 경우가 많은데요, 이때는 명령의 끝에 & 를 붙이면 된다는 것을 잘 아실겁니다.

[shinnara ~/java/ResourceServer/ver1.0]$ java -cp bin/ ResourceServer &
[1] 80460
[shinnara ~/java/ResourceServer/ver1.0]$ Server Started... [7779]

[shinnara ~/java/ResourceServer/ver1.0]$



보시는 바와 같이 ResourceServer 가 7779번 포트로 부터 시작되었음을 알려주는 메시지가  정상적으로 표시되는데, 명령의 끝에 &를 붙였기 때문에 [1]80460 이라는 메시지가 추가적으로 생기게 됩니다. 이 번호가 프로세스 번호를 의미합니다.

[shinnara ~/java/ResourceServer/ver1.0]$ kill 80460
[shinnara ~/java/ResourceServer/ver1.0]$ ps
  PID  TT  STAT      TIME COMMAND
80328  p0  S      0:00.15 -bash (bash)
80487  p0  R+     0:00.00 ps
80303  p1  I+     0:00.04 -bash (bash)
[1]+  Exit 143                java -cp bin/ ResourceServer
[shinnara ~/java/ResourceServer/ver1.0]$


실행시킬때 알게된 80460 이라는 PID를 이용해서 kill을 시켰더니 해당 프로세스가 종료되었음을 알려줍니다.

만약 서버를 실행시킨지 오래되어서 PID 를 모른다면 ps 를 이용해서  PID 를 알아 낼수 있습니다.

저의 경우는 다음과 같은 명령을 사용하기도 합니다.

[shinnara ~/java/ResourceServer/ver1.0]$ kill -9   `ps -ef | grep "java" | grep -v grep | grep "Resource" | awk '{print $1}'`

간혹 시스템에 따라서는 awk '{print $2}' 라고 적어야 하는 경우도 있나봅니다. 제가 참고해본 자료들이 대부분이 $2 를 사용하더군요. 제 시스템에서는 첫번째가 PID 로 나오기때문에 $1 를 사용했습니다.

위의 내용을 잘 활용하면 서버의 실행과 중지를 편리하게는 스크립트도 작성할 수 있을 것입니다. ^^




이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/140 관련글 쓰기

댓글을 달아 주세요


동료 직원이 어제 C에서 Java 라이브러리를 사용하는 법을 물어보길래, 찾아본 자료입니다.

그동안은 Java 에서 C를 사용하는 방법에 대해서만 고려하고 있었는데, 이런 방법도 있군요.

이올린에 북마크하기(0) 이올린에 추천하기(0)
TAG c, java
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/139 관련글 쓰기

댓글을 달아 주세요

TCP_NODELAY 적용

Computer/Programming/Java 2007/09/13 15:28 by Shinnara
socket 을 이용하여 통신을 하는데, 반응 속도가 느리다면 tcp_nodelay 옵션을 사용해보는 것도 좋은 방법일 수 있습니다.

tcp socket 의 경우, 기본적으로 Nagle 알고리즘을 사용합니다. Nagle 알고리즘은 전송의 효율을 위해, 적은 데이터를 모아서 큰 패킷으로 한번에 보내는 방식입니다. 따라서 이경우 적은 양의 데이터가 자주 발생되는 시스템에서는 반응속도가 느려질 수 있습니다.

이를 해결하기 위해 socket.setTcpNoDelay(true) 를 해주면 Nagle 알고리즘이 해제되면서, 패킷이 준비되면 바로 보내게 됩니다.

setTcpNoDelay

public void setTcpNoDelay(boolean on)
                   throws SocketException
    Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm).

Parameters:
    on - true to enable TCP_NODELAY, false to disable.
Throws:
    SocketException - if there is an error in the underlying protocol, such as a TCP error.
Since:
    JDK1.1
See Also:
    getTcpNoDelay()


하지만 TCP_NODELAY 를 사용하게 되면 반응속도가 증가될 수는 있으나, 네트워크의 효율이 감소하고 시스템의 부하가 늘어나게 되니 꼭 필요한 경우에만 사용하시기 바랍니다.

C에서는 다음과 같이 해주시면 됩니다.

nt nValue = 1;

setsockopt( TCP_NODELAY, &nValue, sizeof(int), IPPROTO_TCP );

이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/138 관련글 쓰기

댓글을 달아 주세요


예전에, 아주 먼 옛날에  JSP 로 작업하면서 파일 업로드 때문에 고생했던 기억이 있습니다. 하지만 이제는 너무 너무 쉽더군요.

해결 방법은 Filter 를 이용하는 것입니다. 해당 코드는 아래에서 확인하실 수 있습니다.

http://jetty.mortbay.org/xref/org/mortbay/servlet/MultiPartFilter.html

전에 위의 코드에서 일부분을 추려서 Standalone 서버에서 돌린 적이 있었는데 이번에는 이것을 Tomcat 에 붙여보았습니다. 그러기 위해서는 몇개의 관련 파일들을 함께 가져와야 합니다.

컴파일을 하고, web.xml 에 해당 Filter 를 등록해주면 준비는 모두 끝납니다.

저의 web.xml 화일의 일부입니다.


  <filter>
        <filter-name>MultiPartUploadFilter</filter-name>
        <filter-class>org.mortbay.servlet.MultiPartFilter</filter-class>
  </filter>
  <filter-mapping>
        <filter-name>MultiPartUploadFilter</filter-name>
        <url-pattern>/*</url-pattern>
  </filter-mapping>


너무 당연하다시피 필터를 정의하고, url 을 매핑해주는 것입니다.

소스코드를 확인해보시면 아시겠지만, 컨텐트 타입이 "multipart/form-data" 로 시작하는 경우 해당 필터가 작동하도록 되는 것입니다.

필터가 제대로 동작하게 되면, 서블릿쪽에서의 작업이 매우 간편해집니다.

위의 필터는 파일을 전송받아 임시 디렉토리에 저장한 뒤, 파일에 대한 내용을 request 의 attribute 로 설정하게 됩니다.  이때 attribute 의 이름은 input 태그의 name 이 됩니다.

만약 <input type="file" name="uploadfile1"> 으로 Form 을 사용한 경우,

request.getAttribute("uploadfile1") 을 통해 해당 파일의 정보를 알 수 있습니다.

또한 원래 파일의 이름은

request.getParameter("uploadfile1") 으로 구하실 수 있습니다.

너무 간단하지 않나요? ^^






이올린에 북마크하기(0) 이올린에 추천하기(0)
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/132 관련글 쓰기

댓글을 달아 주세요


처음 Groovy 를 알게 된 것이 Ant 의 Task 중의 Script task 를 보다가 접하게 되었는데, Ant 에서 Groovy 를 활용하는 방법에 대한 내용입니다. Script 를 사용할 수 도 있지만 오늘 보여드릴 예제처럼 taskdef 를 이용하실 수도 있습니다.

먼저 하고자 하는 것은 현재 폴더에 있는 파일들이 과거 특정 시점(여기서는 ant 를 실행했던 직전 시점) 이후에 고쳐진 파일이 무엇인지를 찾아내는 것입니다. 그간 이곳의 글들을 보신 분들은 아시겠지만 작업 환경을 꾸며나가는 과정에서 Ant 와 FTP 를 활용하고 있습니다. 그런데 FTP 를 이용한 업로드의 경우, 수정되지 않은 파일까지 모두 전송하게 되므로, 업로드 후에 빌드를 하려고 하면 처음부터 다시 빌드가 됩니다. 따라서 수정되지 않은 파일은 업로드 하지 않도록 하는 방법을 생각해보던 중에 Script 를 써서 해결하면 되지 않을까하여 접근하게 되었습니다.

일단, 오늘은 위에서 언급한 대로 새로 고쳐진 파일이 있는지를 확인합니다. 확인하는 방법은 직전의 ant 작업의 끝에 특정 파일을 생성합니다. 그리고 다시 ant 를 하게 하면 그 파일의 시간을 기준으로 삼고 새롭게 수정된 내용이 있는지를 확인하는 방법입니다.

제가 작성한 build.xml 파일입니다.

<?xml version="1.0" encoding="UTF-8" ?>
<project name="syncer" default="main" basedir=".">   
    <property name="dir.lib" value="/usr/local/groovy/lib"/>
    <property name="file.last" value="timestamp.log" />
    <taskdef name="groovy"
             classname="org.codehaus.groovy.ant.Groovy">
        <classpath>
          <fileset dir="${dir.lib}">
            <include name="**/*.jar"/>
          </fileset>
        </classpath>
    </taskdef>

    <target name="main">
        <groovy>
        lastTimestamp = (new File("${properties['file.last']}")).lastModified()
       
        files = new File(".").listFiles()
        files.each { if( it.lastModified() > lastTimestamp )
                      {
                        println it.toString() + " is newer!"
                      }
         }
       
        </groovy>
        <touch file="${file.last}" />
    </target>
</project>

실행하면 다음과 같은 결과를 보여줍니다.

[shinnara ~/groovy/work/syncer]$ touch a.out
[shinnara ~/groovy/work/syncer]$ ant
Buildfile: build.xml

main:
   [groovy] ./a.out is newer!

BUILD SUCCESSFUL
Total time: 4 seconds


groovy 를 제대로 활용하면 더욱 편리한 작업환경을 만들수 있을 것 같습니다.







이올린에 북마크하기(0) 이올린에 추천하기(0)
TAG ant, Groovy
0 Trackback, 0 Comment

TRACKBACK :: http://naratalk.com/trackback/131 관련글 쓰기

댓글을 달아 주세요

1 2 
다...... (251)
Computer/Programming (106)
Links (14)
책 읽는 즐거움 (6)
끄적임 (61)
즐거운 과학 나라 (7)
일본 (2)