Eclipse Plug-in Development

현재 저는 연구실 프로젝트 겸 석사학위를 위하여 Verification Integrated CODesign Environment(이하 VICODE)라고 하는 하드웨어/소프트웨어 동시설계 개발환경을 이클립스 플러그인 형태로 개발하고 있습니다. 블로그에서 제가 쓰고자 하는 글은 이클립스 플랫폼을 기반으로 하는 통합개발환경(IDE)를 구축하는 방법에 관한 것 입니다. 따라서 동시설계에 관련된 내용이나 이 툴이 제공하는 기능의 상세 대해서는 생략하겠습니다만, 간략히 하드웨어와 소프트웨어가 모두 존재하는 시스템을 설계하는 도구정도로 생각할 수 있습니다.

올해 초 swing으로 작성하던 어플리케이션을 갑자기 이클립스 기반으로 다시 개발해야 했을 때, 많이 난감했던 기억이 납니다. 이클립스 플러그인 개발에 대한 국내서적은 거의 없었고 번역서 조차도 찾기 어려웠습니다. 물론 몇몇 분들이 쓰신 이클립스 강좌가 있었지만 IDE를 개발하기에는 부족했습니다. 다행히 “자바 개발자를 위한 이클립스 바이블”이라는 책이 출판되었고 이 책을 차근차근 공부하면서 이클립스와 친해질 수 있었지만 책의 구성 탓인지 번역 탓인지 모르겠지만 잘 읽히지가 않아서 인내를 배워야 했습니다.

다른 일에 집중하다가 오랜만에 다시 제가 개발한 플러그인을 뜯어 보았을 때, 잊은 부분이 적지 않았고 제가 개발한 플러그인을 이어서 개발할 후임자를 위해서도 정리가 필요하다고 생각되었습니다. 또한 제가 정리한 글들이 이클립스 플러그인 개발을 처음으로 접하는 분들의 삽질을 조금이라도 줄여드릴 수 있다면 큰 보람이 될 것 입니다.

다른 사람을 가르치면서 배우는 것이 많다는 이야기가 있듯이 그동안 공부한 지식들을 정리 및 공개하여 많은 분들의 피드백을 받으며 제가 공부한 지식을 더욱 굳건히 하고자 하는 욕심도 있습니다. 틀린 부분이나 보충할 부분이 있으면 가감없이 덧글로 남겨주시면 큰 도움이 될 것 같습니다. 질문도 주저없이 덧글로 남겨주시면 제가 모르는 것은 공부해서라도 답변할 수 있도록 노력할 것 입니다.

블로그에서 제가 다룰 것으로 예상되는 부분은 다음과 같습니다. 개발을 진행하면서 틈틈히 쓰는 글이라 예정된 순서가 없고 중간에 필요하다고 생각되는 부분은 추가될 수도 있습니다. 제가 아는 바를 모두 정리한 후 적었던 글들을 체계적으로 정리할 생각입니다.

제가 쓰는 글들이 강좌라고 할 것 까지는 없지만, 인터넷의 강좌나 마소의 기사를 보면 반말(?)을 사용하는 경우가 많아서 저도 글쓰기의 편의를 위해서 반말로 글을 작성하고자 합니다. 플러그인 개발에 대한 글들은 제가 다음의 책들을 공부한 결과를 기반으로 하고 있기에 참조하시면 도움이 될 것 같습니다.

자바 개발자를 위한 이클립스 바이블
Official eclipse 3.0 FAQs
The Definitive Guide to SWT and JFace

Action

Eclipse 플러그인 개발에서 action이라고 하는 것을 간단히 정의하자면 메뉴나 툴바의 아이콘을 클릭하였을 때 수행되는 어떤 일이라고 이야기 할 수 있다. IDE에서 소스를 편집하는 editor 혹은 무언가 정보를 보여주는 viewer를 제외하면 대부분의 기능이 바로 action의 형태로 기여된다고 할 수 있다.

action에 대해서 공부하기 이전에 알아야 할 것이 있는데 action set 이라고 하는 녀석이다. 이는 menu와 action으로 구성되어 있는 action을 정의하기 위한 논리적인 집합이라고 생각하면 되는데 일단 다음의 plugin.xml의 일부를 읽어보자.

<!– Action Definition –>
  <extension
        point=”org.eclipse.ui.actionSets“>
        <actionSet
              id=”edu.kaist.vicode.actionset”
              label=”Co-Design Actions”
              visible=”false”>

           <menu
                 id=”edu.kaist.vicode.interfacemenu”
                 label=”Interface”>
              <separator name=”actionGroup”/>
           </menu>
            <menu
                 id=”edu.kaist.vicode.verificationmenu”
                 label=”Verification”>
              <separator name=”actionGroup”/>
           </menu> 
            <!–  Generate Interface for Simulation –>
           <action
                 class=”edu.kaist.vicode.actions.SimulationInterfaceGenAction
                 disabledIcon=”icons/config-linker.gif”
                 enablesFor=”1″
                 hoverIcon=”icons/config-linker.gif”
                 icon=”icons/config-linker.gif”
                 id=”edu.kaist.vicode.actions.simulationinterfacegen”
                 label=”Generate Interface for Simulation”
                 menubarPath=”edu.kaist.vicode.interfacemenu/actionGroup”
                 style=”push”
                 toolbarPath=”edu.kaist.vicode.interfacemenu”
                 tooltip=”Generate Interface for Simulation”>
              <!– Project에 대해서만 적용가능한 Action –>
              <selection class=”org.eclipse.core.resources.IProject”/>
           </action>

action에 대한 기여는 action 기여를 위한 확장점(extenstion point)이 있어서 여기에 바로 기여(extenstion)하는 것이 아니라 그 것을 둘러싼 action set에 해당하는 확장점(org.eclipse.ui.actionSets)에 기여 함으로써 이루어진다.

menu는 action의 분류를 제공한다. 아래에 정의되어 있는 각 action들이 UI에 나타나려면 적어도 하나 이상의 menu를 지정해야한다. 위의 플러그인을 수행하면 “Interface”와 “Verification”이라는 최상위 메뉴가 (실제로 그 메뉴에 기여하는 action이 존재하는 경우) 메뉴바에 나타난다. menu의 정의는 메뉴바뿐만 아니라 툴바의 분류가 되기도 하며 어떻게 사용되는지는 action 태그의 menubarPathtoolbarPath에 달려있다.

메뉴는 하나 이상의 separator를 가진다. 쉽게 설명하자면 인터넷 익스플로러에 파일 메뉴를 클릭하면 4가지 그룹의 메뉴가 나타나는 것을 볼 수 있으며 이는 4개의 separator를 하나의 menu 태그에서 정의하는 것으로 구현될 수 있다. 그리고 각각의 action은 그들이 속해야 하는 menubarPath = “menu의 id/separator의 name” 형태로 자신이 UI의 어디에 나타나야 하는지를 지정할 수 있다. 툴바에 기여하는 경우에는 separator를 지정할 필요가 없다.

여기서 action set의 중요한 속성을 하나 살펴보자. Eclipse 플랫폼은 다수의 플러그인을 포함하여 동작하고 있다. 모든 플러그인이 제공하는 action과 menu가 UI에 모두 나타난다고 상상해보면 어떨까?

나중에 perspective에 대한 글에서 자세히 설명하겠지만, 이와 같은 문제를 해결하기 위해서 플러그인이 제공하는 환경과 기능을 관리할 수 있는 perspective라는 개념을 도입하였다. Eclipse 플랫폼에 기본적으로 포함되어 있는 perspective에는 Java, Debug, Resource등이 있다. Java라는 perspective를 선택하면 자바프로그램을 편집하는데 필요한 에디터와 뷰어 그리고 툴바의 아이콘등이 나타나는 것을 알 수 있다. Java와 Resource perspective를 변경하면서 툴바를 살펴보면 그 차이를 알 수 있을 것이다.

action set에서 visible 속성을 이용하여 action set에 포함되는 menu와 action들이 perspective와 상관없이 무조건 UI에 나타날 것인지 아닌지를 true, false로 지정할 수 있다. 다시 말하면 이 값이 true인 경우 항상 action set에 포함되는 것들이 UI에 표현되고, false인 경우에는 특정 perspective에 action set을 포함시켜 그 perspective로 전환되었을 때만 UI에 표현하게 한다. 자세한 내용은 perspective에 대한 글에서 다룰 생각이다.

지금까지는 action set의 구조와 메뉴(menubarPath)와 툴바(toolbarPath)에 각 action이 어떻게 추가될 수 있는지를 살펴보았다. 지금부터는 action 자체에 대해서 살펴보자. action에서 지정되는 클래스는 IWorkbenchWindowActionDelegate 인터페이스를 상속해야 한다.

public class SimulationInterfaceGenAction implements
IWorkbenchWindowActionDelegate {
private IStructuredSelection selection;

public void dispose() {
// TODO Auto-generated method stub
}
public void init(IWorkbenchWindow window) {
// TODO Auto-generated method stub
}
public void run(IAction action)
{
// TODO Auto-generated method stub
IProject project = (IProject) selection.getFirstElement();



}
public void selectionChanged(IAction action, ISelection selection) {
// TODO Auto-generated method stub
if (selection instanceof IStructuredSelection) {
  this.selection = (IStructuredSelection) selection;
}

}
}

action을 구현한 클래스는 위와 같은 구조를 가지게 된다. 툴바의 아이콘이나 메뉴를 클릭하면 해당 action의 run() 메서드가 호출되어 특정 작업이 수행된다. 여기서 추가로 알아야 할 것은 특정 action은 특정 selection과 관계를 맺을 수 있다는 것! 즉 action을 수행하는 대상이 무엇인지 알아야 한다. 예를 들어 JDT에서 메서드의 이름을 바꾸는 리펙토링 액션을 수행한다고 한다면 분명 선택한 메서드가 있을 것이다. 이러한 선택을 selection이라고 하며 selectionChanged() 메서드에서 이 selection을 얻어올 수 있다. 이렇게 얻어온 selection을 통해 특정 영역에 해당하는 action을 수행하게 된다.

action은 selection의 형태에 따라 enable/disable 될 수 있다. 이를테면 어떤 action은 프로젝트(IProject)만을 선택으로 받아들일 수 있고, 어떤 action은 디렉토리(IFolder)에 대해서만 작업을 수행할 수 있다.  이는 plugin.xml에서 selection 태그로 지정할 수 있다. 심지어 name 속성을 이용하면 특정 파일명에 대해서만 action을 제한할 수 있다.

Are You Windows Vista Ready?


여기를 방문하면 현재 자신의 컴퓨팅환경에서 Vista를 돌릴 수 있는 확인할 수 있다. 연구실에서 사용하고 있는 나의 시스템에 자부심을 가지고 있었는데 어이없게도 결과는 Minimum Pass! Vista의 GUI가 화려하기 때문인지 다른 부분은 충분하였지만 그래픽 카드에서 많은 부족함을 나타내었다. 살펴보면 CPU 파워도 그리 넉넉한 편은 아닌 것 같다.

어렸을 때 부터 항상 새로운 운영체제가 나타나면 꼭 설치를 해보아야 직성이 풀렸다. 덕분에 리눅스는 수십번도 더 깔았던 것 같다. 매우 다양한 배포판이 수시로 배포되었기 때문이다.

Window Vista RC1이 발표되었고, 여기를 방문하면 인증키를 얻을 수 있다. 현재 64비트 버젼으로 이미지 파일을 다운받고 있는데 그 용량이 3.59GB로 DVD를 이용해야한다. 과연 오늘 새로운 윈도우를 볼 수 있을것인가?

Preference

Preference는 IDE에서 흔히 볼 수 있는 환경설정을 의미한다. 우리가 어떤 특정 플러그인을 개발할 때, 그 플러그인에 특화된 환경설정이 필요한 경우가 있다. 예를 들어 설명하자면, 이전에 소개한 바 있지만 Esterel 언어는 소프트웨어 언어인 C로도 컴파일이 되고 하드웨어 언어인 Verilog로도 컴파일이 가능하다.

Preferences in Eclipse

Esterel 언어를 위한 개발환경에서 위와 같은 환경설정이 존재한다면 각 항목의 체크유무에 따라 소스코드의 컴파일시에 생성되는 아웃풋 파일의 종류를 결정할 수 있다. 이와 같은 작업을 위해서 알아야 할 확장점(Extension Point)에는 두가지가 있으며 이를 포함한 plugin.xml의 일부분의 내용은 다음과 같다.         

  <!– vicode preference page –>
  <extension
        point=”org.eclipse.ui.preferencePages“>
     <page
           class=”kr.ac.kaist.vicode.preference.VicodePreferencePage
           id=”kr.ac.kaist.vicode.preferencepage”
           name=”VICODE”/>
  </extension>
  <!– vicode preference initial data –>
  <extension
        point=”org.eclipse.core.runtime.preferences“>
     <initializer class=”kr.ac.kaist.vicode.preference.Defaults“/>
  </extension>

첫번째 확장점인 org.eclipse.ui.preferencePages을 이용해 Preference page에 기여할 수 있으며, 두번째 확장점인 org.eclipse.core.runtime.preferences에서는 환경설정의 기본값을 지정할 수 있다.

우선 첫번째 확장점을 위한 클래스인 VicodePreferencePage를 살펴보자. 이 클래스는 PreferencePage를 상속하며 UI를 만드는 createContents() 메서드, Restore Defaults 버튼을 눌렀을 때 호출되는 performDefaults(), OK 버튼을 눌렀을 때 호출되는 performOk() 메서드등으로 구성되어 있다. 메서드 이름만으로 각 메서드의 역할을 알 수 있을 것이다.

다음 생성자(Constructor)와 performOk() 메서드를 통해서 환경설정을 어떻게 이용하는지 알아보자.

public VicodePreferencePage() {
super();
setPreferenceStore(Activator.getDefault().getPreferenceStore());
setDescription(“Development environment settings for VICODE”);
fCheckBoxes = new ArrayList<Button>();
}

public boolean performOk() {
IPreferenceStore store = getPreferenceStore();
for (int i = 0; i < fCheckBoxes.size(); i++) {
  Button button = (Button) fCheckBoxes.get(i);
  String key = (String) button.getData();
  store.setValue(key, button.getSelection());
}
return super.performOk();
}

생성자에서는 setPreferenceStore() 메서드를 호출하여 preference page에서 사용할 preference 저장소를 지정한다. performOk() 메서드에서는 preference 저장소를 얻어와 여기에 setValue() 메서드를 이용해 <key, value>의쌍으로 환경설정을 저장한다. 저장할 수 있는 값들은 boolean, double, float, int, String, long 등의 simple data type중에 하나이어야 한다. 위의 예제는 플러그인이 UI에 기여하는 경우(Activator 클래스가 AbstractUIPlugin를 상속하는 경우)에 해당하며 그렇지 않은 경우(Plugin 클래스를 상속하는 경우)에는 getPluginPreferences 메서드를 사용해야 한다.

정리하면 생성자에서 preference 저장소를 지정한 후, createContents() 메서드에서 preference page에 들어가는 UI를 작성한다. 그리고 각 버튼에 해당하는 코드를 작성한다. 이는 UI에 입력된 정보를 환경설정에 저장하거나 그 반대의 일이 될 것이다.

지금부터는 환경설정의 기본값(Default)를 지정하기 위한 클래스인 Defaults를 살펴보자. 여기서는 간단히 initializeDefaultPreferences() 메서드에서 setDefault() 메서드를 호출해서 각 key에 해당하는 기본값을 지정하기만 하면 된다.

public void initializeDefaultPreferences() {
// TODO Auto-generated method stub
// get preference store
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(“CDE_ESTEREL_C”, true);
store.setDefault(“CDE_ESTEREL_VERILOG”, true);
store.setDefault(“CDE_ESTEREL_BLIF”, false);
}

지금까지 Eclipse plug-in 개발시에 preference를 활용하는 방법을 살펴보았다. 아래 첨부한 소스코드를 읽어보면 쉽게 이해할 수 있을 것이다. 예제코드에서 UI 부분은 JDT의 JavaBasePreferencePage 클래스의 것을 응용하였다.

1201822046.java1126047004.java

Visitor 패턴

Visitor 패턴은 AST(Abstract Syntax Tree)와 같은 자료구조를 순회하면서 필요한 작업을 정의하기에 용이하다. 이를 처음으로 접하게 된 것은 CEC(Comlumbia Esterel Compiler)의 소스코드를 분석할때였다. CEC는 Esterel 소스코드를 파싱하여 이를 XML 형태의 IR(Intermediate Representation)로 저장한다. CEC는 C++ 언어로 프로그램되어 있는데 XML 형태의 IR은 C++의 클래스 구조로 구성된 AST에 대응된다.

Esterel Source Code (Text) – AST (C++ Classes) – IR (XML)


배경은 이쯤에서 정리하고 본론으로 들어가보자. Esterel 언어는 하드웨어 언어인 Verilog로 컴파일되기도 하고 소프트웨어 언어인 C로 컴파일 되기도 하는 신기한(?)언어인데, Esterel 소스코드로 부터 C언어 코드가 나오기까지 IR을 XML 포맷으로 유지하며 여러단계의 독립적인 프로그램을 거치게 된다. 이때마다 XML 파일을 읽어 AST를 구축한 후 Visitor 패턴을 이용해 AST를 순회하며 필요한 작업을 수행한다. 물론 그러한 작업에 의해 AST가 수정되면 수정된 상태의 AST가 다시 XML형태로 저장된다.

Esterel Source Code – Parser – Expander – Dismantler – GRC synthesizer – C generator – C Source code


필자가 하고 있는 일은 동시설계 개발환경에서 Esterel 언어를 기반으로 임베디드 시스템을 정의하는 방법론을 제시하고 그 것으로 부터 임베디드 시스템 구현에 필요한 여러가지 인터페이스를 자동으로 생성하는 것이다. 임베디드 시스템을 XML 형태의 언어로 정의하도록 하고 이를 파싱하여 저장하는 나름의(?) AST를 자바 클래스로 구축하였다. 인터페이스 생성등 다양한 작업이 AST를 기반으로 이루어질 것 이며, 여기서는 AST에 저장되어 있는 정보를 다시 XML로 출력하는 프로그램을 예로 들어 Visitor 패턴을 설명하려고 한다.  

모든 AST의 클래스들은 다음과 같은 AbstractSpec 추상클래스를 상속한다. 여기에는 Visitor 패턴을 위한 메서드인 welcome 추상 메서드가 정의되어 있다.

package kr.ac.kaist.vicode.spec;
public abstract class AbstractSpec {
  public abstract void welcome(Visitor v);
}
그리고 당연히 모든 AST 노드에 해당하는 클래스들은 welcome 메서드를 정의해야 하는데 그 내용은 모두 다음과 같이 동일하다. 굳이 설명하자면 파라메터로 넘어온 visitor를 이용해서 자기 자신에 해당하는 visit 메서드를 호출하게 하는 것 이다.

public void welcome(Visitor v)
{
    v.visit(this);
}

다음으로 해야할 일은 Visitor 클래스를 생성하는 것이다. Visitor 패턴을 이용하는 클래스는 이 클래스를 상속받아서 visit 메서드를 구현하기만 하면 된다. 즉 파라메터로 입력받은 AST 노드에 해당하는 작업을 visit 메서드에 정의하면 된다.

package kr.ac.kaist.vicode.spec;

public abstract class Visitor {
  public abstract void visit(Spec n);
  public abstract void visit(Communication n);
  public abstract void visit(Api n);
  public abstract void visit(Rule n);
  public abstract void visit(DataFunc n);
  public abstract void visit(SignalFunc n);
  public abstract void visit(SignalDecl n);
  public abstract void visit(State n);
  public abstract void visit(Transition n);
  public abstract void visit(Set n);
}

Visitor 패턴을 사용하기 위한 준비작업은 모두 끝이 났다. 지금부터는 AST에 저장되어 있는 정보를 Visitor 패턴을 이용하여 AST를 순회하면서 XML형태로 출력하는 예제를 살펴본다. Spec2Xml 클래스는 Vistor 클래스를 상속하여 Visitor 패턴을 구현하고 있다. 각 visit 매서드에서 하는 일은 크게 두가지다. 하나는 해당 노드에 해당하는 작업을 수행하는 것이고, 나머지 하나는 자식 노드를 방문하게 하는 일인데 이과정을 위해서 단순히 각 AST 노드의 welcome 메서드를 호출하는 print 메서드를 정의하였다.

package kr.ac.kaist.vicode.spec.util;

import java.util.*;
import kr.ac.kaist.vicode.spec.*;

public class Spec2Xml extends Visitor {
  public void print(AbstractSpec n) {
       n.welcome(this);
  }

  public void visit(Spec n) {
       output(“<spec>”);
       print(n.getCommunication());
       print(n.getApi());
       print(n.getRule());
       output(“</spec>”);
  }

Vistor 패턴의 시작은 간단하다. AST의 최상위 노드에 대해서 print 메서드를 호출하면 자신의 자식노드에 대해서 visit 메서드를 호출하게 되고 이러한 일련의 과정을 통해 경우에 따라서 모든 노드를 방문하며 특정 작업을 수행할 수 있다. 위의 예제에서 Spec은 AST의 최상위 노드에 해당하며 이는 3가지 자식노드 (communication, api, rule)을 가지고 있으며 각각에 대해서 visit 메서드가 호출되도록 한다.  

지금까지 살펴본 것 처럼 Visitor 패턴은 AST와 유사한 자료구조를 빠짐없이 탐색하며 특정작업을 수행하기에 적합한 방법론을 제공한다. 특히 컴파일러나 인터프리터와 같이 AST를 사용하는 프로그램에서 잘 활용하면 유지보수가 용이하고 코드가 깔끔한 코드를 작성하는데 큰 도움이 될 것 같다.