[[TableOfContents]] == 개요 == jcodegen 은 java 소스코드를 생성하는 java 라이브러리입니다. * 홈페이지 : http://kldp.net/projects/jcodegen/ * 다운로드 : http://kldp.net/frs/?group_id=943 * 한글문서 : [jcodegen] * 영문 문서 : 추가 예정(언젠가는...) == 만든 이유 == 예전에 자바 소스코드를 프로그램을 이용해서 만들어야 했던 적이 있습니다. 그래서 관련 라이브러리를 찾아보았는데 저는 eclipse 의 AST 라이브러리를 알아보았습니다. 그때 제가 보았던 문서는 http://help.eclipse.org/help31/topic/org.eclipse.jdt.doc.isv/guide/jdt_api_manip.htm?resultof=%22%61%73%74%22%20 인데, 중간쯤에 보면 Hello World 를 출력하는 소스코드를 만드는 예제가 나옵니다. 저는 그 예제를 보고 "직접 만들어야겠다."는 결심을 했습니다. -_- eclipse AST 라이브러리는 소스코드의 모든 부분을 객체화해서 처리합니다. 그 결과 이미 있는 소스코드를 분석하여 뭔가를 하는데는 좋지만 백지 상태에서 소스코드를 만들어야 할 경우 코딩량이 상당히 많아 집니다. jcodegen 의 기본 방침은 '''적당히 쓰기 편하게''' 입니다. 블럭레벨(클래스, 메소드 선언, 각종 제어구조 등) 은 객체를 이용하지만 개별문장에는 문자열을 씁니다. 그 결과 jcodegen 은 소스코드를 만드는데는 좋지만 이미 있는 소스코드를 분석하여 뭔가를 하는 것은 거의 불가능합니다. == 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"); } } }}} == 사용법 == 여기에 나온 사용법은 0.2 버전 기준입니다. 0.1.1 과는 많이 다릅니다. 0.2 버전은 현재 소스코드 저장소에서 직접 소스를 다운받으면 됩니다. === 전체적인 구조 === java 의 소스코드는 트리구조로 표현됩니다. 따라서 jcodegen 도 내부적으로 트리구조가 되며 jcodegen의 대부분의 구성요소는 생성자의 첫번째 매개변수로 자신의 부모를 받습니다. === 소스 파일 생성 === jcodegen 에서 소스 파일은 {{{net.kldp.jcodegen.code.JavaSourceFile}}} 클래스로 표현됩니다.처음 생성할 때 이 파일이 속하는 패키지명을 매개변수로 받습니다. 소스 파일을 쓰려면 {{{write}}} 메소드를 호출합니다. 이 메소드는 소스 파일을 만들 디렉토리 경로를 받습니다. === 클래스, 메소드, 필드 생성 === 클래스, 메소드, 필드 정의를 만들 때는 {{{net.kldp.jcodegen.code.type}}} 패키지에 있는 클래스를 이용하면 됩니다. === 제어 구문 === if , for 등의 제어구문과 관련된 것은 {{{net.kldp.jcodegen.code}}} 패키지에 있습니다. ==== 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.IfBlock}}} 나 {{{net.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 인지가 결정됩니다. ==== 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"); } }}} === 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}}} 을 만듭니다. TypeName 의 팩토리 메소드는 Class 객체나 타입의 문자열이름을 받습니다. generic type 부분에는 다른 {{{TypeName}}} 객체를 받는 것도 가능합니다. === 변수명 평가 기능 사용 === 예를 들어 {{{ List list = new ArrayList() ; }}} 이런 문장을 만드는 경우 단순히 문자열 연결 연산을 이용하면 {{{ 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()을 호출한 결과로 치환합니다. == 진행상황 == 2007/04/05 0.2 버전 릴리즈