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.

5 comments:

Ozgur Oktay said...

You may want to check out http://sourceforge.net/projects/jfsm/ for type safe transitions. There is no documentation, but there are simple demo programs using the library.

Randy McMillian said...

Hi Casper,

first of all thank you very much for the effort of stating the problem exactly and offering several approaches for workarounds.

When you are referring to "the Eclipse compiler": Doesn't Eclipse use the configured JDK under the hoods? I was not aware that it has a compiler of its own.

Casper Bang said...

Hi Randy,

I believe the Eclipse compiler is part of JDT (Java Developer Tools). Indeed their compiler feels more human (based on common sense) rather than simply complying with the spec.

For instance, it also does far more static analysis, as demonstrated by the impressive list of SuppressWarnings values: http://help.eclipse.org/help32/index.jsp?topic=/org.eclipse.jdt.doc.isv/guide/jdt_api_compile.htm

Anonymous said...

Weekends to peopleig2tmean that they can have a two-day wowgold4europe good rest. For example, people gameusdcan go out to enjoy themselves or get meinwowgoldtogether with relatives and friends to talk with each storeingameother or watch interesting video tapes with the speebiewhole family.
Everyone spends agamegoldweekends in his ownmmoflyway. Within two days,some people can relax themselves by listening to music, reading novels,or watchingogeworld films. Others perhaps are more active by playing basketball,wimming ormmorpgvipdancing. Different people have different gamesavorrelaxations.
I often spend weekends withoggsalemy family or my friends. Sometimes my parents take me on a visit to their old friends. Sometimesgamersell I go to the library to study or borrow some books tommovirtexgain much knowledge. I also go to see various exhibition to broadenrpg tradermy vision. An excursion to seashore or mountain resorts is my favorite way of spending weekends. Weekends are always enjoyable for me.
igxe swagvaultoforu wowgold-usaignmax wowgoldlivebrogame thsaleGoldRockU

JP@ unix find command example said...

Enum in java is great man and you have indeed covered the topic quite well with an interesting example. Enum in Java are great feature and most versatile and very useful under certain scenario. In my opinion following are some of benefits of enum in java :
1) Enum is type-safe you can not assign anything else other than predefined enum constant to an enum variable.

By the way I have also blogged my experience as 10 examples of enum in java , let me know how do you find it.

Thanks
Javin
How HashMap works in Java