Fit code, part 6 - TypeAdapter
I've gotten some mail letting me know that the C# Fit has forked a bit - there's a newer version that's the regular Fit distribution, and an older/modified version that's part of Fitnesse. I was having trouble extending the Fitnesse version. There's an effort to do some unification work this summer; that should help.
TypeAdapter exists to give a common interface to types, so they can all have setters, getters, and parsers. There are three factory methods, all named on(): one takes a fixture and a class, another a fixture and a field, and a third takes a fixture and a method.
This gives the unification of fields and methods. In Fit, you can have a ColumnFixture with a field, and it has an implicit setter ("name") or getter ("name()"). Or you can have a method (also "name()"). For most purposes, we don't care what it is, we just want to treat it as a setter or getter.
The TypeAdapter has five fields:
- target - the fixture this adapter is "on" (set for a field or method, but not a type)
- fixture - the fixture this adapter is "on" (always set)
- field - the Field (only set for field references)
- method - the Method (only set for method references)
- type - the type, always set
I'm struck by the combinations of "this field set / that one not" - would a couple helper classes be an improvement?
The get() method tries to do a field access if the adapter is a field, or a method invocation if it is a method.
The set() method does a field set. (Could we extend this to call "setter methods" with a signature like setFoo(Foo value)?)
The invoke() method assumes that a method is set, and calls it with no parameters.
The parse() method asks the fixture to parse the string according to type. In C#, parse() is something each type (even primitives) define. I'm sure that simplifies some of this code.
So let's say we have a ColumnFixture where phone() is of type PhoneNumber. How do we make that get parsed naturally? It looks like it works its way back to Fixture, which has a parse() method. So the ColumnFixture overrides it, and checks for an attempt to parse a class it knows about.
It seems like we could do some fanciness here, too, pushing an attempt to parse onto the domain classes. (So let Fixture.parse() take a look for "type.getMethod("parse")"; if we don't want that we could subclass and override to avoid it.)
Primitives and Their Classes
The rest of this file is a whole bunch of subclasses of TypeAdapter: one for each primitive type, and one for each corresponding Class. Most of these are the same: the primitive type's adapter is a subclass of the Class one. The primitive's defines set(), and the class one defines parse()
The last one is the only exception: Arrays. There, the parse() method tokenizes it by looking for commas. Like so many other places in Fit, it trims spaces. Each element of the array is given its own TypeAdapter. The toString() method puts the commas in when printing it out.
The big surprise here is the idea of unifying methods and fields. I'm not sure how I'd have come to the realization that they're the same at a level we care about.
(That insight is of a piece with the whole framework - I've understood reflection for years, but I've used it more for plugin-style work than anything like Fit that uses test data to drive the reflection.)