Java 上の JavaScript エンジン Nashorn で Java ライブラリを使う

f:id:Naotsugu:20170216221743p:plain:w250

Nashorn スクリプトから外部 Java ライブラリを使うのは意外と面倒です。

Nashorn の基本的な使い方は以下を参照ください。

blog1.mammb.com

ここでは、Nashorn スクリプトから Ivy を使い、外部の Java ライブラリを使う方法を紹介します。

クラスパス の指定

Nashorn スクリプトで外部 Java ライブラリを使うには、jjs コマンドに -cp オプションでクラスパスを設定します。

$ jjs script.js -cp external-lib.jar

Nashorn スクリプト内部で shebang で指定することもできます。

#!/usr/bin/jjs -cp external-lib.jar

// ...

しかし外部ライブラリが複数必要な場合、jjs ではクラスパスのワイルドカードインポートに対応していなかったり(たぶん)、依存ライブラリの指定などはちょっと面倒です。

Ivy で依存ライブラリを取得する

以下のようにすれば URLClassLoader で Ivy をロードして実行することができます。

var URL = java.net.URL;
var URLClassLoader = java.net.URLClassLoader;
var URLArrayType = Java.type('java.net.URL[]');

var ivyJarUrl = new URL('https://repo1.maven.org/maven2/org/apache/ivy/ivy/2.4.0/ivy-2.4.0.jar');
var urls = new URLArrayType(1);
urls[0] = ivyJar.toURI().toURL();
var ivyMain = new URLClassLoader(urls).loadClass("org.apache.ivy.Main")

URLClassLoader 経由で Ivy の jar を取得して loadClass() しているだけです。

Ivy は ant から使う意外に、直接 Main メソッドを実行することで、依存解決して必要な Jar を落としてくることができます。

今回は loadClass() でロードした Ivy の Main クラスを使って依存解決してみます。


Ivy の Main は以下のようになっています。

    public static void main(String[] args) throws Exception {
        CommandLineParser parser = getParser();
        try {
            run(parser, args);
            System.exit(0);
        } catch (ParseException ex) {
            System.err.println(ex.getMessage());
            usage(parser, false);
            System.exit(1);
        }
    }

System.exit(0) しちゃっているので、以下のように直接 run メソッド呼んでやればコマンドラインで Ivy 使うのと同じことができそうです。

var ivyRunMethod = ivyMain.getDeclaredMethod('run', parser.class, StringArrayType.class);
    ivyRunMethod.accessible = true;

var opts = ['-dependency'];
     opts = opts.concat(dependency.split(':'));
     opts = opts.concat(['-retrieve', 'lib/[artifact]-[revision](-[classifier]).[ext]']);

var ivyArgs = new ObjectArrayType(2);
    ivyArgs[0] = parser;
    ivyArgs[1] = Java.to(opts, StringArrayType);
ivyRunMethod.invoke(null, ivyArgs);

-retrieve オプションで依存 Jar を lib フォルダに格納します。

-dependency で必要な依存を指定します。

あとは getDeclaredMethod() で取得した run メソッドを起動するだけです。


依存 Jar が lib フォルダに保存されるので、クラスパスに無理やり追加してしまいましょう。

var addUrlMethod = URLClassLoader.class.getDeclaredMethod('addURL', URL.class);
addUrlMethod.accessible = true;

for each (var file in new File('lib/').listFiles()) {
  if (file.isFile &&
      file.name.endsWith('.jar') &&
      !file.name.endsWith('-javadoc.jar') &&
      !file.name.endsWith('-sources.jar')) {
    var addUrlArgs = new ObjectArrayType(1);
    addUrlArgs[0] = file.toURI().toURL();
    addUrlMethod.invoke(java.lang.ClassLoader.getSystemClassLoader(), addUrlArgs);
  }
}

SystemClassLoader の addURL にリフレクションで直接追加しちゃってます。

これらを関数でくるんでおきましょう。

var Ivy = (function() {
  return {
    load : function(dependencies) {
      // ...
    }
  };
})();

結構いいかげんですが、スクリプトなので良しとして、ivy.js とでもして保存します。

全体像は Github にあげておきましたので以下を御覧ください。

github.com

実行する

Nashorn スクリプトから外部スクリプト呼ぶには load() 関数を使います。

load("./ivy.js");

依存を指定して先程作成した ivy.js の関数を呼びます。

commons-lang3guava でも利用してみましょう。

Ivy.load([
  'org.apache.commons:commons-lang3:3.5',
  'com.google.guava:guava:19.0'
]);

Java.type でインポートして使います。

var StringUtils = Java.type('org.apache.commons.lang3.StringUtils');
print('[' + StringUtils.trim('  XX  ') + ']');

var Strings = Java.type('com.google.common.base.Strings');
print('[' + Strings.repeat('*', 10) + ']');

出力は以下のようになります。

[XX]
[**********]

commons-lang3guava が Nashorn スクリプト で使えるようになりました。

まとめ

Nashorn スクリプトでも簡単にJavaの資産である外部ライブラリを使うことができました。

環境に依存せず、ちょっとした事は概ね Nashorn スクリプト でまかなえちゃいそうです。


Maven で似たようなスクリプトもありました。

GitHub - nasven/nasven: Nasven.js core code