FreemarkerをScalaで使う

Scalaの勉強の為に個人的なプロジェクトに使ってみようと思うのだけど,それにはテンプレートエンジンが必要.ということで,Freemarkerを使ってみた.

FreemarkerJavaのテンプレートエンジンなので,Scalaからそのまま呼び出すことはできるのだけど,JavaのMapとScalaのMapはちょっと違うし,Listの実装も違うのでそのままでは使えない.ScalaJava Beansっぽく書くのもばからしい.

Freemarkerレンダリングするオブジェクトツリーの構造を隠蔽するラッパ機構が用意されていて,例えばDomツリーを直接レンダリングできたりする.同じ枠組みを利用して,JythonやGroovyのためのラッパがデフォルトで用意されている.Scalaでもこれらとおなじようなものを用意してやればいい.ラッパ機構は,型に応じたラッピングオブジェクトと,ディスパッチするクラスで構成する.ディスパッチクラスはこんな感じ.要するに,オブジェクトの型に応じて適当なラッピングオブジェクトでラッピングして返してやれば良い.

package freemarker.ext.scala;
import freemarker.template.*;

public class ScalaObjectWrapper extends freemarker.ext.beans.BeansWrapper{
  static ScalaObjectWrapper _instance = new ScalaObjectWrapper();
  static ScalaObjectWrapper instance() {
    return _instance;
  }
  public TemplateModel wrap(Object obj) throws TemplateModelException {
    if (obj instanceof scala.collection.Map)
      return new ScalaHashModel((scala.collection.Map)obj);
    if (obj instanceof scala.Some)
      return wrap(((scala.Some)obj).get());
    if (obj instanceof java.util.Date)
      return new 
       freemarker.template.SimpleDate((java.util.Date)obj, 
       freemarker.template.SimpleDate.UNKNOWN);
    if (obj instanceof String)
      return new 
       freemarker.template.SimpleScalar((String)obj);
    if (obj instanceof Integer)
      return new 
       freemarker.template.SimpleNumber(
         ((Integer)obj).intValue());
    if (obj instanceof Double)
      return new 
       freemarker.template.SimpleNumber(
         ((Double)obj).doubleValue());
    if (obj instanceof scala.$colon$colon)
      return new 
       ScalaCollectionModel(
         ((scala.$colon$colon)obj).elements());
    if (obj.getClass().toString().
       split(" ")[1].startsWith("[")) // looks like array
      return new 
        freemarker.template.SimpleCollection(
          java.util.Arrays.asList((Object[])obj), this);
    return new ScalaModel(obj);
  }
}

Jython用のラッパのコードを見ると,高速化のためにラッピングオブジェクトをキャッシュしているので,そのうちやってみよう.

所感

面倒だったのは,Scalaが用意するオブジェクトツリーがJavaの型として何になるのかよくわからなかったこと.Listは

scala.$colon$colon

などとという得体の知れないクラスになっていた.Arrayが単なるJavaの配列になっていたのも意外といえば意外.