1

I have an interesting requirement, we have large enums of the form

public enum BpcCmsError {

    ERROR_TYPE_1("Error message 1"), 
    ERROR_TYPE_2("Error message 2"),
    ERROR_TYPE_3("Error message 3");


    private int errorCode;
    private final String errorMessage;
    public static final int ERRORCODE_BASE = 100;

I need to assign some unique error codes, starting from a number and counting up, so I have a constructor :

private BpcCmsError(String message) {
        this.errorMessage = message;
 }

and a static block

static {
        int code = ERRORCODE_BASE;
        for (BpcCmsError error : EnumSet.allOf(BpcCmsError.class)) {
            error.errorCode = ++code;
       }
}

which works just fine.

However, I need some added flexibility in assigning these error codes, ideally like this :

    ERROR_TYPE_1("Error message 1"), 
    ERROR_TYPE_2("Error message 2"),
    ERROR_TYPE_3(200,"Error message for code");

with the intention that a 2-args constructor would cause subsequent error codes to start from the value of that first parameter. In my case, the error codes will be 100, 101, 200, 201, etc. Of course I need to check that this jump should be permitted, we may already have counted past 200, for example. Any tips on how to achieve this using enums in Java?

4
  • 1
    Create a static int nextCode and either take next code from it and increment it or use code from constructor (after checking validity) and set nextCode to given code + 1 Commented Jun 8, 2013 at 12:07
  • You can't reference to static fields from enum constructors: error: illegal reference to static field from initializer. Commented Jun 8, 2013 at 12:11
  • @Thomas I see, thank you, but there is a hack: Create a static class inside the enum and put the static int inside this class Commented Jun 8, 2013 at 16:21
  • I can show you how to do this without using an external class. If you're willing to accept my answer (and unaccept the one you accepted), I'll do the work. Let me know. Commented Jun 8, 2013 at 21:44

3 Answers 3

4

That doesn't feel like you need an Enum... you appear to be trying to map a sequence of integer error codes against error messages.

In which case you want some kind of Map

So you'd have something along the lines of (without IDE so apologies for silly syntax errors)

public class BpcCmsError {
    private Map<int, String> errorMap = new HashMap<int, String>();
    private int lastErrorCode = 0;

    public void addError(String message) {
       errorMap.put(lastErrorCode++, message);
    }

     public void addError(int code, String message) {
       errorMap.put(code, message);
       lastErrorCode = code++;
    }

    public String getMessage(int code) {
       return errorMap.get(code);
    }

}
Sign up to request clarification or add additional context in comments.

2 Comments

I won't downvote because alternative approaches can be useful, but it sounds to me like the OP does want an enum. It is unsafe and unreadable to returning arbitrary int values from some API. And once you have the enum, having it carry its own error message is quite elegant.
This has to be an enum due to interoperability with existing code, so I cannot use a map in this case.
2

The obvious answer would be to have a private static int nextErrorCode and increment it each time you use one... but that doesn't work because you cannot reference a static field from an enum constructor (which is called as part of the static initialization of the enum). From the Java 7 spec:

It is a compile-time error to reference a static field of an enum type that is not a constant variable (§4.12.4) from constructors, instance initializer blocks, or instance variable initializer expressions of that type.

The workaround is to keep the counter in a simple static inner class instead:

public enum BpcCmsError {

  ERROR_TYPE_1("Error message 1"),
  ERROR_TYPE_2("Error message 2"),
  ERROR_TYPE_3(200, "Error message 3");

  private final int errorCode;
  private final String errorMessage;

  private BpcCmsError(String message) {
    this.errorMessage = message;
    this.errorCode = NextErrorCode.get();
  }

  private BpcCmsError(int errorCode, String message) {
    this.errorMessage = message;
    this.errorCode = NextErrorCode.get(errorCode);
  }

  private static class NextErrorCode {
    private static int nextErrorCode = 100;

    public static int get() {
      return nextErrorCode++;
    }

    public static int get(int errorCode) {
      if (errorCode < nextErrorCode) {
        throw new IllegalArgumentException("Requested error code " + errorCode + " exceeds next valid error code " + nextErrorCode);
      }
      nextErrorCode = errorCode;
      return nextErrorCode++;
    }
  }
}

Comments

1

I would suggest you keep an EnumMap of error codes in your enum that you build in a static block.

public enum ErrorCode {

    ERROR_TYPE_1("Error message 1"),
    ERROR_TYPE_2("Error message 2"),
    ERROR_TYPE_3(200, "Error message 3"),
    ERROR_TYPE_4("Error message 1"),
    ERROR_TYPE_5(300, "Error message 2"),
    ERROR_TYPE_6("Error message 3");
    private final int errorCode;
    private final String errorMessage;
    private static final Map<ErrorCode, Integer> ERROR_CODES;
    private static final int ERROR_CODE_BASE = 100;

    static {
        ERROR_CODES = new EnumMap<>(ErrorCode.class);
        int code = ERROR_CODE_BASE;
        for (final ErrorCode ec : values()) {
            if (ec.errorCode > 0) {
                if (ec.errorCode <= code) {
                    throw new ExceptionInInitializerError("Non unique code for " + ec);
                }
                code = ec.errorCode;
            }
            ERROR_CODES.put(ec, code++);            
        }
    }

    private ErrorCode(final String errorMessage) {
        this(-1, errorMessage);
    }

    private ErrorCode(final int errorCode, final String errorMessage) {
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public int getErrorCode() {
        return ERROR_CODES.get(this);
    }
}

This is very flexible - you can change the logic of the code generation very easily.

The java EnumMap is also very nifty, it is a Map that is optimised for enum keys.

A quick test:

public static void main(String[] args) throws Exception {
    for(final ErrorCode errorCode : ErrorCode.values()) {
        System.out.println(errorCode + ", code is " + errorCode.getErrorCode());
    }
}

Output:

ERROR_TYPE_1, code is 100
ERROR_TYPE_2, code is 101
ERROR_TYPE_3, code is 200
ERROR_TYPE_4, code is 201
ERROR_TYPE_5, code is 300
ERROR_TYPE_6, code is 301

2 Comments

codes must be unique, I guess this approach can be modified to support that?
@Jan-OlavEide I have changed the code to take account of that. The enum will now throw an ExceptionInInitializerError if code are not unqiue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.