Stream tree transformation

I'm new to Java 8 Streams API stuff but I decided to use it for new functionality but hit a brick wall!

I have a bunch of MenuItem objects:

MenuItem parent1 = new MenuItem(0L, "Code Parent", "Description Parent");
MenuItem item1 = new MenuItem(1L, "Code1", "Description1");
MenuItem item2 = new MenuItem(2L, "Code2", "Description2");
MenuItem item3 = new MenuItem(3L, "Code3", "Description3");
MenuItem item4 = new MenuItem(4L, "Code4", "Description4");

      

I also have a bunch of MenuHierarchy objects that represent the hierarchical relationship between MenuItem (parent / child). This model is fixed as it is, so I'll have to work with what I have.

Constructor - MenyHierarchy (id, parent, child, displayOrder)

MenuHierarchy hierarchy1 = new MenuHierarchy(1L, null, parent1);
MenuHierarchy hierarchy2 = new MenuHierarchy(2L, parent1, item1, 1);
MenuHierarchy hierarchy3 = new MenuHierarchy(3L, item1, item2, 2);
MenuHierarchy hierarchy4 = new MenuHierarchy(4L, item2, item3, 3);
MenuHierarchy hierarchy5 = new MenuHierarchy(5L, item3, item4, 4);

      

MenuHierarchy objects will have a null parent as root.

Now, using the Streams API, I want to convert this relationship to a Tree structure using the MenuNode object I created:

public class MenuNode implements GenericNode<MenuItem> {

    private MenuItem data;
    private List<GenericNode<MenuItem>> children;

    public MenuNode(MenuItem data) {
        this.data = data;
        this.children = new ArrayList<GenericNode<MenuItem>>();
    }

    // Getters, setters

}

      

I'll explain what I have so far:

        /* This is the list of Root Menus (Menus which have no parent) */
    List<MenuNode> rootNodes = new ArrayList<>();
    List<MenuHierarchy> hierarchyList = Arrays.asList(hierarchy1, hierarchy2, hierarchy3, hierarchy4, hierarchy5);

    /* This first stream adds a new root MenuNode object to the above list ordered by the hierarchy display order */
    hierarchyList.parallelStream()
        .filter((h) -> Objects.isNull(h.getParentMenu()))
        .sorted((h, i) -> h.getDisplayOrder().compareTo(i.getDisplayOrder()))
        .map(MenuHierarchy::getChildMenu)
        .forEachOrdered((i) -> rootNodes.add(new MenuNode(i)));

    /* This second one is where i've sort of failed...
       What i need this to do is iterate over the menu hierarchies and for each non-root one
       add it to the MenuNode children collection where MenuNode.data == MenyHeirarchy.parentMenu
       Resulting in a tree of MenuItems...
     */
    hierarchyList.stream()
        .filter((h) -> Objects.nonNull(h.getParentMenu()))
        .sorted((h, i) -> h.getDisplayOrder().compareTo(i.getDisplayOrder()))
        .forEachOrdered((h) -> {

            rootNodes.stream()
                .filter((n) -> n.getData().equals(h.getParentMenu()))
                .forEach((n) -> {
                    n.getChildren().add(new MenuNode(h.getChildMenu()));
                });

        });

      

enter image description here

As you can see, this is not working as expected at the moment as it does not reflect the entire hierarchy ... I'm not sure if this is possible with threads?

Any ideas would be highly recommended.

+3


source to share


1 answer


Am I correct in understanding that you want a bunch of MenuNode objects that represent the menu? If so, I think it would be easier to feed all the node objects and then populate their list of children.

// first we make all the nodes and map them to ID
Map<Long, MenuNode> nodes = hierarchies.stream()
    .map(MenuHierarchy::getChildMenu)
    .collect(toMap(MenuItem::getId, MenuNode::new));

// and now we go over all hierarchies and add children to appropriate node
hierarchies.stream()
    .filter(h -> h.getParent() != null)
    .sorted(comparing(MenuHierarchy::getDisplayOrder))
    .forEach(h -> {
        long parentId = h.getParentMenu().getId();
        long childId  = h.getChildMenu().getId();
        nodes.get(parentId).getChildren().add(nodes.get(childId))
    });

      

Alternatively, the second part can be written by iterating over the nodes first. The advantage of this is that you can make the child list MenuNode

immutable. The downside is that you might find the idea of ​​repeating all the hierarchies over and over, disgusting (although that doesn't matter for any realistic menu size):

nodes.values().forEach( node ->
    node.setChildren(
        hierarchies.stream()
            .filter(h -> h.getParentMenu().getId() == node.getData().getId())
            .sorted(comparing(MenuHierarchy::getDisplayOrder))
            .map(MenuHierarchy::getChildMenu)
            .map(MenuItem::getId)
            .map(nodes::get)
            .collect(toList())
    )
);

      



And for completeness, you can group hierarchies for the same parent using streams and rewrite the second part like this:

hierarchies.stream()
    .filter(h -> null != h.getParent())
    .collect(
        groupingBy(h->g.getParentMenu().getId(), toList())
    ) // now we have a map of parent Ids to list of MenuHierarchy for that parent
    .forEach( (parentId, children) ->
        nodes.get(parentId)).setChildren(
            children.stream()
                .sorted(comparing(MenuHierarchy::getDisplayOrder))
                .map(h -> nodes.get(h.getChildMenu().getId()))
                .collect(toList())
        )
    );

      

You decide what is best for you.

Edit: I wasn't sure if the hierarchy ID is always the same as the child ID. If so, the code can be simplified a little.

+2


source







All Articles