Saturday, December 6, 2008

Java Enum relational modelling

As I have blogged about before, the Java Enum is almost perfect. I say almost because of the annoying restriction that its constructor cannot include a relation to itself, due to forward reference limitations when using the Sun compiler. The issue came up again recently when I wanted to model authority roles in Wicket, so I spent a bit more time with the issue and the following is what I've learned. First though, a recap of the original problem. Consider the following attempt at modeling states of a CD player:



public class IlligalForwardReference {
   
enum Player{
       
PLAYING(Player.PAUSED, Player.STOPPED),
       
PAUSED(Player.PLAYING, Player.STOPPED),
       
STOPPED(Player.PLAYING);

       
private  final EnumSet<Player> transitionStates;

       
Player(final Player... states){
           
this.transitionStates = EnumSet.copyOf( Arrays.asList(states));
       
}

       
public EnumSet<Player> getTransitionStates(){
           
return transitionStates;
       
}
   
}

   
public static void main(String... args){
       
System.out.println(Player.PLAYING);
   
}
}


The compiler will complain with:

IlligalForwardReference.java:18: illegal forward reference
PLAYING(Player.PAUSED, Player.STOPPED),
IlligalForwardReference.java:18: illegal forward reference
PLAYING(Player.PAUSED, Player.STOPPED),
IlligalForwardReference.java:19: illegal forward reference
PAUSED(Player.PLAYING, Player.STOPPED),
3 errors
BUILD FAILED (total time: 0 seconds)


This problem comes up as soon as you need to reference an Enum value which has not yet been initialized. Often you can avoid this if no circular relationship exists, by declaring the ones with no relations first and then adding related ones below. That is not an option however in the above example.

Passing ordinals instead
One could imagine then passing in the ordinal value or String representation instead, as a workaround to this limitation. It's less type safe and somewhat reminiscent of what an Enum is supposed to solve in the first place. The following is an attempt at this, using ordinal values:



public class ExceptionInInitializerError {
   
enum Player{
       
PLAYING(1, 2),
       
PAUSED(0, 2),
       
STOPPED(0);

       
private final EnumSet<Player> transitionStates;

       
Player(final int... ordinals){
           
Collection<Player> states = new ArrayList<Player>();

           
for(int ordinal : ordinals)
               
states.add( Player.values()[ordinal]);

           
transitionStates = EnumSet.copyOf(states);
       
}

       
public EnumSet<Player> getTransitionStates(){
           
return transitionStates;
       
}

       
@Override
       
public String toString(){
           
return name() + " -> " + transitionStates;
       
}
   
}

   
public static void main(String... args){
       
System.out.println(Player.PLAYING);
   
}


But unfortunately not. Although the code now compiles, we have effectively just traded the compile time error with a runtime InitializerError:

Exception in thread "main" java.lang.ExceptionInInitializerError
at ExceptionInInitializerError.main(ExceptionInInitializerError.java:42)
Caused by: java.lang.NullPointerException
at ExceptionInInitializerError$Player.values(ExceptionInInitializerError.java:15)
at ExceptionInInitializerError$Player.(ExceptionInInitializerError.java:26)
at ExceptionInInitializerError$Player.(ExceptionInInitializerError.java:16)
... 1 more
Java Result: 1
BUILD SUCCESSFUL (total time: 0 seconds)


That's because we cannot access the ordinals before the Player Enum has been initialized.

Deferred initialization
Hmm ok, but we can defer this step until we actually start querying for transition states. The following is an attempt at this, using String representations this time around:


public class DeferredStringBased {
   
enum Player{
       
PLAYING("PAUSED", "STOPPED"),
       
PAUSED("STOPPED", "PLAYING"),
       
STOPPED("PLAYING");

       
private final String[] transitionStateStrings;
       
private volatile EnumSet<Player> transitionStates;

       
Player(final String... transitionStateStrings){
           
this.transitionStateStrings = transitionStateStrings;
       
}

       
public EnumSet<Player> getTransitionStates(){
           
if(transitionStates == null){
               
synchronized(this) {
                   
if(transitionStates == null){
                       
transitionStates = EnumSet.noneOf(Player.class);
                       
for(String transitionStateString : transitionStateStrings){
                           
transitionStates.add( Player.valueOf(transitionStateString));
                       
}
                   
}
               
}
           
}
           
return transitionStates;
       
}

       
public String toStringWithTransitions(){
           
return toString() + " -> " + getTransitionStates();
       
}
   
}

   
public static void main(String... args){
       
System.out.println(Player.PLAYING.toStringWithTransitions());
       
System.out.println(Player.PAUSED.toStringWithTransitions());
       
System.out.println(Player.STOPPED.toStringWithTransitions());
   
}
}


Halleluja, it's working, we get:

PLAYING -> [PAUSED, STOPPED]
PAUSED -> [PLAYING, STOPPED]
STOPPED -> [PLAYING]
BUILD SUCCESSFUL (total time: 0 seconds)


Note that if you are modelling something where it makes sence to have transitivity (say authority roles where an administrator automatically assumes the role of user, as long as administrator declares a transition to superuser, and superuser declares a transition to user) then in the inner loop of the getTransitionStates() method, add the line:



transitionStates.addAll( Player.valueOf(transitionStateString).getTransitionStates() );



Modelling transitions instead
It was suggested to me in my last entry about the subject, that I could model the transitions instead of the states, as Josh Bloch recommends in Effective Java SE, item 33. This is true, if you do not need symmetric relations (if playing can go to stopped, then stopped must also go to playing) and is fine with adding another layer consisting of the transition states too. By focusing on modelling transitions rather than states (which makes sence in Bloch's phase change example), you arrive at something like this:


public class Bloch{
   
public enum Player {
       
STOPPED, PLAYING, PAUSED;

       
public enum PlayerTransition {
           
STOPFROMPLAYING(PLAYING, STOPPED), PLAYFROMSTOPPED(STOPPED, PLAYING),
           
PAUSEFROMPLAYING(PLAYING, PAUSED), PLAYFROMPAUSED(PAUSED, PLAYING),
           
STOPFROMPAUSED(PAUSED, STOPPED);

           
private final Player src;
           
private final Player dst;

           
PlayerTransition(Player src, Player dst) {
               
this.src = src;
               
this.dst = dst;
           
}

           
private static final Map<Player, Map<Player, PlayerTransition>> m =
                   
new EnumMap<Player, Map<Player, PlayerTransition>>(Player.class);

           
static {
               
for (Player p : Player.values()) {
                   
m.put(p, new EnumMap<Player, PlayerTransition>(Player.class));
               
}
               
for (PlayerTransition trans : PlayerTransition.values()) {
                   
m.get(trans.src).put(trans.dst, trans);
               
}
           
}

           
public static PlayerTransition from(Player src, Player dst) {
               
return m.get(src).get(dst);
           
}
       
}
   
}

   
public static void main(String... args)
   
{
       
System.out.println( Player.PlayerTransition.from(Player.STOPPED, Player.PLAYING) );
       
System.out.println( Player.PlayerTransition.from(Player.PLAYING, Player.PAUSED) );
       
System.out.println( Player.PlayerTransition.from(Player.PAUSED, Player.STOPPED) );
   
}
}


Which yields the output:

PLAYFROMSTOPPED
PAUSEFROMPLAYING
STOPFROMPAUSED
BUILD SUCCESSFUL (total time: 0 seconds)


While I'm sure there are things you can model with this approach, I would much rather be modelling the states rather than the transitions. There are however an idea that comes to mind from seeing this.

Getting rid of the circular dependency
If the whole problem stems from the fact that we cannot have circular relations, then perhaps we can split it in two such that only one relation is modelled statically, while the other is modelled dynamically - but setup statically. It's hard to explain, but take a look at this code:



public class MirrorEnum {
   
public enum TransitionTo{
       
PLAYING, PAUSED, STOPPED;

       
Player state;

       
private void setState(Player state){
           
this.state = state;
       
}

       
private Player getState(){
           
return state;
       
}

       
public enum Player{
           
PLAYING(TransitionTo.STOPPED, TransitionTo.PAUSED),
           
PAUSED(TransitionTo.PLAYING, TransitionTo.STOPPED),
           
STOPPED(TransitionTo.PLAYING);

           
private final EnumSet<TransitionTo> transitionToSet;
           
private volatile EnumSet<Player> transitionStates;

           
Player(final TransitionTo... transitionToArray){
               
this.transitionToSet = EnumSet.copyOf( Arrays.asList( transitionToArray ) );
           
}

           
public EnumSet<Player> getTransitionStates(){
               
if(transitionStates == null){
                   
synchronized(this){
                       
if(transitionStates == null){

                           
// Make sure each Transition state is in sync with each PlayerState
                           for(TransitionTo transition : TransitionTo.values()){
                               
transition.setState( Player.values()[transition.ordinal()]);
                           
}

                           
// Construct TransitionState's by mapping from Transition to PlayerState
                           transitionStates = EnumSet.noneOf(Player.class);
                           
for(TransitionTo transition : this.transitionToSet){
                               
transitionStates.add( transition.getState() );
                           
}
                       
}
                   
}
               
}
               
return transitionStates;
           
}

           
public String toStringWithTransitions(){
               
return toString() + " -> " + getTransitionStates();
           
}
       
}
   
}

   
public static void main(String... args){
       
System.out.println(TransitionTo.Player.PLAYING.toStringWithTransitions());
       
System.out.println(TransitionTo.Player.PAUSED.toStringWithTransitions());
       
System.out.println(TransitionTo.Player.STOPPED.toStringWithTransitions());
   
}
}


When run, the above results in:

PLAYING -> [PAUSED, STOPPED]
PAUSED -> [PLAYING, STOPPED]
STOPPED -> [PLAYING]
BUILD SUCCESSFUL (total time: 0 seconds)


The whole idea is to have two Enum's which mirror eachother yet are distinct to the Java compiler - not unlike when aliasing a column in SQL. You have to deal with two Enum's (Player and TransitionTo) at compile time, but at runtime only Player comes into play. It's not a solution I will actually use however, for that it is too complex and not DRY enough.

Conclusion
In practice I'll go for the ordinal approach when modeling relations. While there is no code completion to be had and only limited type-safety, it stands out as the cleanest and most elegant solution. It's also relatively easy to add some assertions and unit testing to improve on this a bit.

The best would obviously be for Sun to simply fix this in the compiler. The Eclipse compiler have already shown it is possible to do this and make it more lenient. Until then, if you have a more elegant solution to the stated problem, I'd love to hear about it.

Post a Comment