The Hierarchy API
Tree-like structures are a very common pattern. Just to count instances of this pattern in my FLOSS projects:
- The File Explorer in blueMarine, where photos are shown as they are stored in directories on the local disk.
- The Calendar Explorer in blueMarine, where nodes representing year / month / day are shown for each day in which a photo has been taken.
- The Metadata Explorer in blueMarine, where various metadata directories are shown (ok, this is a very shallow tree with only two levels).
- The Catalog Explorer in blueMarine, where photo tags and tag categories are shown in hierarchical fashion.
- The Gallery Explorer in blueMarine, where it's possible to create galleries of photos, that can contain folders inside (currently only one level of folders, but it's a limitation that I don't like).
- The Trip Explorer in blueMarine, where it's possible to see photos arranged by trip (and eventually associated GPS track).
- The GeoExplorer in forceTen, where it's possible to navigate a tree of geographical entities.
- The forthcoming Place Eplorer of forceTen (and blueMarine too) where it will be possible to arrange a set of favourite locations, and arrange them in folders and subfolders (such as in Google Earth).
Currently each of the mentioned features is implemented in a different way. Enough for trying a common solution?
As all the real-life patterns, there are many variations on the theme. For instance, some trees allow drag-and-drop (e.g. the File Explorer or the Gallery Explorer) others don't (e.g. the Calendar Explorer or the Geo Explorer); some are read-only (e.g. the Metadata Explorer), others aren't (e.g. the Catalog Explorer or the Gallery Explorer).
The NetBeans Plaform provides the Nodes API which apparently sounds as a proper solution, as it focuses around the class Node, which is an implementation of the Composite pattern (that is basically a tree). But it's not the right solution: Nodes are a model for a view component, not a model for the business component. For instance, they implement the specific behaviour of rendered nodes within a NetBeans Platform application (e.g. rendering, associating pop up menus and - with the help of other APIs - enabling contextual actions), but you can't augment them with your specific behaviour. Of course Nodes will be relevant in this scenario, but as I said only for the view component.
For a few time I've thought about using the NetBeans FileSystem API, as it can implement memory-based virtual file systems: after all, a filesystem is a tree. But it lacks a piece semantic that sometimes is needed: the arbitrary placement of an item within its peers. For instance, in the Gallery Explorer one must be able to rearrange photos in a folder in an arbitrary sequence, while files in a folder don't have this property. Sure, the Filesystem API supports attributes and one could introduce an “index” attribute, but it would be a cumbersome approach. Furthermore, files come with some extra semantics that I don't need, as the capability of being managed as a stream.
That's why I've at last decided to go with a API ad hoc, that I've called the Hierarchy API (avoiding any terminology related to trees, to avoid ambiguity with the Node API).
The basic concept is borrowed from my GeoLocation API: on one side you have a set of business objects, upon which we're not setting any constraint; they could be sparse objects or collections, it doesn't matter. On the other side you have one or more ways (“views”) to see them in a hierarchy fashion, whose items can be rearranged as you wish. The two set of objects are tied together and the as(...) idiom is used to navigate the association.
Let's look at this example:
Photo photo1 = ...;
Photo photo2 = ...;
Photo photo3 = ...;
HierarchicalViewManager hvManager = Locator.find(HierarchicalViewManager);
HierarchicalView galleries = hvManager.findOrCreateView().withId("urn:Galleries").
HierarchicalItem gallery1 = galleries.createChild().withDisplayName("Gallery 1").build();
HierarchicalItem folder1 = gallery1.createChild().withDisplayName("Folder 1").build();
HierarchicalView trips = hvManager.findOrCreateView().withId("urn:Trips").
HierarchicalItem trip1 = trips.createChild().withDisplayName("Trip 1").build();
The Hierarchy API has been used to create a collection of galleries and a collection of trips; then a gallery named ”Gallery 1” with the following layout has been created:
+-- Folder 1
while the trip named “Tree 1” is a flat structure containing two photos.
The following code would retrieve photo1 from gallery1:
for (HierarchicalItem item : gallery1.findChildren().boundToType(Photo).results())
Photo photo = item.as(Photo);
The following code would retrieve all the items containing something bound to photo3 (that is Folder 1 and Trip 1):
for (HierarchicalView item : hvManager.findItems().boundTo(photo3).results())
The Hierarchy API is abstract, that is it needs another module providing its implementation, and multiple alternate providers are possible (of course I'm working to an implementation based on an RDF store, as you can guess from the ids I've used in the example). I'll blog about it as I design it - the preliminary code is available in OpenBlueSky, branch fix-OPENBLUESKY-6 (that is, after you clone the repository run hg up -C fix-OPENBLUESKY-6); look in the modules/OpenBlueSky/Hierarchy folder.