3

On our current project we are mapping some magic numbers from database (sigh, I know) to java enums like so:

public interface WithCode {
    Integer getCode();
}


public enum Role implements WithCode {
    OWNER(1),
    ADMIN(2),
    USER(3);

    @Getter Integer code;

    Role(Integer code) {
        this.code = code;
    }
}

We have handful of these, so we created an utility that finds appropriate enum by ID like so:

public interface EnumLookuper {
    static <T extends Enum<T> & WithCode> T ofCode(int code, Class<T> enumType) {
        return Arrays.stream(enumType.getEnumConstants())
                .filter(value -> Objects.equals(value.getCode(), code))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException(String.format("Unknown %s with code %d", enumType.getName(), code)));
    }
}

Our algorithm is perhaps a bit too aggressive - it fails on unknown values. Some other team members have added new magic numbers to DB and our project started to throw bunch of exceptions.

Now, I'd like to add UNKNOWN value to each enum (we have tens of those) and I was thinking of doing it a bit more generically, so we'd change the exception throwing line to something like:

.orElseGet(() -> defaultEnumValue())

Enforcing the defaults with an interface wouldn't work really, as we'd need to provide default value for each enum member. Any ideas of introducing elegant fallback/unknown values for each enum?

EDIT

Using a custom "unknown value interface" would force me to implement unknown value per enum member, for example:

interface UnknownValueProvider<T> {
  T unknown();
}

public enum Role implements WithCode, UnknownValueProvider<Role> {
    OWNER(1) {
    public Role unknown() {
        return ...;
    }
}
Xorty
  • 18,367
  • 27
  • 104
  • 155
  • you are not able to return some new enum value, unless you add it to your enum class directly – AdamSkywalker Dec 15 '15 at 13:23
  • maybe returning null / Optional will help – AdamSkywalker Dec 15 '15 at 13:25
  • I was thinking of using `Optional` but I would need to change a *lot* of client code in that case – Xorty Dec 15 '15 at 13:27
  • The best solution I see is to add this UNKNOWN field to each enum (the need of them could be anticipated in past). Trying to hack enums is not worth. – AdamSkywalker Dec 15 '15 at 13:32
  • If you're further using the enum instances themselves then I think @AdamSkywalker is right. However if you use an abstraction, such as the `WithCode` interface or something else common, then you can simply provinde a custom `Unknown` implementation (somewhat similar to returning a [Null object](https://en.wikipedia.org/wiki/Null_Object_pattern#Java) instead of plain old `null`?!) – Morfic Dec 15 '15 at 13:39
  • 2
    Apologies, I may have not been clear enough. I was referring to what you do from the point where you obtain your instance forward. If you really need to use the enum instance, or you can substitute it with a common interface, say `Command` for example. Your enum will implement both `WithCode & Command` and you'll also have an `UnknownCommand` class implementing the interface. Your *lookuper* will then return a `Command` which will either be an enum instance if it can find a match, or an *Unkown* one, and your code will simply call `command.execute()`. – Morfic Dec 15 '15 at 13:59
  • What Java version are you using? Maybe a default implementation in a UnknownValueProvider interface could help? – yedidyak Dec 15 '15 at 14:02
  • @Morfic that sounds interesting, could you write a real answer with (ideally) compilable code? – Xorty Dec 15 '15 at 15:13
  • I can certainly try but first I'd like to understand what you plan on doing with it after successfully mapping a number to an enum. Without having this context I can't say whether my suggestion adds any value or not – Morfic Dec 16 '15 at 08:31
  • well the enum becomes a member of database-mapped entity and it is used when executing various business logic (eg. checking if the user making a http call has appropriate role to access a resource or so) – Xorty Dec 16 '15 at 08:49
  • If it's used in entities which get persisted I'm afraid my solution would not work and @AdamSkywalker's suggestion makes more sense. – Morfic Dec 17 '15 at 18:14

1 Answers1

0

(Note: I am unfamiliar with portions of the code you have, namely most of the 2nd code block that gets the enum by ID. It seems overly complex/unintuitive, and it's different from all the code I've found that performs a similar function, see here. Is there a reason for it to be like that that I'm missing?)


I ran into a similar issue before, with some minor differences. In my case, I was using valueOf() to get the enum, but I wanted to make it case insensitive. So, inside my enum, I overloaded(?) valueOf(anyCaseParam) to return valueOf(AnyCaseParam.toUpperCase()).

My Suggestion

You should add UNKNOWN(-1) to the list of enums (so that you have an enum to retrieve). I would then use something like this answer has for a way to get the enum by its ID value.

From that answer, I modified the last bit of code to fit what you're asking:

public T getByID(int num) {

    // Old code: 
       //return map.get(num);


    int unknownEnum= -1;

    if(map.get(num)!=null)
      return map.get(num);
    else
      return map.get(unknownEnum);
}

If you wanted, you could even have another method that throws an exception instead of returning the UNKNOWN value.

Community
  • 1
  • 1
Laurel
  • 5,965
  • 14
  • 31
  • 57