KoreanFoodie's Study

이클립스 실전 플러그인 개발 : #2 - 예제로 살펴보는 플러그인 개념 기초 본문

Tutorials/Java

이클립스 실전 플러그인 개발 : #2 - 예제로 살펴보는 플러그인 개념 기초

GoldGiver 2021. 10. 6. 10:26

플러그인 정의

모든 플러그인은 META-INF/MANIFEST.INF 파일을 포함한다. MANIFEST 파일은 ID, 버전, 이름 등의 정보를 갖고 있다.

Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Favorites Plug-in Bundle-SymbolicName: com.qualityeclipse.favorites; singleton:=true Bundle-Version: 1.0.0 Bundle-ClassPath: favorites.jar Bundle-Activator: com.qualityeclipse.favorites.FavoritesPlugin Bundle-Vendor: Quality Eclipse Bundle-Localization: plugin Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime;bundle-version="[3.0.0,3.1.0)";resolution:=optional;visibility:=reexport Eclipse-AutoStart: true Export-Package: com.qualityeclipse.favorites.views

plugin.xml

plugin.xml 파일은 확장점과 확장을 표현하는 데 쓰인다. 자세한 내용은 이 글을 참조하자.

<?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.0"?> <plugin> <extension point="org.eclipse.ui.views"> <category name="Quality Eclipse" id="com.qualityeclipse.favorites"> </category> <view name="Favorites" icon="icons/sample.gif" category="com.qualityeclipse.favorites" class="com.qualityeclipse.favorites.views.FavoritesView" id="com.qualityeclipse.favorites.views.FavoritesView"> </view> </extension> </plugin>


Dependencies 항목을 이용하면 어떤 플러그인에 종속되어 있는지를 표현할 수 있다. 위 예제에서는 org.eclipse.ui와 org.eclipse.core.runtime 플러그인을 필요로 하고 있다.

플러그인 클래스

모든 플러그인은 플러그인을 대표하는 클래스를 내역서 편집기의 개요 페이지에서 선언할 수 있다. 혹은 3.2 이상에서는 이 클래스를 Activator라고 부르기도 한다. 아래 예제 코드를 보자.

package com.qualityeclipse.favorites; import org.eclipse.ui.plugin.*; import org.eclipse.jface.resource.ImageDescriptor; import org.osgi.framework.BundleContext; /** * The main plugin class to be used in the desktop. */ public class FavoritesPlugin extends AbstractUIPlugin { // The shared instance. private static FavoritesPlugin plugin; /** * The constructor. */ public FavoritesPlugin() { plugin = this; } /** * This method is called upon plug-in activation */ public void start(BundleContext context) throws Exception { super.start(context); } /** * This method is called when the plug-in is stopped */ public void stop(BundleContext context) throws Exception { super.stop(context); plugin = null; } /** * Returns the shared instance. */ public static FavoritesPlugin getDefault() { return plugin; } /** * Returns an image descriptor for the image file * at the given plug-in relative path. * * @param path * the path * @return the image descriptor */ public static ImageDescriptor getImageDescriptor(String path) { return AbstractUIPlugin.imageDescriptorFromPlugin( "com.qualityeclipse.favorites", path); } }

플러그인이 활성화될 때, 가장 먼저 플러그인 클래스의 인스턴스가 생성되며, 이는 싱글턴 패턴을 따른다.

제품 빌드

제품을 빌드하는 방식은 여러가지가 있는데, 먼저 이클립스 기본기능인 File->Export를 선택해서 손쉽게 내보낼 수 있다. export된 zip파일은 단일 플러그인 JAR 파일을 포함한다. 하지만 이런 수동 빌드보다 더 좋은 방식이 있었으니...

아파치 Ant Save Us

아파치 Ant 스크립트는 상용 플러그인 프로젝트에 대한 빌드 프로세스를 유연하고 반복 가능하게 제공한다. 간단한 Ant 스크립트를 통해 내용을 살펴보자 (build-favorites.xml이라는 파일을 생성했다)

<?xml version="1.0" encoding="UTF-8"?> <project default="plugin_export" name="build"> <target name="plugin_export"> <!-- Define build directories --> <property name="build.root" location="/Build/QualityEclipse" /> <property name="build.temp" location="${build.root}/temp" /> <property name="build.out" location="${build.root}/product" /> <!-- Create build directories --> <delete dir="${build.temp}" /> <mkdir dir="${build.temp}" /> <mkdir dir="${build.out}" /> <!-- Read the manifest.mf --> <copy file="meta-inf/manifest.mf" todir="${build.temp}" /> <replace file="${build.temp}/manifest.mf"> <replacefilter token=":=" value="="/> <replacefilter token=":" value="="/> <replacetoken>;</replacetoken> <replacevalue> </replacevalue> </replace> <property file="${build.temp}/manifest.mf"/> <!-- Plugin locations --> <property name="plugin.dir" value= "com.qualityeclipse.favorites_${Bundle-Version}" /> <property name="plugin.files" location= "${build.temp}/files/${plugin.dir}" /> <property name="plugin.jar" location= "${build.temp}/jars/QualityEclipse/Favorites/eclipse/plugins/${plugin.dir}.jar" /> <property name="product.zip" value= "${build.out}/Favorites_v${Bundle-Version}.zip"/> <!-- Assemble the files --> <mkdir dir="${plugin.files}" /> <jar destfile="${plugin.files}/favorites.jar"> <fileset dir="bin" /> </jar> <jar destfile="${plugin.files}/favoritessrc.zip"> <fileset dir="src" /> </jar> <copy todir="${plugin.files}"> <fileset dir="." includes="META-INF/MANIFEST.MF" /> <fileset dir="." includes="plugin.xml" /> <fileset dir="." includes="icons/*.gif" /> </copy> <!-- Assemble plug-in jar --> <mkdir dir="${build.temp}/jars/QualityEclipse/Favorites/eclipse/plugins" /> <zip destfile="${plugin.jar}"> <zipfileset dir="${plugin.files}"> <include name="**/*.*"/> </zipfileset> </zip> <!-- Assemble the product zip --> <zip destfile="${product.zip}"> <fileset dir="${build.temp}/jars" /> </zip> </target> </project>

Ant 스크립트를 실행하려면 build-favorites.xml 파일에서 마우스 오른쪽 클릭을 하고 2.Ant Build... 를 클릭하면 된다.

제품 설치와 실행

플러그인 설치는 간단하다. 이클립스 디렉토리 (C:/eclipse) 에 플러그인 zip 파일의 압축을 해제해서 넣어주기만 하면 된다.

플러그인 테스트 생성

플러그인도 마찬가지로 Unit Test가 필요하다.
먼저 plugin.xml 파일을 더블 클릭해서 플러그인 내역서 편집기를 열고 Runtime 페이지로 전환한다. 내보낸 패키지(Exported packages)에서 추가(...Add)를 클릭하고 com.qualityeclipse.favorites.views 패키지를 선책한 후 저장을 해서 변경 내용을 저장한다. 그 후, 다음의 스텝을 밟는다.
1. 프로젝트를 생성한다. (ex : com.qualityeclipse.favorites.test)
2. 클래스 경로에 favoritesTest.jar을 추가한다. (프로젝트 생성시 이를 추가할 수 있다)
3. 플러그인 클래스 이름을 com.qualityeclipse.favorites.test.FavoriteTestPlugin으로 변경한다. (이는 추후 사용할 클래스 명이 FavoriteTestPlugin이기 때문이다)
4. 템플릿 중 하나를 사용하여 플러그인 작성 (Create a plug-in using one of these templates) 옵션의 체크를 해제한다.
5. 프로젝트를 생성한 후에는 종속성(Dependencies)페이지를 사용해 다음 플러그인을 필수 플러그인(Required Plug-ins)로 추가하고 저장한다. (com.qualityeclipse.favorites, org.junit)
아래는 실제 유닛 테스트의 샘플 코드이다.

package com.qualityeclipse.favorites.test; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import com.qualityeclipse.favorites.views.FavoritesView; /** * The class <code>FavoritesViewTest</code> contains tests * for the class {@link * com.qualityeclipse.favorites.views.FavoritesView}. * * @pattern JUnit Test Case * @generatedBy CodePro Studio */ public class FavoritesViewTest extends TestCase { private static final String VIEW_ID = "com.qualityeclipse.favorites.views.FavoritesView"; /** * The object that is being tested. * * @see com.qualityeclipse.favorites.views.FavoritesView */ private FavoritesView testView; /** /** * Construct new test instance. * * @param name the test name */ public FavoritesViewTest(String name) { super(name); } /** * Perform pre-test initialization. * * @throws Exception * * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); // Initialize the test fixture for each test // that is run. waitForJobs(); testView = (FavoritesView) PlatformUI .getWorkbench() .getActiveWorkbenchWindow() .getActivePage() .showView(VIEW_ID); // Delay for 3 seconds so that // the Favorites view can be seen. waitForJobs(); delay(3000); // Add additional setup code here. } /** * Perform post-test cleanup. * * @throws Exception * * @see TestCase#tearDown() */ protected void tearDown() throws Exception { super.tearDown(); // Dispose of test fixture. waitForJobs(); PlatformUI .getWorkbench() .getActiveWorkbenchWindow() .getActivePage() .hideView(testView); // Add additional teardown code here. } /** * Run the view test. */ public void testView() { TableViewer viewer = testView.getFavoritesViewer(); Object[] expectedContent = new Object[] { "One", "Two", "Three" }; Object[] expectedLabels = new String[] { "One", "Two", "Three" }; // Assert valid content. IStructuredContentProvider contentProvider = (IStructuredContentProvider) viewer.getContentProvider(); assertEquals(expectedContent, contentProvider.getElements(viewer.getInput())); // Assert valid labels. ILabelProvider labelProvider = (ILabelProvider) viewer.getLabelProvider(); for (int i = 0; i < expectedLabels.length; i++) assertEquals(expectedLabels[i], labelProvider.getText(expectedContent[i])); } /** * Process UI input but do not return for the * specified time interval. * * @param waitTimeMillis the number of milliseconds */ private void delay(long waitTimeMillis) { Display display = Display.getCurrent(); // If this is the UI thread, // then process input. if (display != null) { long endTimeMillis = System.currentTimeMillis() + waitTimeMillis; while (System.currentTimeMillis() < endTimeMillis) { if (!display.readAndDispatch()) display.sleep(); } display.update(); } // Otherwise, perform a simple sleep. else { try { Thread.sleep(waitTimeMillis); } catch (InterruptedException e) { // Ignored. } } } /** * Wait until all background tasks are complete. */ public void waitForJobs() { while (Platform.getJobManager().currentJob() != null) delay(1000); } /** * Assert that the two arrays are equal. * Throw an AssertionException if they are not. * * @param expected first array * @param actual second array */ private void assertEquals(Object[] expected, Object[] actual) { if (expected == null) { if (actual == null) return; throw new AssertionFailedError( "expected is null, but actual is not"); } else { if (actual == null) throw new AssertionFailedError( "actual is null, but expected is not"); } assertEquals( "expected.length " + expected.length + ", but actual.length " + actual.length, expected.length, actual.length); for (int i = 0; i < actual.length; i++) assertEquals( "expected[" + i + "] is not equal to actual[" + i + "]", expected[i], actual[i]); } }

플러그인 테스트 실행

패키지 탐색기(Package Explorer)에서 FavoritesViewTest 프로젝트를 오른쪽 클릭한 후, Run As -> JUnit Plug-in TestJUnit을 선택한다. 그럼 Favorites뷰가 열린다음 닫히며 테스트가 검증된다.
Run As -> (Run...) 을 선택하여 단일 테스트만 실행할 것인지 전체 테스트를 동시에 실행할 것인지 지정할 수도 있다.

플러그인 설치 제거

플러그인 설치와 마찬가지로, 이클립스 플러그인 디렉토리에 들어 있는 jar 파일을 삭제한 후, 이클립스를 재실행해 주면 된다.


Comments