(dc..bos): Train Stations as a Groovy Range

I’ve been working on a presentation about interesting features in Groovy, and I came up with an example that I like but is probably too long to do in the available time, so I thought I’d show it here. The idea is to illustrate how any class can be made into a Groovy range by implementing the right methods.

Actually, my larger theme is that understanding Groovy (and the Groovy JDK in particular) is helped considerably by thinking about operator overloading. I don’t do a lot of operator overloading in my own code (present example excepted), but it appears all over the place in the Groovy library. I find it helps Java developers understand Groovy better if they know that whenever they see any operator, they should mentally convert it to a method. For example, + is the plus method, – is minus, and, better yet, […] is getAt or putAt and even as is really asType.

This helps Java devs understand that by reading the groovydocs for String, they realize they can do things like:
[sourcecode language=”groovy”]
String s = ‘this is a string’
assert ‘this’ == s[0..3]
assert ‘ing’ == s[-3..-1]
assert ‘gni’ == s[-1..-3]
assert ‘thisisastring’ == s[0..3, 5..6, 8, -6..-1]
assert ‘th is a string’ == s – ‘is’
[/sourcecode]
and so on.

A groovy range is simply two values separated by a pair of dots, as shown in the previous example. Many classes can be used in a range, as in::
[sourcecode language=”groovy”]
assert [1, 2, 3, 4, 5] == 1..5
assert [‘a’, ‘b’, ‘c’] == ‘a’..’c’
Date now = new Date()
Date then = now + 2
assert [‘Jun 16’, ‘Jun 17’, ‘Jun 18’] == (now..then)*.format(‘MMM dd’)
[/sourcecode]
(Adjust the last one based on when you run the script, of course. And how cool is it that the Groovy JDK adds a format method to the Date class?)

As the most excellent book Groovy in Action points out (2nd edition available through the Manning Early Access Program), any class can be used in a range if it:

  • implements the java.util.Comparable interface
  • has a next method
  • has a previous method

That’s all you need. I wanted to show an example of this, and based on all my traveling I decided to use train stations. Here’s my first pass at it:
[sourcecode language=”groovy”]
class Station implements Comparable<Station> {
String name
Station next
Station previous
int position

Station next() { next }
Station previous() { previous }

int compareTo(Station s) {
this.position – s.position
}
String toString() { name }
}
[/sourcecode]
My Station class is really a node in a doubly-linked list. It has a name and pointers to the next station and the previous station. To make the class implement Comparable, I decided to assign each station an integer position as I added it to a track.

My next step was to use put stations together. At first I was going to use an addStation method, and then I realized that’s really what the plus method was all about. So instead I did this:
[sourcecode language=”groovy”]
Station plus(Station s) {
s.position = ++position
s.previous = this
this.next = s
return s
}
[/sourcecode]
Here’s a script using the Station class:
[sourcecode language=”groovy”]
Station dc = new Station(name:’DC’)
Station phl = new Station(name:’PHL’)
Station nyc = new Station(name:’NYC’)
Station bos = new Station(name:’BOS’)

// operator overloading to make a route:
dc + phl + nyc + bos

// Stations are a range in each direction:
def northBound = (dc..bos)*.name
def southBound = (bos..dc)*.name

assert northBound == [‘DC’, ‘PHL’, ‘NYC’, ‘BOS’]
assert southBound == [‘BOS’, ‘NYC’, ‘PHL’, ‘DC’]
[/sourcecode]

That’s all there is to it. I could easily add a minus method to Station in order to remove a node, and if I ever have to use this class in a real system I probably will. The position value is only used for comparison, so the actual number doesn’t matter, but I can imagine that if I have to add and remove lots of stations I’ll need some way to make that more rigorous. I also can’t escape the feeling that a better design would involve a Track class to hold the resulting route, but I didn’t need it for this simple demonstration.

Finally, those of us who occasionally travel the Acela route up and down the northeast corridor know that I left out a lot of stations, but I suppose I can dream that someday our trains will be in the same class (no pun intended) as their European or Asian counterparts. 🙂

(Obligatory marketing: My book Making Java Groovy, also available through the Manning Early Access Program, just went out for its 2/3rds review. I hope to be “text complete” by the end of the summer, for a dead treeware release just in time for the holiday gift-giving season.)

One response to “(dc..bos): Train Stations as a Groovy Range”

  1. Hi Ken-
    Thanks for the nice example. Unfortunately, the code is rendering with funky link breaks in Chrome, although the view source is fine. Makes the code hard to follow. Screen shot:
    http://screencast.com/t/N03QO1tz

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.