前回のエントリの補足。
[Jython-bugs] [issue1642] Proxy jsr223 Nullpointer no arguments の回避策を考える。
綺麗な方法は思いつかなかったと書いたけど、ラッパークラスでそこそこ見栄え良くなるかも。
まず、大前提として目的の再確認。
  • 呼び出し側の Java は引数と戻り値の型を知っていたいので、インターフェースを Java 側で切る
  • 各スクリプト言語でそれを実装するが、どの言語でどのように実装しているかは呼び出し側は意識する必要は無い
  • 上記二つを満たしつつ、Jython の NullPointerException 問題に対処したい
つまり、NullPointerException が飛んだらどうこう、というのは呼び出し側では意識したくない。
また、必ず引数を渡すようにインターフェースを変更すると
各スクリプト言語実装(今回の場合、変更の必要の無い JavaScript や Ruby)も全て変更しないといけなくなるので却下。

そこで、ラッパークラス案。
まず、インターフェースに引数ありの getName() を追加。
通常使う必要は無いので @Deprecated アノテーションをつけておく。
次に、ラッパークラスを追加する。
getName() で NullPointerException が飛んだときのみ引数ありの方を呼ぶ。
これだけでは getName(String) を呼んだときに
javax.script.ScriptException: TypeError: getName() takes no arguments (1 given)
が発生してしまうため、Python 側の実装も変更する。
準備OK。ラッパーでくるんで返してやろう。
public static void main(String[] args) throws Throwable {
  ScriptExecutor executor = new ScriptExecutor();
  executor.execute("javascript", "serviceImpl.js");
  executor.execute("python", "serviceImpl.py");
  executor.execute("ruby", "serviceImpl.rb");
}
public void execute(String shortName, String filePath) throws Throwable {
  try {
    ScriptService service = getService(shortName, filePath);
    service.echo("hello!");
    System.out.println(service.getName());
    System.out.println(service.calculate(10));
    
  } catch (UndeclaredThrowableException e) {
    if (e.getUndeclaredThrowable() != null) {
      throw e.getUndeclaredThrowable();
    }
    throw e;
  }
}
private ScriptService getService(String shortName, String filePath)
    throws FileNotFoundException, ScriptException {
  ScriptEngineManager manager = new ScriptEngineManager();
  ScriptEngine engine = manager.getEngineByName(shortName);
  engine.eval(new FileReader(filePath));
  return new ScriptServiceWrapper(((Invocable) engine).getInterface(ScriptService.class));
}
引数の無いメソッド全てに使いもしない引数ありメソッドを
インターフェース側で用意しないといけないけれど、
呼び出し側や JavaScript, Ruby の実装を変える必要が無く、
NullPointerException の発生を意識するのがラッパーのみに閉じられるので
まずまず許せる回避策なんじゃないかな。

Copyright© 2011-2021 Shunsuke Otani All Right Reserved .