Defines a user-defined enumeration (also known as an enumerated type or enum). An enum is made up of a list of strongly typed, named constants called members. The value of a variable defined as an enum is restricted to the list of members defined for that enum. Some of the advantages of enums are that they are self-documenting, strongly typed, and validated at compile time (i.e., the compiler verifies that a variable defined as an enum can only contain values defined by that enum).
In addition to being named, each member also has an underlying numeric value associated with it. By default, the compiler sets those associated values automatically. Generally, you do not need to worry about the numerical values, but you can specify one or more of the values explicitly if desired.
You can define two types of enum. In the standard version, a variable defined as an enum can only be assigned one value at a time. You can also define a flag enum. Any variable defined as a flag enum can be assigned any combination of the members (also referred to as "flags" for flag enums) at one time. You can perform operations on flag enum variables to set and unset one or more of the flags.
All enums implicitly inherit from one of two classes. The single-value enums inherit from Progress.Lang.Enum, and flag enums inherit from Progress.Lang.FlagsEnum, which is a subclass of Progress.Lang.Enum. See the Progress.Lang.Enum class and Progress.Lang.FlagsEnum class entries for more information.
Syntax
ENUM enum-type-name[FLAGS]:
DEFINE ENUM enum-member-definition [enum-member-definition]... END [ENUM].
enum-type-name
Defines the type name for a user-defined enum. You can specify an ABL enum name in a way similar to how you specify an ABL class type. All the requirements and restrictions on how to specify an ABL class type apply to an enum. See the CLASS statement and Type-name syntax entries for more details.
FLAGS
Defines the enum as a flag enum, with the defined members acting as flags. This allows you to set one or more flags into the same variable. You can define up to 64 distinct members to be used as flags. You can also define a flag as a combination of other flags, and a flag defined this way does not count as a distinct flag.
Specifies the name of the member. Each member name must be unique among the names of all members defined in the enum as well as all properties, events, and variable data members that are defined in the class hierarchy and accessible to the defining class. The rules for member names follow the same rules that are in place for variable and property names. Member names are case insensitive and can be reserved keywords.
member-name[, member-name]
Assigns the same numeric value as a previously defined member. In this way, you can define multiple members as equivalent with the same underlying value. For flag enums, you can also equate an member with multiple, previously defined members, entered as a comma-delimited list. See the Examples section for more information.
constant
Defines the numeric value associated with the member. The value must be a constant of type INT64. The value can be represented in hexadecimal notation (for easier readability with flag enums, for example).
If the first member is not explicitly assigned a value, the compiler starts with a value of 1. For each subsequent member not assigned a value, the compiler assigns a value 1 larger than the previous member for a single-value enum, or the next power of 2 for a flag enum.
Flag enums are the primary use case for explicitly setting the underlying values of members. Progress recommends that, when defining a flag enum, you always explicitly define a member with a value of 0.
END [ENUM]
Specifies the end of the enum definition. You must end the enum definition with the END statement.
Examples
The following example shows how to define an enum with the four cardinal directions as its named values:
ENUM Direction:
DEFINE ENUM North
South
East
West.
END ENUM.
The next example shows how to define an member as equivalent to another:
ENUM Direction:
DEFINE ENUM North
Default = North
South
East
West.
END ENUM.
In this case, both North and Default have an underlying value of 1.
The following is an example of a flag enum, including the member ReadWrite being defined as a combination of previously defined members:
For flag enums, it is recommended that you include an member with an underlying value of 0, representing no flags set. Because the compiler automatically starts with a value of 1 for both single-value and flag enums, you must define an member with a value of 0 explicitly.
The syntax for assigning a value to a variable defined as an enum is similar to the syntax used to assign values to static data members of a class:
enum-type-name:member-name
Using the Direction enum from the first example, this code fragment defines myDirection as a variable of type Direction and assigns myDirection the member South:
DEFINE VARIABLE myDirection AS Direction.
myDirection = Direction:South.
This syntax cannot be used to set multiple flags for a variable defined as a flag enum. To set multiple flags, you need to use bitwise operators or the SetFlag( ), ToggleFlag( ), and UnsetFlag( ) methods, as described in the Notes section of this entry.
Members are read-only, so the following code will not compile:
Direction:South = myDirection.
Like other uninitialized class references, variables defined as enums have an initial value of the unknown (?) value.
Notes
Each member represents an instance of an object of the type of enum that defines them. When you reference a member, you get an instance that is maintained by the AVM.
Because enums are implicitly class based, you can use them in many of the same ways you can use a class type or instance. You can define variables, parameters, return types, and class-based properties as an enum. These data elements can then hold a reference to an enum instance. For example, using the Permission enum defined in the Examples, the following code fragments are all valid:
METHOD PUBLIC Permission SomeOperation (INPUT perm AS Permission):
END.
PROCEDURE proc:
DEFINE INPUT-OUTPUT PARAMETER perm AS Permission.
END.
FUNCTION func1 RETURNS Permission (OUTPUT perm AS Permission):
END.
You can explicitly pass members as INPUT parameters using the enum-type-name:member-name syntax, but because the member itself cannot be modified, it cannot be passed as an OUTPUT or INPUT-OUTPUT parameter. For example, the following is not allowed:
RUN proc1 (INPUT-OUTPUT Permission:Read).
You can also assign an enum object reference to a temp-table field defined as the Progress.Lang.Object class type, but you cannot assign an enum object reference to a database table field. For more information on object references in general, see the reference entry for a Class-based object reference.
Enums must be defined in a .cls file by themselves, not with other class or interface definitions.
Although enums are implicitly class based, you can not instantiate an enum using NEW. You must use the type-name syntax shown in the Examples section to access the members defined in an enum, or use the GetEnum( ) method to get an enum instance. The method takes an enum member name as a CHARACTER or the underlying numeric value of a member as an INT64 and returns an enum instance. For example, both of the following lines of code create an enum instance using the Direction enum type, and in both cases, myDirection is assigned the member South:
This method is not part of the implementation of Progress.Lang.Enum, but it is automatically available for all built-in and user-defined enum types. For full details, see the GetEnum( ) method entry.
All enums are final. They also cannot explicitly inherit from another class or implement interfaces.
Enums are serializable. You can pass parameters defined as enums between an ABL client and an AppServer.
Much of the functionality for classes is also supported for enums, including package names, the USING statement, reflection, and assignment and casting rules. Unlike classes, however, enums cannot have data members, properties, events, or user-defined methods.
You can define variables, properties, and parameters using the AS CLASS phrase. For example:
DEFINE VARIABLE myDirection AS CLASS Direction.
In the original Direction enum example, the underlying values for the members are automatically set by the compiler as 1 for North, 2 for South, and so on. However, you can change this pattern by explicitly setting one or more of the numerical values. For example:
ENUM Direction:
DEFINE ENUM North
South
East = 4
West.
END ENUM.
In this case, the underlying values for the members are still automatically set by the compiler as 1 for North, 2 for South. East is explicitly set as 4, so the compiler sets the value of West as one larger than the previous member, which in this case will be 5. The same rules hold true for flag enums, except that the compiler assigns the next power of 2 to each member that does not have its underlying value explicitly set.
When you have to explicitly set the underlying value of an member, you can use hexadecimal notation, particularly for ease of readability with flag enums. Following is the Permission enum with the underlying values explicitly set:
If desired, you can split the defined list of members in an ENUM statement into two or more DEFINE ENUM statements for readability. For example:
ENUM Direction:
DEFINE ENUM North
South.
DEFINE ENUM East
West.
END ENUM.
The same rules for how the compiler assigns the underlying numerical values still apply, i.e., in the example above, South has an underlying value of 2, and East has an underlying value of 3. In other words, the underlying numerical values will be assigned according to the order of the members, regardless of whether or not the member list is split into multiple DEFINE ENUM statements.
Note: Although you can split the list of members into separate DEFINE ENUM statements, the list of members for a given enum must be completely defined in a single ENUM statement. Additional members cannot be added to a defined enum in a separate ENUM statement.
The following comparison operators can be used with enums, as long as the two expressions being compared are both defined as the same enum type:
EQ (=)
NE (<>)
GT (>)
LT (<)
GE (>=)
LE (<=)
When used with expressions that are enum types, the operators compare the underlying numerical values of the members. Comparisons can be made using either single-value enums or flag enums. Using the previously defined Direction enum, this code fragment shows a comparison and results in the message being displayed:
DEFINE VARIABLE myDirection AS Direction.
myDirection = Direction:South.
IF myDirection NE Direction:North THEN
MESSAGE "The current heading is not north."
VIEW-AS ALERT-BOX.
You can only use the comparison operators for two expressions defined as the same enum type.
The CompareTo( ) method of Progress.Lang.Enum also allows you to compare enum instances based on the members' numeric values. See the CompareTo( ) method entry for details.
The CASE statement also supports the use of enums. For example, using the Direction enum defined in the Examples section:
DEFINE VARIABLE myDirection AS Direction.
myDirection = Direction:South.
CASE myDirection:
WHEN Direction:North THEN
MESSAGE "Your current direction is north.".
WHEN Direction:South THEN
MESSAGE "Your current direction is south.".
OTHERWISE
MESSAGE "Your current direction is east, west, or unknown.".
END CASE.
The expressions in the WHEN phrases must be members from the enum type in the CASE statement.
Note: You can use flag enums with the CASE statement, but be aware that the WHEN phrases check for equality of the members strictly based on numeric values, not as combinations of multiple flags. For example, in the following code, the two WHEN statements for Permission:Read and Permission:Write will not fire for the given value of myPermission; the OTHERWISE statement will.
DEFINE VARIABLE myPermission AS Permission.
myPermission = Permission:ReadWrite.
CASE myPermission:
WHEN Permission:Read THEN
MESSAGE "You can read data.".
WHEN Permission:Write THEN
MESSAGE "You can write data.".
OTHERWISE
...
END CASE.
The following bitwise operators can be used with flag enums. Each operator does a bitwise comparison of two enum expressions and returns a new enum instance as a result:
Bitwise AND: Check the state of a particular flag.
Bitwise OR: Set a flag.
Bitwise XOR (exclusive OR): Toggle a flag.
Bitwise NOT (complement): Turn off a flag.
Caution: Because bitwise NOT turns off flags, it is possible to end up with an enum instance with no flags set, i.e., a value of 0. Always defining an member with value 0 is recommended to avoid an enum expression that doesn't correspond to any defined members.
As with the logical comparison operators, both expressions must be the same enum type. If they are not, the code will not compile.
Using the Permission enum from the Examples section, the following examples show use cases of the bitwise operators:
DEFINE VARIABLE myPermissions AS Permission.
/* myPermissions is set with a Permission instance with the Read and
Write flags set. */
myPermissions = Permission:Read OR Permission:Write.
/* All flags are set with the possible exception of Create, which is
toggled. */
myPermissions = myPermissions XOR Permission:Create.
/* Ensures that the Create flag is not set. */
myPermissions = myPermissions AND NOT Permission:Create.
/* All flags are set except Create. */
myPermissions = NOT Permission:Create.
/* Checks to see if Read is set. */
IF (myPermissions AND Permission:Read) = Permission:Read THEN ...
/* Three additional ways to check if Read is set. */
IF (myPermissions AND Permission:Read):Equals(Permission:Read) THEN ...
IF myPermissions:IsFlagSet(Permission:Read) THEN ...
IF (myPermissions AND Permission:Read) NE Permission:None THEN ...
The order of operator precedence for bitwise and logical operators is the following:
bitwise NOT
logical NOT
bitwise AND
bitwise XOR
bitwise OR
logical AND
logical OR
In practice, the order of precedence for bitwise and logical operators are independent because the bitwise operators return an enum instance and the logical operators return a logical. Code like the following will not compile because enum1 AND enum2 returns an enum instance, and a logical is required for the OR part of the statement:
IF enum1 AND enum2 OR iCount <> 1...
See the Expression entry for the full table of operator precedence.
In addition to the bitwise operators, there are three methods that you can use to set and unset flags: the ToggleFlag( ) method, the SetFlag( ) method, and the UnsetFlag( ) method. These methods are not part of the implementation of Progress.Lang.FlagsEnum, but they are automatically available for all built-in and user-defined flag enums.
The MINIMUM and MAXIMUM functions do not support enums.
The STRING and QUOTER functions return a member name for enum-valued expressions. For flag enum-valued expressions with multiple flags set, STRING returns a comma-separated list of member names. Similarly, PUT will send the appropriate member name(s) to the output destination.
The INTEGER, INT64, and DECIMAL functions return the underlying numeric value of an member for enum-valued expressions. Given that the numeric values of an member are of type INT64, it is possible the INTEGER function will raise an error with an member if the numeric value cannot be represented as an INTEGER.
Calling DELETE OBJECT on an enum instance will be silently ignored because enum instances are maintained by the AVM. Enums also do not appear in the SESSION-FIRST-OBJECT/SESSION-LAST-OBJECT chain.