The Beauty of Quasi-Enumerations
See how to, with a bit of syntax magic and method invocation, you can create the appearance of an enumeration.
Join the DZone community and get the full member experience.
Join For FreeEverybody knows at least some HTTP Status Codes, even if they don’t realize it. Most people did at one point or another in their web browsing career see the famous 404 – Not Found, or 200 – OK codes.
How could these codes be represented in Java code, however, when writing HTTP client or server code?
Enumerations
The first idea might be to use Enumerations, and just list all the known status codes from all the relevant specifications like this:
public enum HttpStatusCode {
OK(200),
NOT_FOUND(404),
...;
public static HttpStatusCode valueOf(int code) {
...
}
}
This is the way org.springframework.http.HttpStatus was written for example. The benefit of this design is that the code can be encapsulated in a class, so that no other class needs to know how a status code looks like, how to parse or interpret it. Additionally, because the values are named constants, switch constructs become possible, and conditions become as easy as:
if (response.getStatusCode() == HttpStatusCode.UNAUTHORIZED) {
// Do authorization
}
However new specifications may extend the list of known status codes at any time, what’s worse, HTTP servers are not actually required to only return known codes. Unknown codes can not be converted into an enumeration value, because they were not defined at compile time, resulting in an exception if tried. In other words, HTTP Status Codes are not an enumeration, because the values are not enumerable at the time the code is written.
This problem actually was raised for this class in a bug report, which resulted in a workaround.
Constant Numbers
The more traditional approach would be to define all the codes as constant numbers. This option is used by Apache’s org.apache.http.HttpStatus, which looks like this:
public interface HttpStatusCode {
private static final int OK = 200;
private static final int NOT_FOUND = 404;
...
}
The benefit of this strategy is that it solves the problem of unknown codes, because ints can contain any number. Also switch statements and readable conditions are still possible the same way:
if (response.getStatusCode() == HttpStatusCode.UNAUTHORIZED) {
// Do authorization
}
The obvious problem with this is that the semantics of a status code is now lost because it is represented by a normal number typed “int”. There is no actual type representing a status code, it is merely “contained” in a class just because there is no other way to define constants in Java.
The failed encapsulation leads to additional problems:
- Method signatures containing status codes will become more cryptic
- Possibility of mixing other numbers with status code numbers, making the code more error prone
- Possibility of unintentionally using arithmetic operators on status codes, most probably leading to unexpected results
- Leads to creation of static “utility” methods to parse and handle status code numbers
In summary, this option makes code less readable and more error prone.
Mixing Enumerations and Constant Numbers (the JAX-RS Way)
JAX-RS has a curious mix of both the solutions above. Instead of defining status codes in a class, it starts with an interface:
public interface StatusType {
Family getFamily();
int getStatusCode();
}
Then gives an implementation of this interface as an enumeration:
public enum Status implements StatusType {
OK(200),
NOT_FOUND(404),
...;
public Status fromStatusCode(int statusCode) {
...
}
...
}
The benefit of this design is that at least the “family” of the status code is encapsulated. This means user code can determine whether a response was successful or failed without knowing details. Also, unknown status codes can be represented with a new class implementing the StatusType interface.
The downside is, that it inherits a lot of problems from both previous designs, with some additional ones. One would be perhaps tempted to write a condition like this:
if (response.getStatusInfo() == Response.Status.UNAUTHORIZED) {
// Do authorization
}
However, this would be wrong, because remember, the getStatusInfo() method returns an interface which might not be a value from the enumeration with the known values, even if the status code is actually a known value. The proper way to do this check is this:
if (response.getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) {
// Do authorization
}
This means encapsulation needs to be broken and status codes are still essentially handled as numbers. All the problems of the constant numbers approach is therefore inherited here.
Also notice, that none of the benefits of an enumeration approach is realized. Neither switch statements, nor simple conditions are possible with the enumeration defined.
Quasi-Enumerations
One way to solve the unknown code problem, but still get all the benefits of enumerations is to code a Quasi-Enumeration. This is the way Netty’s io.netty.handler.codec.http.HttpResponseStatus and Gerec’s com.vanillasource.gerec.HttpStatusCode was coded.
The first trick is, that these classes are essentially an enumeration, in that they define constant (object, not number!) values, but do not use the actual Java enumeration syntax:
public class HttpStatusCode {
public static final HttpStatusCode OK = ...;
public static final HttpStatusCode NOT_FOUND = ...;
...
private HttpStatusCode(int code) {
...
}
public static HttpStatusCode valueOf(int code) {
...
}
}
The second trick is to implement the valueOf() method in a way that will return these constant values for codes that are known, and return a completely unknown (new) instance when an unknown code is encountered. This is how this looks in Gerec:
public static HttpStatusCode valueOf(int code) {
HttpStatusCode value = KNOWN_CODES.get(code);
if (value == null) {
value = new HttpStatusCode(code);
}
return value;
}
This approach enables then to use switch statements, or simple conditions like this:
if (response.getStatusCode() == HttpStatusCode.UNAUTHORIZED) {
// Do authorization
}
And at the same time enables the status code to be an unknown code without throwing an exception or requiring additional classes to be written. After the code is parsed with the valueOf() method, it becomes properly encapsulated, and does not need to appear in number form anywhere in the code.
Summary
Sometimes it is not possible or desirable to enumerate all the values some external system might generate, while the code that uses these values might only need to handle a handful of named values. In these cases, enumerations just don’t work, and constant numbers are not precise enough.
Quasi-Enumerations can express these properties all at the same time. These are basically hand-written enumerations, very similar to proper Java enums, in that they have constant object values defined and can not be directly instantiated, but they don’t use the Java enum syntax. Instead they have a valueOf() method that returns the constant values if they are defined, or new instances for unknown values.
Published at DZone with permission of Robert Brautigam. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments