Tuesday, January 15, 2008

Enums in ActionScript 3

The introduction of Enums in Java 1.5 was a welcome addition. Prior to Java 1.5, one had to simulate Enums with some special patterns. In ActionScript 3 there is no native enum construct in the language. So we need to apply patterns similar to what we did in Java prior to 1.5. While similar, the pattern for ActionScript is a bit different.

Let's start with the basic Enum pattern in ActionScript:

package enumexample
{
public class Color
{
public var _value:String;

public function Color(val:String):void {
this._value = val;
}

public static const RED:Color = new Color("RED");
public static const BLUE:Color = new Color("BLUE");
public static const GREEN:Color = new Color("GREEN");
}
}


The issues with the above Enum implementation are:
  • The constructor is public. So one could do: var c:Color = new Color("FOO"); which violates the notion of the Color Enum constants being restricted to RED, GREEN or BLUE. ActionScript does not allow private constructors.
  • Color does not have a default no-arg constructor which is needed if an instance of Color is de-serialized by the Flash Player from data from the back-end e.g. when using LCDS's RemoteObject to invoke a Java method that returns an instance of Color (see this for LCDS Enum support). For default serialization based on property-value pairs, not only do we need a no-arg constructor but we also need to make _value public or provide a setter. Both these are undesirable because one could change an enum as follows: var c:Color = Color.RED; c._value = "MAROON";
  • If an Enum cannot guarantee uniqueness of enum constant instances, object comparisons based on == will fail. var c:Color = new Color("RED"); if (c == Color.RED); // false. ActionScript does not have the notion of operator overloading.
  • When a Color instance is de-serialized, an instance is first created using the default no-arg constructor and the _value property is then populated. There is no object substitution mechanism available in ActionScript like the readResolve() Java serialization mechanism. So created instances will fail the == comparison as Enum constant instance uniqueness cannot be guaranteed.

The solution I use in conjunction with LCDS RemoteObject is to have Color implement Externalizable and have readExternal() and writeExternal() methods read/write value for _value. In addition, the class is "locked" after the initial construction of the valid Enum constant instances - see the lock member in the Color class below. An exception is thrown when an attempt is made to construct an Enum instance after it is locked. However, since the de-serialization will construct an Enum instance with no args, the exception is not thrown if no args are specified to the constructor. Unfortunately there is no way to detect in the constructor that the instance is being created in the context of de-serialization. The _value member is made private. An equals() method is added which is the way to compare enum instances rather than using ==. However, this is just a guideline/convention and the use of == cannot be enforced. Here's what Color looks like finally - the best one can do given the limitations of the language.

package enumexample
{
[RemoteClass(alias="javaenumexample.Color")]
public class Color implements IExternalizable
{
private var _value:String;
private static var lock:Boolean=false;

public function Color(val:String=null):void {
if (lock && val != null)
throw new Error("Cannot instantiate Enum");

this._value = val;
}

public function get value():String {
return this._value;
}

public static const RED:Color = new Color("RED");
public static const BLUE:Color = new Color("BLUE");
public static const GREEN:Color = new Color("GREEN");

{
lock = true;
}

public function equals(enum:Color):Boolean {
if (enum == null)
return false;

return enum.value == this._value;
}

public function readExternal(input:IDataInput):void {
this._value = input.readUTF();
}

public function writeExternal(output:IDataOutput):void {
output.writeUTF(this._value);
}
}
}

2 comments:

Harish said...

Hi

I tried your method to implement Java Enums in AS3. But I hit this error:

[RPC Fault faultString="Unable to create a new instance of type 'gov.hhs.cms.ehrds.datacollection.model.ReferenceLookupType'." faultCode="Client.Message.Encoding" faultDetail="Types cannot be instantiated without a public, no arguments constructor."]
at mx.rpc::AbstractInvoker/http://www.adobe.com/2006/flex/mx/internal::faultHandler()[E:\dev\4.0.0\frameworks\projects\rpc\src\mx\rpc\AbstractInvoker.as:345]
at mx.rpc::Responder/fault()[E:\dev\4.0.0\frameworks\projects\rpc\src\mx\rpc\Responder.as:68]
at mx.rpc::AsyncRequest/fault()[E:\dev\4.0.0\frameworks\projects\rpc\src\mx\rpc\AsyncRequest.as:113]
at NetConnectionMessageResponder/statusHandler()[E:\dev\4.0.0\frameworks\projects\rpc\src\mx\messaging\channels\NetConnectionChannel.as:609]
at mx.messaging::MessageResponder/status()[E:\dev\4.0.0\frameworks\projects\rpc\src\mx\messaging\MessageResponder.as:264]

Any ideas why this is happening. Would appreciate your feedback

Thanks

Harish

Vijay K Ganesan said...

Can you post your gov.hhs.cms.ehrds.datacollection.model.ReferenceLookup class?