Whiteship's Note


불친절한 코드 생성기 5(일단 끝) - 프리마커 기반 코드 생성기 만들기

모하니?/Coding : 2009. 12. 3. 18:02


서비스 인터페이스를 만듭니다.
public interface CodeGenerationService {

    void generateController(String module, Class domainClass) throws CodeGenerationException;

}


일단은 컨트롤러만 생성할테니, 컨틀로러 생성 메서드만 만듭니다. 이때 만들어질 컨트롤러가 속할 module의 이름과 어떤 도메인 클래스에 대한 컨트롤러인지 알려줍니다.

문제가 생기면 RuntimeException을 던집니다. 자, 이제 이 인터페이스를 프리마커 기반으로 구현할 시간이군요.

public class FreemarkerCodeGenerationService implements CodeGenerationService {

    private Configuration configuration;
    private Template controllerTemplate;
    private String destinationDir;
    private Stack<File> createdFilesWhileGenerateController;

    public FreemarkerCodeGenerationService(Configuration configuration, String controllerTemplateName, String destinationDir){
        this.destinationDir = destinationDir;
        this.configuration = configuration;
        try {
            this.controllerTemplate = configuration.getTemplate(controllerTemplateName);
        } catch (IOException e) {
            throw new CodeGenerationException("template file loading fail with [" + controllerTemplateName + "]", e);
        }
    }

    public void generateController(String module, Class domainClass) throws CodeGenerationException {
        createdFilesWhileGenerateController = new Stack<File>();
       
        Map<String, String> map = new HashMap<String,  String>();
        String className = domainClass.getSimpleName();
        map.put("module", module);
        map.put("domainClass", className);
        map.put("domainName", ClassUtils.getShortNameAsProperty(domainClass));

        File desticationFolder = new File(destinationDir);
        boolean created = desticationFolder.mkdir();
        if(created)
            createdFilesWhileGenerateController.push(desticationFolder);
       
        desticationFolder = new File(destinationDir + "/" + module);
        created = desticationFolder.mkdir();
        if(created)
            createdFilesWhileGenerateController.push(desticationFolder);

        File destinationFile = new File(destinationDir + "/" + module + "/" + className + "Controller.java");
        FileWriter writer = null;

        try {
            writer = new FileWriter(destinationFile);
            controllerTemplate.process(map, writer);
            writer.flush();
            writer.close();
            System.out.println(destinationFile.getAbsolutePath()  + " created");
             createdFilesWhileGenerateController.push(destinationFile);
        } catch (IOException e) {
            throw new CodeGenerationException("destincation file creation fail", e);
        } catch (TemplateException e) {
            throw new CodeGenerationException("template processing fail", e);
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

...

    public void deleteController() {
        while(!createdFilesWhileGenerateController.empty()){
            File file = createdFilesWhileGenerateController.pop();
            System.out.println(file.getAbsolutePath());
            boolean deleted = file.delete();
            if(deleted)
                System.out.println(file.getAbsolutePath() + " deleted");
            else
                System.out.println(file.getAbsolutePath() + " not deleted");
        }
    }
}

왠지 좀.. 부끄럽네요. 으흑;;

저 클래스에 필요한 속성(configuration 타입 객체, 템플릿 파일들이 들어있는 위치, 컨틀롤러 템플릿 파일)을 봄싹 프로젝트에 맞게 기본으로 가지고 있는 클래스를 하나 만듭니다.

public class SpringSproutCodeGenerationService extends FreemarkerCodeGenerationService {

    private static final String DESTINATION_DIR = "src/springsprout/modules";
    private static final String CONTROLLER_TEMPLATE_NAME = "controller.ftl";

    public SpringSproutCodeGenerationService(Configuration configuration) {
        super(configuration, CONTROLLER_TEMPLATE_NAME, DESTINATION_DIR);
    }

}

이제 테스트를 해봅니다.

public class SpringSproutCodeGenerationServiceTest {

    @Test
    public void generationTst() throws IOException {
        Configuration configuration = new Configuration();
        configuration.setObjectWrapper(new DefaultObjectWrapper());
        configuration.setDirectoryForTemplateLoading(new FileSystemResource("doc/template").getFile());

        assertNotNull(configuration);

        FreemarkerCodeGenerationService service = new SpringSproutCodeGenerationService(configuration);
        service.setDestinationDir("test/springsprout/modules");
        service.generateController("test", Study.class);

        assertTrue(new File("test/springsprout/modules/test/StudyController.java").exists());
        service.deleteController();
        assertFalse(new File("test/springsprout/modules/test/StudyController.java").exists());
    }
}

끝...




top

  1. 처루 2009.12.04 07:56 PERM. MOD/DEL REPLY

    하루만에 다 만드셨군요. ㅎㄷㄷ 어제 구상2까지 보고는 그냥 추상 클래스 만들어서 상속 받으면 안될까 생각도 했었는데, 그 새 다 만드셨으니 토 달기도 어려워졌어요. ㅎㅎ

    Favicon of http://whiteship.me BlogIcon 기선 2009.12.04 10:05 PERM MOD/DEL

    앗 괜찮아요. 아직 완성은 아니라 일단 완성이라서요;
    오늘은 저기에 기능을 추가해서 DAO 코드도 자동완성 하려구요.

Write a comment.


불친절한 코드 생성기 4 - 템플릿 만들기

모하니?/Coding : 2009. 12. 3. 17:48


가장 단순한 컨트롤러 코드를 가져다가 군대 군대 코드를 끼워 넣을 지점에 프리마커 태그(?)로 표시를 합니다.

...
@Controller
@SessionAttributes("${domainName}")
public class ${domainClass}Controller {

    @Autowired ${domainClass}Service service;
    @Autowired ${domainClass}Validator validator;

    @RequestMapping(value="/${domainName}/list.do")
    public void list(Model model) throws ServletRequestBindingException {
        model.addAttribute("list",service.getAll());
    }

    @RequestMapping(value="/${domainName}/{id}.do")
    public String view(Model model, @PathVariable int id) {
        model.addAttribute(service.get(id));
        return "${domainName}/view";
    }

    @RequestMapping(value="/${domainName}/add.do", method=RequestMethod.GET)
    public void add(Model model) {
        model.addAttribute(new ${domainClass}());
    }
...


이런식입니다. 참 쉽죠?
- 자바 코드에서 최대한 제네릭하게 편집한 다음 프리마커 편집기로 가져오는 것이 좋겠습니다.
- 태그로 교체할 때는 replace 툴을 이용합시다.
- 친절한 코드 생성기를 만들 때는 템플릿을 어떻게 만드냐에 따라 코드 생성기와 모델의 복잡도가 달라질 겁니다.
- 저는 불친절한 코드 생성기를 만들고 있기 때문에 맘편히 쉽게 만들었습니다.(생성뒤 필요한 import는 알아서 하도록..ㅋ)


top

Write a comment.


불친절한 코드 생성기 3 - Freemarker 학습 테스트

모하니?/Coding : 2009. 12. 3. 15:19


참조: http://freemarker.org/docs/pgui_quickstart_all.html

프로젝트의 sandbox에 패키지를 하나 만들고, main 메서드로 학습 테스트를 작성할 클래스 하나와 프리마커 템플릿 하나를 만듭니다. 그리고 위 참조 링크에서 코드를 가져다가 살짝 바꿔서 테스트 해봅니다.

템플릿 파일은 매우 간단하게;;

${message}

main 메서드에 들어갈 코드는 위 링크에서 복사해서 가져온 다음에 File은 스프링의 ClassPathResource를 이용해서 바꾸고, 템플릿 파일 이름은 위에서 만든 템플릿 파일이름으로, Map에는 message에 들어가야 할 것만 넣어 봅니다.

public class SimpleExample {

    public static void main(String[] args) throws Exception {
        /* ------------------------------------------------------------------- */
        /* You should do this ONLY ONCE in the whole application life-cycle:   */

        /* Create and adjust the configuration */
        Configuration cfg = new Configuration();
        cfg.setObjectWrapper(new DefaultObjectWrapper());
        cfg.setDirectoryForTemplateLoading(new ClassPathResource("/sandbox/freemarker").getFile());

        /* ------------------------------------------------------------------- */
        /* You usually do these for many times in the application life-cycle:  */

        /* Get or create a template */
        Template temp = cfg.getTemplate("testTemplate.ftl");

        /* Create a data-model */
        Map root = new HashMap();
        root.put("message", "Hello Freemarker");

        /* Merge data-model with template */
        Writer out = new OutputStreamWriter(System.out);
        temp.process(root, out);
        out.flush();
    }
}

결과는 그냥 눈으로 확인합니다.

Hello Freemarker
Process finished with exit code 0

끝!

이제 Configuration, Template, Map의 관계를 알았으니 본격적으로 코드 생성기를 작성해 보겠습니다.

top

Write a comment.


불친절한 코드 생성기 2 - 구상2

모하니?/Coding : 2009. 12. 3. 14:38


아랫 글에 댓글이 달렸지만, 이 기능에 대해 성윤군과 논의를 하다가 IDE가 제공하는 코드 템플릿 기능에 대해 들었습니다. 아차.. 싶더군요. 그래서 생각을 해봤습니다.

IDE 템플릿 기능을 이용할 경우

장점
- 매우 간편하게 코드 생성을 할 수 있습니다. 단축키를 입력하면 코드가 좌르륵.. 생겨나겠죠.

단점
- IDE 별로 템플릿을 작성해 줘야 합니다. 현재 봄싹 개발자 중 20%는 인텔리J IDE를 사용하고 있습니다. 따라서 이클립스용과 인텔리J용 템플릿을 만들어둬야 합니다.
- 개발자가 사용하는 IDE 마다 템플릿을 등록해줘야 합니다. 템플릿 파일도 버전 관리에 포함시켜서 들고다니면 배포하는 방법은 간편하지만 등록은 수동으로 해줘야 합니다. 그건 이클립스 자체를 패키징
- 자동 생성할 파일이 여러개면 매번 파일 만들고 그 파일 돌아다니면서 코드 템플릿 생성 해야함

템플릿 생성 프레임워크를 이용하여 구현할 경우

장점
- 배포 방법 고려할 필요 없음. 소스 코드에 들어있으니 그냥 실행.
- 나중에 여러 파일을 한 방에 생성하는 것도 가능

단점
- 코딩 쬐끔 해야 함.
- 라이브러리 추가 해야 함.

결국은 그냥 코딩 쬐끔 하는 편으로 기울었습니다.
top

Write a comment.


불친절한 코드 생성기 1 - 구상

모하니?/Coding : 2009. 12. 3. 12:44


0123


사실 좀 고민입니다. OSAF처럼 GenericController를 만들까. 그냥 저 코드를 찍어내주는 코드 생성기를 만들까. 어떤게 더 사용하기 편하고 확장하기 편할까? GenericControlle를 확장하기 좋게 잘 만들면 되겠지만, 클래스 만들 때 프레임워크 코드를 상속해야 한다는게 귀찮기도 하고, 그래서 어차피 그 부분을 찍어내는 코드 생성기가 필요해지는 상황입니다. 게다가 GenericController를 만들려면 GenericService 인터페이스까지도 있어야 할테니, 일은 더 커지겠죠; 이러다 배보다 배꼽이 커지겠다 싶어서...

일단은 코드 생성기부터 만들기로 마음 먹었습니다. 간단하게;; 위와 비슷한 코드를 그냥 찍어내 주고, 컴파일 에러가 나던 말던 알아서 고쳐서 쓰도록!!!

이름하야.. 불친절한 코드 생성기!


top

  1. 처루 2009.12.03 14:20 PERM. MOD/DEL REPLY

    이클립스의 코드 탬플릿을 사용해보시는건 어떤가요?

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.12.03 14:24 신고 PERM MOD/DEL

    넵! 바로 그것도 생각을 해봤습니다.

Write a comment.


이번주 토요일 IBM dW "웹 개발 다반사"

모하니?/Planning : 2009. 12. 3. 10:16


참조: http://www.ibm.com//developerworks/kr/event/seminar/dwlive_1205/index.html

지난 번 KSUG 모임에서는 봄싹 소스 코드를 보면서 이런 저런 스프링 기능을 보여드리려고 했으나, 불발했습니다. 개인적으로 많이 아쉽고 속상했지만, 아직은 그런 모임 운영이 전문적이지 못한 신생 운영진이었는데다가, 이전 발표 하신 분들의 내용이 워낙 참신하고 재미있었기에 제 발표 시간이 없어진 것에 대해 머리로는 이해할 수 있었습니다. 그래도 아쉬운 속내는 어쩔 수가 없었죠.

그런데 이번에는 조금 다른 형식으로 봄싹을 소개할 자리가 마련됐습니다. 당일에도 스터디가 있어서 시작 시간에 맞춰서 갈 수 있을지는 의문이지만.. 발표 시간에는 늦지 않게 참석할 생각입니다.

최종 선정된 발표 주제는 다음과 같습니다.
* 괜찮은 오픈 API 제공하기 + VLAAH API 소개 - 홍민희
* 봄싹 싸이트(http://springsprout.org) 개발 협업 방법 및 사용 기술 - 백기선
* 코드 품질 포탈 SONAR 적용기 - 고경철
* 흑백무성영화한편! (HTTP) - 이동욱
* 자바스크립트 삽질(실수?) 베스트 10 - 장동수
* (Startup기업 CEO의 관점에서 본) 기술의 경제학 - 정지웅
* Realtime Web 간보기 - 김석준
* Spring Framework with JavaFX - 이승철
* 추상 계층의 딜레마 - 황대산
* timelog 업무 적용 실험기 - 송승렬

흠.. 개인적으로는 자바스크립트 삽질 베스트, Realtime Web, 추상 계층의 딜레마 JavaFX with Spring(설마 JavaFX를 이용해서 스프링 프로그래밍을 한 건 한 건 아니겠죠? 제목이 좀...)이 기대됩니다. 다른건 저에게 생소한 것들(VLAAH, timelog, SONAR)이라;; 뭔지 잘 몰라서..;

'모하니? > Planning' 카테고리의 다른 글

블로깅 계획  (0) 2010.05.06
백기선's 2010 Weekly Report - 2/23  (2) 2010.02.23
목표 실천은 진행 중  (12) 2010.02.17
백기선 2010 목표  (12) 2010.02.15
봄싹 Career Path  (6) 2009.12.17
이번주 토요일 IBM dW "웹 개발 다반사"  (2) 2009.12.03
봄싹을 알리러 갑니다.  (6) 2009.11.24
[Atlassian] 이직 계획  (2) 2009.11.02
[ToDo] 20091016  (0) 2009.10.16
[ToDo] 오늘 할 일 - 할일(예상 소요 시간)(실제 소요 시간)  (4) 2009.10.15
2009년 마무리로 할 일  (4) 2009.10.08
top

  1. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.12.03 15:18 PERM. MOD/DEL REPLY

    발표 축하드립니다. :)
    못 가보니 아쉽네요.
    발표도 잘 하시고 다른분들 발표도 재밌게 잘 보고 오세요.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.12.03 15:20 신고 PERM MOD/DEL

    넹~ 비록 짧은 시간이지만 스터디 이외의 장소에서 하는 발표는 오랜만이네요.ㅋ

Write a comment.