You might have noticed in my previous entry that I introduced a new annotation to map enum values to their encoded representation. I already thought about that a couple of times, but never found time to do it. Now I introduced it, I notice that there is actually something missing here. Question is: will it be possible to introduce it without overhauling the entire framework? I think it is possible.
The ProblemSo what is actually missing here? Well, with the latest addition of @BoundEnumOption, you can easily map an enum value to its encoded representation. That's nice; now you can have a typed collection of enum values instead of arbitrary integer values.
However, there is a gotcha. What will you will see
a lot is that the enum value read influences the way you are supposed to read the remainder of the file. In the snoop example, this is no different. Currently, the example doesn't do anything with the actual data packets, but if it did, then it would have to decode it differently depending on the value of datalinkType.
In the past, you could easily use @If or @Switch and refer to the value of datalinkType, as long as it was an integer, String or boolean. However, now you need to compare that value to another enum value, and that's something that wasn't supported yet.
The RequirementsWe want to be able to do something like this:
@BoundNumber(size="8")
private DatalinkType datalinkType;
…
@If("datalinkType == DatalinkType.ETHERNET")
This means that Preon needs to be able to compare enums, and that it also needs to be able to resolve references to enum values.
The ChallengeHaving the ability to compare enums doesn't look like a huge problem. In fact, I just wrote an additional test in LimboTest, and it seems to be working fine:
public void testComparingEnums() {
EasyMock.expect(defs.getType("a")).andReturn(Direction.class).anyTimes();
EasyMock.expect(defs.getType("b")).andReturn(Direction.class).anyTimes();
EasyMock.expect(resolver.get("a")).andReturn(Direction.LEFT).anyTimes();
EasyMock.expect(resolver.get("b")).andReturn(Direction.RIGHT).anyTimes();
EasyMock.replay(defs, resolver);
assertTrue(condition(context, resolver, "a == a"));
assertFalse(condition(context, resolver, "a == b"));
assertTrue(condition(context, resolver, "b == b"));
}
public static enum Direction {
LEFT,
RIGHT;
}
The difficulty is somewhere else: it's in resolving the enum values. Problem is Direction.LEFT is not something Limbo is capable of dereferencing; it's just not in lexical scope. So, the question is, do we need another version of Limbo now? One that is capable of resolving enum value references? The good new is: no, you don't.
The reason for that is that Limbo is extensible. It defines something called a ReferenceContext that needs to be implemented to allow Limbo to execute in a context.
Let's look at an example to clear things up a little. Let's assume that you want to evaluate "foo.bar + 3". When you feed this expression to Limbo, it will first try to see if this expression makes sense at all. So it will check if it *will* actually be able to resolve something named 'foo', and if that thing has an attribute called 'bar'. If it doesn't then it will just fail directly. Now, in order to check if there is something called 'foo' in its context, it will simply call selectAttribute("foo") on the current ReferenceContext. If that ReferenceContext decides that something like that is referenceable, it will return a Reference object representing the "foo" reference. Next, Limbo will check if there is actually an attribute "bar" on the type of object referenced by the Reference just created. If so, then it will return a Reference to that property.
The important thing to note here is that - whatever Limbo is trying to do - all requests always hit the ReferenceContext first. Once the References are 'linked', it will call resolve(…) on the references to resolve the values on an instance of that context.
So, if we want to put the enums into lexical scope, all we need to do is rewrite the ReferenceContext. And our ReferenceContext is defined by Preon, so we don't have to rewrite Limbo; we just need to write a small adjustment in Preon, in order to integrate Limbo slightly differently.
The SolutionThe actual code required to make this work is a little bit more than I'm willing to list here. (It turns out you need a decorator for RefenceContext, and then two additional Reference implementations. One to refer to Classes, and one to reference static fields on a class. The solution works in the same way as outlined above. The only thing that you need to add is an @ImportStatic annotation, at the top of the class from which you are referencing the enum values:
@ImportStatic(DatalinkType.class)
public class SnoopFile {
@BoundNumber(size="8")
private DatalinkType datalinkType;
…
@If("datalinkType == DatalinkType.ETHERNET")