Thursday, January 21, 2010

Groovy: Switch on Steroids

UPDATE: This post underwent significant updates on 17 November 2016 to correct erroneous statements and examples, to fix the underlying HTML layout (not obvious to readers unless you view HTML source in a web browser), and to fix some spelling issues. If for some reason you want to see the old, incorrect post, check out the version archived by the Wayback Machine at https://web.archive.org/web/20150328214619/http://marxsoftware.blogspot.com/2010/01/groovy-switch-on-steroids.html.


I have blogged before regarding Groovy's support for switching on String. Groovy can switch on much more than just literal Strings (and literal integral types that Java allows switching on) and I demonstrate this briefly here.

Groovy's switch statement will use a method implemented with the name "isCase" to determine if a particular switch option is matched. This means that custom objects are "switchable" in Groovy. For the simple example in this blog post, I'll use the Java classes SimpleState and State.java.

SimpleState.java

package dustin.examples;

import static java.lang.System.out;

/**
 * Java class to be used in demonstrating the "switch on steroids" in Groovy.
 * The Groovy script will be able to {@code switch} on instances of this class
 * via the implicit invocation of {@code toString()} if the {@code case}
 * statements use {@code String}s as the items to match.
 */
public class SimpleState
{
   private String stateName;

   public SimpleState(final String newStateName)
   {
      this.stateName = newStateName;
   }

   @Override
   public String toString()
   {
      return this.stateName;
   }
}

The above Java class's String representation can be switched on in a Groovy script as shown in the next code listing for switchOnSimpleState.groovy:

switchOnSimpleState.groovy

#!/usr/bin/env groovy

import dustin.examples.SimpleState

SimpleState state = new SimpleState("Colorado")
print "The motto for the state of ${state.stateName} is '"
switch (state)
{
   case "Alabama":      print "Audemus jura nostra defendere"
                        break
   case "Alaska":       print "North to the future"
                        break
   case "Arizona":      print "Ditat Deus"
                        break
   case "Arkansas":     print "Regnat populus"
                        break
   case "California":   print "Eureka"
                        break
   case "Colorado":     print "Nil sine numine"
                        break
   case "Connecticut":  print "Qui transtulit sustinet"
                        break
   default : print "<<State ${state.stateName} not found.>>"
}
println "'"

When the above Groovy script is run against the above simple Java class, the code prints out the correct information because Groovy implicitly invokes the toString() method on the "state" instance of State being switched on. Similar functionality can now be achieved in Java, but one needs to explicitly call toString() on the object being switched on. It's also worth keeping in mind that when I wrote the original version of this post in early 2010, Java did not support switching on Strings. The output of running the above is shown in the screen snapshot below (the name of the script doesn't match above because this is an old screen snapshot from this original post before it was corrected and updated).

With Groovy and the isCase method, I can switch on just about any data type I like. To demonstrate this, the Java class State will be used and its code listing is shown below. It includes a isCase(State) method that Groovy will implicitly call when instances of State are being switched against as the case choices. In this case, the isCase(State) method simply calls the State.equals(Object) method to determine if that case is true. Although this is the typical behavior for implementations of isCase(Object), we could have had it determine if it was the case or not in any way we wanted.

State.java

package dustin.examples;

import static java.lang.System.out;

public class State
{
   private String stateName;

   public State(final String newStateName)
   {
      this.stateName = newStateName;
   }

   /**
    * Method to be used by Groovy's switch implicitly when an instance of this
    * class is switched on.
    *
    * @param compareState State passed via case to me to be compared to me.
    */
   public boolean isCase(final State compareState)
   {
      return compareState != null ? compareState.equals(this) : false;
   }

   public boolean equals(final Object other)
   {
      if (!(other instanceof State))
      {
         return false;
      }
      final State otherState = (State) other;
      if (this.stateName == null ? otherState.stateName != null : !this.stateName.equals(otherState.stateName))
      {
         return false;
      }
      return true;
   }

   @Override
   public String toString()
   {
      return this.stateName;
   }
}

The simple standard Java class shown above implements an isCase method that will allow Groovy to switch on it. The following Groovy script uses this class and is able to successfully switch on the instance of State.

#!/usr/bin/env groovy

import dustin.examples.State
State state = new State("Arkansas")
State alabama = new State("Alabama")
State arkansas = new State("Arkansas")
State alaska = new State("Alaska")
State arizona = new State("Arizona")
State california = new State("California")
State colorado = new State("Colorado")
State connecticut = new State("Connecticut")

print "The motto for the state of ${state.stateName} is '"
switch (state)
{
   case alabama     : print "Audemus jura nostra defendere"
                      break
   case alaska      : print "North to the future"
                      break
   case arizona     : print "Ditat Deus"
                      break
   case arkansas    : print "Regnat populus"
                      break
   case california  : print "Eureka"
                      break
   case colorado    : print "Nil sine numine"
                      break
   case connecticut : print "Qui transtulit sustinet"
                      break
   default : print "<<State ${state.stateName} not found.>>"
}
println "'"

The output in the next screen snapshot indicates that the Groovy script is able to successfully switch on an instance of a State object. The first command is using the "simple" example discussed earlier and the second command is using the example that needs to invoke State's isCase(State) method.

The beauty of this ability to have classes be "switchable" based on the implementation of an isCase() method is that it allows more concise syntax in situations that otherwise might have required lengthy if/else if/else constructs. It's preferable to avoid such constructs completely, but sometimes we run into them and the Groovy switch statement makes them less tedious.

It is entirely possible with the Groovy switch to have multiple switch options match the specified conditions. Therefore, it is important to list the case statements in order of which match is preferred because the first match will be the one executed. The break keyword is used in Groovy's switch as it is in Java.

There is much more power in what the Groovy switch supports. Some posts that cover this power include Groovy Goodness: The Switch Statement, Groovy, let me count the ways in which I love thee, and the Groovy documentation.

1 comment:

daniel said...

Yes i am really agree with all of your knowledge....and its really gr8 you have added coding here.....