ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Enums in Java (One More Time)
Pages: 1, 2, 3

A Mini-Language for Enums

I have designed a mini-language called jEnum for declaring enums, and I have written a small "compiler-like" translator that translates jEnum declarations into the equivalent Java class. For example, the declaration for enum Day given in the beginning of this article is translated into the equivalent Java class listed above--this is actually how the class was created. Moreover, jEnum permits an optional package declaration (simply copied over into the class) and an optional class comment (also copied), and allows the specification of underlying integer values and string representations for the objects, as illustrated in the following example:



package com.softmoore.util;

/**
  * Various USA coins
  */
enum Coin { PENNY("penny") = 1, NICKEL("nickel") = 5, DIME("dime") = 10,
            QUARTER("quarter") = 25, HALF_DOLLAR("half dollar") = 50 };

Although the enums-as-integer-constants alternative has merit in certain circumstances, the enums-as-objects alternative is generally preferable because of its type safety. Of course the enums-as-objects alternative still has its shortcomings. This section briefly outlines jEnum, which retains the strengths of the enums-as-objects alternative while at the same time providing a compact, efficient declaration of enumerated values. In other words, it also satisfies criteria number 2.

For the compiler-oriented reader, here is the simple grammar for the jEnum mini-language.

compilationUnit = ( packageDecl )? ( docComment )? enumTypeDecl .

packageDecl     = "package" packagePath ";" .

packagePath     = packageName ( "." packageName )* .

docComment      = "/**" commentChars "*/" .

enumTypeDecl    = "enum" enumTypeName "{" enumList "}" ";" .

enumList        = enumDecl ( "," enumDecl )* .

enumDecl        = enumLiteral ( "(" stringLiteral ")" )? ( "=" intLiteral )? .

packageName     = identifier .

enumTypeName    = identifier .

enumLiteral     = identifier .

commentChars    = any-char-sequence-except-"*/"

The grammar allows an optional package declaration in the beginning that looks just like any Java package declaration, optionally followed by a standard Javadoc comment. The enum type declaration begins with the word "enum" (itself also a reserved word in the jEnum language). The enumerated values are enclosed in curly braces and are separated by commas. Each declaration of an enumerated value consists of a standard Java identifier, optionally followed by a string literal enclosed in parentheses, optionally followed by the assignment operator "=" and an integer literal.

If you omit the string literal, then the name for the enumerated value will be used. If you omit the assignment of an integer literal, then the compiler will assign values sequentially after the last assigned value, starting with zero, if none are assigned. The string values are returned as part of the toString() method, and the integer values are returned by the ord() method. For example, given the following enum declaration:

enum Color { RED("Red") = 2, WHITE("White") = 4, BLUE };
  • RED has the label "Red" and ord 2
  • WHITE has the label "White" and ord 4
  • BLUE has the label "BLUE" and ord 5

Note that Java reserved words are still reserved in jEnum; you can't have a package named "this" or an enum type named "for." Additionally, all string labels (string literals or enumerated value names) must be distinct, and all integer literals must be strictly increasing. Thus the following would not compile, as labels are not distinct:

enum Color { RED("Red"), WHITE("BLUE"), BLUE };

Nor would this declaration compile, because integers are not increasing (since WHITE is auto-assigned the value 2):

enum Color { RED = 1, WHITE, BLUE = 2 };

As a more complete and realistic example, consider this enum type declaration that, through the magic of bootstrapping, is actually used by the jEnum translator.

package com.softmoore.jEnum;

/**
  * This class encapsulates the symbols (a.k.a. token types)
  * of a language token.
  */
enum Symbol {
    identifier,
    enumRW("Reserved Word: enum"),
    abstractRW("Reserved Word: abstract"),
    assertRW("Reserved Word: assert"),
    booleanRW("Reserved Word: boolean"),
    breakRW("Reserved Word: break"),
    byteRW("Reserved Word: byte"),
    caseRW("Reserved Word: case"),
    catchRW("Reserved Word: catch"),
    charRW("Reserved Word: char"),
    classRW("Reserved Word: class"),
    constRW("Reserved Word: const"),
    continueRW("Reserved Word: continue"),
    defaultRW("Reserved Word: default"),
    doRW("Reserved Word: do"),
    doubleRW("Reserved Word: double"),
    elseRW("Reserved Word: else"),
    extendsRW("Reserved Word: extends"),
    finalRW("Reserved Word: final"),
    finallyRW("Reserved Word: finally"),
    floatRW("Reserved Word: float"),
    forRW("Reserved Word: for"),
    gotoRW("Reserved Word: goto"),
    ifRW("Reserved Word: if"),
    implementsRW("Reserved Word: implements"),
    importRW("Reserved Word: import"),
    instanceOfRW("Reserved Word: instanceOf"),
    intRW("Reserved Word: int"),
    interfaceRW("Reserved Word: interface"),
    longRW("Reserved Word: long"),
    nativeRW("Reserved Word: native"),
    newRW("Reserved Word: new"),
    nullRW("Reserved Word: null"),
    packageRW("Reserved Word: package"),
    privateRW("Reserved Word: private"),
    protectedRW("Reserved Word: protected"),
    publicRW("Reserved Word: public"),
    returnRW("Reserved Word: return"),
    shortRW("Reserved Word: short"),
    staticRW("Reserved Word: static"),
    strictfpRW("Reserved Word: strictfp"),
    superRW("Reserved Word: super"),
    switchRW("Reserved Word: switch"),
    synchronizedRW("Reserved Word: synchronized"),
    thisRW("Reserved Word: this"),
    throwRW("Reserved Word: throw"),
    throwsRW("Reserved Word: throws"),
    transientRW("Reserved Word: transient"),
    tryRW("Reserved Word: try"),
    voidRW("Reserved Word: void"),
    volatileRW("Reserved Word: volatile"),
    whileRW("Reserved Word: while"),
    equals("="),
    leftParen("("),
    rightParen(")"),
    leftBrace("{"),
    rightBrace("}"),
    comma(","),
    semicolon(";"),
    period("."),
    intLiteral,
    stringLiteral,
    docComment,
    EOF,
    unknown
};

The mini-language jEnum translator accompanies this article. If the enum declaration for Day is defined in a file named "Day.enum," then the command

$ java -jar jEnum.jar Day.enum

will generate the complete class Day.java shown above for the enums-as-objects alternative, including standard Javadoc comments for the methods. You can combine the call to java and its command line arguments into an executable file (a Unix shell script or a Windows batch file), so that the translator can be invoked as simply as:

$ jec Day.enum 

There are four important points that you should know about using the jEnum translator. First, the source file name does not have to end in ".enum." Any suffix is accepted as long as the full file name is supplied.

Second, if the file name does end in the suffix .enum, then supplying the suffix as part of the command line is optional. The translator will search first for a file with the exact name specified on the command line, and then for a file ending in .enum. The following is also acceptable:

$ java -jar jEnum.jar Day

Related Reading

Mac OS X for Java Geeks
By Will Iverson

Third, the name of the Java file produced by the translator is based on the name given in the enum definition, not the name of the source file. In other words, if the file were named simply "d.enum," the Java source file would still be named "Day.java." Of course, good naming conventions recommend naming the source file "Day.enum."

Fourth, the compiler accepts three flags that allow you to generate the different enum alternatives as follows:

  • -o generates the enums-as-objects alternative. (This is the default.)
  • -c generates the enums-as-integer-constants alternative, implemented as a class.
  • -i generates the enums-as-integer-constants alternative, implemented as an interface.

Additionally, the -c flag not only creates the class containing the integer constants, but also attempts to provide some of the functionality of the enums-as-objects approach by defining several public static methods such as first(), last(), toString(int n), prev(int n), and next(int n).

Resources

John I. Moore, Jr. is Principal Consultant for SoftMoore Consulting.


Return to ONJava.com.