views:

2098

answers:

14

Hi,

I have a large number of Enums that implement this interface:

/**
 * Interface for an enumeration, each element of which can be uniquely identified by it's code
 */
public interface CodableEnum {

    /**
     * Get the element with a particular code
     * @param code
     * @return
     */
    public CodableEnum getByCode(String code);

    /**
     * Get the code that identifies an element of the enum
     * @return
     */
    public String getCode();
}

A typical example is:

public enum IMType implements CodableEnum {

    MSN_MESSENGER("msn_messenger"),
    GOOGLE_TALK("google_talk"),
    SKYPE("skype"),
    YAHOO_MESSENGER("yahoo_messenger");

    private final String code;

    IMType (String code) {
     this.code = code;
    }

    public String getCode() {
     return code;
    } 

    public IMType getByCode(String code) {
     for (IMType e : IMType.values()) {
      if (e.getCode().equalsIgnoreCase(code)) {
       return e;
      }
     }
    }
}

As you can imagine these methods are virtually identical in all implementations of CodableEnum. I would like to eliminate this duplication, but frankly don't know how. I tried using a class such as the following:

public abstract class DefaultCodableEnum implements CodableEnum {

    private final String code;

    DefaultCodableEnum(String code) {
     this.code = code;
    }

    public String getCode() {
     return this.code;
    } 

    public abstract CodableEnum getByCode(String code);  
}

But this turns out to be fairly useless because:

  1. An enum cannot extend a class
  2. Elements of an enum (SKYPE, GOOGLE_TALK, etc.) cannot extend a class
  3. I cannot provide a default implementation of getByCode(), because DefaultCodableEnum is not itself an Enum. I tried changing DefaultCodableEnum to extend java.lang.Enum, but this doesn't appear to be allowed.

Any suggestions that do not rely on reflection? Thanks, Don

+1  A: 

Unfortunately, I don't think that there is a way to do this. Your best bet would pro ably be to give up in emums altogether and use conventional class extension and static members. Otherwise, get used to duplicating that code. Sorry.

Daniel Spiewak
A: 

It seems like you are actually implementing run time type information. Java provides this as a language feature.

I suggest you look up RTTI or reflection.

Dave Hillier
Could you elaborate on this? I don't see what you mean.
Mwanji Ezana
A: 

I don't think this is possible. However, you could use the enum's valueOf(String name) method if you were going to use the enum value's name as your code.

Jorn
But where the code is not the same as the Enum's name, that won't work
Don
True, of course, but the code wás the same in the example.
Jorn
A: 

How about a static generic method? You could reuse it from within your enum's getByCode() methods or simply use it directly. I always user integer ids for my enums, so my getById() method only has do do this: return values()[id]. It's a lot faster and simpler.

Could you show the implementation of this method?
Don
+5  A: 

Abstract enums are potentially very useful (and currently not allowed). But a proposal and prototype exists if you'd like to lobby someone in Sun to add it:

http://freddy33.blogspot.com/2007/11/abstract-enum-ricky-carlson-way.html

Sun RFE:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570766

Alex Miller
+2  A: 
Greg Mattes
Nice idea, thanks for sharing the implementation
Don
A: 

If you really want inheritance, don't forget that you can implement the enum pattern yourself, like in the bad old Java 1.4 days.

Mwanji Ezana
A: 

About as close as I got to what you want was to create a template in IntelliJ that would 'implement' the generic code (using enum's valueOf(String name)). Not perfect but works quite well.

Javamann
+8  A: 

You could factor the duplicated code into a CodeableEnumHelper class:

public class CodeableEnumHelper {
    public static CodeableEnum getByCode(String code, CodeableEnum[] values) {
        for (CodeableEnum e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

Each CodeableEnum class would still have to implement a getByCode method, but the actual implementation of the method has at least been centralized to a single place.

public enum IMType implements CodableEnum {
    ...
    public IMType getByCode(String code) {
        return (IMType)CodeableEnumHelper.getByCode(code, this.values());
    } 
}
dave
As an improvement, you could just eliminate the getByCode from the interface. It's enough to provide it in the helper (as a static method). That way there's even less duplication in the individual enums.
sleske
this.values() referes to a static method and should be IMType.values(). I would even remove the requirement to pass in an array of value an instead pass in an enum class literal allowing to use type.getEnumConstants().
Willi
+1  A: 

Create a type-safe utility class which will load enums by code:

The interface comes down to:

public interface CodeableEnum {
    String getCode();
}

The utility class is:

import java.lang.reflect.InvocationTargetException;


public class CodeableEnumUtils {
    @SuppressWarnings("unchecked")
    public static <T extends CodeableEnum>  T getByCode(String code, Class<T> enumClass) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  T[] allValues = (T[]) enumClass.getMethod("values", new Class[0]).invoke(null, new Object[0]);
  for (T value : allValues) {
   if (value.getCode().equals(code)) {
    return value;
   }
  }
  return null;
}

}

A test case demonstrating usage:

import junit.framework.TestCase;


public class CodeableEnumUtilsTest extends TestCase {
    public void testWorks() throws Exception {
 assertEquals(A.ONE, CodeableEnumUtils.getByCode("one", A.class));
      assertEquals(null, CodeableEnumUtils.getByCode("blah", A.class));
    }

enum A implements CodeableEnum {
 ONE("one"), TWO("two"), THREE("three");

 private String code;

 private A(String code) {
  this.code = code;
 }

 public String getCode() {
  return code;
 } 
}
}

Now you are only duplicating the getCode() method and the getByCode() method is in one place. It might be nice to wrap all the exceptions in a single RuntimeException too :)

triggerNZ
Great answer, but I should have specified in my original question, that I'd prefer answers that don't rely on reflection. I've updated the question.
Don
No need to use reflection. There is Class.getEnumConstants().
Willi
+3  A: 

To tidy up dave's code:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> E getByCode(
        String code, E[] values
    ) {
        for (E e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

public enum IMType implements CodableEnum {
    ...
    public IMType getByCode(String code) {
        return CodeableEnumHelper.getByCode(code, values());
    } 
}

Or more efficiently:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> Map<String,E> mapByCode(
        E[] values
    ) {
        Map<String,E> map = new HashMap<String,E>();
        for (E e : values) {
            map.put(e.getCode().toLowerCase(Locale.ROOT), value) {
        }
        return map;
    }
}

public enum IMType implements CodableEnum {
    ...
    private static final Map<String,IMType> byCode =
        CodeableEnumHelper.mapByCode(values());
    public IMType getByCode(String code) {
        return byCode.get(code.toLowerCase(Locale.ROOT));
    } 
}
Tom Hawtin - tackline
I think you need to substitute 'extends' for 'implements' in the code above
Don
Yup. Thanks. I are idiot. Sorry for the delay updating.
Tom Hawtin - tackline
A: 

In your specific case, the getCode() / getByCode(String code) methods seems very closed (euphemistically speaking) to the behaviour of the toString() / valueOf(String value) methods provided by all enumeration. Why don't you want to use them?

Nicolas
Because the codes are stored in the database and the enum name is a symbol only useful to the programmer. At least, that's the way it is around here.
Mr. Shiny and New
A: 

Another solution would be not to put anything into the enum itself, and just provide a bi-directional map Enum <-> Code for each enum. You could e.g. use ImmutableBiMap from Google Collections for this.

That way there no duplicate code at all.

Example:

public enum MYENUM{
  VAL1,VAL2,VAL3;
}

/** Map MYENUM to its ID */
public static final ImmutableBiMap<MYENUM, Integer> MYENUM_TO_ID = 
new ImmutableBiMap.Builder<MYENUM, Integer>().
put(MYENUM.VAL1, 1).
put(MYENUM.VAL2, 2).
put(MYENUM.VAL3, 3).
build();
sleske
A: 

In my opinion, this would be the easiest way, without reflection and without adding any extra wrapper to your enum.

You create an interface that your enum implements:

public interface EnumWithId {

    public int getId();

}

Then in a helper class you just create a method like this one:

public <T extends EnumWithId> T getById(Class<T> enumClass, int id) {
    T[] values = enumClass.getEnumConstants();
    if (values != null) {
        for (T enumConst : values) {
            if (enumConst.getId() == id) {
                return enumConst;
            }
        }
    }

    return null;
}

This method could be then used like this:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);
Marius Burz