네이쳐와 빌더

PDE 2010. 10. 15. 11:42

네이쳐

네이쳐(Nature)는 한 프로젝트가 갖는 일부 성질들에 대한 추상성을 제공한다. 예를 들어 한 개의 플러그인 프로젝트는, 자바 프로젝트의 특성과, PDE의 특성을 갖는다. 이런 프로젝트의 특성을 네이쳐라고 하며, 한 프로젝트는 여러개의 네이쳐를 가질 수 있다.

네이쳐의 주임무는 다음과 같다.

  • 프로젝트가 해당 특성을 갖기 위해 필요한 자원의 준비와 해제
  • 필요한 경우, 프로젝트의 빌드 방법을 플랫폼에 알려줌

네이쳐의 선언

   
      
         
      
      
      
   

최근 RCP등과 더불어 추가된 확장점들은 확장 노드(<extension />)가 아닌 그 하위 IConfigurationElement에서 상세 기술 및, ID지정을 많이 하지만, 네이쳐는 확장노드 그자체에 이름과 ID가 기술된다. 오래전 부터 존재하던 확장점들은 대체로 이런 성격이다. 익스텐젼 노드 자체가 네이쳐 노드란 사실을 빨리 눈치채지 못하면 네이쳐 노드를 만들려고 헤맬수도 있다.

3라인에서 지정된 네이쳐 클래스는 IProjectNature 인터페이스를 구현해야 하며, 네이쳐가 설치되거나 해제될 때 처리해야 할 일을 맡는다. 5라인에서 연관된 빌더 ID를 지정해 줄 수 있지만, 네이쳐 확장점 자체가 빌더를 관리해 주지는 않으며, 빌더절에서 다시 설명한다. required-nature 노드를 이용하여 디펜던시를 지정할 수 있다. 네이쳐 추가/삭제시 조건이 만족되지 않으면 Exception을 발생시켜준다.

프로그래밍으로 네이쳐 추가

IProject p = ...

IProjectDescription description = p.getDescription();
List<String> natureIds = new ArrayList<String>();

// 기존 네이쳐 아이디들 추가
natureIds.addAll(Arrays.asList(description.getNatureIds()));
natureIds.add("추가할 네이쳐 아이디");

// 프로젝트 디스크립션 갱신
description.setNatureIds(natureIds.toArray(new String[natureIds.size()]));

// 프로젝트에 새로운 디스크립션 반영
p.setDescription(description, new NullProgressMonitor());

프로젝트를 변경 할 때는 IProjectDescription을 얻어 수정 한 다음, 프로젝트에 다시 그 디스크립션을 넘김으로서 반영한다. 새로운 네이쳐가담긴 디스크립션이 프로젝트에 주어질 때 다음과 같은 일이 일어난다.

Project -> NatureManager: 네이쳐 구성 요청
NatureManager -> 네이쳐: (de)configure
네이쳐 -> Project: 구성

네이쳐 Configuration

네이쳐가 프로젝트에 추가되면, 네이쳐 객체는 configure 메소드를 콜백 받는다. 이때 필요한 런타임 라이브러리를 클래스패스에 추가한다던가, 빌더를 구성한다던가와 같은 작업을 수행 할 수 있다. 이 작업은 대칭성을 이루어 deconfigure()를 통해 해제된다. 하지만, 네이쳐의 추가와 삭제는 FacetedProject와 달리 사용자가 자유자재로 추가/삭제할 수 있는 UI가 없기 때문에, 규칙의 강제성은 적은 편이다. 네이쳐는 위저드 내지, 프로그래머가 작성한 코드에 의해서만 추가 삭제된다.

빌더

빌더란 워크스페이스에 리소스 변경이 있을 때마다 (자동 빌드의 경우) 또는 사용자가 명시적으로 빌드 요청을 내릴 때, 모든 프로젝트들에 대해 주어진 빌드 스펙에 따라 결과물을 만들어내는 개체들이다. 이 과정중에 문제가 생기면 리소스 마커를 이용하여 에러나 경고등을 문제 뷰에 나타나게 하기도 한다.

빌더의 선언


  
    
    
  

나머지 속성은 직관적이므로 생략하고, isConfigurable이라는 것은 사용자가 직접 증분, 클린, 풀 빌드를 선택할 수 있는지 여부를 체크하게 하겠느냐라는 것이다. 프로젝트의 속성 다이얼로그의 빌더 항목에 표시된다.

빌더 클래스의 주요 인터페이스

protected IProject[] build(int kind, Map args, IProgressMonitor monitor);
public ISchedulingRule getRule(int kind, Map args);

1번라인이 주 진입점으로 실제로 빌드 작업을 수행해야 하는 곳이다. 리턴값은 빌드에 의하여, 다른 프로젝트가 영향을 받아, 다시 빌드해야 하는 경우를 위한 것으로 Frozen될 때 까지 반복되니 주의하자. kind는 빌드 타입을 나타내는 것으로 전체 빌드인지, 증분 빌드인지, 클린 빌드인지등의 정보가 넘어오며, 맵을 통해 옵션들이 전달된다. 두번째 라인의 스케쥴링 룰은 이 빌드 작업이 다른 작업과 배타적으로 동작하기 위한 스케쥴링 룰을 정의 한다. 기본 구현은 워크스페이스 록이다. 스케쥴링 룰에 대해서는 Job과 스케쥴링 룰문서를 참조할 수 있다.

빌드 스펙 구성

빌드관련 기능을 가진 네이쳐는 프로젝트를 configure()할 때, 프로젝트의 빌드 스펙을 변경하여 빌더를 사용할 수 있도록 해야 한다. 프로젝트들은 빌더 커맨드라는 것을 가지고 있는데, 프로젝트의 컨텐츠가 변경될 때마다(자동 빌드의 경우) 가지고 있는 커맨드들을 수행하게 된다. 이러한 커맨드의 집합을 프로젝트의 빌드 스펙이라고 한다.

빌더 커맨드

public interface ICommand {
	public Map getArguments();
	public String getBuilderName();
	public boolean isBuilding(int kind);
	public boolean isConfigurable();
	public void setArguments(Map args);
	public void setBuilderName(String builderName);
	public void setBuilding(int kind, boolean value);
}

결국 빌더 커맨드는 어떤 빌더에게 어떤 변수들을 가지고 빌드를 시킬 것이냐 하는 명세서 객체이다. 빌더 커맨드는 IProjectDescription의 newCommand()로 새로 만들 수 있다. 주의 할 점은 get/set builderName의 인자는 빌더 확장점에서 주었던 ID이다. 이름이 아니다. 확장점에서 준 이름은 UI에서만 표시되고, 코드상으로 빌더 이름은 ID를 의미한다.

description.setBuildSpec(newCommands);
getProject().setDescription(description, new NullProgressMonitor());

프로젝트가 원래가지고 있던 빌더 커맨드 리스트에 새 커맨드를 확장하여, 디스크립션에 추가한다음, 디스크립션을 프로젝트에 제출하면, 이제 이 프로젝트는 주어진 빌더 커맨드에 의해, 리소스가 변경될 때마다 빌더를 호출한다.

Faceted Proejct

프로젝트 네이쳐는 프로젝트에 대하여 상대적으로 정적이다. 예를 들어 자바 프로젝트가 C++ 프로젝트로 변모하는 일은 없다. 하지만 J2EE프로젝트에 스프링, 스트러츠, 태그 라이브러리등의 추가삭제는 훨씬 더 빈번하게 일어난다. 네이쳐의 정적 구조로는 이들에 대응하기 힘들기 때문에, 웹 파생 프로젝트 특성은 Nature가 아닌 Facet으로 처리한다. 따라서 관련 니즈가 있는 사람들은 Faceted Project에 관한 문서들을 찾아 보면 된다. 해당 소스는 BeA가 대부분 기여했는데, IBM과 달리 주석이 빈약하고, 소스 변동이 심해 고생을 좀 각오해야한다. IBM만세. 나 일좀 시켜줘.

Posted by 지이이이율
,

사실 에디터란, 편집되는 대상에 따라 평가 방식이 달라지기 때문에, 그 전체에 대해 품질 점검표를 만들 수는 없습니다. 본 기사에서는 이클립스로 만들어진 에디터로서 지켜야 할 기본 품질 속성에 대해 알아봅니다. 100점 부터 시작하여, 자신이 만든 에디터가 감점 요소에 해당되면 해당 점수만 큼 감점하시면 됩니다.

ps. 이클립스를 배우면 배울 수록 기존의 이클립스에 있는 에디터들이 얼마나 잘 만들어졌나에 심한 충격을 받게 됩니다. 그래도 힘 냅시다.

1. File 선택 동기화 : 5점

  1. 에디터를 두 세개 정도 엽니다.
  2. 리소스 네비게이터나, 패키지 익스플로러 뷰는 좌우화살표 모양의 Link with Editor 버튼이 있습니다. 이 버튼을 누릅니다.
  3. 활성 에디터와 뷰의 파일 선택이 동기화 되어야 합니다.

만약 에디터의 입력이 복수의 파일등으로 구성되는 경우라면, 아마도 여러분은 직접 IEditorInput을 만드셨을 겁니다. 이 경우에는 에디터와 IEditorInput이 IFile.class에 대한 어댑터 질의에 응답하여야지만 그러한 기능이 정상작동합니다.

2. 에디터 복원 : 5점

  1. 본인이 만든 에디터를 두 세개 정도 엽니다.
  2. 이클립스를 종료하고 다시 실행합니다.
  3. 열어 두었던 모든 에디터가 되살아나 있어야 합니다.

IFileEditorInput 등과 같은 경우만 입력으로 고려한 경우, 이러한 내용은 모두 저절로 처리 되어 잘 알기 어렵지만, 실제로 에디터의 입력은 뷰에 표시되는 한 모델 객체 일 수도 있습니다. 예를 들면 MyLyn의 태스크 엘리먼트들은 파일이 아닙니다. 이러한 경우엔, 입력을 보존하고, 복원하는 작업을 직접 수행해야 합니다.

보존과정

워크벤치 -> IEditorInput : getPersistable
IEditorInput --> 워크벤치: IPersistable
워크벤치 -> IPersistable: getFactoryId
워크벤치 -> 워크벤치: id를 이용하여 메멘토 노드 생성
워크벤치 -> IPersistable: saveState
IPersistable-> IPersistable: 입력 상태 저장

복원 과정

워크벤치 -> 워크벤치: id로 팩토리 탐색
워크벤치 -> 엘레멘트 팩토리: 메멘토 노드 전달
엘레멘트 팩토리 -> 엘레멘트 팩토리 : IEditorInput  복원
엘레멘트 팩토리 -> 워크벤치: IEditorInput
워크벤치 -> 워크벤치 : 에디터 복원

메멘토 노드는 일종의 XML 노드입니다. 워크벤치는 종료될때 XML로 종료직전의 상황을 기록합니다. 팩토리는 Element Factory 확장점을 이용하여 등록합니다.

3. 파일 동기화: 총 배점 20

  1. 에디터를 엽니다.
  2. 텍스트 에디터등 다른 에디터로도 그 파일을 엽니다.
  3. 다른 에디터에서 수정한 뒤 저장합니다.
  4. 원래 에디터를 활성화 합니다.
  5. 더티 마크가 없던 경우, 파일을 즉시 다시 읽어 들여 표시해야 하고, 더티마크가 있는 경우, 파일의 내용이 바뀌었으니 다시 읽겠냐고 질의해야 합니다. : 10점
  6. 에디터가 열린 채로 해당 파일을 삭제하거나, 부모 폴더를 삭제하거나, 프로젝트를 삭제하거나 Close합니다. 이 때 에디터는 사라져야 합니다. : 5점
  7. 에디터가 열린 상태로 파일을 리네임 하거나 다른 곳으로 이동시킵니다. 에디터에서 조금 작업을 한 뒤 저장하면, 바뀐 파일로 안전하게 저장되야 하며, Save as... 기능도 작동해야 합니다. : 5점

이 기능은 SVN, CVS등 업데이트, get contents등과 같은 기능이 제대로 동작하는데에도 매우 중요합니다. 입력이 IFileEditorInput이었던 경우, 다음과 같이 워크스페이스를 리스닝 해야 합니다. 왜 해당 파일이 아니라, 전체 워크스페이스를 감시해야 하는지는 이어서 설명합니다.

if (input instanceof IFileEditorInput) {
	ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}

해당 리소스에 영향을 미치는 방법은 아주 다양합니다. 프로젝트 닫기, 프로젝트 리네임, 부모 폴더 이동, 부모폴더 삭제, 부모폴더 리네임등등 경우의 수가 매우 다양합니다. 따라서, 워크스페이스 전체의 이벤트를 감시한다음, 그것을 해당 리소스를 기준으로 의미를 번역하여 사용합니다.

public void resourceChanged(IResourceChangeEvent e) {
	IFileEditorInput input = (IFileEditorInput) getEditorInput();
	IFile file = input.getFile();
	IPath fullPath = file.getFullPath();

	// 프로젝트가 닫긴경우. bug 8593
	if (e.getDelta() == null) {
		return;
	}

	IResourceDelta relatedDelta = e.getDelta().findMember(fullPath);
	if (relatedDelta == null) {
		return;
	}

	int kind = relatedDelta.getKind();

	if (kind == IResourceDelta.REMOVED) {
		handleResourceRemoved(relatedDelta);
	}

	if (kind == IResourceDelta.CHANGED && !this.isSaving) {
		handleResourceChanged(relatedDelta);
	}
}

11 번째 라인이 리소스 변경 이벤트를, 입력 파일을 기준으로 상대적 이벤트로 변환하는 코드입니다.

4. 문서 동기화 : 10점

  • 에디터를 엽니다.
  • 메뉴의 Window / New Window 를 선택하여, 워크벤치 윈도우를 하나 더 엽니다.
  • 새 윈도우에서도 같은 파일에 대한 같은 에디터를 엽니다.
  • 어느 한쪽을 수정하면 즉시 동일한 컨텐츠를 보여 주어야 합니다.
  • 어느 한쪽에서 저장하면, 나머지 한쪽에서도 더티마크가 사라져야 합니다.

이클립스 텍스트 기반 에디터는 DocuemntProvider를 통해서 문서를 공급받습니다. 따라서, JavaEditor와 Text Editor를 동시에 열어 둔 경우, 둘은 같은 도큐먼트를 공급받게 되어 수정 즉시, 저장하지 않도라도 동일한 내용을 표시하게 됩니다. 에디터가 파일로부터 모델을 직접만드는 방식으로 구현해서는 안됩니다.

여러개의 파일로부터 나온 모델(문서)들이 서로 참조를 갖는 경우, 한 파일로 부터 하나의 모델만 공급되는 중간 체계가 있어야 하는것은 매우 중요합니다. 그렇지 않다면, 모델단위로 쉽게 구현될 수 있었던 리팩토링이, 파일 변경이벤트로만 처리되어야 하므로 매우 어렵고 복잡한 문제로 바뀔 것입니다.

GEF 에디터의 기본구현에 포함된 EditDomain은 EditorPart를 하나만 갖으면서 CommandStack을 내장하고 있기 때문에, 이 사항을 충족하는데는 상당히 까다롭고 높은 비용이 듭니다. ㅠㅠ

5. 사용자 단축키 지정: 10점

  • 메뉴에서 Windows / Preference 를 엽니다.
  • 필터에서 key라고 입력하고, key 노드를 선탣합니다.
  • 우측 필터에 에디터의 이름을 입력합니다.
  • 에디터와 연관된 모든 단축키가 즉시 알아볼 수 있는 형태로 나타나야 하며 변경할 수 있어야 합니다. 예를 들어 '삭제', '추가'와 같은 형태로 나타난다면 사용자는 "뭘 삭제하고 추가한단 말이지?" 라고 생각할 것입니다.
  • 변경한 단축키는 노출된 모든 경로에 즉시 표시되고 적용되야 합니다. (팝업 메뉴 등등)
  • When 필드에서 "in My Editor" 와 같은 여러분이 만든 에디터를 사용중일때를 의미하는 컨텍스트가 공급되야 합니다.
  • 여러분의 에디터를 사용하는 도중 Ctrl+3을 누르고, "삭제", "~추가" 등과 같은 명령어를 직접 타이핑 하여 해당 명령을 실행 시킬 수 있어야 합니다.: 예를 들어 자바 에디터에서 필드를 선택한 뒤 Ctrl+3을 누르고 generate라고 입력해 보십시오.

이클립스 3.2의 주요한 변화는 액션이 가벼워 졌다는 점입니다. IAction객체는 UI생성과 비즈니스 로직-run()이 함께 있기 때문에 매우 무겁습니다. 단지 툴바에 버튼을 하나 그리기 위해서 클래스가 로드되고, 그로 인해 플러그인까지 시작되어 버리기 때문에, 이클립스의 시작시간 아주 느리게 만듭니다. 이클립스는 어마어마하게 많은 플러그인들이 전사적으로 뒤섞여 동작하기 때문에, 이 룰을 따르지 않으면 치명적인 결과가 나타납니다. 이러한 명령들은 모두 Command라고 부르는 확장점으로 선언적으로 설계되어야 합니다. 그러면 이클립스는 XML파일을 로드하는 것 만으로도 UI를 만들 수 있고, 플러그인은 시작되지 않아도 됩니다. 또한 이렇게 선언적으로 만들어진 명령들은, 자동적으로 단축키 설정등과 같은 플랫폼의 지원을 받을 수 있게 됩니다. 이클립스 위키에서 Command Framework, Core Expression, Menu Contribution, UIContext를 참조하십시오.

6. Team Support: 10점

  • 새파일을 만들고 에디터를 엽니다.
  • SVN등에 이 프로젝트를 셰어하고 커밋합니다.
  • 문서 변경, 저장 커밋을 몇 차례 반복합니다.
  • history 뷰를 열어, 과거버전을 더블 클릭하면 제대로 열려야 하며, *읽기 전용*으로 열려야 합니다.
  • 읽기 전용으로 열린 과거 버전의 파일을 Save as... 로 저장하면, 편집이 가능해져야 합니다.
  • 만약 이전 버전과의 비교 에디터를 공급했다면 당신은 이 평가표를 읽을 필요도 없는 구루이십니다. 부끄럽게 왜 그럽시니까. 원츄-3-b

7. 클립보드, 프린팅: 10점

  • 당신의 에디터에서 일부 모델을 Copy 합니다.
  • 메모장에 붙여 넣으면 텍스트로, 워드에 붙여 넣으면 스타일 텍스트로, 그림판에 붙여 넣으면 그림으로 붙여 넣어져야 합니다.
  • 인쇄가 되어야 합니다.

'PDE' 카테고리의 다른 글

빌드 자동화 하기 #1 - Hello Ant  (2) 2011.03.07
이클립스 플러그인에 DLL 포함시키기  (0) 2010.12.10
제품 빌드시 자주 발생하는 문제들  (0) 2010.11.26
Bundle과 Resource  (0) 2010.10.28
네이쳐와 빌더  (0) 2010.10.15
Posted by 지이이이율
,

마우스 클릭 시나리오

마우스 -> Tool: 클릭
Tool -> EditPart: 드래그 트래커 요청
EditPart --> Tool: 드래그 트래커
Tool -> DragTracker : 마우스 이벤트
DragTracker -> DragTracker : 리퀘스트 생성
DragTracker ->EditPart: 리퀘스트
EditPart -> EditPolicy*: 리퀘스트 전달
EditPolicy* -> EditPart: Command
EditPart -> Tool: Command
Tool -> Tool: 커맨드 수행
Tool -> 마우스: 복귀

개체의 역할 설명

  • Tool: 대게의 그래픽 편집이 그러하듯, UI이벤트는 일차적으로 툴에게 전달됩니다. 툴은 상태기반 머신의 일종으로, 대게는 마우스 아래에 있는 에디트파트나, 현재 선택된 에디트 파트와 연동하게 됩니다. 이 때, 그 연동의 역할을 수행하는 것이 DragTracker입니다. Selection Tool은 EditPart에게 드래그 트래커를 요청하고, 이벤트를 전달합니다. 예를 들어 선택된 에디트파트의 드래그 트래커에게 마우스가 눌렸다는 사실을 통보하고, 이 드래그 트래커는 선택 요청(SelectionRequest) 객체를 만들게 됩니다.
  • DragTracker: 드래그 트래커는 Selection Tool을 도와주는 객체입니다. 다른 툴들은 보통 드래그 트래커를 사용하지 않습니다. 대부분의 작업은 Selection Tool에 의해 이루어지므로(선택, 이동, 크기 조절, 입양, 직접 편집 삭제 등등), 드래그 트래커는 매우 중요한 역할을 하며, 각 파트의 동작을 개인화 하는 주요 진입점이 됩니다. 드래그 트래커는 셀렉션 툴이 전달한 UI이벤트를 바탕으로, Request 객체를 만들어 EditPart에 전달합니다.
  • 에디트 파트는 최상위 CEO급 컨트롤러이므로, 이 시퀀스 다이어그램에서 나타난 롤만을 설명합니다. 에디트 파트는 Request를 받으면, 자기 자신이 가진 편집 정책(EditPolicy)들에게 이 요구를 전달하고, 편집 정책들이 특정 행동을 하게 하거나, 정책들이 반환 해준 Command를 다시 툴에게 전달합니다.
  • EditPolicy: 기본적으로는 요구를 받으면, 해당 요구를 분석하여, 모델을 수정하는 커맨드를 만들어 리턴합니다. 독자적으로 호스트를 감시하면서 동작을 개인화 하기도 합니다.
  • 사족

    Posted by 지이이이율
    ,