package com.metservice.kanban.model; import com.metservice.kanban.utils.WorkItemUtils; import static com.metservice.kanban.model.WorkItem.ROOT_WORK_ITEM_ID; import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** * Builds Kanban boards and backlog from a collection of WorkItems. * @author Janella Espinas, Chris Cooper */ public class KanbanBoardBuilder { private final KanbanBoardColumnList columns; private final WorkItemTree tree; private final WorkItemTypeCollection workItemTypes; /** * Default constructor for KanbanBoardBuilder * @param columns - columns within the project * @param workItemTypes - a collection of all types represented by WorkItems in the project * @param tree - a tree representation of all WorkItems in the project */ public KanbanBoardBuilder(KanbanBoardColumnList columns, WorkItemTypeCollection workItemTypes, WorkItemTree tree) { this.columns = columns; this.tree = tree; this.workItemTypes = workItemTypes; } /** * Generates the Kanban board starting from the root of the current collection of WorkItems. * @return the generated Kanban board */ public KanbanBoard build(String workStream, Comparator<WorkItem> workItemComparator) { return combineHomogenousChildren(ROOT_WORK_ITEM_ID, workItemTypes.getRoot(), workStream, workItemComparator); } /** * Generates the backlog of the Kanban project from the collection of WorkItems. This method creates a list entry * for each WorkItem in the backlog. * @return the generated Kanban backlog */ public KanbanBacklog buildKanbanBacklog(String workStream) { List<WorkItem> workItems = tree.getChildrenWithType(ROOT_WORK_ITEM_ID, workItemTypes.getRoot().getValue(), workStream); List<KanbanCell> cells = new ArrayList<KanbanCell>(); List<WorkItem> list = columns.filter(workItems); WorkItem workItemTop = WorkItemUtils.topWorkItem(list); // create cell for each workitem for (int i = 0; i < list.size(); i++) { WorkItem workItem = list.get(i); if (workItem.isInWorkStream(workStream)) { WorkItem workItemBefore = (i - 1) >= 0 ? list.get(i - 1) : null; WorkItem workItemAfter = (i + 1) < list.size() ? list.get(i + 1) : null; KanbanCell cell = new KanbanCell(workItem.getType()); cell.setWorkItem(workItem); cell.setWorkItemAbove(workItemBefore); cell.setWorkItemBelow(workItemAfter); cell.setWorkItemTop(workItemTop); cells.add(cell); } } return new KanbanBacklog(cells); } /** * Generates a Kanban board from a given root WorkItem. * @param workItemTypeTreeNode - the root WorkItemTreeNode with which to generate the board from * @param workItem - the root WorkItem for this Kanban board * @param workItemBefore - the WorkItem immediately before the given root * @param workItemAfter - the WorkItem immediately after the given root * @param workItemTop - first WorkItem * @return the generated Kanban board */ private KanbanBoard build(TreeNode<WorkItemType> workItemTypeTreeNode, WorkItem workItem, WorkItem workItemBefore, WorkItem workItemAfter, String workStream, WorkItem workItemTop) { KanbanBoard board = new KanbanBoard(columns); if (isVisible(workItem)) { board.insert(workItem, workItemBefore, workItemAfter, workItemTop); // build child boards and merge them with the main Kanban board for (TreeNode<WorkItemType> childType : workItemTypeTreeNode.getChildren()) { // combine child boards depending on the type of the WorkItem KanbanBoard childBoard = combineHomogenousChildren(workItem.getId(), childType, workStream, null); board.merge(childBoard); } } return board; } /** * Retrieves all WorkItem children of the same type and builds a child board. * @param parentId - the id of the parent WorkItem node * @param childType - the type of child WorkItems within the child board * @return the child Kanban board containing WorkItem children of the given childType */ private KanbanBoard combineHomogenousChildren(int parentId, TreeNode<WorkItemType> childType, String workStream, Comparator<WorkItem> workItemComparator) { List<WorkItem> workItems = tree.getChildrenWithType(parentId, childType.getValue(), workStream); if (childType.hasChildren()) { // space rows on the Kanban board for clearer display if there are children in the child board return stack(childType, workItems, workStream, workItemComparator); } else { // pack rows tightly on the Kanban board if there are no children in the child board return pack(workItems, workItemComparator); } } /** * Formats the Kanban board to allow for spacing between child WorkItems within a child board. * @param type - the type of child WorkItems within the child board * @param workItems - the list of child WorkItems with the given WorkItemType * @return a formatted and spaced child Kanban board */ private KanbanBoard stack(TreeNode<WorkItemType> type, List<WorkItem> workItems, String workStream, Comparator<WorkItem> workItemComparator) { KanbanBoard board = new KanbanBoard(columns); List<WorkItem> list = columns.filter(workItems, workItemComparator); WorkItem workItemTop = WorkItemUtils.topWorkItem(list); for (int i = 0; i < list.size(); i++) { WorkItem workItem = list.get(i); WorkItem workItemBefore = (i - 1) >= 0 ? list.get(i - 1) : null; WorkItem workItemAfter = (i + 1) < list.size() ? list.get(i + 1) : null; // build the corresponding child board format it KanbanBoard childBoard = build(type, workItem, workItemBefore, workItemAfter, workStream, workItemTop); board.stack(childBoard); } return board; } /** * Formats the Kanban board to leave no gaps between child WorkItems within a child board. * @param workItems - the list of child WorkItems * @return a formatted and compacted child Kanban board */ private KanbanBoard pack(List<WorkItem> workItems, Comparator<WorkItem> workItemComparator) { KanbanBoard board = new KanbanBoard(columns); for (KanbanBoardColumn column : columns) { List<WorkItem> sublist = new KanbanBoardColumnList(column).filter(workItems, workItemComparator); WorkItem workItemTop = WorkItemUtils.topWorkItem(sublist); for (int i = 0; i < sublist.size(); i++) { WorkItem workItem = sublist.get(i); WorkItem workItemBefore = (i - 1) >= 0 ? sublist.get(i - 1) : null; WorkItem workItemAfter = (i + 1) < sublist.size() ? sublist.get(i + 1) : null; // insert the WorkItem into the appropriate position in the list board.insert(workItem, workItemBefore, workItemAfter, workItemTop); } } return board; } /** * Check if WorkItem is visible (i.e., displayed in a column on the Kanban board) * @param workItem - the WorkItem to check * @return true if the item is visible on the Kanban board, false if item is not on the Kanban board */ private boolean isVisible(WorkItem workItem) { return columns.containsPhase(workItem.getCurrentPhase()); } }