Konrad Höffner
Konrad Höffner

Reputation: 12207

Enum-like structure with instances?

I use the following enum type:

enum Status {OK,TIMEOUT,EXCEPTION}

But now I want to store what exactly the Exception is. Unfortunately you cannot instantiate an enum type. What is the best way to make something like the following possible?

switch(status)
{
 case(OK)        {System.out.println("Everything OK!");break;}
 case(TIMEOUT)   {System.out.println("Timeout :-(");break;}
 case(EXCEPTION) {System.out.println("We have an exception: "+status.exception);break;}
}

My ideas

  1. Class with singletons

    class Status
    {
     final Exception e;
     public final Status OK = new Status(null);
     public final Status TIMEOUT = new Status(null);
     public Status(Exception e) {this.e=e;}
    }
    

Then I could do:

 if(status==Status.OK) {System.out.println("Everything OK!");}
 else if(status==Status.TIMEOUT) {System.out.println("Timeout :-(");}
 else {System.out.println("We have an exception: "+status.exception);}

2. Several Classes

class Status {}
class StatusOK extends Status {}
class StatusTimeout extends Status {}
class StatusException extends Status
{
 final Exception e;    
 public StatusException(Exception e) {this.e=e;}    
}

Then I would need a bunch of "instanceOf"-statements.

P.S.: OK it seems that I didn't explain it clearly enough. In my program I answer requests and I store the status of the processing of those requests:

Map<Request,Status> request2Status;

Thus I cannot use something like Status.getMessage(exception); because at that position in my code I do not know which exception it was. That why I want to save it inside the status.

Chosen solution

private static class LearnStatus implements Serializable
    {               
        private static final long   serialVersionUID    = 1L;
        public static final LearnStatus OK = new LearnStatus(null);
        public static final LearnStatus TIMEOUT = new LearnStatus(null);
        public static final LearnStatus NO_TEMPLATE_FOUND = new LearnStatus(null);
        public static final LearnStatus QUERY_RESULT_EMPTY = new LearnStatus(null);
        public static final LearnStatus NO_QUERY_LEARNED = new LearnStatus(null);

        public final Exception exception;

        private LearnStatus(Exception exception) {this.exception = exception; }

        public static LearnStatus exceptionStatus(Exception cause)
        {
            if (cause == null) throw new NullPointerException();
            return new LearnStatus(cause);
        }

        @Override public String toString()
        {
            if(this==OK) {return "OK";}
            if(this==TIMEOUT) {return "timeout";}
            if(this==NO_TEMPLATE_FOUND) {return "no template found";}
            if(this==QUERY_RESULT_EMPTY) {return "query result empty";}
            if(this==NO_QUERY_LEARNED) {return "no query learned";}
            return "<summary>Exception: <details>"+exception.getLocalizedMessage()+"</details></summary>"; 
        }
    }

Problems with that

If I serialize an object with Status.OK in it, after deserialization if(status==Status.OK) does not work anymore.

New solution

I now included an enum type within the class. What do you think about it?

private static class LearnStatus implements Serializable
    {
        public enum Type {OK, TIMEOUT, NO_TEMPLATE_FOUND,QUERY_RESULT_EMPTY,NO_QUERY_LEARNED,EXCEPTION}

        public final Type type;

        private static final long   serialVersionUID    = 1L;
        public static final LearnStatus OK = new LearnStatus(Type.OK,null);
        public static final LearnStatus TIMEOUT = new LearnStatus(Type.TIMEOUT,null);
        public static final LearnStatus NO_TEMPLATE_FOUND = new LearnStatus(Type.NO_TEMPLATE_FOUND,null);
        public static final LearnStatus QUERY_RESULT_EMPTY = new LearnStatus(Type.QUERY_RESULT_EMPTY,null);
        public static final LearnStatus NO_QUERY_LEARNED = new LearnStatus(Type.NO_QUERY_LEARNED,null);

        public final Exception exception;

        private LearnStatus(Type type, Exception exception) {this.type=type;this.exception = exception;}

        public static LearnStatus exceptionStatus(Exception cause)
        {
            if (cause == null) throw new NullPointerException();
            return new LearnStatus(Type.EXCEPTION,cause);
        }

        @Override public String toString()
        {
            switch(type)
            {
                case OK:                return "OK";
                case TIMEOUT:           return "timeout";
                case NO_TEMPLATE_FOUND: return "no template found";
                case QUERY_RESULT_EMPTY:return "query result empty";
                case NO_QUERY_LEARNED:  return "no query learned";
                case EXCEPTION:         return "<summary>Exception: <details>"+exception.getLocalizedMessage()+"</details></summary>";
                default: throw new RuntimeException("switch type not handled");
            }           
        }
    }

Upvotes: 1

Views: 1175

Answers (2)

nd.
nd.

Reputation: 8932

There are several approaches to this, but all of them depend that you don't use Enums or that you don't use them exclusively. Keep in mind that an enum is basically a class that only has well-defined singletons as value.

One possible refactoring of this is to use a normal class with well-defined singletons instead of enums:

class Status implements Serializable {
  // for serialization
  private enum InternalStatus {
    OK, TIMEOUT, EXCEPTION
  }
  public static final Status OK = new Status(null, InternalStatus.OK);
  public static final Status TIMEOUT = new Status(null, InternalStatus.TIMEOUT);

  private final Exception exception;
  private final InternalStatus internalStatus;

  private Status(Exception exception, InternalStatus internalStatus) {
    this.exception = exception;
    this.internalStatus = internalStatus;
  }

  public Exception getException() {
    return exception;
  }

  public static Status exceptionStatus(Exception cause) {
    if (cause == null) throw new NullPointerException();
    return new Status(cause, InternalStatus.EXCEPTION);
  }

  // deserialization logic handling OK and TIMEOUT being singletons
  private final Object readResolve() {
    switch (internalStatus) {
    case InternalStatus.OK:
      return OK;
    case InternalStatus.TIMEOUT:
      return TIMEOUT;
    default:
      return this;
    }
  }      
}

You can now check for status == Status.OK and status == Status.TIMEOUT. If your status variable is neither OK nor TIMEOUT, it must be caused by an exception, which you can retrieve via getException.

As a downside, you lose the switch functionality and must check via if.

Upvotes: 1

Peter Lawrey
Peter Lawrey

Reputation: 533492

I would use an Exception unless everything is OK.

Like

   System.out.println("Everything OK!");
} catch(TimeoutException te) {
   System.out.println("Timeout :-(")
} catch(Exception e) {
   System.out.println("We have an exception: " + e);
}

I don't see any need to use an enum when Exceptions are designed to do this sort of thing.


Adding yet another layer on top of the layer between you and the original exception you can do this.

interface Status {
   String getMessage();
}

enum Statuses implements Status {
   OK("Everything OK"), TIMEOUT("Timeout :-(");

   private final String message;
   private Statuses(String message) { this.message = message; }

   String getMessage() { return message; }
}

class ExceptionStatus implement Status {
   private final String message;
   String getMessage() { return "Exception: " + message; }
}

// to print the message
System.out.println(status.getMessage());

Upvotes: 3

Related Questions