Antの自作タスクで外部クラスを扱う(指定されたクラス名のインスタンスを生成して呼び出す)方法。
例えばjavaタスクでは、クラス名やそのクラスの存在するクラスパスを指定できる。
それと同様にクラス名(とクラスパス)を指定してそのインスタンスを生成し、メソッドを呼び出すことが出来る。
<?xml version="1.0" encoding="Shift_JIS"?> <project name="ant classpath sample" basedir=".."> <target name="sample1"> <taskdef name="call" classname="jp.hishidama.sample.ant.CallTask" classpath="classes" /> <call classname="jp.hishidama.sample.Exec" classpath="../sample/classes" /> </target> </project>
package jp.hishidama.sample.ant;
import java.lang.reflect.Method; import org.apache.tools.ant.AntClassLoader; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.Path;
public class CallTask extends Task { protected String classname; protected Path classpath; public void setClassname(String name) { classname = name; } public void setClasspath(Path path) { if (classpath == null) { classpath = path; } else { classpath.add(path); } }
@Override public void execute() throws BuildException { AntClassLoader cl = getProject().createClassLoader(classpath); try { Class<?> c = cl.loadClass(classname); Object obj = c.newInstance(); Method m = c.getMethod("execute"); m.invoke(obj); } catch (Exception e) { throw new BuildException(e); } } }
メソッドはリフレクションで呼び出すので、クラス名はFQCNで指定する。
クラスパスはorg.apache.tools.ant.types.Pathで保持する。
package jp.hishidama.sample; public class Exec { public void execute() { System.out.println("hello, classpath."); } }
※Exec.classとCallTask.classは別のディレクトリー(クラスパス)に存在する状態
build.xml上でクラスパスを指定する場合、classpath属性とclasspathref属性、それとボディー部にclasspath要素を指定する方法が一般的。
これらを全て満たすには、以下のようなメソッドを用意する。
import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference;
public class CallTask extends Task {
protected Path classpath; public void setClasspath(Path path) { if (classpath == null) { classpath = path; } else { classpath.add(path); } } public void setClasspathRef(Reference r) { createClasspath().setRefid(r); } public Path createClasspath() { if (classpath == null) { classpath = new Path(getProject()); } return classpath; }
〜 }
これらのクラス名・クラスパスの指定やインスタンス生成(クラスローダーの取得)を処理するクラスが用意されている。(Ant1.6以降)
そのクラスを使って書き換えると、以下のようになる。
package jp.hishidama.sample.ant;
import java.lang.reflect.Method; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; import org.apache.tools.ant.util.ClasspathUtils; import org.apache.tools.ant.util.ClasspathUtils.Delegate;
public class CallTask2 extends Task { private Delegate cpDelegate; @Override public void init() throws BuildException { super.init(); cpDelegate = ClasspathUtils.getDelegate(this); } protected final Delegate getDelegate() { return cpDelegate; }
public void setClassname(String name) { getDelegate().setClassname(name); } public void setClasspath(Path path) { getDelegate().setClasspath(path); } public void setClasspathRef(Reference r) { getDelegate().setClasspathref(r); } public Path createClasspath() { return getDelegate().createClasspath(); }
@Override public void execute() throws BuildException { Object obj = getDelegate().newInstance(); try { Method m = obj.getClass().getMethod("execute"); m.invoke(obj); } catch (Exception e) { throw new BuildException(e); } } }
<target name="sample Delegate"> <taskdef name="call" classname="jp.hishidama.sample.ant.CallTask2" classpath="classes" /> <!-- classpath属性の例 --> <call classname="jp.hishidama.sample.Exec" classpath="../sample/classes" /> <!-- classpathref属性の例 --> <path id="sample.class.path"> <pathelement location="../sample/classes" /> </path> <call classname="jp.hishidama.sample.Exec" classpathref="sample.class.path" /> <!-- classpath要素の例 --> <call classname="jp.hishidama.sample.Exec"> <classpath> <pathelement location="../sample/classes" /> </classpath> </call> </target>
上記の例ではリフレクションを使ってメソッド呼び出しをしているが、決まったメソッドを呼び出すなら、インターフェースやアダプタークラスを作ってそのメソッドを呼び出す方が素直。
package jp.hishidama.sample.ant; public interface Listener3 { public void execute(); }
package jp.hishidama.sample.ant; 〜 public class CallTask3 extends Task { 〜 @Override public void execute() throws BuildException { Listener3 obj = (Listener3)getDelegate().newInstance(); obj.execute(); } }
※CallTask3.classとListener3.classは 同一のディレクトリー(クラスパス)に存在する状態とする
package jp.hishidama.sample; import jp.hishidama.sample.ant.Listener3; public class Exec3 implements Listener3 { @Override public void execute() { System.out.println("hello, listener."); } }
※Exec3.classとCallTask3.class(Listener3.class)は別のディレクトリー(クラスパス)に存在する状態
<target name="call3-sample"> <taskdef name="call" classname="jp.hishidama.sample.ant.CallTask3" classpath="classes" /> <call classname="jp.hishidama.sample.Exec3" classpath="../sample/classes" /> </target>
↓
が、これで実行すると以下のようなエラーになる。
BUILD FAILED
C:\workspace\ant\bin\build.xml:129: Class jp.hishidama.sample.Exec3 could not be loaded because of an invalid dependency.
Exec3用に指定しているクラスパスの中にはExec3しか無く、それが依存している(implementsしている)Listener3はそこには無い為、ロードすることが出来ない。
なのでListener3の場所もクラスパスに指定してやると…
<target name="call3-sample2"> <taskdef name="call" classname="jp.hishidama.sample.ant.CallTask3" classpath="classes" /> <call classname="jp.hishidama.sample.Exec3" classpath="../sample/classes;classes" /> </target>
↓実行
BUILD FAILED
C:\workspace\ant\bin\build.xml:129: java.lang.ClassCastException: jp.hishidama.sample.Exec3 cannot be cast to jp.hishidama.sample.ant.Listener3
今度はクラスは見つかったのだが、Listener3へのキャストに失敗している。
これは、同名のインターフェース(クラス)でもAnt内のクラスローダーが異なると別インターフェース(クラス)として認識されてしまう為。
なので、呼び出したいクラスのクラスローダーも、CallTask3自身と同じクラスローダーを使う必要がある。
protected String classname; public void setClassname(String name) { this.classname = name; // getDelegate().setClassname(name); }
※Delegateにはクラス名を取得するメソッドが何故か無いので、クラス名は自分で保持しておく必要がある。
//Ant1.7用
@Override
public void execute() throws BuildException {
ClassLoader cl = getProject().createClassLoader(
this.getClass().getClassLoader(),
getDelegate().getClasspath());
Listener3 obj = (Listener3) ClasspathUtils.newInstance(classname, cl);
obj.execute();
}
//Ant1.6用
@Override
public void execute() throws BuildException {
// AntClassLoader acl = getProject().createClassLoader(getDelegate().getClasspath());
AntClassLoader acl = (AntClassLoader) getDelegate().getClassLoader();
acl.setParent(this.getClass().getClassLoader());
Listener3 obj = (Listener3) ClasspathUtils.newInstance(classname, acl);
obj.execute();
}
<target name="call3-sample3"> <taskdef name="call" classname="jp.hishidama.sample.ant.CallTask4" classpath="classes" /> <call classname="jp.hishidama.sample.Exec3" classpath="../sample/classes" /> </target>
これでちゃんと呼び出せる^^