· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
jcodegen


1. 개요

jcodegen 은 java 소스코드를 생성하는 java 라이브러리입니다.

2. 만든 이유

예전에 자바 소스코드를 프로그램을 이용해서 만들어야 했던 적이 있습니다. 그래서 관련 라이브러리를 찾아보았는데 저는 eclipse 의 AST 라이브러리를 알아보았습니다. 그때 제가 보았던 문서는 http://help.eclipse.org/help31/topic/org.eclipse.jdt.doc.isv/guide/jdt_api_manip.htm?resultof="ast" 인데, 중간쯤에 보면 Hello World 를 출력하는 소스코드를 만드는 예제가 나옵니다. 저는 그 예제를 보고 "직접 만들어야겠다."는 결심을 했습니다. -_-

eclipse AST 라이브러리는 소스코드의 모든 부분을 객체화해서 처리합니다. 그 결과 이미 있는 소스코드를 분석하여 뭔가를 하는데는 좋지만 백지 상태에서 소스코드를 만들어야 할 경우 코딩량이 상당히 많아 집니다.

jcodegen 의 기본 방침은 적당히 쓰기 편하게 입니다. 블럭레벨(클래스, 메소드 선언, 각종 제어구조 등) 은 객체를 이용하지만 개별문장에는 문자열을 씁니다. 그 결과 jcodegen 은 소스코드를 만드는데는 좋지만 이미 있는 소스코드를 분석하여 뭔가를 하는 것은 거의 불가능합니다.

3. Hello World 예제

import java.io.IOException;

import net.kldp.jcodegen.Modifier;
import net.kldp.jcodegen.Visibility;
import net.kldp.jcodegen.code.JavaSourceFile;
import net.kldp.jcodegen.code.Statement;
import net.kldp.jcodegen.code.type.ClassType;
import net.kldp.jcodegen.code.type.MethodBlock;


public class CreateHelloWorld {
    public static void main(String[] args) throws IOException {
        // 소스 파일 생성
        JavaSourceFile source = new JavaSourceFile("example") ;
        
        // 클래스 생성
        ClassType hello = new ClassType(source , Visibility.PUBLIC , "HelloWorld") ;
        hello.addCommentLine("'Hello World' printing example program.") ;
        
        // main 메소드
        MethodBlock main = new MethodBlock(hello , Visibility.PUBLIC , "main") ;
        main.addModifier(Modifier.STATIC) ;
        main.setReturnType(void.class) ;
        main.addParam(String[].class, "args", "command line args") ;

        // 출력문
        new Statement(main , "System.out.println(\"Hello \"+\"World\")") ;
        
        source.write(System.getenv("TEMP")) ;
        
    }

}
위의 코드는 $TEMP/example/HelloWorld.java 소스코드를 만듭니다.
package example ;



/** 
 * 'Hello World' printing example program.
 */ 
public class HelloWorld {
    /** 
     * 
     * @param args command line args
     */ 
    public static  void main (String[] args) {
        System.out.println("Hello "+"World");
    }
}

4. 사용법

여기에 나온 사용법은 0.2 버전 기준입니다. 0.1.1 과는 많이 다릅니다. 0.2 버전은 현재 소스코드 저장소에서 직접 소스를 다운받으면 됩니다.

4.1. 전체적인 구조

java 의 소스코드는 트리구조로 표현됩니다. 따라서 jcodegen 도 내부적으로 트리구조가 되며 jcodegen의 대부분의 구성요소는 생성자의 첫번째 매개변수로 자신의 부모를 받습니다.

4.2. 소스 파일 생성

jcodegen 에서 소스 파일은 net.kldp.jcodegen.code.JavaSourceFile 클래스로 표현됩니다.처음 생성할 때 이 파일이 속하는 패키지명을 매개변수로 받습니다.

소스 파일을 쓰려면 write 메소드를 호출합니다. 이 메소드는 소스 파일을 만들 디렉토리 경로를 받습니다.

4.3. 클래스, 메소드, 필드 생성

클래스, 메소드, 필드 정의를 만들 때는 net.kldp.jcodegen.code.type 패키지에 있는 클래스를 이용하면 됩니다.

4.4. 제어 구문

if , for 등의 제어구문과 관련된 것은 net.kldp.jcodegen.code 패키지에 있습니다.

4.4.1. if 사용

if 문을 단독으로 사용할 때는 net.kldp.jcodegen.code.IfBlock 를 그냥 쓰면 됩니다.
IfBlock is_0 = new IfBlock(parent , "num == 0") ;
new Statement (is_0 , "System.out.println(\"num is 0\")") ;
if(num == 0) {
   System.out.println("num is 0") ;
}

만약 else if 나 else 도 필요한 경우 net.kldp.jcodegen.code.IfWrapper 를 만든다음 이 객체에 net.kldp.jcodegen.code.IfBlocknet.kldp.jcodegen.code.ElseBlock 를 추가해야 합니다.
        String num_name = "num" ;
        new Statement(main , "int @[num_name] = 3").setVariable("num_name", num_name) ;
        
        IfWrapper if_list = new IfWrapper(main) ;
        for(int i = 0 ; i < 5 ; ++i) {
            IfBlock num_check = new IfBlock(if_list , "@{num_name} == @{i}") ;
            num_check.setVariable("num_name", num_name) ;
            num_check.setVariable("i", i) ;
            
            Statement print = new Statement(num_check , "System.out.println(\"@{i}\")") ;
            print.setVariable("i", i) ;
            
        }
        
        ElseBlock else_block = new ElseBlock(if_list) ;
        new Statement(else_block , "System.out.println(\"Unknown\")") ;
        int num = 3;
        if(num == 0) {
            System.out.println("0");
        }
        else if(num == 1) {
            System.out.println("1");
        }
        else if(num == 2) {
            System.out.println("2");
        }
        else if(num == 3) {
            System.out.println("3");
        }
        else if(num == 4) {
            System.out.println("4");
        }
        else {
            System.out.println("Unknown");
        }
jcodegen 에는 else if 를 나타내는 클래스는 따로 없고 IfBlock 이 추가된 순서에 따라 if 인지 else if 인지가 결정됩니다.

4.4.2. try 사용

try , catch , finally 는 각각 net.kldp.jcodegen.code.TryBlock , net.kldp.jcodegen.code.CatchBlock , net.kldp.jcodegen.code.FinallyBlock 로 나타냅니다. 사용할 때는 먼저 net.kldp.jcodegen.code.TryWrapper 객체를 만든 뒤 여기에 추가해야 합니다.
        TryWrapper trys = new TryWrapper(main) ;
        
        TryBlock try_block = new TryBlock(trys) ;
        new Statement(try_block , "System.out.println(\"TRY\")") ;
        
        CatchBlock catch_runtime = new CatchBlock(trys , RuntimeException.class , "e") ;
        new Statement(catch_runtime , "System.out.println(\"CATCH\")") ;
        
        FinallyBlock final_block = new FinallyBlock(trys) ;
        new Statement(final_block  , "System.out.println(\"FINALLY\")") ;
        try {
            System.out.println("TRY");
        }
        catch (RuntimeException e) {
            System.out.println("CATCH");
        }
        finally {
            System.out.println("FINALLY");
        }

4.5. import 문 처리하기

jcodegen 은 내부적으로 import 문과 관련된 것을 자동으로 처리합니다. 예를 들어 특정 메소드가 java.util.ArrayList 타입을 매개변수로 받는 경우
    method.addParam(java.util.ArrayList.class , "list" , "") ;
이렇게 하면 ArrayList 에 대한 import 문이 적절하게 추가됩니다.

어떤 경우에는 문자열 내에서 타입명을 사용해야 할 경우가 있습니다. 예를 들어
    List list = new ArrayList() ;
이런 문장을 만들어야 할 경우가 있습니다. 이런 경우를 위해 jcodegen 은 net.kldp.jcodegen.base.TypeName 클래스를 제공합니다.
    TypeName list = TypeName.create(parent , java.util.List.class) ;
    TypeName arr_list = TypeName.create(parent , java.util.ArrayList.class) ;
    new Statement(parent , list + " list = new " + arr_list + "()") ;
TypeName 객체에 대해 toString() 메소드가 호출되면, 내부적으로 import 에 대한 처리를 한 후 소스코드에서 사용할 수 있는 타입명을 리턴합니다.

TypeName 클래스는 generic type 도 지원합니다.
    TypeName list_of_string = TypeName.create(parent , java.util.List.class , java.lang.String.class) ;
이 코드는 소스코드에 List<String> 을 만듭니다.

TypeName 의 팩토리 메소드는 Class 객체나 타입의 문자열이름을 받습니다. generic type 부분에는 다른 TypeName 객체를 받는 것도 가능합니다.

4.6. 변수명 평가 기능 사용

예를 들어
   List<String> list = new ArrayList<String>() ; 
이런 문장을 만드는 경우 단순히 문자열 연결 연산을 이용하면
    TypeName list_of_string = TypeName.create(parent , java.util.List.class , java.lang.String.class) ;
    TypeName arr_list_of_string = TypeName.create(parent , "java.util.ArrayList" , "java.lang.String") ;
    new Statement(parent , list_of_string + " list = new " + arr_list_of_string + "()") ;
이렇게 됩니다. 하지만 이경우 문자열 덧셈 연산 때문에 생성되는 코드가 한눈에 들어오지 않습니다. 이는 생성해야 하는 코드가 복잡해질수록 더 심해질 것입니다. 이런 경우를 위해 변수명 평가 기능을 제공합니다.

문자열 내에 @{} 로 변수가 사용되는 부분을 만든 다음, setVariable 로 해당 변수에 치환되는 객체를 설정하면 됩니다.
    TypeName list_of_string = TypeName.create(parent , java.util.List.class , java.lang.String.class) ;
    TypeName arr_list_of_string = TypeName.create(parent , "java.util.ArrayList" , "java.lang.String") ;
    
    Statement stmt = (parent , "@{list} = new @{arr_list}()") ;
    stmt.setVariable("list" , list_of_string) ;
    stmt.setVariable("arr_list" , arr_list_of_string)

setVariable의 키는 올바른 자바 변수명 문자열이면 되고, 값은 임의의 java 객체가 됩니다. 내부적으로 키에 대한 값을 찾아 toString()을 호출한 결과로 치환합니다.


5. 진행상황

2007/04/05 0.2 버전 릴리즈




sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2007-09-07 21:21:25
Processing time 0.0063 sec