IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  오픈 소스  >

Eclipse 마법사를 이용한 빠른 개발

마법사를 사용하여 재사용 가능한 프로세스 구현하기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Nathan A. Good, Author and Software Engineer, Consultant

2007 년 7 월 24 일

Eclipse 프레임웍과 통합 개발 환경(IDE)의 가장 큰 특징들 중 하나는 확장성입니다. 이 글에서, 새로운 파일을 추가하는 프로세스를 자동화 할 마법사를 구현하는 방법을 설명합니다. 파일의 내용은 사전 정의될 수 있기 때문에, 마법사는 일관성과 자동화를 통해서 더 나은 개발을 이룩할 수 있습니다.

시작하기

소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

이 글에서는 마법사를 사용하여 새로운 파일을 기존 Eclipse 프로젝트에 추가하는 방법을 설명한다. Eclipse 마법사는 빌트인 템플릿 기능으로 충분하지 않을 때 파일 유형에 반복 가능한 템플릿을 정의하는 좋은 방법이다. 이를 수행한 후에, Eclipse에서 고유의 마법사를 구현하여 새로운 파일을 만들 수 있다.

이것을 최대한 활용하려면, 자바™ 프로그래밍 언어 클래스 구현에 익숙해야 하며, 상속과 인터페이스 사용에 익숙해야 한다. 여러분이 Eclipse에 정통한 것으로 간주하고 이 글을 진행하겠다.

예제 실행에 필요한 것:

Eclipse V3.2 또는 이후 버전
이전 버전으로도 사용할 수 있지만, 이 글의 코드는 Eclipse V3.2.2로 테스트되었다. 이 버전은 이 글을 쓰고 있는 현재 공식적인 최신 버전이다.
IBM 또는 Sun JDK V1.5 또는 이후 버전
Java V1.5.0_07 on Mac OS® X V10.4.8에서 구현되었다. 여러분이 사용하는 OS는 중요하지 않다. 여러분 머신에 설치된 자바 환경의 버전은 중요하다. Java 5를 권장한다.

Eclipse 마법사 개요

필자는 Eclipse IDE의 많은 기능들을 좋아한다. 그 중 하나가 확장성이다. 클래스, 인터페이스, 프로젝트, 기타 리소스의 생성을 자동화 하는 마법사를 포함하여 기능을 제공하는 플러그인을 추가함으로써, IDE를 쉽게 커스터마이징 할 수 있다. 이는 Eclipse IDE에 기반하여 플러그인을 구현 및 배포하는 과정을 체계화 할 수 있는 대기업에 알맞다.

어떤 기업이라도, 팀이 같은 방식으로 애플리케이션을 구현할 수 있는 프로세스를 사용한다는 것은 매우 효과적이다. 이들이 일관성 있게 구현된다면 애플리케이션은 관리가 훨씬 쉬워진다. 일관성은 실수를 줄이는데도 도움이 될 수 있다. 또한, 애플리케이션은 같은 방식으로 구현되기 때문에 팀 멤버들이 프로젝트에서 프로젝트로 이동하기도 더욱 쉽다.

Eclipse 프레임웍용 커스텀 마법사를 만드는 기능을 통해 엔터프라이즈는 자신들의 스팩에 맞는 마법사를 구현하여 팀이 일관성 있는 베스트 프랙티스를 사용하여 애플리케이션을 구현할 기회를 제공한다.




위로


새로운 마법사 생성하기

여기에서 여러분이 구현하는 커스텀 마법사는 Eclipse의 플러그인 프로젝트에 있다. 코드를 시작하는 다른 마법사 덕택에 커스텀 마법사를 시작하는 것은 쉽다. 다음 단계에서, Plug-in Project Wizard를 사용하여 새로운 플러그인을 만든다.

새로운 플러그인 프로젝트 구현하기:

  1. File > New > Project를 선택하여 프로젝트 마법사를 선택한다.
  2. Plug-in Development 밑에서, Plug-in Project를 선택한다.
  3. Next를 클릭하고 다음 단계로 진행한다.

    그림 1. 플러그인 프로젝트 선택하기
    플러그인 프로젝트 선택하기

  4. 프로젝트 이름을 추가한다. (이 글에서, 이름은 그림 2에 제시된 것처럼 평범한 ExampleWizard이다.) 특별한 이유가 없는 한 기본 위치를 사용한다. Next를 클릭한다.

    그림 2. 새로운 플러그인 프로젝트
    새로운 플러그인 프로젝트

  5. 버전 넘버를 Plug-in Version에 기입하고 플러그인의 이름과 플러그인 공급자의 이름을 추가한다. (여러분 또는 여러분의 팀일 것이다.) 패키지 이름을 Activator로 바꾼다. 기본적으로 프로젝트 이름을 소문자로 쓴 것이다. 여러분 회사의 표준에 맞는 패키지 이름을 사용하는 것이 낫다. 예를 들어, com.example.eclipse.wizards로 한다. 아래와 같이 정보를 기입한 후에, Next를 클릭한다.

    그림 3. 플러그인 프로젝트 선택하기
    플러그인 프로젝트 선택하기

  6. Custom plug-in wizard를 선택한다. 이 옵션은 컴포넌트를 조정할 수 있다. 전에 새로운 플러그인 프로젝트를 만들지 않았다면, 다른 템플릿의 디스크립션을 보고 어떤 것이 가능한지를 확인한다. Next를 클릭한다.
  7. Template Selection에서, Deselect All을 클릭하여 모든 옵션을 선택 해제 한다. New File Wizard를 선택한다. Next를 클릭한다.

    그림 4. 템플릿 선택하기
    템플릿 선택하기

  8. Eclipse 마법사는 여러분이 만든 새로운 마법사에 대한 정보를 보여준다. (그림 5) 패키지 이름을 업데이트 한다. 이상적으로는 Activator (com.example.eclipse.wizards)에 사용했던 것과 같은 이름으로 업데이트 한다. Wizard Category Name을 새로운 마법사용 폴더 이름으로 업데이트 한다. 이 값은 그림 1에서 보았던 Plug-in Development 카테고리와 같이 사용된다. Wizard Class NameWizard에서 상속을 받은 클래스에 대한 자바 클래스 이름이고, INewWizard 인터페이스를 실행한다. Wizard Page Class NameWizardPage 클래스를 확장한다.

    그림 5. New Wizard Options 윈도우
    New Wizard Options 윈도우

  9. Finish를 클릭한다. Eclipse는 필수 클래스와 라이브러리들로 새로운 프로젝트를 추가한다.

아직 끝나지는 않았다. 이제 마법사 이외에 몇 가지 구현들을 추가할 준비가 되었다.




위로


Wizard 클래스와 INewWizard 인터페이스

이제 세 개의 클래스들(NewXHTMLFileWizard, NewXHTMLFileWizardPage, Activator)가 프로젝트에 생겼다. 다음 섹션에서는 NewXHTMLFileWizard 클래스를 처리한다. 이 클래스는 메소드에 모든 코드 없이 Listing 1에 나타나 있다.


Listing 1. NewXHTMLFileWizard 클래스
                
public class NewXHTMLFileWizard extends Wizard implements INewWizard {

    private NewXHTMLFileWizardPage page;
    private ISelection selection;

    public NewXHTMLFileWizard() {
        // snipped...
    }
    
    public void addPages() {
        // snipped...
    }

    public boolean performFinish() {
        // snipped...
    }
    
    private void doFinish(
        // snipped...
    }
    
    private InputStream openContentStream() {
        // snipped...
    }

    private void throwCoreException(String message) throws CoreException {
        // snipped...
    }

    public void init(IWorkbench workbench, IStructuredSelection selection) {
        // snipped...
    }
}

마지막 메소드인 init()INewWizard 인터페이스를 구현하는데 필요하다. 이제는 이 템플릿에 자동으로 포함된 나머지 메소드를 설명하겠다.

addPages() 메소드

addPages() 메소드는 페이지를 마법사에 추가한다. 이 메소드(Listing 2)는 하나의 페이지를 마법사에 추가한다: NewXHTMLFileWizardPage.


Listing 2. addPages() 메소드는 페이지를 마법사에 추가한다.
                
    /**
     * Adding the page to the wizard.
     */

    public void addPages() {
        page = new NewXHTMLFileWizardPage(selection);
        // You can add more pages here...
        addPage(page);
    }

NewXHTMLFileWizardPage 클래스에는 사용자가 페이지 이름을 지정할 수 있도록 하는 컨트롤이 포함되어 있다. 컨트롤을 페이지에 추가하여 엔드 유저가 추가 정보를 입력하도록 한다.

performFinish() 메소드

performFinish() 메소드는 사용자가 마법사에서 Finish 버튼을 클릭할 때 호출된다. 몇 가지 체크를 한 후에, IRunnableWithProgress 인터페이스를 사용하여 doFinish() 메소드를 호출한다. 인터페이스를 사용한다는 것은 doFinish() 메소드가 실행되는 동안 프로그레스 바를 보여주는 모든 UI 엘리먼트를 작성할 필요가 없다는 것을 의미한다. (이 경우, 실행하는데 오랜 시간이 걸린다.)


Listing 3. performFinish() 메소드
                
    /**
     * This method is called when 'Finish' button is pressed in
     * the wizard. We will create an operation and run it
     * using wizard as execution context.
     */
    public boolean performFinish() {
        final String containerName = page.getContainerName();
        final String fileName = page.getFileName();
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException {
                try {
                    doFinish(containerName, fileName, monitor);
                } catch (CoreException e) {
                    throw new InvocationTargetException(e);
                } finally {
                    monitor.done();
                }
            }
        };
        try {
            getContainer().run(true, false, op);
        } catch (InterruptedException e) {
            return false;
        } catch (InvocationTargetException e) {
            Throwable realException = e.getTargetException();
            MessageDialog.openError(getShell(), "Error", realException.getMessage());
            return false;
        }
        return true;
    }

doFinish() 메소드

doFinish() 메소드는 새로운 파일을 만들고 새로운 파일이 포함된 IDE에서 에디터를 연다. openContentStream() 메소드는 새로운 파일에 콘텐트를 채울 스트림을 가져오기 위해 호출된다.


Listing 4. 초기 doFinish() 메소드
                
    /**
     * The worker method. It will find the container, create the
     * file if missing or just replace its contents, and open
     * the editor on the newly created file.
     */

    private void doFinish(
        String containerName,
        String fileName,
        IProgressMonitor monitor)
        throws CoreException {
        // create a sample file
        monitor.beginTask("Creating " + fileName, 2);
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IResource resource = root.findMember(new Path(containerName));
        if (!resource.exists() || !(resource instanceof IContainer)) {
            throwCoreException("Container \"" + containerName + "\" does not exist.");
        }
        IContainer container = (IContainer) resource;
        final IFile file = container.getFile(new Path(fileName));
        try {
            InputStream stream = openContentStream();
            if (file.exists()) {
                file.setContents(stream, true, true, monitor);
            } else {
                file.create(stream, true, monitor);
            }
            stream.close();
        } catch (IOException e) {
        }
        monitor.worked(1);
        monitor.setTaskName("Opening file for editing...");
        getShell().getDisplay().asyncExec(new Runnable() {
            public void run() {
                IWorkbenchPage page =
                    PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                try {
                    IDE.openEditor(page, file, true);
                } catch (PartInitException e) {
                }
            }
        });
        monitor.worked(1);
    }

openContentStream() 메소드

openContentStream() 메소드는 템플릿의 일부로서 생성된 정적 스트링을 포함하고 있는 ByteArrayInputStream을 리턴한다. 이 글에서, 스트링은 템플릿 파일의 콘텐트로 대체된다.

이 메소드의 코드는 더 많은 사용 가능한 콘텐트가 새로운 파일에 추가되도록 하기 위해 바꾸어야 할 부분이다.


Listing 5. openContentStream() 메소드
                
    /**
     * Initialize file contents with a sample text.
     */

    private InputStream openContentStream() {
        String contents =
            "This is the initial file contents for *.html " +
            "file that should be word-sorted in the Preview " +
            "page of the multi-page editor";
        return new ByteArrayInputStream(contents.getBytes());
    }




위로


기본 콘텐트 추가하기

새로운 파일의 콘텐트에 대한 정적 스트링 값을 사용하는 대신, getResourceAsStream() 메소드를 사용하여 파일의 콘텐트를 doFinish() 메소드가 새로운 파일을 채우는데 사용하는 InputStream 코드에 로딩한다. 변경된 모습은 아래와 같다.


Listing 6. 리소스에서 스트림 가져오기
                
    /**
     * Initialize the file contents to contents of the 
     * given resource.
     */
    private InputStream openContentStream() {
        return this.getClass()
                    .getResourceAsStream("templates/index-xhtml-template.resource");
    }

유효 Extensible Hypertext Markup Language (XHTML) V1.0 Strict 웹 페이지는 index-xhtml-template.resource 파일에 있다. 몇 가지 기본 태그들을 갖고 있고 엔터프라이즈 스타일시트와 JavaScript 파일을 가장한다. 이 파일은 Listing 7을 참조하라. 이 파일은 NewXHTMLFileWizard 클래스와 같은 패키지에 배치되기 때문에, com.example.eclipse.wizards 패키지에 배치된다. 다른 패키지에 두려면, 디렉토리 안에 있는 파일인 것처럼 액세스 한다. (com.example.resources/com/example/resources).


Listing 7. index-xhtml-template.resource 파일
                
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>

  <title>This is an Example.com Web page</title>
  <link rel="stylesheet" href=
  "http://www.example.com/enterprise/styles/main.css" type=
  "text/css" />
  <script type="text/javascript" language="JavaScript" src=
  "http://www.example.com/scripts/main.js">
</script>
</head>

<body>
  <div id="search">
    <form id="searchForm" name="searchForm" action=
    "http://www.example.com/search.jsp">
      <input type="text" name="searchText" /> <input type="button"
      value="Search Example.com" />
    </form>
  </div>

  <div id="mainMenu">
    <p class="menu">Main menu</p>

    <ul class="mainMenu">
      <li><a href="#home">Home</a></li>

      <li><a href="#item1">Item 1</a></li>
    </ul>
  </div><!-- Put the body of your page here -->

  <div id="body"></div>
</body>
</html>

여기에서, Eclipse 플러그인을 실행하여 어떻게 보이는지를 확인할 수 있다.




위로


새로운 마법사 테스트하기

언제라도, Eclipse가 마법사에서 사용되는 세 개의 클래스를 생성한 후에, 플러그인을 실행 및 테스트 할 수 있는 또 다른 Eclipse 인스턴스를 시작할 수 있다. 플러그인 프로젝트를 시작하려면, 프로젝트를 오른쪽 클릭하고, Run As > Eclipse Application을 선택한다. (그림 6) Eclipse의 새로운 인스턴스가 시작된다.


그림 6. Eclipse 애플리케이션으로서 프로젝트 실행하기
Eclipse 애플리케이션으로서 프로젝트 실행하기

이제, 새로운 파일을 포함하고 있는 임시 프로젝트를 만들어야 한다. 프로젝트의 이름은 상관이 없다. — "temp" 정도면 무난하다. 새로운 프로젝트가 워크스페이스에 생기면, File > New > Other를 선택하여 새로운 템플릿을 추가한다.

모든 것이 기대한 대로 작동하면, 마법사를 위해 정의했던 카테고리 밑에 Select a Wizard 윈도우에 새로운 카테고리가 리스팅 된다. 필자는 Example.com Enterprise Templates를 사용했다.


그림 7. 새로운 템플릿 사용하기
새로운 템플릿 사용하기

나머지 마법사를 완료하면, Eclipse는 여러분이 정의했던 콘텐트를 포함하고 있는 새로운 파일을 만든다. 템플릿에서 필요한 것이 단순한 기능이라면 여기서 멈춘다. 파일용 콘텐트를 입력할 때 여러분이 사용할 수 있는 인풋을 사용자에게 나타내야 한다.




위로


마법사 페이지 커스터마이징

원래의 NewXHTMLFileWizardPage는 폼에 단 두 개의 컨트롤만 갖고 있다. 하나는 컨테이너의 이름이고(프로젝트 또는 폴더), 또 하나는 생성된 새로운 파일에 대한 이름이다. createControl() 메소드(Listing 8)는 이러한 컨트롤을 생성하고 이를 다이얼로그에 추가하는 역할을 한다.


Listing 8. createControl() 메소드
                
    /**
     * @see IDialogPage#createControl(Composite)
     */
    public void createControl(Composite parent) {
        Composite container = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        container.setLayout(layout);
        layout.numColumns = 3;
        layout.verticalSpacing = 9;
        Label label = new Label(container, SWT.NULL);
        label.setText("&Container:");

        containerText = new Text(container, SWT.BORDER | SWT.SINGLE);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        containerText.setLayoutData(gd);
        containerText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });

        Button button = new Button(container, SWT.PUSH);
        button.setText("Browse...");
        button.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                handleBrowse();
            }
        });
        label = new Label(container, SWT.NULL);
        label.setText("&File name:");

        fileText = new Text(container, SWT.BORDER | SWT.SINGLE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        fileText.setLayoutData(gd);
        fileText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });
        initialize();
        dialogChanged();
        setControl(container);
    }

새로운 컨트롤을 이 메소드에 추가하기 전에, 파일에 다른 컨트롤과 함께 선언되어야 한다.


Listing 9. 새로운 컨트롤 선언하기
                
public class NewXHTMLFileWizardPage extends WizardPage {
 /* Newly added for the page title */
    private Text titleText;

    // the rest of the class...	
}

텍스트용 getter를 추가한다. NewXHTMLFileWizard 클래스는 새로운 파일을 구현할 때 getter를 사용한다. getTitle() 메소드는 아래와 같다.


Listing 10. getTitle() 메소드
                
    /**
     * Gets the HTML title for the new file
     */
    public String getTitle() {
        return titleText.getText();
    }

createControl() 메소드를 수정하여 새로운 인풋 컨트롤과 타이틀 레이블을 추가한다. 새로운 코드는 아래와 같다.


Listing 11. 수정된 createControl() 메소드
                
    /**
     * @see IDialogPage#createControl(Composite)
     */
    public void createControl(Composite parent) {
        Composite container = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        container.setLayout(layout);
        layout.numColumns = 3;
        layout.verticalSpacing = 9;
        Label label = new Label(container, SWT.NULL);
        label.setText("&Container:");

        containerText = new Text(container, SWT.BORDER | SWT.SINGLE);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        containerText.setLayoutData(gd);
        containerText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });

        Button button = new Button(container, SWT.PUSH);
        button.setText("Browse...");
        button.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                handleBrowse();
            }
        });
        label = new Label(container, SWT.NULL);
        label.setText("&File name:");

        fileText = new Text(container, SWT.BORDER | SWT.SINGLE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        fileText.setLayoutData(gd);
        fileText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });
       
			 
        /* Need to add empty label so the next two controls
         * are pushed to the next line in the grid. */
        label = new Label(container, SWT.NULL);
        label.setText("");
        
        /* Adding the custom control here */
        
        label = new Label(container, SWT.NULL);
        label.setText("&Title:");
        
        titleText = new Text(container, SWT.BORDER | SWT.SINGLE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        titleText.setLayoutData(gd);
        titleText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });
				

        /* Finished adding the custom control */
        
        initialize();
        dialogChanged();
        setControl(container);
    }

코드를 NewXHTMLFileWizard 클래스에 추가하기 전에, 지금까지의 변경 사항들을 테스트 한다.




위로


사용자 인풋에 대한 유효성 검사 수행하기

새로운 마법사의 Title 필드에 사용자가 입력한 텍스트는 새로운 HTML 파일에 있는 <title> 엘리먼트의 텍스트로서 사용되어야 한다. 이 값이 필요하기 때문에, 인풋을 체크하여 사용자가 무엇을 입력했는지를 확인하는 코드를 추가한다.

dialogChanged() 메소드에 대한 변경 사항은 아래와 같다.


Listing 12. dialogChanged()에서 인풋에 대한 유효성 검사하기
                
    /**
     * Ensures that both text fields are set.
     */

    private void dialogChanged() {
        IResource container = ResourcesPlugin.getWorkspace().getRoot()
                .findMember(new Path(getContainerName()));
        String fileName = getFileName();
       	 
        String titleText = getTitle();  

        if (getContainerName().length() == 0) {
            updateStatus("File container must be specified");
            return;
        }
        if (container == null
                || (container.getType() & (IResource.PROJECT | IResource.FOLDER)) == 0) {
            updateStatus("File container must exist");
            return;
        }
        if (!container.isAccessible()) {
            updateStatus("Project must be writable");
            return;
        }
        if (fileName.length() == 0) {
            updateStatus("File name must be specified");
            return;
        }
        if (fileName.replace('\\', '/').indexOf('/', 1) > 0) {
            updateStatus("File name must be valid");
            return;
        }
        int dotLoc = fileName.lastIndexOf('.');
        if (dotLoc != -1) {
            String ext = fileName.substring(dotLoc + 1);
            if (ext.equalsIgnoreCase("html") == false) {
                updateStatus("File extension must be \"html\"");
                return;
            }
        }
       	 
        if (titleText.length() ==0 )
        {
            updateStatus("Title must be specified");
            return;
        }
				
        
        updateStatus(null);
    }

수정한 후에, 마법사는 Title 값이 입력되지 않았다면 에러 메시지를 제공한다. Title에 만족하는 값이 지정되기 전까지 Finish 버튼은 작동하지 않는다. 플러그인 프로젝트를 실행하여 기능을 검사한다.




위로


커스텀 컨텐트 추가하기

마법사 페이지 NewXHTMLFileWizardPage는 사용자로부터 HTML 타이틀에 대한 값을 획득하지만, 그 값을 파일에 결합하지는 않는다. 값을 파일에 추가하려면, index-xhtml-template.resource 파일을 편집하여 그 값에 대한 플레이스홀더를 포함시킨다. <title> 엘리먼트를 <title>${title}</title>로 바꾸면 변경이 쉽다.

performFinish() 메소드를 수정하여 마법사 페이지에서 타이틀을 가져오고 이것을 나머지 값과 함께 doFinish() 메소드로 전달한다.


Listing 13. 마지막 performFinish() 메소드
                
    /**
     * This method is called when 'Finish' button is pressed in the wizard. We
     * will create an operation and run it using wizard as execution context.
     */
    public boolean performFinish() {
        final String containerName = page.getContainerName();
        final String fileName = page.getFileName();
        final String title = page.getTitle();
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor)
                    throws InvocationTargetException {
                try {
                    doFinish(containerName, fileName, title, monitor);
                } catch (CoreException e) {
                    throw new InvocationTargetException(e);
                } finally {
                    monitor.done();
                }
            }
        };
        try {
            getContainer().run(true, false, op);
        } catch (InterruptedException e) {
            return false;
        } catch (InvocationTargetException e) {
            Throwable realException = e.getTargetException();
            MessageDialog.openError(getShell(), "Error", realException
                    .getMessage());
            return false;
        }
        return true;
    }

doFinish() 메소드를 수정하여 매개변수로서 타이틀을 수락하고 이를 openContentStream() 메소드로 전달한다.


Listing 14. 마지막 doFinish() 메소드- 타이틀을 매개변수로 수락함.
                
    /**
     * The worker method. It will find the container, create the file if missing
     * or just replace its contents, and open the editor on the newly created
     * file.
     */
    private void doFinish(String containerName, String fileName, String title,
            IProgressMonitor monitor) throws CoreException {

        monitor.beginTask("Creating " + fileName, 2);
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IResource resource = root.findMember(new Path(containerName));

        if (!resource.exists() || !(resource instanceof IContainer)) {
            throwCoreException("Container \"" + containerName
                    + "\" does not exist.");
        }
        IContainer container = (IContainer) resource;

        final IFile file = container.getFile(new Path(fileName));
        try {

            InputStream stream = openContentStream(title);

            try {
                if (file.exists()) {
                    file.setContents(stream, true, true, monitor);
                } else {
                    file.create(stream, true, monitor);
                }
            } finally {
                stream.close();
            }

        } catch (IOException e) {
        }
        monitor.worked(1);
        monitor.setTaskName("Opening file for editing...");
        getShell().getDisplay().asyncExec(new Runnable() {
            public void run() {
                IWorkbenchPage page = PlatformUI.getWorkbench()
                        .getActiveWorkbenchWindow().getActivePage();
                try {
                    IDE.openEditor(page, file, true);
                } catch (PartInitException e) {
                }
            }
        });
        monitor.worked(1);
	}

마지막으로 openContentStream() 메소드를 수정하여 파일에서 찾은 $title 값을 사용자게 제공한 값으로 대체한다. (Listing 15) 다양한 값들로 이루어진 템플릿에서, FilterInputStream을 확장하고 전체 다른 값들을 대체하는 새로운 클래스 같은, 보다 고급의 솔루션을 사용할 수 있다.


Listing 15. 마지막 openContentStream() 메소드
                
    /**
     * Initialize the file contents to contents of the given resource.
     */
    private InputStream openContentStream(String title) throws CoreException {

        
        final String newline = "\n"; // System.getProperty("line.separator");
        String line;
        StringBuffer sb = new StringBuffer();
        try {
            InputStream input = this.getClass().getResourceAsStream(
                    "index-xhtml-template.resource");
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    input));
            try {

                while ((line = reader.readLine()) != null) {
                    line = line.replaceAll("\\$\\{title\\}", title);
                    sb.append(line);
                    sb.append(newline);
                }

            } finally {
                reader.close();
            }

        } catch (IOException ioe) {
            IStatus status = new Status(IStatus.ERROR, "ExampleWizard", IStatus.OK,
                    ioe.getLocalizedMessage(), null);
            throw new CoreException(status);
        }

        return new ByteArrayInputStream(sb.toString().getBytes());
        

    }

openContentStream() 메소드는 리소스 파일의 콘텐트를 로딩하고 이들을 InputStream으로서 리턴하는 것 보다 훨씬 더 많은 것을 수행한다. 새로운 코드는 스트림을 통해 반복하고, InputStreamReader를 사용하여 이를 읽으며, 각 라인에서 $title 값을 대체한다. 결과는 ByteArrayInputStream으로 리턴 되는데, 이것은 NewXHTMLFileWizard 클래스가 처음 생성되었을 때 사용되었던 것과 같은 스트림 객체이다.

쓰레드 액세스

필자가 작성했던 첫 번째 코드는 마법사에서 Finish를 클릭했을 때 Invalid thread access 에러를 만들어 냈다. 필자는 openContentStream() 메소드에서 직접 page.getTitle()로 액세스 하려고 했고, 이것은 옳지 않았다. 이 값은 NewXHTMLFileWizard에 의해 결정되어야 하고 performFinish() 메소드의 op 객체로 전달되어야 한다.




위로


새로운 프로젝트 마법사 만들기

진행하다 보면, 기존 프로젝트에 새로운 파일을 생성하는 마법사가 필요하다. 그런데 왜 여기에서 멈춰야 하는가? 엔터프라이즈가 XHTML 파일 같은 리소스에서 따라야 할 규약들을 갖고 있듯이 프로젝트가 전개되는 방법에도 규약이 있다.

기존 프로젝트에 적은 부분을 추가하여, 전체 프로젝트를 폴더와 초기 파일들을 갖고 있는 워크스페이스에 추가하는 마법사를 구현할 수 있다. 이 마법사는 Example.com용 새로운 폴더를 만들고 이미지와 스타일 폴더를 만든다. 스타일 폴더 내에서, 마법사는 site.css라고 하는 Cascading Style Sheets (CSS) 파일을 만든다. 이 마법사는 NewXHTMLFileWizard 클래스에서 메소드를 재사용하여 타이틀이 초기화 된 새로운 XHTML 파일을 새로운 프로젝트의 이름과 새로운 텍스트에 추가함으로써 끝난다.




위로


새로운 NewSiteProjectWizard 구현하기

여러분은 이미 플러그인 프로젝트를 설정 및 실행했기 때문에, 마법사를 사용하여 새로운 클래스를 구현할 필요가 없다. 대신, Wizard에서 확장되고 두 개의 인터페이스(INewWizardIExecutableExtension)를 실행하는 새로운 클래스를 만듦으로써 새로운 마법사를 구현할 수 있다.

새로운 NewSiteProjectWizard 클래스를 NewXHTMLFileWizard 클래스와 같은 패키지에 추가한다. Listing 16의 NewSiteProjectWizard 클래스 선언을 보고, Wizard 클래스를 확장하는지 확인한다. 또한, INewWizardIExecutableExtension 인터페이스를 추가한다.

NewSiteProjectWizard 클래스는 같은 클래스를 확장하고 NewXHTMLFileWizard 클래스가 구현하는 인터페이스들 중 하나를 구현하기 때문에, 이 두 가지를 비교한다면 공통 메소드를 알 수 있을 것이다. NewSiteProjectWizard는 Listing 16에 나타나 있다. 간단히 하기 위해 메소드 콘텐트는 삭제했다. (이 글 후반에 제공된다.)


Listing 16. NewSiteProjectWizard 클래스
                
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.WizardNewProjectCreationPage;
import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;

public class NewSiteProjectWizard extends Wizard implements INewWizard,
        IExecutableExtension {

    /*
     * Use the WizardNewProjectCreationPage, which is provided by the Eclipse
     * framework.
     */
    private WizardNewProjectCreationPage wizardPage;

    private IConfigurationElement config;

    private IWorkbench workbench;

    private IStructuredSelection selection;

    private IProject project;

    /**
     * Constructor
     */
    public NewSiteProjectWizard() {
        super();
    }

    public void addPages() {
        // snipped...
    }

    @Override
    public boolean performFinish() {
        // snipped...
    }

    /**
     * This creates the project in the workspace.
     * 
     * @param description
     * @param projectHandle
     * @param monitor
     * @throws CoreException
     * @throws OperationCanceledException
     */
    void createProject(IProjectDescription description, IProject proj,
            IProgressMonitor monitor) throws CoreException,
            OperationCanceledException {
        // snipped...
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench,
     *      org.eclipse.jface.viewers.IStructuredSelection)
     */
    public void init(IWorkbench workbench, IStructuredSelection selection) {
        // snipped...
    }

    /**
     * Sets the initialization data for the wizard.
     */
    public void setInitializationData(IConfigurationElement config,
            String propertyName, Object data) throws CoreException {
        // snipped...
    }

    /**
     * Adds a new file to the project.
     * 
     * @param container
     * @param path
     * @param contentStream
     * @param monitor
     * @throws CoreException
     */
    private void addFileToProject(IContainer container, Path path,
            InputStream contentStream, IProgressMonitor monitor)
            throws CoreException {
        // snipped
    }
}

plugin.xml 수정하기

새로운 클래스를 추가한 후에 Eclipse에서 이를 마법사로서 실행하기 전에, 프로젝트의 베이스에 위치한 plugin.xml 파일을 수정해야 한다.


Listing 17. plugin.xml 파일
                
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>

   <extension
         point="org.eclipse.ui.newWizards">
      <category
            name="Example.com Enterprise Templates"
            id="ExampleWizard">
      </category>
      <wizard
            name="Example.com Static Web Page"
            icon="icons/sample.gif"
            category="ExampleWizard"
            class="com.example.eclipse.wizards.NewXHTMLFileWizard"
            id="com.example.eclipse.wizards.NewXHTMLFileWizard">
      </wizard>
      <wizard
            category="ExampleWizard"
            class="com.example.eclipse.wizards.NewSiteProjectWizard"
            icon="icons/sample.gif"
            id="com.example.eclipse.wizards.NewSiteProjectWizard"
            name="Example.com Static Web Site"
            project="true">
      </wizard>
   </extension>

</plugin>

plugin.xml을 수정하면 Eclipse는 NewSiteProjectWizard 클래스가 Eclipse에 의해 호출될 수 있는 마법사라는 것을 안다. 또한 앞서 설명했던 NewXHTMLFileWizard 클래스와 같은 카테고리 밑에 구성된다. project="true" 애트리뷰트는 Eclipse에게 이것이 프로젝트라는 것을 알려주기 때문에, 올바른 콘텍스트에 디스플레이 된다.

addPages() 메소드

Eclipse API에는 기본 기능을 수행하고 커스터마이징을 할 필요가 없다면 유용하게 사용될 마법사 클래스와 마법사-페이지 클래스들이 포함되어 있다. NewSiteProjectWizardBasicNewProjectResourceWizard — 기본 프로젝트를 생성하는데 사용되는 기존 프로젝트 마법사 — 를 확장할 수 있다. 비록 JavaDoc의 디자이너 노트에는 이것이 하위 클래스로 나뉘어지도록 의도하지 않았다고 나타나 있다. 프로젝트 이름 같은 기본 프로젝트 정보를 얻으려면, 아래와 같이 BasicNewProjectResourceWizardWizardNewProjectCreationPage 클래스에 의해 사용된 같은 마법사 페이지를 사용할 수 있다.


Listing 18. addPages() 메소드
                
   public void addPages() {
      /*
       * Unlike the custom new wizard, we just add the pre-defined one and
       * don't necessarily define our own.
       */
      wizardPage = new WizardNewProjectCreationPage(
            "NewExampleComSiteProject");
      wizardPage.setDescription("Create a new Example.com Site Project.");
      wizardPage.setTitle("New Example.com Site Project");
      addPage(wizardPage);
   }

이 메소드는 페이지 클래스의 새로운 인스턴스를 만들고, 디스크립션과 타이틀을 설정하고, 이를 마법사 페이지에 추가한다.

performFinish() 메소드

NewXHTMLFileWizard 클래스와 마찬가지로, NewSiteProjectWizard는 사용자가 마법사에서 단계를 끝마치고 Finish를 클릭했을 때 실행되는 performFinish() 메소드(Listing 9)를 갖고 있다. 이 메소드는 프로젝트, 폴더, 파일을 생성하는 등 대부분의 일을 수행하는 createProject() 메소드를 실행하는 프로세스를 호출한다.


Listing 19. performFinish() 메소드
                
    @Override
    public boolean performFinish() {

        if (project != null) {
            return true;
        }

        final IProject projectHandle = wizardPage.getProjectHandle();

        URI projectURI = (!wizardPage.useDefaults()) ? wizardPage
                .getLocationURI() : null;

        IWorkspace workspace = ResourcesPlugin.getWorkspace();

        final IProjectDescription desc = workspace
                .newProjectDescription(projectHandle.getName());

        desc.setLocationURI(projectURI);

        /*
         * Just like the ExampleWizard, but this time with an operation object
         * that modifies workspaces.
         */
        WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
            protected void execute(IProgressMonitor monitor)
                    throws CoreException {
                createProject(desc, projectHandle, monitor);
            }
        };

        /*
         * This isn't as robust as the code in the BasicNewProjectResourceWizard
         * class. Consider beefing this up to improve error handling.
         */
        try {
            getContainer().run(true, true, op);
        } catch (InterruptedException e) {
            return false;
        } catch (InvocationTargetException e) {
            Throwable realException = e.getTargetException();
            MessageDialog.openError(getShell(), "Error", realException
                    .getMessage());
            return false;
        }

        project = projectHandle;

        if (project == null) {
            return false;
        }

        BasicNewProjectResourceWizard.updatePerspective(config);
        BasicNewProjectResourceWizard.selectAndReveal(project, workbench
                .getActiveWorkbenchWindow());

        return true;
    }

createProject()를 호출하여 파일과 폴더를 생성한 후에 performFinish() 메소드는 두 개의 정적 메소드를 호출하여 현재 perspective 를 업데이트 하고 IDE에 새롭게 생성된 프로젝트를 선택한다.

createProject() 메소드

Listing 20의 createProject() 메소드는 새로운 프로젝트를 만들어서 연다. 이 메소드는 두 개의 파일과 두 개의 새로운 폴더를 프로젝트에 추가한다. 이 파일은 addFileToProject()라고 하는 private 메소드에 의해 추가되고, 이는 createProject()를 깨끗하게 유지한다.


Listing 20. createProject() 메소드
                
    /**
     * This creates the project in the workspace.
     * 
     * @param description
     * @param projectHandle
     * @param monitor
     * @throws CoreException
     * @throws OperationCanceledException
     */
    void createProject(IProjectDescription description, IProject proj,
            IProgressMonitor monitor) throws CoreException,
            OperationCanceledException {
        try {

            monitor.beginTask("", 2000);

            proj.create(description, new SubProgressMonitor(monitor, 1000));

            if (monitor.isCanceled()) {
                throw new OperationCanceledException();
            }

            proj.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(
                    monitor, 1000));

            /*
             * Okay, now we have the project and we can do more things with it
             * before updating the perspective.
             */
            IContainer container = (IContainer) proj;

            /* Add an XHTML file */
            addFileToProject(container, new Path("index.html"),
                    NewXHTMLFileWizard.openContentStream("Welcome to "
                            + proj.getName()), monitor);

            /* Add the style folder and the site.css file to it */
            final IFolder styleFolder = container.getFolder(new Path("styles"));
            styleFolder.create(true, true, monitor);
            
            InputStream resourceStream = this.getClass().getResourceAsStream(
            "templates/site-css-template.resource");

            addFileToProject(container, new Path(styleFolder.getName()
                    + Path.SEPARATOR + "style.css"),
                    resourceStream, monitor);

            resourceStream.close();
            
            /*
             * Add the images folder, which is an official Exmample.com standard
             * for static web projects.
             */
            IFolder imageFolder = container.getFolder(new Path("images"));
            imageFolder.create(true, true, monitor);
        } catch (IOException ioe) {
            IStatus status = new Status(IStatus.ERROR, "ExampleWizard", IStatus.OK,
                    ioe.getLocalizedMessage(), null);
            throw new CoreException(status);
        } finally {
            monitor.done();
        }
    }

addFileToProject() 메소드

addFileToProject() 메소드에 있는 대부분의 코드가 Listing 14에 있는 doFinish() 메소드와 같다는 것을 알 것이다. 메소드의 서명은 확실히 다르다. 이는 createProject() 메소드의 정황 속에서 재사용이 잘 되도록 매개변수를 수락하도록 수정되었다.


Listing 21. addFileToProject() 메소드
                
    /**
     * Adds a new file to the project.
     * 
     * @param container
     * @param path
     * @param contentStream
     * @param monitor
     * @throws CoreException
     */
    private void addFileToProject(IContainer container, Path path,
            InputStream contentStream, IProgressMonitor monitor)
            throws CoreException {
        final IFile file = container.getFile(path);

        if (file.exists()) {
            file.setContents(contentStream, true, true, monitor);
        } else {
            file.create(contentStream, true, monitor);
        }

    }

파일이 이미 존재한다면, 파일의 콘텐트는 메소드로 전달된 contentStream의 콘텐트로 설정된다. 파일이 존재하지 않으면, 이 안에 있는 콘텐트로 생성된다.

전체 NewSiteProjectWizard 클래스는 이 글 다운로드 섹션을 참조하라. 메소드 구현을 추가하고, INewWizardIExecutableExtension 인터페이스용 구현을 추가한 후에, Eclipse 애플리케이션으로서 프로젝트를 실행할 수 있다. 이번에는, NewXHTMLFileWizard로 새로운 파일을 생성하는 것 외에도 새로운 프로젝트를 생성할 수 있다.




위로


문제 해결

.jar 파일을 전개할 때 (NewFileWizard_1.0.0.jar), 플러그인 폴더에 둔다. plugin.xml 파일로 가서 .jar를 만들고, Eclipse에서 이것을 더블 클릭하면서, Overview 탭을 검색하고 Export Wizard 링크를 오른쪽으로 클릭한다. .jar를 Eclipse 플러그인 폴더로 옮기고 압축을 푼다.

Eclipse를 시작할 때 "Plugin does not have a valid identifier" 또는 "Plugin does not have a valid version" 에러가 생기면, -clean 매개변수로 명령행에서 Eclipse를 시작한다.

문제가 생긴다면 디버깅 하는 동안 Eclipse 플러그인으로서 프로젝트를 실행할 수 있다. 필자는 Eclipse IDE에서 Debug Perspective를 사용한다. Run > Debug Last Launched를 선택하여 Eclipse에서 start the plug-in을 실행한다.

performFinish() 메소드의 첫 번째 라인에 중단점을 설정할 것이다. 디버거는 마법사에서 Finish를 클릭하면 (에러가 중단점 앞에서 발생하지 않는 한) 중단점에서 중지한다.

요약

Eclipse IDE의 가장 큰 특징들 중 하나는 새로운 파일을 생성하는 새로운 마법사를 추가하는 플러그인을 만듦으로써 확장된 기능을 제공한다는 점이다. 엔터프라이즈 스팩의 마법사를 사용하면 일관성 있고 신속한 애플리케이션 개발이 가능하다.





위로


다운로드 하십시오

설명이름크기다운로드 방식
Codeos-eclipse-custwiz_NewFileWizard.zip12KBHTTP
다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

토론


필자소개

Nathan A. Good는 미네소타의 트윈 시티에 살고 있다. 소프트웨어 개발, 소프트웨어 아키텍처, 시스템 관리 전문가이다. 여가 시간에는 PC와 서버를 구현하거나, 신 기술 관련 서적을 읽고, 친구들과 오픈 소스 소프트웨어 전향에 대해 논한다. Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, Regular Expression Recipes for Windows Developers: A Problem-Solution Approach, PHP 5 Recipes: A Problem-Solution Approach, Foundations of PEAR: Rapid PHP Development(최신작) 등을 집필 및 공동 집필했다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의