Joe Developer

Saturday Dec 30, 2006

ikvm and freemarker in C#

I've been doing a fair bit of .NET work lately, mostly C#, and I've a need for a templating engine for dynamic text generation. Coming from the Java realm my first thought was of Freemarker. I've used FM a decent amount, am comfortable with it, and want the same fucntionality in a C# app.

Perhaps there's something out there, I've searched around a bit, but couldn't find just the thing to get the job done.

Enter ikvm. ikvm is an interesting project in that it aims to do .NET<->Java interop in a native way, offering a lot of options. One of the options is the simplest - use your Java libraries directly in your .NET code by translating the java bytecode into msil (Microsoft Intermediate Language - the .NET bytecode equiv).

Turns out this works exactly as advertised, and within an hour or so I was using .NET pojos (poNos?) in a little console app with a little FM template hard coded.
There are a few problems though, the largest of which is the difference between the javabeans naming convention for properties and the .NET convention.

For example, if I have a Java class Person with a property name, I'd have:

  public String getName(){...}
  public void setName(String value){...}
in C#, I'd have:
  public string Name
  {
    get
    {
       return this.name;
    }
    set
    {
      this.name = value;
    }
  }
note that "value" is an implicitly typed object of the type that the property is... in this case a string. It's a different syntax to get used to, but you can do everything you'd do in a getter and setter in what is an arguably more readable syntax.

So I use the ikvm tool ikvmc to translate my bytecode (had to fiddle a bit to also translate all the runtime dependencies) into freemarker-2.3.8wdeps.dll (like a jar file but I "compiled" all the dependencies into one library - bad for complex dependencies but easier for a quick test).

Simple, examples go off without a hitch. (using primatives in the FM context), but when I put an instance of Person in, things don't go so well. I try to use ${person.Name} in my FM template and it blows up exactly the way you'd think it would - FM attempts to find a method called "getName" on the Person class, it's not there on the .NET class, so things don't work. Obviously. It occurs to me that the .NET way with properties is much easier to think about what with the not having to create getName from name to call the right method, but that's the way things are. (see the getter/setter versus Property bit above)

Quick solution - create a little decorator that extends HashMap, overrides get(Object key), use the .NET reflection system there and return the right value. By the way, I extended HashMap in .NET via the wonder that is the IKVM.GNU.Classpath library that ships with ikvm. Thanks to the folks who make this little bit of magic possible.

This creates a number of issues as far as I'm concerned, starting with how absolutely kludgy it is. In my template rather than ${person.Name} I have to use ${person['Name']}. This creates all sorts of other issues, as I'd have to do far more messy magic to get this to work for complex object graphs (say, the Address property of a Person expression like ${person.Address.ZipCode}).

The "right" answer is, I think, to create a different object wrapper implementation for FM and use that in the FM configuration, one that can do all the nice Method/Property caching for me(as the FM implementation does for performance), resolve indexed properties, and the like. I'll be working on that in my copious free time as I love the power that FM gives, but I'm living (not unhappily) in a .NET world quite a bit these days.

The short story is - ikvm and the GNU classpath project rock, as I was able to start to get the job done using a great Java library from within a .NET application in a matter of a couple of hours. Amazing. I love code re-use, and since there are so many great OSS Java libs out there, I'm sure now that some of them will be available to me in .NET - a boon to productivity and surely a more less clunky interop than web services, a .NET embedded JVM (though ikvm does that too as a fully embeddable .NET JVM implementation), or Java->JNI->COM->.NET.

Note too - this says nothing of the performance of the generated msil... right now it's about POC and functionality, not billion transactions per second on a 486.

For the curious, below is the command line I used to get my version of FM and dependencies compiled to a dll. There are a TON of warnings with this configuration, but hey, I just DL'ed ikvm a few hours ago, it's going to take me a minute to get the kinks worked out with the tool.
  set PATH=c:\java\lib\ikvm.32\ikvm-0.32.0.0\bin;%PATH%
  ikvmc -out:freemarker-2.3.8wdeps.dll -target:dll ant.jar dom4j-1.6.jar javax.servlet.jar javax.servlet.jsp.jar jaxen-core.jar jaxen-jdom.jar jdom.jar log4j-1.2.11.jar xalan-2.6.0.jar xerces-2.4.0.jar xerces.jar xercesImpl.jar xml-apis.jar freemarker.jar 
And here's my shady little test code in C#
using System;
using System.Collections.Generic;
using System.Text;
using freemarker;
using freemarker.template;
using java.io;

namespace freemarker_test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting...");
            Configuration config = new Configuration();
            Person p = new Person();
            p.Name = "Brian";
            string templateText = "number: ${testNumber}, name: ${person['Name']}";
            StringReader reader = new StringReader(templateText);
            Template t = new Template("fooTemplate", reader, config);
            java.util.Map map = new java.util.HashMap();
            map.put("testNumber", 1);
            map.put("person", new DecoratingMap(p));
            StringWriter writer = new StringWriter();
            t.process(map, writer);
            Console.WriteLine("Text: " + writer.toString());
            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }

    public class Person
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }

    //a terrible little hack until a full on .NET object wrapper can be created for FM
    public class DecoratingMap : java.util.HashMap
    {
        private object o;

        public DecoratingMap(object subject)
        {
            this.o = subject;
        }

        public override object get(object key)
        {
            System.Reflection.PropertyInfo p = o.GetType().GetProperty((string)key);
            object ret = p.GetValue(o, null);
            return ret;
        }
    }
}

Calendar

Feeds

Search

Links

Navigation

Referrers