As of Today our so far internal java to c# conversion utility, sharpen, is being released as free software.
As an example of its usage we're going to write a simple contact management application in java and convert it to c#.
While it is possible to use sharpen to convert a complete application it is advisable to handle this as an iterative process: java a little, sharpen a little. The reason for this is that not all java constructs and idioms can be handled out of the box by sharpen, thus the translation might influence the design of the original java code.
As a first step we'll come up with a minimum skeleton for our console application, ignoring any "business logic" for now:
package contacts;
public class Program {
public static void main(String[] args) {
boolean running = true;
while (running) {
String option = Console.prompt("(a)dd new entry, (l)ist entries, (q)uit");
if (option.isEmpty()) continue;
switch (option.charAt(0)) {
case 'q':
running = false;
break;
default:
System.out.println("'" + option + "' DOES NOT COMPUTE!");
}
}
}
}
package contacts;
import java.io.*;
public class Console {
public static String prompt(String prompt) {
System.out.println(prompt);
return readLine();
}
private static String readLine() {
try {
return new BufferedReader(new InputStreamReader(System.in)).readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Here we can already see the first design decision triggered by sharpen. The console APIs for java and .net are quite different and although sharpen can handle IO primitives gracefully, it doesn't support translating our specific use of "System.in". The obvious way of handling situations like this is to factor out the required functionality into a specialized class and provide dedicated implementations for java and .net.
With the first java iteration complete let's get to sharpening it.
Before we can get any conversion done, the sharpen.core plugin must be installed. From inside eclipse:
- Checkout sharpen.core from db4o's subversion repository at https://source.db4o.com/db4o/trunk/sharpen/
- Right click the freshly checked out project in the "Package Explorer" and choose "Export" from the context menu;
- Expand the "Plug-in Development" folder and select "Deployable plug-ins and fragments";
- Set "Destination" to the root folder of your eclipse installation and click "Finish";
We're ready to automate the conversion from java to c# with an ant script. In order to make this task easier, we will reuse a few sharpen related macros defined in sharpen-common.xml.
<project name="ContactList" default="build-dotnet">
<import file="build-properties.xml" />
<import file="sharpen-common.xml" />
<target name="build-dotnet" depends="sharpen">
<exec executable="${nant.exe}">
</exec>
</target>
<target name="sharpen" depends="init">
<prepare-sharpen-workspace project="ContactList" dir="${sharpen.workspace.dir}" />
<sharpen workspace="${sharpen.workspace.dir}" resource="ContactList/src">
</sharpen>
</target>
<target name="init">
<reset-dir dir="${sharpen.workspace.dir}" />
</target>
</project>
Here we are using the common strategy of externalizing environment specific properties to a dedicated file (build-properties.xml):
<project name="build properties">
<property name="sharpen.workspace.dir" value="build" />
<property name="eclipse.home" value="c:/java/eclipse" />
<property name="nant.exe" value="c:/dotnet/nant-0.85/bin/NAnt.exe" />
</project>
We're not using a plain java properties file because the nant script which is used for compiling the converted sources also needs some of these definitions:
<?xml version="1.0"?>
<project name="ContactList" default="build">
<include buildfile="build-properties.xml" />
<property name="src.dir" value="${sharpen.workspace.dir}/ContactList.net" />
<property name="bin.dir" value="${src.dir}/bin" />
<target name="build">
<csc target="exe" output="${bin.dir}/ContactList.exe">
<sources basedir="${src.dir}">
<include name="**/*.cs" />
</sources>
</csc>
</target>
</project>
Running ant we get good news and bad news. The good news are sharpening went fine:
sharpen:
[mkdir] Created dir: build\ContactList
[copy] Copying 2 files to build\ContactList
[echo] org.eclipse.core.launcher.Main -data build -application sharpen.core.application ContactList/src
[java] project: ContactList
[java] source folder: src
[java] Pascal case mode: None
[java] Console.java
[java] Program.java
[java] Conversion finished in 3110ms.
C# compilation didn't go so well:
[exec] [csc] Compiling 2 files to 'ContactList.net\bin\ContactList.exe'.
[exec] [csc] ContactList.net\src\contacts\Console.cs(15,16): error CS0246: The type or namespace name 'java' could not be found (are you missing a using directive or an assembly reference?)
...
[exec] [csc] ContactList.net\src\contacts\Program.cs(12,16): error CS0117: 'string' does not contain a definition for 'isEmpty'
Before we fix those errors let's take a look at how Program.java got translated to c#:
namespace contacts
{
public class Program
{
public static void Main(string[] args)
{
bool running = true;
while (running)
{
string option = contacts.Console.prompt("(a)dd new entry, (l)ist entries, (q)uit"
);
if (option.isEmpty())
{
continue;
}
switch (option[0])
{
case 'q':
{
running = false;
break;
}
default:
{
System.Console.Out.WriteLine("'" + option + "' DOES NOT COMPUTE!");
break;
}
}
}
}
}
}
It looks good except for javaisms like the lower case namespace and method names which can be fixed with the introduction of the "pascalCase+" sharpen command line option.
To get rid of the c# compilation errors we must provide a platform specific version of the Console class and tell sharpen to ignore its java counterpart using the @sharpen.ignore javadoc annotation.
/**
* @sharpen.ignore
*/
public class Console {
...
As a very useful convention we like to keep native and converted c# sources separate from each other by introducing a subfolder named "native".
Here's our native c# Console implementation:
namespace Contacts
{
public static class Console
{
public static string Prompt(string message)
{
System.Console.WriteLine(message);
return System.Console.ReadLine();
}
}
}
The remaining compiler error is caused by calling the String.isEmpty which has no equivalent on the .net side. We'll sneak around this issue for the time being by rewriting the java side condition in terms of String.length:
if (option.length() == 0) continue;
Our build runs green now and the .net application is functional. We're ready to move on to the business logic.
We'll start with a collection based implementation to be enhanced with persistence later in the process.
package contacts;
public class Contact {
private String _name;
private String _email;
public Contact(String name, String email) {
_name = name;
_email = email;
}
public String name() {
return _name;
}
public String email() {
return _email;
}
}
package contacts;
import java.util.*;
public class ContactList {
private List<Contact> _entries = new ArrayList<Contact>();
public void add(Contact contact) {
_entries.add(contact);
}
public Iterable<Contact> entries() {
return _entries;
}
}
package contacts;
public class Program {
private ContactList _contacts = new ContactList();
public void readEvalLoop() {
boolean running = true;
while (running) {
String option = prompt("(a)dd new entry, (l)ist entries, (q)uit: ");
if (option.length() == 0) continue;
switch (option.charAt(0)) {
case 'a':
addEntry();
break;
case 'l':
listEntries();
break;
case 'q':
running = false;
break;
default:
System.out.println("'" + option + "' DOES NOT COMPUTE!");
}
}
}
private String prompt(String message) {
return Console.prompt(message);
}
private void listEntries() {
for (Contact c : _contacts.entries()) {
System.out.println(c.name() + " <" + c.email() + ">");
}
}
private void addEntry() {
String name = prompt("Name: ");
String email = prompt("Email: ");
_contacts.add(new Contact(name, email));
}
public static void main(String[] args) {
new Program().readEvalLoop();
}
}
A quick look at the converted object model shows that although correct the code still looks a bit alien to .net:
namespace Contacts
{
public class Contact
{
private string _name;
private string _email;
public Contact(string name, string email)
{
_name = name;
_email = email;
}
public virtual string Name()
{
return _name;
}
public virtual string Email()
{
return _email;
}
}
}
namespace Contacts
{
public class ContactList
{
private System.Collections.Generic.IList<Contacts.Contact> _entries = new System.Collections.Generic.List
<Contacts.Contact>();
public virtual void Add(Contacts.Contact contact)
{
_entries.Add(contact);
}
public virtual System.Collections.Generic.IEnumerable<Contacts.Contact> Entries()
{
return _entries;
}
}
}
Contact.Name, Contact.Email and ContactList.Entries would look better as properties and the qualified names used everywhere look rather untidy.
We can solve the first issue using the @sharpen.property javadoc annotation:
/**
* @sharpen.property
*/
public Iterable<Contact> entries() {
return _entries;
}
Proper "using" directives can be enforced with the "-organizeUsings" command line option:
<sharpen workspace="${sharpen.workspace.dir}" resource="ContactList/src">
<args>
<arg value="-pascalCase+" />
<arg value="-organizeUsings" />
</args>
</sharpen>
The c# version of ContactList is now indistinguishable from hand crafted code:
using System.Collections.Generic;
using Contacts;
namespace Contacts
{
public class ContactList
{
private IList<Contact> _entries = new List<Contact>();
public virtual void Add(Contact contact)
{
_entries.Add(contact);
}
public virtual IEnumerable<Contact> Entries
{
get
{
return _entries;
}
}
}
}
We now have a complete cross platform contact management application. The only feature missing for the huge marketing success it deserves is persistence which can be easily implemented with db4o.
We downloaded the db4o production packages for both java and .net 2.0 from here and added the corresponding paths to our build-properties.xml file:
<property name="db4o.jar" value="c:/java/db4o-7.2/lib/db4o-7.2.39.10644-java5.jar" />
<property name="db4o.dll" value="c:/dotnet/db4o-7.2/bin/net-2.0/Db4objects.Db4o.dll" />
<property name="eclipse.home" value="c:/java/eclipse" />
All we have to do now is to change ContactList to use a db4o ObjectContainer instead of an ArrayList as its storage backend:
package contacts;
import com.db4o.*;
public class ContactList {
private ObjectContainer _container = Db4o.openFile("contacts.db4o");
public void add(Contact contact) {
_container.store(contact);
}
/**
* @sharpen.property
*/
public Iterable<Contact> entries() {
return _container.query(Contact.class);
}
public void close() {
_container.close();
}
}
We also have to make sure that the ObjectContainer is shutdown cleanly on application exit, for that purpose we introduced the ContactList.close method above which must be called from the main application class:
public static void main(String[] args) {
Program program = new Program();
try {
program.readEvalLoop();
} finally {
program.close();
}
}
Getting the c# version to work requires additional configuration switches for the sharpen task. First the java code now depends on the db4o jar which must be known at translation time. Second, the .net db4o interface operates against the native .net type system (this is a great candidate for a default setting) and uses namespace and interface names more inline with the .net design guidelines:
<sharpen workspace="${sharpen.workspace.dir}" resource="ContactList/src">
<args>
<arg value="-pascalCase+" />
<arg value="-organizeUsings" />
<arg value="-cp" />
<arg file="${db4o.jar}" />
<arg value="-nativeTypeSystem" />
<arg value="-nativeInterfaces" />
<arg value="-typeMapping" />
<arg value="com.db4o.Db4o" />
<arg value="Db4objects.Db4o.Db4oFactory" />
<arg value="-namespaceMapping" />
<arg value="com.db4o" />
<arg value="Db4objects.Db4o" />
</args>
</sharpen>
Here's an screenshot of both versions of the application running side by side:

This was a minimalist example of converting a java application to c# using the open source sharpen tool. Our intention was to show how sharpen can be used in a realistic setting requiring some manual intervention of the developer during all phases of the process. For a more complex cross-platform project based on sharpen take a look at the db4o source code repository :).
The complete source code for this application together with build scripts can be found here.
Sharpen documentation can be found here.
The issue tracker is here.
We are looking forward to see which projects will make use of sharpen in the future now that it is out in the wild.