tags:

views:

95

answers:

3

I tend to use enums to create mini data tables all over my code. I use this pattern often. Often enough that I thought I'd ask for others opinion. I'm just curious if I'm taking enums too far, or if I should be doing something else instead.

A common example is an enum I'll use to drive the TableFormat(from glazed lists) in a JXTable(from swingx). But you could do the same with a TableModel and JTable. Here there is an enum value for each column in the table.

public enum Column {
  INDEX("Order",false, Integer.class, 30) ,
  ENVIRONMENTS("Environments", false, String.class, 50) ,
  LOGICALS("Logicals", false, String.class, 100),
  URL("Url", false, String.class, 200),
  SQL("Text", false, String.class, 200),
  SOURCE("Source", false, String.class, 50) ,
  TYPE("Type", false, String.class, 40) ,
  FORMATED("Formated", false, Boolean.class, 30) ,
  ACTIVE("Active", true, Boolean.class, 30);

  private String headerName;
  private boolean isEditable;
  private Class<? extends Object> viewClass;
  private int defaultWidth;

  PlanTableColumn (String headerName, boolean isEditable, Class<? extends Object> viewClass, int defaultWidth) {
    this.headerName = headerName;
    this.isEditable = isEditable;
    this.viewClass = viewClass;
    this.defaultWidth = defaultWidth;
  }

  public String getHeaderName() {
    return headerName;
  }
  public boolean isEditable() {
    return isEditable;
  }
  public Class<? extends Object> getViewClass() {
    return viewClass;
  }
  public int getDefaultWidth() {
    return defaultWidth;
  }
  public static Column fromOrdinal(int position){
    return Column.values()[position];
  }
}

With this enum defined, writing the TableModel or TableFormat is rudimenatary, and in some cases re-usable between unrelated tables. Especially if each enum value provides its own implementation of a getColumnValue(rowData).

This is a small example, as other tables use Column enums that also contain properties for things like; isVisibleByDefault, sortable, maxWidth, draggable, selectable. And I'll use these enums for far more things just just table column specifications as well.

Here is a larger example from some code that handles objects in an oracle database.

public enum ObjectType {
    //          Display     Oracle          Gets    | Permissions     |
    // Enum     Name        Name            Synonym Execute Select DML  Compiled
    TABLE(      "Table",    "TABLE",        true,   false, true,  true,  false),
    VIEW(       "View",     "VIEW",         true,   false, true,  false, true),
    MATERIALIZED_VIEW("Materialized View", "MATERIALIZED VIEW",
                                        true,   false, true,  false, true),
    PROCEDURE(  "Procedure","PROCEDURE",    true,   true,  false, false, true),
    PACKAGE(    "Package",  "PACKAGE",      false,  false, false, false, true),
    FUNCTION(   "Function", "FUNCTION",     true,   true,  false, false, true),
    TRIGGER(    "Trigger",  "TRIGGER",      false,  false, false, false, true),
    SYNONYM(    "Synonym",  "SYNONYM",      true,   false, true,  true,  false),
    INDEX(      "Index",    "INDEX",        false,  false, false, false, false),
    CONSTRAINT( "Constraint","CONSTRAINT",  false,  false, false, false, false),
    SEQUENCE(   "Sequence", "SEQUENCE",     true,   false, true,  false, false),
    TABLE_PARTITION( "Table Partition", "TABLE PARTITION", false, false, false, false, false);

    private String name;
    private String nameLC;
    private boolean synonym;
    private boolean grantExecute;
    private boolean grantSelect;
    private boolean grantDML;
    private boolean compiled;
    // "oracleName" is what is used in oracle *_objects tables to identify the object type.
    private String oracleName;

    ObjectType(String name, String oracleName, boolean synonym, boolean execute, boolean select, boolean dml, boolean compiled) {
        this.name = name;
        this.oracleName = oracleName;
        this.nameLC = name.toLowerCase();
        this.synonym = synonym;
        this.grantExecute = execute;
        this.grantSelect = select;
        this.grantDML = dml;
        this.compiled = compiled;
    }

    public String getOracleName() {
        return oracleName;
    }
    public String getName() {
        return name;
    }
    public boolean getsSynonym() {
        return synonym;
    }
    public boolean isGrantExecute() {
        return grantExecute;
    }
    public boolean isGrantSelect() {
        return grantSelect;
    }
    public boolean isGrantDML() {
        return grantDML;
    }
    public boolean isCompiled() {
        return compiled;
    }

    public static ObjectType fromName(String string) throws UnknownObjectTypeException {
        String stringLC = string.toLowerCase();

        for ( ObjectType type : ObjectType.values() ) {
            if ( type.nameLC.equals(stringLC) ) {
                return type;
            }
        }

        throw new UnknownObjectTypeException("found no object type with name " + string);
    }

    public static ObjectType fromOracleName(String string) throws UnknownObjectTypeException {
        for ( ObjectType type : ObjectType.values() ) {
            if ( type.getOracleName().equals(string) ) {
                return type;
            }
        }
        throw new UnknownObjectTypeException("found no object type with oracle name " + string);
    }
}

So what do you think? Am I going overboard with the enum properties?

+1  A: 

If your code refers to the enum values (by name) you have a good case for using them ... and exposing them. The same applies if you need to implement a Column lookup(String) method. Otherwise, they might be a bit heavy-weight.

But if these things are not exposed in public APIs, it probably doesn't matter which approach you take.

(In case you are interested, I take the view that it is OK to leave out the getter and setter method for a private inner class, especially if the fields are declared as final. You could apply that here ... if the enum is declared as private inner ... to cut out some of the verbiage.)

Stephen C
+1 "it is OK to leave out the getter and setter method for a private inner class"
rsp
yes, I refer to them by name most of the time (by enum, not by string name). the lookup(ordinal) static method is only there because somethings like to refer to table columns by the column index (JTable).
DragonFax
as for the private and final mentions. These are often used outside of the class, or are top level classes themselves. so they're not private. but I should use `final` more often, throughout my code. For instance, this is all immutable data.
DragonFax
A: 

I've gone even more overboard, writing an enum where CONSTANT.get_foo(mode) methods take another enum as a "mode" key, with doubly-nested EnumMaps (first layer by mode constant, second layer by this enum's constants) that get loaded with data the first time you refer to a given mode, using the mode to decide which data table file to read. The inside of the getter methods looks like

synchronized(fooMap){ if(!fooMap.containsKey(mode)) loadDataFor(mode); }
return fooMap.get(mode).get(this)

This baroque scheme actually made sense at the time, because it was read-only unchanging data, but the details varied per configuration.

Julian Morrison
I get tempted to do crazy stuff like that on a first version. And then later refactor it out of guilt into something 3rd class which just takes 2 enums to drive it, instead of putting one enum into another.
DragonFax
Ooooo.... This really pushes the limits of the Shirley Temple principle.
Dr. Tim
A: 

I think it is fine to do this with only one downside, it is a compilation change if you need to update the structure. That said, you could make it a little more flexible by adding the ability for a property file to change the (internal) data (I have done something like this where the enum was the default and used properties to override values).

Something that I haven't seen mentioned is that this gives you the ability to use switch statements... This can really optimize your code if you need to be able to branch based on the structure (as opposed to textual matches in if/else statements).

In the end, it is probably a matter of taste... if it works and keeps your code uncluttered without any tangible performance hit, then stick with it.

PaulP1975