Complex Node Hierarchies
By Geertjan-Oracle on Jul 22, 2015
When you're using the Nodes API, things can become a bit tricky, especially when you want to visualize different Node types on the same level within a hierarchy.
As an example, let's imagine our domain model represents music bands, e.g., 'Oasis', etc. Here's a very simple node hierarchy that simply lists the names of the bands, i.e., each Node below visualizes an underlying Band object that is defined by a name:
After you've done the above, you want to show the members of the band. So, now the Band object is defined by a name and a list of Members:
However, let's say each Band has a list of Members, as well as one Manager. Here things start getting a bit tricky. The ChildFactory class has a 'createNodesForKey' method that returns multiple Nodes, so your inclination would be to use that, something like this:
@Override protected Node[] createNodesForKey(Band key) { int size = key.getMembers().size() + 1; Node[] nodes = new Node[size]; for (int i = 0; i < key.getMembers().size(); i++) { try { nodes[i] = new MemberNode(key.getMembers().get(i)); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } } try { nodes[size-1] = new ManagerNode(key.getManager()); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } return nodes; }
What the above gets you is a structure like this:
However, you're more likely to want the structure below instead, i.e., a container Node for all the Members, together with a leaf Node for the Manager, of which there will always be one:
To achieve the above, I learned this approach from Sven Reimers during JCrete:
public class MusicBandContainerChildFactory extends ChildFactory<MusicBandContainerChildFactory.Container> { private final Band band; public enum Container { MEMBERS, MANAGER } public MusicBandContainerChildFactory(Band band) { this.band = band; } @Override protected boolean createKeys(List<Container> list) { list.add(Container.MEMBERS); list.add(Container.MANAGER); return true; } @Override protected Node createNodeForKey(Container key) { switch (key) { case MEMBERS: { try { return new MemberContainerNode(key); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } } case MANAGER: { try { return new ManagerNode(key); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } } } return null; } private class ManagerNode extends BeanNode { public ManagerNode(Container key) throws IntrospectionException { super(key); setDisplayName("Manager: "+band.getManager().getName()); } } private class MemberContainerNode extends BeanNode { public MemberContainerNode(Container key) throws IntrospectionException { super(key, Children.create(new BandMemberChildFactory(band), true)); setDisplayName("Members"); } } private class BandMemberChildFactory extends ChildFactory<Member> { private final Band bean; public BandMemberChildFactory(Band bean) { this.bean = bean; } @Override protected boolean createKeys(List<Member> list) { list.addAll(bean.getMembers()); return true; } @Override protected Node createNodeForKey(Member key) { BandMemberNode node = null; try { node = new BandMemberNode(key); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } return node; } } private class BandMemberNode extends BeanNode<Member> { public BandMemberNode(Member bean) throws IntrospectionException { super(bean, Children.LEAF); setDisplayName(bean.getName()); } } }
Thanks, Sven!