S-JIS[2013-02-23/2014-12-28] 変更履歴

Eclipseプラグイン クラス実行

Eclipseプラグイン開発のクラスの実行について。


概要

EclipseプラグインからJavaのクラスを実行する(mainメソッドを呼び出す)為に、Eclipseの実行構成を利用する。

Eclipseの実行構成(デバッグ構成)では、クラスパスや実行するクラス・引数を指定して実行できる。
また、通常のアプリケーションとして実行する他に、JUnitやアプレットとして実行することが出来る。

依存プラグインは「org.eclipse.debug.core」。Eclipseにおけるアプリケーションの実行はデバッグ機能なんですなw
「org.eclipse.jdt.launching」も使う。


ワークスペース上のjava1というプロジェクト内のjp.hishidama.Exampleを呼び出す例。

LaunchTask.java

呼び出し処理はIWorkspaceRunnableで記述することにする。

import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
public class LaunchTask implements IWorkspaceRunnable {
	@Override
	public void run(IProgressMonitor monitor) throws CoreException {
		monitor.beginTask("launch実験", 100);
		try {
			ILaunchConfiguration config = createConfiguration(new SubProgressMonitor(monitor, 20));
			launch(new SubProgressMonitor(monitor, 80), config, false);
		} finally {
			monitor.done();
		}
	}
	private ILaunchConfiguration createConfiguration(IProgressMonitor monitor) throws CoreException {
		ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
		ILaunchConfigurationType type = manager.getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
		ILaunchConfigurationWorkingCopy config = type.newInstance(null, Activator.PLUGIN_ID);
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, true);
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, true);
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "java1");
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, "jp.hishidama.exampleClasspathProvider");
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, "jp.hishidama.Example");
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "");
		config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, "abc def ghi");
		return config;
	}

DebugPluginを用いて実行情報を定義する。
typeには普通のアプリケーションを指定する。(他にもアプレットのIDとかが用意されている)
それから、実行情報として、対象プロジェクト・クラスパス・クラス名・VM引数・実行時引数を指定する。(引数に「${〜}」そのものは渡せないので注意
クラスパス以外は文字列なので見れば分かるだろう。
クラスパスの定義方法は独特なので、後述

	private void launch(IProgressMonitor monitor, ILaunchConfiguration config, boolean debugMode) throws CoreException {
		monitor.beginTask("launch", 100);
		try {
			String mode = debugMode ? ILaunchManager.DEBUG_MODE : ILaunchManager.RUN_MODE;
			boolean build = false;
			boolean register = false;
			ILaunch launch = config.launch(mode, new SubProgressMonitor(monitor, 20), build, register);
			if (!launch.hasChildren()) {
				throw new OperationCanceledException();
			}
			monitor.worked(10);

			while (!launch.isTerminated()) {
				checkCancel(monitor);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					launch.terminate();
					throw new OperationCanceledException();
				}
				monitor.worked(1);
			}
			launch.terminate();
		} finally {
			monitor.done();
		}
	}

実行情報(config)のlaunchメソッドを呼び出すことで、実際に実行が開始される。
このメソッドはブロックされないので、制御がすぐに戻ってくる。
終了したかどうかをポーリングする必要がある。

modeは、デバッグモードかどうか。(「実行」と「デバッグ」の違いだと思われる)
buildは、実行前にビルドするかどうか。
registerは、実行構成を保存するかどうか。

launchを作ったら、中にプロセスがちゃんと存在しているかどうかを確認した方が良さそう。[2013-05-10]
というのは、編集途中(保存していない)のファイルがあるときに保存するかどうかのダイアログが出るのだが、キャンセルするとlaunchインスタンスは作られるが中のプロセスは存在しない状態(hasChildren()がfalse)になる為。
この場合、isTerminated()がずっとfalseのままなので、hasChildren()をチェックしないと無限ループになってしまう。

	private static void checkCancel(IProgressMonitor monitor) {
		if (monitor.isCanceled()) {
			throw new OperationCanceledException();
		}
	}
}

環境変数の定義

環境変数はconfigに指定する。[2014-12-28]

import java.util.HashMap;
import java.util.Map;

import org.eclipse.debug.core.ILaunchManager;
		ILaunchConfigurationWorkingCopy config = 〜;

		Map<String, String> map = new HashMap<String, String>();
		map.put("ENV_NAME", "value");
		config.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, map);

引数に「${〜}」そのものは渡せないので注意


クラスパスの定義

クラスパス(ClasspathProvider)はextensionで定義する。

plugin.xml:

   <extension
         point="org.eclipse.jdt.launching.classpathProviders">
      <classpathProvider
            id="jp.hishidama.exampleClasspathProvider"
            class="org.eclipse.jdt.launching.StandardClasspathProvider">
      </classpathProvider>
   </extension>

StandardClasspathProviderは、プロジェクト内のビルドパスで定義されているクラスパスを返すクラス。
自分で独自のクラスパスを指定したい場合は、StandardClasspathProviderを継承したクラスを作ってcomputeUnresolvedClasspath()をオーバーライドする。

クラスパスプロバイダーからライブラリー群を取得する例


LaunchHandler.java

呼び出すハンドラーは以下の様な感じになる。

import org.eclipse.core.commands.AbstractHandler;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.ui.progress.WorkbenchJob;
public class LaunchHandler extends AbstractHandler {
	public Object execute(ExecutionEvent event) throws ExecutionException {
		WorkbenchJob job = new WorkbenchJob("launch-job") {
			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				LaunchTask task = new LaunchTask();
				try {
					task.run(monitor);
				} catch (CoreException e) {
					return e.getStatus();
				}
				return Status.OK_STATUS;
			}
		};
		job.schedule();

		return null;
	}
}

WorkbenchJobは(Jobクラスと同様に)scheduleメソッドで実行する。


今回の例で実行されるクラス。何の変哲も無い。

ワークスペース/java1/src/jp/hishidama/Example.java

package jp.hishidama;

import java.util.Arrays;
public class Example {

	public static void main(String[] args) throws InterruptedException {
		System.out.println(Arrays.toString(args));
	}
}

ところで、実行は出来たのだが、Eclipseのコンソール上に何も表示されなかったorz
コンソールへ出力する方法


コンソールへの出力

上記の方法では、Eclipseのコンソール上に何も出力されない。[2013-02-25]
どうも、LaunchManagerにlaunchを登録しないと表示されないようだ。

LaunchTask.java:

			ILaunch launch = config.launch(mode, new SubProgressMonitor(monitor, 20), build, register);

			ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
			launchManager.addLaunch(launch);

			monitor.worked(10);

アドバイス: ashigeruさん


クラスパスプロバイダーからのクラスパスエントリーの取得

クラスパスプロバイダーのIDからクラスパスエントリー(クラスパスプロバイダーに含まれるライブラリー・jarファイル)を取得するのもランチャーを利用する。[2013-06-14]

import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.IRuntimeClasspathProvider;
import org.eclipse.jdt.launching.JavaRuntime;
	ILaunchManager launches = DebugPlugin.getDefault().getLaunchManager();
	ILaunchConfigurationType type = launches.getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
	ILaunchConfigurationWorkingCopy copy = type.newInstance(null, Activator.PLUGIN_ID);
	copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, "jp.hishidama.exampleClasspathProvider");

	IRuntimeClasspathProvider provider = JavaRuntime.getClasspathProvider(copy);
	IRuntimeClasspathEntry[] unresolved = provider.computeUnresolvedClasspath(copy);

	// JREコンテナーを除外
	List<IRuntimeClasspathEntry> unlist = new ArrayList<IRuntimeClasspathEntry>(unresolved.length);
	for (IRuntimeClasspathEntry ce : unresolved) {
		if (ce.getClasspathEntry().getEntryKind() == IClasspathEntry.CPE_CONTAINER && ce.getPath().toPortableString().contains("JRE_CONTAINER")) {
			continue;
		}
		unlist.add(ce);
	}

	IRuntimeClasspathEntry[] cp = provider.resolveClasspath(unlist.toArray(new IRuntimeClasspathEntry[unlist.size()]), copy);
	for (IRuntimeClasspathEntry ce : cp) {
		String location = ce.getLocation(); //jarファイル等のパス
		〜
	}

変数の置換に関する注意

launchでは、アプリケーションの引数環境変数の値に「${」「}」 という文字そのものを渡すことは出来ない。[2014-12-28]

例えば「target/hadoopwork/${execution_id}」という文字列を引数として渡そうとすると、以下のような例外が発生する。

!MESSAGE Reference to undefined variable execution_id
	at org.eclipse.core.internal.variables.StringSubstitutionEngine.resolve(StringSubstitutionEngine.java:262)
	at org.eclipse.core.internal.variables.StringSubstitutionEngine.substitute(StringSubstitutionEngine.java:195)
	at org.eclipse.core.internal.variables.StringSubstitutionEngine.performStringSubstitution(StringSubstitutionEngine.java:87)
	at org.eclipse.core.internal.variables.StringVariableManager.performStringSubstitution(StringVariableManager.java:574)
	at org.eclipse.core.internal.variables.StringVariableManager.performStringSubstitution(StringVariableManager.java:350)
	at org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate.getProgramArguments(AbstractJavaLaunchConfigurationDelegate.java:511)
	at org.eclipse.jdt.launching.JavaLaunchDelegate.launch(JavaLaunchDelegate.java:63)
	at org.eclipse.debug.internal.core.LaunchConfiguration.launch(LaunchConfiguration.java:858)
	〜

${}で囲まれた部分を変数とみなして置換しようとし、置換できる値が無い(execution_idという変数が定義されていない)からエラーということらしい。
${}をエスケープするような仕組みは提供されていない。詰んだorz


変数の置換を行うStringVariableManagerでは、拡張ポイントで変数を定義しておくことが出来る。
(IDynamicVariableResolverを使うには、「依存関係」の「必須プラグイン」で「org.eclipse.core.variables」を追加する必要がある)

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.variables.IDynamicVariable;
import org.eclipse.core.variables.IDynamicVariableResolver;
public class MyVariableResolver implements IDynamicVariableResolver {

	@Override
	public String resolveValue(IDynamicVariable variable, String argument) throws CoreException {
		return "value";	// 変数の値
	}
}

plugin.xml:

   <extension
         point="org.eclipse.core.variables.dynamicVariables">
      <variable
            description="example"
            name="execution_id"
            resolver="com.example.MyVariableResolver"
            supportsArgument="false">
      </variable>
   </extension>

拡張ポイントのvariableのname属性で定義した名前の変数が来ると、resolverのクラスが呼ばれる。

が、このresolveValue()で${}入りの値を返すと、ご丁寧にそれも置換しようとする。
したがって、「${execution_id}」を返すようにしても、以下のようなエラーになってしまう。詰んだorz

!MESSAGE Variable execution_id does not accept arguments
	at org.eclipse.core.internal.variables.StringSubstitutionEngine.performStringSubstitution(StringSubstitutionEngine.java:107)
	at org.eclipse.core.internal.variables.StringVariableManager.performStringSubstitution(StringVariableManager.java:574)
	at org.eclipse.core.internal.variables.StringVariableManager.performStringSubstitution(StringVariableManager.java:350)
	at org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate.getProgramArguments(AbstractJavaLaunchConfigurationDelegate.java:511)
	at org.eclipse.jdt.launching.JavaLaunchDelegate.launch(JavaLaunchDelegate.java:63)
	at org.eclipse.debug.internal.core.LaunchConfiguration.launch(LaunchConfiguration.java:858)
	〜

Eclipseプラグインへ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま