JMRI: Using Enums

The Minimal Enum

The minimal enum is really small. Here's an example of embedding an enum in a class to represent seven different constants:

        class MyClass {
            public enum Day {
                SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 
                THURSDAY, FRIDAY, SATURDAY
            }

            void checkParty(Day day) {
                if (day == Day.FRIDAY) { 
                    doParty();
                }
            }
        }

Examples of use:

        for (Day day : Day.values()) {
            // do what you want
        }
        String whichDay = "THURSDAY";
        Day day = Day.valueOf(whichDay);
        checkParty(day);          // disappointment!
        checkParty(Day.TUESDAY);  // disappointment!

Note there are no meaningful integer values for this.

Migrating Integer Constants to Enum

If you have a set of constants like

              static final int DOG  = 1;
              static final int CAT  = 2;
              static final int FISH = 3;
              static final int BIRD = 12;

converting those to an enum can make them more type-safe.

              public enum Pet {
                DOG(1),
                CAT(2),
                FISH(3),
                BIRD(12);
                
                private final int value;
                Pet(int v) { value = v; }
                int asInt() { return value; }
              }

To use this, just replace "int" with "Pet" where-ever one is defined or passed as a parameter. Then compile and see what else needs to be changed; if you're not doing arithmetic with the constants (in which case maybe they're not enums), there shouldn't be much else that needs to be changed.

        void check(int val) {
            switch (val) {
                case DOG: 
                    // do stuff and return
                case CAT:
                    // do stuff and return
                default:
                    // do stuff and return
            }
        }

becomes

        void check(Pet val) {
            switch (val) {
                case DOG: 
                    // do stuff and return
                case CAT:
                    // do stuff and return
                default:
                    // do stuff and return
            }
        }

(The only change was in the 1st line)

Adding String Values

Sometimes you want the elements to also have user-readable names separate from the names of the individual items. For example, you might want to call it TUESDAY in the code, but have it provide "Tuesday" for pretty printing.

    public enum NamedPet {
        DOG(1, "Dog"),
        CAT(2, "Cat"),
        FISH(3, "Fish"),
        BIRD(12, "Bird");

        private final int value;
        private final String name;
        Pet(int v, string s) { 
            value = v; 
            name = s;
        }
        int asInt() { return value; }
        String toString() { return name; }
    }

Note that you might want to internationalize these in the constructor. See java/src/jmri/LocoAddress.java for an example of this.

In general, you should not build a lot of code to convert between ints and Enums or between Strings and enums. That's a code smell that indicates something wrong with how you're using the enums. If users have to select a specific one, for example, provide a combobox of values, don't have them type a String that you then have to error-check and convert.

The one exception to this is the ConvertXML persistance system, which wants to convert your enum values to and from String values to store and load in XML files, and may also need to convert from the numeric values for historical reasons. See below.

ConvertXML

Enums can provide interfaces. We use the StringConvertibleEnum and IntConvertibleEnum interfaces as flags.

Migration

Start writing a new schema that only allows the specific elements, but make sure your code can take the old numeric values. Since the file contains the schema it obeys, this is OK. But it might require a lot of migrations if you do this between test releases....

Schema limiting to a set of specific enum values

        <xs:attribute name="connection" default="unspecified" >
          <xs:simpleType>
            <xs:restriction base="xs:token" >
              <xs:enumeration value="unspecified"/>
              <xs:enumeration value="plug"/>
              <xs:enumeration value="wire"/>
              <xs:enumeration value="solder"/>
              <xs:enumeration value="LED"/>
              <xs:enumeration value="bulb"/>
              <xs:enumeration value="other"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:attribute>

Schema also permitting older numeric values

        <xs:attribute name="size">
         <xs:simpleType>
          <xs:union>
           <xs:simpleType>
            <xs:restriction base="xs:positive-integer">
              <xs:maxInclusive="10"/>
            </xs:restriction>
           </xs:simpleType>
           <xs:simpleType>
            <xs:restriction base="xs:NMTOKEN">
             <xs:enumeration value="small"/>
             <xs:enumeration value="medium"/>
             <xs:enumeration value="large"/>
            </xs:restriction>
           </xs:simpleType>
          </xs:union>
         </xs:simpleType>
        </xs:attribute>
This can also be used for elements (which is the generally prefered approach)