Data Model in Starmapper
Version 3 of Starmapper brought a new cool thing: Starmapper is no longer tied to Stars!, that is it no longer talks of Planets, Players and so on. Now it talks of Nodes, Regions and Node Infos. These are all interfaces - Starmapper cares only about some data it expects to get from concrete implementations. Where necessary, interfaces are generified to provide some additional type safety for implementers of Data Model - every concrete implementation of an interface is supposed to work with types belonging to this Data Model, not with base interfaces - this allows for Data-Model-private additional methods and properties of these classes that can be used internally for whatever purpose (usually to calculate node value). See the default implementation in
jezuch.utils.starmapper3.model.stars∞ package for examples and some enlightenment, if you find that my explanations don't explain enything ;) Code speaks for itself the best!
So, Data Model consists of concrete implementations of interfaces in
jezuch.utils.starmapper3.model∞ package:
- Node∞: Starmapper wants to know only node's position (x and y on The Map) and its name. That's all.
- Region∞: this one's easy. Just a name, and "region number", assigned to it by a configuration file or internally by the Data Model (generally the same as passed to DataModel.createNode()∞).
- NodeInfo<N extends Node, R extends Region, I extends NodeInfo<N, R, I>>∞: this is where fun begins. NodeInfo is the most important thing in all this mess, the former two are just supporters. NodeInfo descibes a Node (so it needs to be generified with proper implementation - N); Node has an owner, a Region (so we have generification on R); NodeInfo also consumes (and produces) other NodeInfos, and needs to know concrete implementation of itself (hence I). Actually you'll see this generic declaration in lots of places in Starmapper.
- DataModel<N extends Node, R extends Region, I extends NodeInfo<N, R, I>>∞: finally, the thing that keeps it all together. When Starmapper wants a "Data Model", it expects an implemetation of this interface. It is basically a factory of objects in this Data Model. The default application, starmapper-app-cli, creates instances of implementations of this interface dynamically, so they have a public no-argument constructor.
Some of the methods also return a plain old
Iterables∞ to simplify processing (and complicate error handling (yes, I've fallen into my own trap ;) )) instead of custom "readers" for model objects. The Data Model implementation knows best where to get the data and how to process it and return to Starmapper.
The Data Model has a concept of "session", which makes an instance of
DataModel interface a stateful object (which, fortunately, doesn't have to be thread-safe). At the start of a session a Starmapper application passes a
Config∞ object, from which the Data Model can read additional configuration data (the default Stars! data model does this too, if you're curious). It is even allowed to modify the
Config object, but then it is required to undo any modifications at the end of the session.
The DataModel interface has a
startApplication∞ method. This is a trick to make Data Models and Starmapper work with all this generic stuff. The trick is called "double dispatch" (although usually used for a bit different purposes) - the application knows nothing about concrete implementations in the Data Model, but the Model should know what it provides, so the application asks the DataModel instance to call back with proper types. That's what the
Application∞ interface is for, and its generified (with the usual declaration)
start∞ method. If you don't want to do it in your DataModel, you can extend
AbstractDataModel∞, which does what's necessary (it's pretty straightforward and classifies as a boilerplate code).
There's also another convenience class:
AbstractNodeInfo∞. It takes care of some basic tasks, like providing getters for standard properties (and providing storage for them), implementing
equals∞ (you still need to override it if you add your own properties, but calling super.equals() is not a bad idea) and defining an additional method
shouldMergeWith∞ which performs basic conflict detection for NodeInfos (conflict is when two NodeInfos describe the same Node in the same point in time).
There are 275 comments on this page. [Display comments]