Making a Groovy class a range

In a recent post, I used the Date class in a range. This always jumps out at Java developers, who aren’t used to seeing it.

def now = new Date()
def then = now + 9
assert 10 == (now..then).size()

It’s really cool you can do that in Groovy. That example illustrates both the concept of a range as a collection of values, and that Groovy can add methods to an existing library class — in this case, java.util.Date.

As Groovy in Action (GinA) points out, any class can be used in a range if:

  • It has a next() method to determine the following element,
  • It has a previous() method to determine the preceding element, and
  • It implements the java.util.Comparable interface.

The Comparable interface, from Java, has a single method called compareTo(obj). It should return a positive value if the current object is greater than the argument, a negative number if the current object is less than the argument, and zero otherwise. Any class that implements the Comparable interface should also override the equals(obj) method in a consistent way, i.e., if compareTo returns zero, the equals method ought to return true.

There’s an example in GinA, but I decided to try to put something a bit more nontrivial together as an illustration.

I started with a class representing a train station:

class TrainStation {
    String name

    String toString() { name }

    boolean equals(Object o) {
        return this.name == o.name
    }

    int hashCode() { name.hashCode() }

    // ... more to come

So my station has a name field, and the equals and hashCode methods both delegate to that field.

Since I needed the TrainStation class to have a next() and a previous() method, I decided the easiest way to do that was to give the station a reference to the surrounding stations:

// ... from above, then
    TrainStation nextStation
    TrainStation previousStation

    TrainStation next() { nextStation }
    TrainStation previous() { previousStation }
// ...

So far, so good. I needed to initialize the next and previous station values, so I created a constant called END and assigned both pointers to that value in the constructor.

//...
    public static final TrainStation END = new TrainStation(name:'END')

    TrainStation() {
        nextStation = END
        previousStation = END
    }

(If this is starting to resemble a “circular doubly-linked list” to you, the resemblance is purely intentional.)

The hard part was implementing Comparable. If a station is in the “next” direction, it’s considered greater, and if it’s in the “previous” direction, it should be less than the current station. The problem is, if all I have available is pointers to the immediate neighbors, how can I compare to stations that could be many steps away?

There are probably lots of ways to do it, but the technique that occurred to me was recursion. I also needed to check in both directions, because I don’t know whether the supplied argument will be greater, lesser, or equal. After a lot of playing around, I eventually implemented it this way.

// ...
	int compareTo(Object o) {
		boolean greater = false
		boolean lesser = false
		
		greater = isGreater(o)
		if (greater) { return 1 } 
		
		lesser = isLesser(o)
		if (lesser) { return -1 }

		return 0
	}

	boolean isLesser(TrainStation ts) {
		if (this.equals(END)) { return false }

		if (this.next().equals(END)) { return false }

		if (this.next().equals(ts)) { return true }

		return nextStation.isLesser(ts)
	}
	
	boolean isGreater(TrainStation ts) {
		if (this.equals(END)) { return false }

		if (this.previous().equals(END)) { return false }
		
		if (this.previous().equals(ts)) { return true }

		return this.previous().isGreater(ts)
	}

In each direction, I check the current node against its neighbor. If that isn’t conclusive, then I move from the current node to the next or previous node and check again, continuing until either I reach the desired node or I hit the end of the line.

Now, to test all this, I needed a Track class (if the TrainStation is a node in a linked list, the Track class is the linked list itself). The only complicated part of that is the method to add a new TrainStation, as shown below.

import java.util.List
import java.util.LinkedList

public class Track {
	List<TrainStation> stations = new LinkedList<TrainStation>()
	
	Track() {
                // all new stations need a boundary on each end
		stations.add(TrainStation.END)
	}

	void addStation(TrainStation ts) {
                // play with pointers to append the new station
		TrainStation last = stations.last()
		ts.previousStation = last
		ts.nextStation = TrainStation.END
		last.nextStation = ts

                // might as well store the stations, too
		stations.add(ts)
	}
	
	def visitStations(TrainStation from, TrainStation to) {
		def result = []
                // Whoo hoo!  TrainStations are a range!
		for (station in (from..to)) {
			result << station
		}
		return result
	}
	
	String toString() {
		return stations.toString()
	}
}
&#91;/sourcecode&#93;

The <code>visitStations</code> method is where the <code>TrainStation</code> class, at long last, is used in a range.  A client asks the track to visit stations from a beginning station to an ending station.  The intermediate stations are all added to an <code>ArrayList</code> (i.e., the <code>[]</code> value), which is then returned to the client.

If it's not tested, it's broken.  I'll omit the <code>TrainStationTests</code> class, which just verified that <code>next()</code>, <code>previous()</code>, and <code>compareTo()</code> worked, but here's the <code>TrackTests</code> class.

public class TrackTests extends GroovyTestCase {
	Track acela
	
	TrainStation bos = new TrainStation(name:'Boston')
	TrainStation nh = new TrainStation(name:'New Haven')
	TrainStation ny = new TrainStation(name:'New York')
	TrainStation ph = new TrainStation(name:'Philadelphia')
	TrainStation dc = new TrainStation(name:'Washington')
	
	void setUp() throws Exception{
		acela = new Track()
		acela.addStation(bos)
		acela.addStation(nh)
		acela.addStation(ny)
		acela.addStation(ph)
		acela.addStation(dc)
	}
	
	void testVisitOneStation() {
		def expected = [bos].toArray()
		def result = acela.visitStations(bos,bos).toArray()
		assertArrayEquals(expected,result)
	}
	
	void testVisitTwoStations() {
		def expected = [bos,nh].toArray()
		def result = acela.visitStations(bos,nh).toArray()
		assertArrayEquals(expected,result)
	}
	
	void testVisitManyStations(){
		def acela_sb = [bos, nh, ny, ph, dc]
		def result = acela.visitStations(bos,dc)
		assertArrayEquals(acela_sb.toArray(),result.toArray())
		
		def acela_nb = [dc, ph, ny, nh, bos]
		result = acela.visitStations(dc,bos)
		assertArrayEquals(acela_nb.toArray(),result.toArray())
	}	
}

Using GroovyTestCase gives me the convenient assertArrayEquals method, which is also cool.

So the result is that I have a new class that I can use in a range. Maybe the GinA authors were right not keep their example simpler than this, but I had fun. πŸ™‚

As an aside, it should be noted that computing the size of a range is not an efficient operation. The implementation requires the loop to traverse the range, element by element, using the next() method, counting elements as it goes. So a date range spanning years is going to look at every single day in between. It’s worth keeping that in mind when you want to compute how many elements are traversed.

Links from my GroovyMag article

I’m really happy to have an article in the current issue of GroovyMag, but after it appeared I realized something. When I submitted the article I added several links in the text, thinking that they would still be active in the resulting PDF.

That, as they say, turned out not to be the case. I thought, therefore, that I’d list the actual links here, in case anybody needs them. In my next article I’ll be more careful. πŸ™‚

Some of these I did spell out, but I thought I’d include everything for completeness.

The Spring Framework home page is at http://springframework.org, of course. The version 2.5 reference guide is at http://static.springframework.org/spring/docs/2.5.x/reference/index.html, and the specific section on dynamic language beans is at http://static.springframework.org/spring/docs/2.5.x/reference/dynamic-language.html#dynamic-language-refreshable-beans .

Neal Ford’s Meme Agora blog is at http://memeagora.blogspot.com .

The Wikipedia page on credit default swaps is at http://en.wikipedia.org/wiki/Credit_default_swaps . (I hope you enjoyed that gag, too, half as much as I enjoyed writing it.)

The IMDB page for Mr. Potter in It’s a Wonderful Life is http://www.imdb.com/character/ch0004662/ .

I referred to the Groovy usage pattern “Liquid Heart,” which is discussed in Dierk Koenig’s presentation “7 Groovy usage patterns for Java projects” at http://www.javagruppen.dk/hindsgavl08/Sessions/GroovySlides.pdf/Groovy_Usage_Patterns.pdf . That is a very valuable presentation, which I highly recommend.

The Grails wiki page for grails.spring.BeanBuilder is at http://grails.org/Spring+Bean+Builder . I mentioned in a footnote that I didn’t have anything to do with Graeme suddenly adding namespace support to BeanBuilder, but that might not be true. I asked a question on the Grails dev list about the lack of namespace support back on 1/5. Peter Ledbrook was kind enough to reply right away, but the next day Graeme himself replied, saying,

“I had a brief look, we would have to mock the generation of XML as the Spring namespace stuff is pretty closely tied to XML.”

The next thing I knew, though, namespace support was in there. Maybe I can take a tiny bit of credit after all, though obviously Graeme really deserves it.

The home page for Groovy in Action (GinA) is http://www.manning.com/koenig/ . Like so many others, I’ve spent many a pleasant evening with GinA. πŸ˜‰

I mentioned Scott Leberknight’s blog, which is at http://www.sleberknight.com/blog/sleberkn/ . His excellent two-part article, entitled “Groovier Spring,” is at http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=groovier+spring .

If none of this makes any sense to you, you might want to purchase the latest issue of GroovyMag. With great contributions by people like Andres Almiray, Glen Smith, Dave Klein and others, the issues just keep getting better. I feel really honored to be included this month.

Groovy Groundhogs

So the groundhog saw his shadow today, implying six more weeks of Winter. Β Let’s settle this nonsense once and for all.

def cal = Calendar.instance
cal.set(2009,Calendar.FEBRUARY,2)
def ghday = cal.time
cal.set(2009,Calendar.MARCH,21)
def spring = cal.time
def days = (ghday..spring).size()
println "Between Feb 2 and Mar 21, there are $days days"

The output is

Between Feb 2 and Mar 21, there are 48 days

In other words, there are six weeks and six days between Groundhog Day and the first day of Spring. Β So we’re already going to have an early Spring. πŸ™‚

%d bloggers like this: