Enums & GWT
I've been working with GWT (Google's Web Toolkit) a bit recently, and I must first say it's an amazing tool. It does have some downfalls, like requiring you to use J2SE 1.4 syntax, so no
enum or generics when developing with GWT.. That said, there's nothing stopping you from using the Java SE 5 or 6 framework, so concurrency and all the other goodies are still there (to be used on the server side, obviously).
And that's just unfortunate, because I really got used to using enums! Still, not having the syntax is not going to stop me. After all, enums in Java are eventually translated into classes, so I can just write my own GWT-compatible Enum-class.
I started with the following base:
public class Enum implements IsSerializable {
private final String name;
private final int ordinal;
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public final class ExampleEnum extends Enum {
private ExampleEnum(String name, int ordinal) {
super(name, ordinal);
}
public static final ExampleEnum example1 =
new ExampleEnum("example1", 0);
public static final ExampleEnum example2 =
new ExampleEnum("example2", 1);
}
However, that didn't work, as there are some restrictions in how GWT translates Java code to web browser code. First of all, it didn't pass compilation because GWT requires that a serializable class have a no-args, public constructor. This is unfortunate, but if GWT requires, so be it. Second, even after doing that the classes wouldn't go through serialization properly, and after a short examination it appeared that final fields are the same as transient fields when it comes to serialization, which meant that the name and ordinal fields weren't being deserialized. So, I've made the changes needed, added a couple of getters, and got the following:
public class Enum implements IsSerializable {
private /* final */ String name;
private /* final */ int ordinal;
public Enum() {
this("error", -1);
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public final String getName() {
return name;
}
public final int getOrdinal() {
return ordinal;
}
}
Now I wanted to add the much needed hashCode and equals methods. The hashCode method was simple: return getOrdinal(). The user had to be trusted to use a unique number for each value. The equals method proved harder, though: Usually during an equality check, the type of the class is compared. However, since class type is not saved (more specifically, the Object.getClass method doesn't go through GWT-compilation), a simple getClass() == o.getClass() doesn't work here. In order to overcome this, GWT offers a utility method, GWT.getTypeName, which returns the name of the class. This could have been great, however this method doesn't work on the server side, which makes it impossible to implement any type checking at all. I could have chosen to pass another parameter at the constructor, asking for the type name - But eventually decided against it. Instead, I tried to bring the chance of clash to a minimum, and since it's for personal use, I know I won't use it badly.
(If you're interested in why the GWT.getTypeName doesn't work: GWT uses the keyword native to write native JavaScript code. The GWT utility class' methods are all marked native, since they're all supposed to be used on the client-side. However, when this is run on the server-side, the real Java VM is running there, and it's looking for a JNI library, which is obviously nowhere to be found - and therefore, a UnsatisfiedLinkError is thrown.)
I've also added a little mechanism to Enum to make it easier to implement a valueOf method easier. This brings the final code to the following:
public class Enum implements IsSerializable {
private /* final */ String name;
private /* final */ int ordinal;
public Enum() {
this("error", -1);
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public final String getName() {
return name;
}
public final int getOrdinal() {
return ordinal;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Enum anEnum = (Enum) o;
if (!name.equals(anEnum.name) || ordinal != anEnum.ordinal)
return false;
return true;
}
public int hashCode() {
return ordinal;
}
protected static Map makeValuesMap(Enum[] enums) {
Map result = new HashMap(enums.length);
for (int i = 0; i < enums.length; i++) {
Enum anEnum = enums[i];
result.put(anEnum.getName(), anEnum);
}
return result;
}
}
public class ExampleEnum extends Enum {
public ExampleEnum() { }
private ExampleEnum(String name, int ordinal) {
super(name, ordinal);
}
public static /* final */ ExampleEnum example1 =
new ExampleEnum("example1", 0);
public static /* final */ ExampleEnum example2 =
new ExampleEnum("example2", 1);
private static /* final */ Map values =
makeValuesMap(new Enum[] { example1, example2 });
public static ExampleEnum valueOf(String name) {
return values.get(name);
}
}
The biggest shame is that even with this admittedly-quite-cool-code, I still can't use the EnumMap and EnumSet classes, which are great help and are very efficient when it comes to enums.
Update: With GWT 1.5, Java enums are supported and can be used freely!
Comments (8)
java.util.concurrencyetc.