package com.metservice.kanban.model; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.NotImplementedException; import org.joda.time.LocalDate; import com.metservice.kanban.charts.ChartUtils; import com.metservice.kanban.charts.burnup.BurnUpDataModel; import com.metservice.kanban.charts.burnup.ProjectedDatasetPopulator; import com.metservice.kanban.web.KanbanPersistence; //TODO This class needs more unit tests. /** * @author Janella Espinas, Chris Cooper */ public class DefaultKanbanProject implements KanbanProject { private final WorkItemTypeCollection workItemTypes; private final KanbanBoardConfiguration columnsByBoard; private final WorkItemTree tree; private final KanbanPersistence persistence; private final String name; /** * Default constructor for DefaultKanbanProject. * @param workItemTypes - the collection of all WorkItemTypes represented in the tree of workItems. * @param phaseSequences - the representation of project phases on a board * @param tree - the collection of all WorkItems in the project * @param persistence - the raw file containing the data in the project */ public DefaultKanbanProject(WorkItemTypeCollection workItemTypes, KanbanBoardConfiguration phaseSequences, WorkItemTree tree, KanbanPersistence persistence, String name) { this.workItemTypes = workItemTypes; this.columnsByBoard = phaseSequences; this.tree = tree; this.persistence = persistence; this.name = name; } /** * Advances a WorkItem to the next phase on the Kanban board and logs date of phase start. * @param id - the id of the WorkItem * @param date - the start date of the next phase */ @Override public void advance(int id, LocalDate date) { tree.getWorkItem(id).advance(date); } /** * Testing to see if this works */ @Override public void stop(int id) { tree.getWorkItem(id).stop(); } /** * Adds a new WorkItem to the project. After being added to the project, it is advanced to * the first phase on the Kanban board and the date is logged. * @param parentId - the id of the parent WorkItem * @param type - the type of the WorkItem * @param itemName - the name of the WorkItem * @param averageCaseEstimate - the size of the WorkItem * @param worstCaseEstimate - the worstCaseEstimate of the WorkItem * @param importance - the importance of the WorkItem * @param notes - relevant notes regarding the WorkItem * @param backlogDate - the date that the new WorkItem was added to the project and backlog */ @Override public int addWorkItem(int parentId, WorkItemType type, String itemName, int averageCaseEstimate, int worstCaseEstimate, int importance, String notes, String color, boolean excluded, String workStreams, LocalDate backlogDate) { int newId = tree.getNewId(); WorkItem workItem = new WorkItem(newId, parentId, type); workItem.setName(itemName); workItem.setAverageCaseEstimate(averageCaseEstimate); workItem.setWorstCaseEstimate(worstCaseEstimate); workItem.setImportance(importance); workItem.setNotes(notes); workItem.setColour(color); workItem.setExcluded(excluded); workItem.setWorkStreamsAsString(workStreams); tree.addWorkItem(workItem); // add the WorkItem onto the board and log the date advance(newId, backlogDate); return newId; } /** * Modifies order in which the WorkItems are displayed on the Kanban board. * @param id - the id of the WorkItem to be moved * @param targetId - the id of the new WorkItem either before or after the WorkItem * @param after - whether the WorkItem is to be placed before or after targetId */ @Override public void move(int id, int targetId, boolean after) { tree.move(tree.getWorkItem(id), tree.getWorkItem(targetId), after); } /** * Returns all columns on a Kanban board. * @param boardType - the type of board * @return the list of columns on the current Kanban board */ @Override public KanbanBoardColumnList getColumns(BoardIdentifier boardType) { return columnsByBoard.get(boardType); } @Override public KanbanBoardColumnList getWallColumns() { return getColumns(BoardIdentifier.WALL); } /** * Returns the tree representation of the WorkItems in the project. * @return the tree representation of all WorkItems on the current Kanban board */ @Override public WorkItemTree getWorkItemTree() { return this.tree; } /** * Returns the {@link WorkItem} identified by the {@code id} from this projects WorkItems. * @param id - the id of the {@link WorkItem} to return * @return the {@link WorkItem} identified by the id. */ public WorkItem getWorkItemById(int id) { return getWorkItemTree().getWorkItem(id); } /** * Removes a WorkItem from the project tree. * @param id - the id of the WorkItem to be deleted */ @Override public void deleteWorkItem(int id) { tree.delete(id); } /** * Removes a WorkItem from the project tree. * @exception IOException if there is an error writing to the data file */ @Override public void save() throws IOException { persistence.write(tree); } /** * Change parent of specified work item. * @param id - the id of the WorkItem to be changed * @param newParentId - the id of the new parent WorkItem */ @Override public void reparentWorkItem(int id, int newParentId) { tree.reparent(id, newParentId); } /** * Returns an instance of the current Kanban board. * @param boardType - the type of board * @return the Kanban board corresponding to the given boardType */ @Override public KanbanBoard getBoard(BoardIdentifier boardType, String workStream) { KanbanBoardBuilder kanbanBoardBuilder = new KanbanBoardBuilder(columnsByBoard.get(boardType), workItemTypes, tree); return kanbanBoardBuilder.build(workStream, null); } /** * Builds backlog screen. * @return the Kanban backlog corresponding to the collection of WorkItems */ @Override public KanbanBacklog getBacklog(String workStream) { // TODO Why a board builder to build the backlog screen? KanbanBoardBuilder kanbanBoardBuilder = new KanbanBoardBuilder( new KanbanBoardColumnList( new KanbanBoardColumn(getRootWorkItemType(), getRootWorkItemType().getBacklogPhase())), workItemTypes, tree); KanbanBacklog backlog = kanbanBoardBuilder.buildKanbanBacklog(workStream); return backlog; } @Override public KanbanBoard getCompleted(String workStream) { // get last column KanbanBoardBuilder kanbanBoardBuilder = new KanbanBoardBuilder( new KanbanBoardColumnList( new KanbanBoardColumn(getRootWorkItemType(), getRootWorkItemType().getCompletedPhase())), workItemTypes, tree); return kanbanBoardBuilder.build(workStream, WorkItem.LAST_PHASE_DATE_COMPARATOR); } /** * Returns the type of the topmost level (root) WorkItem within the project. * @return the type of the root WorkItem */ private WorkItemType getRootWorkItemType() { return workItemTypes.getRoot().getValue(); } /** * Returns a collection of all WorkItem types represented in the project. * @return the collection of all types represented by WorkItems in the project */ @Override public WorkItemTypeCollection getWorkItemTypes() { return workItemTypes; } /** * Reorders a single WorkItem in the tree and rebuilds the tree. * @param id - the id of the WorkItem to be moved * @param newIdList - the new ids of other WorkItems in the project */ @Override public void reorder(Integer id, Integer[] newIdList) { List<WorkItem> list = new ArrayList<WorkItem>(); for (Integer i : newIdList) { list.add(tree.getWorkItem(i)); } tree.reorder(tree.getWorkItem(id), list); } /* @Override public String getJournalText() { String catchString = ""; try { catchString = persistence.journalRead(); } catch (IOException e) { e.printStackTrace(); } return catchString; } @Override public void writeJournalText(String journalText) { try { persistence.journalWrite(journalText); } catch (IOException e) { e.printStackTrace(); } } */ @Override public List<KanbanJournalItem> getJournal() { try { return persistence.journalRead(); } catch (IOException e) { e.printStackTrace(); return null; } } public String getName() { return name; } @Override public Set<String> getWorkStreams() { // TODO: for large projects this can be inefficient - consider caching this set Set<String> workStreams = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); for (WorkItem workItems : tree.getWorkItemList()) { for (String ws : workItems.getWorkStreams()) { workStreams.add(ws); } } return workStreams; } @Override public void addJournalItem(KanbanJournalItem journalItem) { try { List<KanbanJournalItem> journal = persistence.journalRead(); journalItem.setId(nextJournalId(journal)); journal.add(journalItem); persistence.journalWrite(journal); } catch (IOException e) { e.printStackTrace(); } } private int nextJournalId(List<KanbanJournalItem> journal) { int max = 0; for (KanbanJournalItem item : journal) { max = Math.max(max, item.getId()); } return max + 1; } @Override public void updateJournalItem(KanbanJournalItem journalItem) { throw new NotImplementedException(); } @Override public void deleteJournalItem(int itemId) { try { List<KanbanJournalItem> journal = persistence.journalRead(); for (KanbanJournalItem item : journal) { if (item.getId() == itemId) { journal.remove(item); break; } } persistence.journalWrite(journal); } catch (IOException e) { e.printStackTrace(); } } @Override public LocalDate getStartDate() { return ChartUtils.getFirstDate(tree.getWorkItemList()); } @Override public LocalDate getProjectedEndDate(LocalDate startDate, LocalDate endDate) { BurnUpDataModel model = new BurnUpDataModel(getRootWorkItemType(), tree.getWorkItemList(), startDate, endDate); ProjectedDatasetPopulator projectedDatasetPopulator = new ProjectedDatasetPopulator(model); return projectedDatasetPopulator.getProjectedEndDate(); } @Override public WorkItemType getChildType(WorkItemType parentType) { return getWorkItemTypes().getTreeNode(parentType).getChild(0).getValue(); } }