Projects >> kanban-app >>2ef061294c40616a0c7c62d6d4b488db55fcdfda

Chunk
Conflicting content
<<<<<<< HEAD
package com.metservice.kanban.web;

import static com.metservice.kanban.model.WorkItem.ROOT_WORK_ITEM_ID;
import static com.metservice.kanban.utils.DateUtils.currentLocalDate;
import static com.metservice.kanban.utils.DateUtils.parseConventionalNewZealandDate;
import static java.lang.Integer.parseInt;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.CategoryDataset;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.metservice.kanban.KanbanService;
import com.metservice.kanban.charts.burnup.BurnUpChartGenerator;
import com.metservice.kanban.charts.burnup.DefaultBurnUpChartGenerator;
import com.metservice.kanban.charts.burnup.DefaultChartWriter;
import com.metservice.kanban.charts.cumulativeflow.CumulativeFlowChartBuilder;
import com.metservice.kanban.charts.cycletime.CycleTimeChartBuilder;
import com.metservice.kanban.model.BoardIdentifier;
import com.metservice.kanban.model.KanbanBoard;
import com.metservice.kanban.model.KanbanProject;
import com.metservice.kanban.model.WorkItem;

import com.metservice.kanban.model.WorkItemComment;
import com.metservice.kanban.model.WorkItemTree;
import com.metservice.kanban.model.WorkItemType;
import com.metservice.kanban.utils.DateUtils;
import com.metservice.kanban.utils.JsonLocalDateTimeConvertor;

//TODO This class needs more unit tests.

/**
 * @author Nicholas Malcolm - malcolnich - 300170288
 */
@Controller
@RequestMapping("{projectName}")
@SessionAttributes("workStreams")
public class KanbanBoardController {

    @Autowired
    private KanbanService kanbanService;
    private Gson gson;

    public KanbanBoardController() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(LocalDateTime.class, new JsonLocalDateTimeConvertor());
        this.gson = gsonBuilder.create();
    }

    @ModelAttribute("project")
    public synchronized KanbanProject populateProject(@PathVariable("projectName") String projectName)
        throws IOException {

        return kanbanService.getKanbanProject(projectName);
    }

    /*
        @ModelAttribute("boardName")
        public String populateBoardName(@PathVariable("board") String board) {
            return board;
        }
        */

    @ModelAttribute("projectName")
    public String populateProjectName(@PathVariable("projectName") String board) {
        return board;
    }

    @ModelAttribute("workStreams")
    public synchronized Map populateWorkStreams() {
        return new HashMap();
    }

    //    @ModelAttribute("redirectView")
    //    public synchronized RedirectView populateRedirectView(
    //                                                          @PathVariable("projectName") String projectName,
    //                                                          @PathVariable("board") String board) {
    //        return new RedirectView("/projects/" + projectName + "/" + board, true);
    //    }

    @ModelAttribute("chartGenerator")
    public synchronized BurnUpChartGenerator populateChartGenerator() {
        return new DefaultBurnUpChartGenerator(new DefaultChartWriter());
    }

    private Map initBoard(String boardType, String projectName, String scrollTop) {
        //        boardType = cleanBoardType(boardType);

        Map model = buildModel(projectName, boardType);

        // TODO model used to have kanbanTransaction now it has kanban... need
        // to fix view

        model.put("scrollTop", scrollTop == null ? "0" : scrollTop);

        return model;
    }

    private ModelAndView finishBoard(String boardType, String projectName, KanbanProject project,
                             Map workStreams,
                                     Map model,
                                     String view) {
        KanbanBoard board = project.getBoard(BoardIdentifier.valueOf(boardType.toUpperCase()),
            workStreams.get(projectName));

        model.put("board", board);

        return new ModelAndView(view, model);

    }

    @RequestMapping("wall")
    public synchronized ModelAndView wallBoard(@ModelAttribute("project") KanbanProject project,
                                               @PathVariable("projectName") String projectName,
                                               @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                               @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        String boardType = "wall";

        Map model = initBoard("wall", projectName, scrollTop);

        if (boardType.equals("backlog")) {
            model.put("kanbanBacklog", project.getBacklog(workStreams.get(projectName)));
    //    }
            model.put("type", project.getWorkItemTypes().getRoot().getValue());
            model.put("phase", project.getWorkItemTypes().getRoot().getValue()
                .getPhases().get(0));
            return new ModelAndView("/backlog.jsp", model);
        } else if (boardType.equals("completed")) {
            model.put("type", project.getWorkItemTypes().getRoot().getValue());
            List phases = project.getWorkItemTypes().getRoot()
                .getValue().getPhases();
            model.put("phase", phases.get(phases.size() - 1));

            KanbanBoard board = project.getBoard(BoardIdentifier.valueOf(boardType.toUpperCase()),
                workStreams.get(projectName));

            model.put("board", board);

            return new ModelAndView("/completed.jsp", model);
        }
        else if (boardType.equals("journal")) {
            model.put("kanbanJournal", project.getJournalText());
            return new ModelAndView("/journal.jsp", model);
        }

        return finishBoard("wall", projectName, project, workStreams, model, "/project.jsp");
    }

    @RequestMapping("backlog")
    public synchronized ModelAndView backlogBoard(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                  @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("backlog", projectName, scrollTop);

        model.put("kanbanBacklog", project.getBacklog(workStreams.get(projectName)));
        model.put("type", project.getWorkItemTypes().getRoot().getValue());
        model.put("phase", project.getWorkItemTypes().getRoot().getValue().getPhases().get(0));
        return new ModelAndView("/backlog.jsp", model);
    }

    @RequestMapping("journal")
    public synchronized ModelAndView journalBoard(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                  @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("journal", projectName, scrollTop);
        model.put("kanbanJournal", project.getJournalText());
        return new ModelAndView("/journal.jsp", model);
    }

    @RequestMapping("completed")
    public synchronized ModelAndView completedBoard(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("projectName") String projectName,
                                                    @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                    @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("completed", projectName, scrollTop);

        model.put("type", project.getWorkItemTypes().getRoot().getValue());
        List phases = project.getWorkItemTypes().getRoot()
            .getValue().getPhases();
        model.put("phase", phases.get(phases.size() - 1));

        return finishBoard("completed", projectName, project, workStreams, model, "/completed.jsp");
    }

    //    private String cleanBoardType(String boardType) {
    //        int sep = boardType.indexOf(":");
    //        if (sep > -1) {
    //            return boardType.substring(0, sep);
    //        }
    //        return boardType;
    //    }

    //    private String extractScrollPositionInfoFromBoardType(String boardType) {
    //        int sep = boardType.indexOf(":");
    //        if (sep > -1) {
    //            return boardType.substring(sep + 1);
    //        }
    //        return null;
    @RequestMapping(value = "{board}/advance-item-action")
    public synchronized RedirectView advanceItemAction(@ModelAttribute("project") KanbanProject project,
                                                       @PathVariable("board") String boardType,
                                                       @RequestParam("id") String id) throws IOException {

        project.advance(parseInt(id), currentLocalDate());
        project.save();

        return new RedirectView("../" + boardType);
    }

    @RequestMapping(value = "{board}/stop-item-action", method = RequestMethod.POST)
    public synchronized RedirectView stopItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") String id) throws IOException {

        project.stop(parseInt(id));
        project.save();

        // Redirect
        return new RedirectView("../" + boardType);

        //return new RedirectView(includeScrollTopPosition(boardType, scrollTop));
    }

    private String includeScrollTopPosition(String boardType, String scrollTop) {
        return "../" + boardType + "?scrollTop=" + scrollTop;
    }

    /** Creates empty item model to display in add form with preset parent id. **/
    @RequestMapping("{board}/add-item")
    public synchronized ModelAndView addItem(@ModelAttribute("project") KanbanProject project,
                                             @PathVariable("projectName") String projectName,
                                             @RequestParam("id") int id) throws IOException {

        // Search for parent id
        WorkItem parent = project.getWorkItemTree().getWorkItem(id);

        WorkItemType type = project.getWorkItemTypes().getRoot().getValue();
        String parentName = "";
        int parentId = ROOT_WORK_ITEM_ID;
        String legend = "Add " + type;

        if (parent != null) {
            parentName = parent.getName();
            parentId = parent.getId();
            type = project.getWorkItemTypes().getTreeNode(parent.getType()).getChild(0).getValue();
            legend = "Add a " + type + " to " + parentName;
        }

        Map model = new HashMap();

        model.put("legend", legend);
        model.put("parentId", parentId);
        model.put("type", type);
        model.put("topLevel", true);

        return new ModelAndView("/add.jsp", model);
    }

    /**
     * Responds to edit-item request, passes the item in its current state to
     * the edit form
     *
     * @param project
     * @param projectName
     * @param boardType
     * @param id
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/edit-item")
    public synchronized ModelAndView editItem(@ModelAttribute("project") KanbanProject project,
                                              @PathVariable("board") String board,
                                              @PathVariable("projectName") String projectName,
                                              @RequestParam("id") Integer id) throws IOException {

        // Get a model ready to take some attributes
        Map model = new HashMap();
        //buildModel(projectName, boardType);

        // Fetch the item we want to edit
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        // Add some variables to the model hashmap
        model.put("workItem", workItem);
        model.put("children", project.getWorkItemTree().getChildren(id));
        model.put("parentAlternativesList", project.getWorkItemTree().getParentAlternatives(workItem));
        model.put("board", board);

        // TODO: Figure out what this does
        Map map = new LinkedHashMap();
        for (String phase : workItem.getType().getPhases()) {
            LocalDate date = workItem.getDate(phase);

            String dateString = "";
            if (date != null) {
                dateString = DateUtils.formatConventionalNewZealandDate(date);
            }
            map.put(phase, dateString);
        }
        model.put("phasesMap", map);

        // Pass the model to edit.jsp
        return new ModelAndView("/edit.jsp", model);
    }

    /**
     * Add item to Kanban project Needs a bunch of parameters in the request.
     * (Comes from add.jsp)
     **/
    @RequestMapping("{board}/add-item-action")
    public synchronized RedirectView addItemAction(@ModelAttribute("project") KanbanProject project,
                                                   @PathVariable("board") String board,
                                                   @RequestParam(value = "parentId", required = false) Integer parentId,
                                                   @RequestParam("type") String type,
                                                   @RequestParam("name") String name,
                                                   @RequestParam("averageCaseEstimate") String averageCaseEstimateStr,
                                                   @RequestParam(value = "worstCaseEstimate", required = false) String worstCaseEstimateStr,
                                                   @RequestParam("importance") String importanceStr,
                                                   @RequestParam("notes") String notes,
                                                   @RequestParam("color") String color,
                                                   @RequestParam(value = "excluded", required = false) String excludedStr,
                                                   @RequestParam(value = "redirectTo", required = false) String redirectTo,
                                                   @RequestParam(value = "workStreamsSelect", required = false) String workStreams) throws IOException {

        WorkItemType typeAsWorkItemType = project.getWorkItemTypes().getByName(type);

        if (parentId == null) {
            parentId = WorkItem.ROOT_WORK_ITEM_ID;
        }

        int averageCaseEstimate = parseInteger(averageCaseEstimateStr, 0);
        int importance = parseInteger(importanceStr, 0);
        boolean excluded = parseBoolean(excludedStr);
        int worstCaseEstimate = parseInteger(worstCaseEstimateStr, 0);

        // Add it and save it
        int newId = project.addWorkItem(parentId, typeAsWorkItemType, name, averageCaseEstimate, worstCaseEstimate,
            importance, notes, color, excluded, workStreams, currentLocalDate());
        project.save();

        if ("print".equals(redirectTo)) {
            return new RedirectView("../print-items?printSelection=" + newId);
        }
        else {
            return new RedirectView("../" + board);
        }
    }


    /**
     * Responds to a form submission which passes an edited item
     *
     * @param project
     * @param boardType
     * @return
     * @throws IOException
     * @throws ParseException
     */
    @RequestMapping("{board}/edit-item-action")
    public synchronized RedirectView editItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") int id,
                                                    @RequestParam("parentId") Integer parentId,
                                                    @RequestParam("name") String name,
                                                    @RequestParam("averageCaseEstimate") String averageCaseEstimateStr,
                                                    @RequestParam("worstCaseEstimate") String worstCaseEstimateStr,
                                                    @RequestParam("importance") String importanceStr,
                                                    @RequestParam("notes") String notes,
                                                    @RequestParam("color") String color,
                                                    @RequestParam(value = "excluded", required = false) String excludedStr,
                                                    @RequestParam(value = "workStreamsSelect", required = false) String workStreams,
                                                    @RequestParam(value = "redirectTo", required = false) String redirectTo,
                                                    HttpServletRequest request) throws IOException, ParseException {
    
        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);
    
        @SuppressWarnings("unchecked")
        Map parameters = request.getParameterMap();
    
        int averageCaseEstimate = parseInteger(averageCaseEstimateStr, 0);
        int worstCaseEstimate = parseInteger(worstCaseEstimateStr, 0);
        int importance = parseInteger(importanceStr, 0);
        boolean excluded = parseBoolean(excludedStr);
    
        // Save all the updates
        workItem.setName(name);
        workItem.setAverageCaseEstimate(averageCaseEstimate);
        workItem.setWorstCaseEstimate(worstCaseEstimate);
        workItem.setImportance(importance);
        workItem.setNotes(notes);
        workItem.setExcluded(excluded);
        workItem.setColour(color);
        workItem.setWorkStreamsAsString(workStreams);
    
        // TODO Figure this out
        for (String phase : workItem.getType().getPhases()) {
            String key = "date-" + phase;
            String[] valueArray = parameters.get(key);
            if (valueArray != null) {
                if (valueArray[0].trim().isEmpty()) {
                    workItem.setDate(phase, null);
                } else {
                    workItem.setDate(phase, parseConventionalNewZealandDate(valueArray[0]));
                }
            }
        }
    
        // If it's changed parent, reset the parent.
        if (workItem.getParentId() != parentId) {
            project.getWorkItemTree().reparent(id, parentId);
        }
    
        // Save the whole project
        project.save();
    
        if ("print".equals(redirectTo)) {
            return new RedirectView("../print-items?printSelection=" + id);
        }
        else {
            return new RedirectView("../" + boardType);
        }
    }

    @RequestMapping(value = "print-items")
    public synchronized ModelAndView printItems(@ModelAttribute("project") KanbanProject project,
                                                @PathVariable("projectName") String projectName,
                                                @RequestParam("printSelection") String[] ids) throws IOException {

        List items = new ArrayList();

        for (String id : ids) {
            items.add(project.getWorkItemTree().getWorkItem(parseInteger(id, 0)));
        }

        return new ModelAndView("/printCards.jsp", "items", items);
    }

    @RequestMapping("{board}/move-item-action")
    public synchronized RedirectView moveItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") String id,
                                                    @RequestParam("targetId") String targetId,
                                                    @RequestParam("after") Boolean after,
                                                    @RequestParam("scrollTop") String scrollTop) throws IOException {
        int idAsInteger = parseInt(id);
        int targetIdAsInteger = parseInt(targetId);
        project.move(idAsInteger, targetIdAsInteger, after);
        project.save();
        return new RedirectView(includeScrollTopPosition(boardType, scrollTop));
    }

    @RequestMapping("{board}/reorder")
    public synchronized RedirectView reorder(@ModelAttribute("project") KanbanProject project,
                                             @PathVariable("board") String boardType,
                                             @RequestParam("id") Integer id, @RequestParam("ids") Integer[] ids,
                                             @RequestParam("scrollTop") String scrollTop) throws IOException {

        project.reorder(id, ids);
        project.save();

        return new RedirectView(includeScrollTopPosition(boardType, scrollTop));

    }

    @RequestMapping(value = "{board}/edit-item/{id}/name", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemName(@ModelAttribute("project") KanbanProject project,
            @PathVariable("board") String boardType, @PathVariable("id") int id, @RequestParam("newValue") String newValue)
                    throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setName(newValue);
        project.save();

        // Go home.
        return new ResponseEntity(
                String.format("Name change successfully.  New name: %s", workItem.getName()), HttpStatus.OK);
    }

    @RequestMapping(value = "{board}/edit-item/{id}/size", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemSize(@ModelAttribute("project") KanbanProject project,
            @PathVariable("board") String boardType, @PathVariable("id") int id, @RequestParam("newValue") String newValue)
                    throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setAverageCaseEstimate(parseInteger(newValue, 0));
        project.save();

        // Go home.
        return new ResponseEntity(
                String.format("Size change successfully.  New size: %s", workItem.getAverageCaseEstimate()), HttpStatus.OK);
    }

    @RequestMapping(value = "{board}/edit-item/{id}/importance", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemImportance(@ModelAttribute("project") KanbanProject project,
            @PathVariable("board") String boardType, @PathVariable("id") int id, @RequestParam("newValue") String newValue)
                    throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setImportance(parseInteger(newValue, 0));
        project.save();

        // Go home.
        return new ResponseEntity(
                String.format("Importance change successfully.  New importance: %s", workItem.getImportance()),HttpStatus.OK);
    }

    @RequestMapping("edit-journal-action")
    public synchronized RedirectView editJournalAction(@ModelAttribute("project") KanbanProject project,
                                                       @RequestParam("journalText") String journalText,
                                                       HttpServletRequest request) throws IOException, ParseException {

        project.writeJournalText(journalText);
        return new RedirectView("journal");

    }

    /**
     * Responds to a request to delete an item
     *
     * @param project
     * @param id
     *            - the id of the item you want to delete
     * @param board
     *            - the view you want to redirect to
     * @return
     * @throws IOException
     */
        try {
    @RequestMapping("{board}/delete-item-action")
    public synchronized RedirectView deleteWorkItem(@ModelAttribute("project") KanbanProject project,
                                            @RequestParam("id") int id,
                                            @PathVariable("board") String board) throws IOException {

        // Delete the workitem, save and redirect.
        project.deleteWorkItem(id);
        project.save();
        return new RedirectView("../" + board);
    }

    @RequestMapping("chart")
    public synchronized ModelAndView chart(@ModelAttribute("project") KanbanProject project,
                                           @RequestParam("chartName") String chartName,
                                           @RequestParam("workItemTypeName") String workItemTypeName,
                                           @PathVariable("projectName") String projectName,
                                           @RequestParam(value = "startDate", required = false) String startDate,
                                           @RequestParam(value = "endDate", required = false) String endDate) {

        Map model = initBoard("chart", projectName, null);

        model.put("workItemTypeName", workItemTypeName);
        model.put("imageName", chartName + ".png");
        model.put("startDate", startDate);
        model.put("endDate", endDate);
        model.put("kanbanJournal", project.getJournalText());
        model.put("chartName", chartName);

        return new ModelAndView("/chart.jsp", model);
    }

    // TODO check in this class for redundent model.put("kanban...

    @RequestMapping("cumulative-flow-chart.png")
    public synchronized void cumulativeFlowChartPng(@ModelAttribute("project") KanbanProject project,
                                                    @RequestParam("startDate") String startDate,
                                                    @RequestParam("endDate") String endDate,
                                                    @RequestParam("level") String level,
                                                    @RequestParam(value = "workStream", required = false) String workStream,
                                                    OutputStream outputStream) throws IOException {

        WorkItemType type;

        try {
            type = project.getWorkItemTypes().getByName(level);
        } catch (IllegalArgumentException e) {
            // TODO produce image with text from exception
            return;
        }

        List workItemList = project.getWorkItemTree().getWorkItemsOfType(type, workStream);

        LocalDate start = null;
        LocalDate end = null;

        try {
            start = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(startDate));
        } catch (ParseException e) {
            // keep start as null
        }
        try {
            end = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(endDate));
        } catch (ParseException e) {
            // keep end as null
        }

        // add start and end date params here
        CumulativeFlowChartBuilder builder = new CumulativeFlowChartBuilder(start, end);

        CategoryDataset dataset = builder.createDataset(type.getPhases(), workItemList);
        JFreeChart chart = builder.createChart(dataset);
        int width = dataset.getColumnCount() * 15;
        ChartUtilities.writeChartAsPNG(outputStream, chart, width < 800 ? 800 : width, 600);
    }

    @RequestMapping("cycle-time-chart.png")
    public synchronized void cycleTimeChartPng(@ModelAttribute("project") KanbanProject project,
                                               @RequestParam("startDate") String startDate,
                                               @RequestParam("endDate") String endDate,
                                               @RequestParam(value = "workStream", required = false) String workStream,
                                               @RequestParam("level") String level, OutputStream outputStream)
        throws IOException {

                    if (column.equals(columnName)) {
        WorkItemType type = project.getWorkItemTypes().getByName(level);
        CycleTimeChartBuilder builder = new CycleTimeChartBuilder();
        List workItemList = project.getWorkItemTree().getWorkItemsOfType(type, workStream);

        CategoryDataset dataset = builder.createDataset(builder
            .getCompletedWorkItemsInOrderOfCompletion(workItemList));
        JFreeChart chart = builder.createChart(dataset);
        int width = dataset.getColumnCount() * 15;
        ChartUtilities.writeChartAsPNG(outputStream, chart, width < 800 ? 800 : width, 600);
    }

    /**
     * Provides the variables of the current project to either a new project
     * form,
     * or to the edit project page, depending on createNewProject's boolean
     * value.
     *
     * @param project
     * @param projectName
     * @param boardType
     * @param createNewProject
     *            - whether we want this to create a new project.
     * @return
     * @throws IOException
     */
    @RequestMapping("edit-project")
    public synchronized ModelAndView editProject(@ModelAttribute("project") KanbanProject project,
                                                 @PathVariable("projectName") String projectName,
                                                 @RequestParam("createNewProject") boolean createNewProject)
        throws IOException {

        Map model = buildModel(projectName, "wall");

        // Get the settings of this current project and pass it to the form.
        model.put("settings", kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString());
        // Create a new project if true
        if (createNewProject) {
            return new ModelAndView("/createProject.jsp", model);
        }
        // else edit the current project
        else {
            return new ModelAndView("/editProject.jsp", model);
        }
    }

    @RequestMapping("edit-project-action")
    public synchronized RedirectView editProjectAction(@ModelAttribute("project") KanbanProject project,
                                                       @PathVariable("projectName") String projectName,
                                                       @RequestParam("newProjectName") String editProjectName,
                                                       @RequestParam("content") String content) throws IOException {

        kanbanService.editProject(editProjectName, content);
        return openProject(projectName, "wall", editProjectName, null, null);
    }

    @RequestMapping("edit-wiplimit-action")
    public synchronized ModelAndView editWIPLimitAction(@ModelAttribute("project") KanbanProject project,
                                                        @PathVariable("projectName") String projectName,
                                                        @RequestParam("columnName") String columnName,
                                                        @RequestParam("columnType") String columnType,
                                                        @RequestParam("wipLimit") String wipLimit) throws IOException,
        ParseException {

        String content = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(content);
        String temp = "";
        String newContent = "";

        //Keep track of how many columns there are
        int totalColumns = 0;
        //This is set once,
        int insertAfterComma = -1;

        String wipLine = "workItemTypes." + columnType + ".wipLimit=";

        while (sc.hasNext()) {

            temp = sc.nextLine();
            //Find out where the new wipLimit should go
            if (temp.contains("workItemTypes." + columnType + ".phases=")) {
                String columns = temp.split("=")[1];
                for (String column : columns.split(",")) {
                        insertAfterComma = totalColumns;
                    }

                    totalColumns++;

                }
            }
            if (temp.contains(wipLine)) {
                String wipLimits = temp.split("=")[1];
                String[] limits = wipLimits.split(",");
                //limts.count should == totalColumns
                String newLimits = "";

                String limit = "";
                for (int i = 0; i < totalColumns; i++) {
                    //Get the old limit
                    try {
                        limit = limits[i];
                    } catch (ArrayIndexOutOfBoundsException e) {
                        //Set default if no limit
                        limit = "-1";
                    }
                    //Do we need to set it to a new limit?
                    if (i == insertAfterComma) {
                        //Set the new limit
                        limit = wipLimit;
                    }

                    newLimits += limit + ",";
                }
                temp = wipLine + newLimits;
            }
            newContent += temp + "\n";
        }

        kanbanService.editProject(projectName, newContent);

        return new ModelAndView("/admin.jsp", null);
    }

    @RequestMapping("create-project-action")
    public synchronized RedirectView createProjectAction(@ModelAttribute("project") KanbanProject project,
                                                         @PathVariable("projectName") String projectName,
                                                         @RequestParam("newProjectName") String newProjectName,
                                                         @RequestParam("content") String content) throws IOException {

        kanbanService.createProject(newProjectName, content);
        return openProject(projectName, "wall", newProjectName, null, null);
    }

    /**
     * Opens a project and goes to boardType (e.g. wall, backlog etc)
     *
     * @param projectName
     * @param boardType
     * @param newProjectName
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/open-project")
    public synchronized RedirectView openProject(@PathVariable("projectName") String projectName,
                                                 @PathVariable("board") String boardType,
                                                 @RequestParam("newProjectName") String newProjectName,
                                                 @RequestParam(value = "chartName", required = false) String chartName,
                                                 @RequestParam(value = "workItemTypeName", required = false) String workItemTypeName)
        throws IOException {

        String params = "";

        if (!StringUtils.isEmpty(chartName) && !StringUtils.isEmpty(workItemTypeName)) {
            params = "?chartName=" + chartName + "&workItemTypeName=" + workItemTypeName;
        }
        return new RedirectView(
            "/projects/" + newProjectName + "/" + boardType + params, true);
    }

    /**
     * Models are a hashmap used to pass attributes to a view.
     *
     * @param projectName
     * @param boardType
     * @return the model hashmap
     */
    private Map buildModel(String projectName, String boardType) {
        Map model = new HashMap();
        model.put("projectName", projectName);
        model.put("boardType", boardType);
        return model;
    }

    @RequestMapping("burn-up-chart.png")
    public void burnUpChartPng(@ModelAttribute("project") KanbanProject project,
                }
                               @ModelAttribute("chartGenerator") BurnUpChartGenerator chartGenerator,
                               @RequestParam("startDate") String startDate,
                               @RequestParam("endDate") String endDate,
                               @RequestParam(value = "workStream", required = false) String workStream,

                               OutputStream outputStream) throws IOException {

        WorkItemTree tree = project.getWorkItemTree();
        WorkItemType type = project.getWorkItemTypes().getRoot().getValue();
        List topLevelWorkItems = tree.getWorkItemsOfType(type, workStream);

        LocalDate start = null;
        LocalDate end = null;

        try {
            start = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(startDate));
        } catch (ParseException e) {
            // keep start as null
        }
        try {
            end = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(endDate));
        } catch (ParseException e) {
            // keep end as null
        }

        chartGenerator.generateBurnUpChart(type, topLevelWorkItems, start,
            end, outputStream);
    }

    @RequestMapping("{board}/add-column-action")
    public synchronized RedirectView addColumn(@ModelAttribute("project") KanbanProject project,
                                               @PathVariable("projectName") String projectName,
                                               @PathVariable("board") String boardType,
                                               @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";
        boolean addedCol = false;

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains("phases") && !addedCol) {
                addedCol = true;
                String[] phases = temp.split(",");
                String last = phases[phases.length - 1];
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                }
                else {
                    last = name + "," + last;
                }

                for (int i = 0; i < phases.length - 1; i++) {
                    newContent += phases[i] + ",";
                }
                newContent += last + "\n";

            }
            else if (temp.contains("boards.wall")) {
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                    newContent += temp + "\n";
                }
                else {
                    temp += "," + name;
                    newContent += temp + "\n";
                }
            }
            else {
                newContent += temp + "\n";
            }

        }
        //For when Ok button is pressed and no input is entered
        if (newContent.length() < 10) {
            newContent = orig;
        }
        kanbanService.editProject(projectName, newContent);
        return new RedirectView(
            "/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/add-waitingcolumn-action")
    public synchronized RedirectView addWaitingColumn(@ModelAttribute("project") KanbanProject project,
                                                      @PathVariable("projectName") String projectName,
                                                      @PathVariable("board") String boardType,
    }
                                                      @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains(name)) {
                String[] phases = temp.split(",|=");

                for (int i = 0; i < phases.length; i++) {

                    if (phases[i].equals(name)) {
                        newContent += "Pre - " + name + ",";
                    }
                    if (phases[i].contains(".")) {
                        newContent += phases[i] + "=";
                    }
                    else {
                        newContent += phases[i] + ",";
                    }

                }
                newContent += "\n";

            }
            else if (temp.contains("boards.wall")) {
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                    newContent += temp + "\n";
                }
                else {
                    temp += "," + name;
                    newContent += temp + "\n";
                }
            }

            else {
                newContent += temp + "\n";
            }

        }
        if (newContent.length() < 10) {
            newContent = orig;
        }

        kanbanService.editProject(projectName, newContent);

        return new RedirectView(
            "/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/delete-column-action")
    public synchronized RedirectView deleteColumn(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @PathVariable("board") String boardType,
                                                  @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains(name)) {
                String[] phases = temp.split(",|=");

                for (int i = 0; i <= phases.length - 1; i++) {

                    if (phases[i].equals(name)) {
                        //DO NOTHING
                    }
                    else {
                        if (i == 0) {
                            newContent += phases[i] + "=";
                        }
                        else if (i == phases.length - 1) {
                            newContent += phases[i];
                        }
                        else if (i == phases.length - 2 && name.equals(phases[phases.length - 1])) {
                            newContent += phases[i];
                        }
                        else {

                            newContent += phases[i] + ",";
                        }
                    }
                newContent += "\n";
            }
            else {
                newContent += temp + "\n";
            }
        }

        if (newContent.length() < 10) {
            newContent = orig;
        }

        kanbanService.editProject(projectName, newContent);

        return new RedirectView("/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/set-work-stream")
    public RedirectView setWorkStream(@ModelAttribute("project") KanbanProject project,
                                      @PathVariable("projectName") String projectName,
                                      @PathVariable("board") String boardType,
                                      @RequestParam("workStream") String selectedWorkStream,
                                      @RequestParam(value = "chartName", required = false) String chartName,
                                      @RequestParam(value = "workItemTypeName", required = false) String workItemTypeName,
                                      @ModelAttribute("workStreams") Map workStreams) {

        workStreams.put(projectName, selectedWorkStream);

        String params = "";

        if (!StringUtils.isEmpty(chartName) && !StringUtils.isEmpty(workItemTypeName)) {
            params = "?chartName=" + chartName + "&workItemTypeName=" + workItemTypeName;
        }

        return new RedirectView("/projects/" + projectName + "/" + boardType + params, true);
    }

    public void setKanbanService(KanbanService kanbanService) {
        this.kanbanService = kanbanService;
    }

    private boolean parseBoolean(String excludedStr) {
        if (excludedStr == null) {
            return false;
        }
        if ("on".equals(excludedStr)) {
            return true;
        }
        return Boolean.parseBoolean(excludedStr);
    }

    private int parseInteger(String sizeStr, int defaultValue) {
        try {
            return Integer.parseInt(sizeStr);
        } catch (NumberFormatException nfe) {
        }
        return defaultValue;
    }

    @RequestMapping(value = "comment", method = RequestMethod.POST)
    public ResponseEntity addComment(@ModelAttribute("project") KanbanProject project,
            @RequestParam int id,
            @RequestParam String userName,
            @RequestParam String comment) throws IOException {

        WorkItemComment workItemComment = new WorkItemComment(userName, comment);
        WorkItem workItem = project.getWorkItemById(id);
        workItem.addComment(workItemComment);

        project.save();

        String body = gson.toJson(workItemComment);
        ResponseEntity response = new ResponseEntity(body, HttpStatus.OK);
        return response;
    }

    @RequestMapping("")
    public RedirectView redirectToWall(@PathVariable("projectName") String projectName) {
        return new RedirectView("/projects/" + projectName + "/wall", true);
    }
}
=======
package com.metservice.kanban.web;

import static com.metservice.kanban.model.WorkItem.ROOT_WORK_ITEM_ID;
import static com.metservice.kanban.utils.DateUtils.currentLocalDate;
import static com.metservice.kanban.utils.DateUtils.parseConventionalNewZealandDate;
import static java.lang.Integer.parseInt;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.CategoryDataset;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.metservice.kanban.KanbanService;
import com.metservice.kanban.charts.burnup.BurnUpChartGenerator;
import com.metservice.kanban.charts.burnup.DefaultBurnUpChartGenerator;
import com.metservice.kanban.charts.burnup.DefaultChartWriter;
import com.metservice.kanban.charts.cumulativeflow.CumulativeFlowChartBuilder;
import com.metservice.kanban.charts.cycletime.CycleTimeChartBuilder;
import com.metservice.kanban.model.BoardIdentifier;
import com.metservice.kanban.model.KanbanBoard;
import com.metservice.kanban.model.KanbanProject;
import com.metservice.kanban.model.WorkItem;
import com.metservice.kanban.model.WorkItemComment;
import com.metservice.kanban.model.WorkItemTree;
import com.metservice.kanban.model.WorkItemType;
import com.metservice.kanban.utils.DateUtils;
import com.metservice.kanban.utils.JsonLocalDateTimeConvertor;

//TODO This class needs more unit tests.

/**
 * @author Nicholas Malcolm - malcolnich - 300170288
 */
@Controller
@RequestMapping("{projectName}")
@SessionAttributes("workStreams")
public class KanbanBoardController {

    @Autowired
    private KanbanService kanbanService;
    private Gson gson;

    public KanbanBoardController() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(LocalDateTime.class, new JsonLocalDateTimeConvertor());
        this.gson = gsonBuilder.create();
    }

    @ModelAttribute("project")
    public synchronized KanbanProject populateProject(@PathVariable("projectName") String projectName)
        throws IOException {

        return kanbanService.getKanbanProject(projectName);
    }

    /*
        @ModelAttribute("boardName")
        public String populateBoardName(@PathVariable("board") String board) {
            return board;
        }
        */

    @ModelAttribute("projectName")
    public String populateProjectName(@PathVariable("projectName") String board) {
        return board;
    }

    @ModelAttribute("workStreams")
    public synchronized Map populateWorkStreams() {
        return new HashMap();
    }

    //    @ModelAttribute("redirectView")
    //    public synchronized RedirectView populateRedirectView(
    //                                                          @PathVariable("projectName") String projectName,
    //                                                          @PathVariable("board") String board) {
    //        return new RedirectView("/projects/" + projectName + "/" + board, true);
    //    }

    @ModelAttribute("chartGenerator")
    public synchronized BurnUpChartGenerator populateChartGenerator() {
        return new DefaultBurnUpChartGenerator(new DefaultChartWriter());
    private Map initBoard(String boardType, String projectName, String scrollTop) {
        //        boardType = cleanBoardType(boardType);

        Map model = buildModel(projectName, boardType);

        // TODO model used to have kanbanTransaction now it has kanban... need
        // to fix view

        model.put("scrollTop", scrollTop == null ? "0" : scrollTop);

        return model;
    }

    private ModelAndView finishBoard(String boardType, String projectName, KanbanProject project,
                                     Map workStreams,
                                     Map model,
                                     String view) {
        KanbanBoard board = project.getBoard(BoardIdentifier.valueOf(boardType.toUpperCase()),
            workStreams.get(projectName));

        model.put("board", board);

        return new ModelAndView(view, model);

    }

    @RequestMapping("wall")
    public synchronized ModelAndView wallBoard(@ModelAttribute("project") KanbanProject project,
                                               @PathVariable("projectName") String projectName,
                                               @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                               @RequestParam(value = "error", required = false) String error,
                                               @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        String boardType = "wall";

        Map model = initBoard("wall", projectName, scrollTop);
        model.put("error", error);

        if (boardType.equals("backlog")) {
            model.put("kanbanBacklog", project.getBacklog(workStreams.get(projectName)));
            model.put("type", project.getWorkItemTypes().getRoot().getValue());
            model.put("phase", project.getWorkItemTypes().getRoot().getValue()
                .getPhases().get(0));
            return new ModelAndView("/backlog.jsp", model);
        } else if (boardType.equals("completed")) {
            model.put("type", project.getWorkItemTypes().getRoot().getValue());
            List phases = project.getWorkItemTypes().getRoot()
                .getValue().getPhases();
            model.put("phase", phases.get(phases.size() - 1));

            KanbanBoard board = project.getBoard(BoardIdentifier.valueOf(boardType.toUpperCase()),
                workStreams.get(projectName));

            model.put("board", board);

            return new ModelAndView("/completed.jsp", model);
        }
        else if (boardType.equals("journal")) {
            model.put("kanbanJournal", project.getJournalText());
            return new ModelAndView("/journal.jsp", model);
        }

        return finishBoard("wall", projectName, project, workStreams, model, "/project.jsp");
    }

    @RequestMapping("backlog")
    public synchronized ModelAndView backlogBoard(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                  @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("backlog", projectName, scrollTop);

        model.put("kanbanBacklog", project.getBacklog(workStreams.get(projectName)));
        model.put("type", project.getWorkItemTypes().getRoot().getValue());
        model.put("phase", project.getWorkItemTypes().getRoot().getValue().getPhases().get(0));
        return new ModelAndView("/backlog.jsp", model);
    }

    @RequestMapping("journal")
    public synchronized ModelAndView journalBoard(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,

                                                  @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                  @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("journal", projectName, scrollTop);
        model.put("kanbanJournal", project.getJournalText());
        return new ModelAndView("/journal.jsp", model);
    }

    @RequestMapping("completed")
    public synchronized ModelAndView completedBoard(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("projectName") String projectName,
                                                    @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                    @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("completed", projectName, scrollTop);

        model.put("type", project.getWorkItemTypes().getRoot().getValue());
        List phases = project.getWorkItemTypes().getRoot()
            .getValue().getPhases();
        model.put("phase", phases.get(phases.size() - 1));

        return finishBoard("completed", projectName, project, workStreams, model, "/completed.jsp");
    }

    //    private String cleanBoardType(String boardType) {
    //        int sep = boardType.indexOf(":");
    //        if (sep > -1) {
    //            return boardType.substring(0, sep);
    //        }
    //        return boardType;
    //    }

    //    private String extractScrollPositionInfoFromBoardType(String boardType) {
    //        int sep = boardType.indexOf(":");
    //        if (sep > -1) {
    //            return boardType.substring(sep + 1);
    //        }
    //        return null;
    //    }

    @RequestMapping(value = "{board}/advance-item-action", method = RequestMethod.POST)
    public synchronized RedirectView advanceItemAction(@ModelAttribute("project") KanbanProject project,
                                                       @PathVariable("board") String boardType,
                                                       @RequestParam("id") String id,
                                                       @RequestParam("phase") String phase) throws IOException {

        // check item hasn't already been advanced
        WorkItem workItem = project.getWorkItemById(Integer.parseInt(id));
        if (!workItem.getCurrentPhase().equals(phase)) {
            //TODO display error to user
            return new RedirectView("../" + boardType + "?error=Something happened");
        }

        project.advance(parseInt(id), currentLocalDate());
        project.save();

        return new RedirectView("../" + boardType);
    }

    @RequestMapping(value = "{board}/stop-item-action", method = RequestMethod.POST)
    public synchronized RedirectView stopItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") String id) throws IOException {

        project.stop(parseInt(id));
        project.save();

        // Redirect
        return new RedirectView("../" + boardType);

        //return new RedirectView(includeScrollTopPosition(boardType, scrollTop));
    }

    private String includeScrollTopPosition(String boardType, String scrollTop) {
        return "../" + boardType + "?scrollTop=" + scrollTop;
    }

    /** Creates empty item model to display in add form with preset parent id. **/
    @RequestMapping("{board}/add-item")
    public synchronized ModelAndView addItem(@ModelAttribute("project") KanbanProject project,
                                             @PathVariable("projectName") String projectName,
                                             @RequestParam("id") int id) throws IOException {

        // Search for parent id
        WorkItem parent = project.getWorkItemTree().getWorkItem(id);

        WorkItemType type = project.getWorkItemTypes().getRoot().getValue();
        String parentName = "";
        int parentId = ROOT_WORK_ITEM_ID;
        String legend = "Add " + type;

        if (parent != null) {
            parentName = parent.getName();
            parentId = parent.getId();
            type = project.getWorkItemTypes().getTreeNode(parent.getType()).getChild(0).getValue();
            legend = "Add a " + type + " to " + parentName;
        }

        Map model = new HashMap();

        model.put("legend", legend);
        model.put("parentId", parentId);
        model.put("type", type);
        model.put("topLevel", true);

        return new ModelAndView("/add.jsp", model);
    }

    /**
     * Responds to edit-item request, passes the item in its current state to
     * the edit form
     * 
     * @param project
     * @param projectName
     * @param boardType
     * @param id
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/edit-item")
    public synchronized ModelAndView editItem(@ModelAttribute("project") KanbanProject project,
                                              @PathVariable("board") String board,
                                              @PathVariable("projectName") String projectName,
                                              @RequestParam("id") Integer id) throws IOException {

        // Get a model ready to take some attributes
        Map model = new HashMap();
        //buildModel(projectName, boardType);

        // Fetch the item we want to edit
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        // Add some variables to the model hashmap
        model.put("workItem", workItem);
        model.put("children", project.getWorkItemTree().getChildren(id));
        model.put("parentAlternativesList", project.getWorkItemTree().getParentAlternatives(workItem));
        model.put("board", board);

        // TODO: Figure out what this does
        Map map = new LinkedHashMap();
        for (String phase : workItem.getType().getPhases()) {
            LocalDate date = workItem.getDate(phase);

            String dateString = "";
            if (date != null) {
                dateString = DateUtils.formatConventionalNewZealandDate(date);
            }
            map.put(phase, dateString);
        }
        model.put("phasesMap", map);

        // Pass the model to edit.jsp
        return new ModelAndView("/edit.jsp", model);
    }

    /**
     * Add item to Kanban project Needs a bunch of parameters in the request.
     * (Comes from add.jsp)
     **/
    @RequestMapping("{board}/add-item-action")
    public synchronized RedirectView addItemAction(@ModelAttribute("project") KanbanProject project,
                                                   @PathVariable("board") String board,
                                                   @RequestParam(value = "parentId", required = false) Integer parentId,
                                                   @RequestParam("type") String type,
                                                   @RequestParam("name") String name,
                                                   @RequestParam("averageCaseEstimate") String averageCaseEstimateStr,
                                                   @RequestParam(value = "worstCaseEstimate", required = false) String worstCaseEstimateStr,
                                                   @RequestParam("importance") String importanceStr,
                                                   @RequestParam("notes") String notes,
                                                   @RequestParam("color") String color,
                } else {
                                                   @RequestParam(value = "excluded", required = false) String excludedStr,
                                                   @RequestParam(value = "redirectTo", required = false) String redirectTo,
        // If it's changed parent, reset the parent.
                                                   @RequestParam(value = "workStreamsSelect", required = false) String workStreams)
        throws IOException {

        WorkItemType typeAsWorkItemType = project.getWorkItemTypes().getByName(type);

        if (parentId == null) {
            parentId = WorkItem.ROOT_WORK_ITEM_ID;
        }

        int averageCaseEstimate = parseInteger(averageCaseEstimateStr, 0);
        int importance = parseInteger(importanceStr, 0);
        boolean excluded = parseBoolean(excludedStr);
        int worstCaseEstimate = parseInteger(worstCaseEstimateStr, 0);

        // Add it and save it
        int newId = project.addWorkItem(parentId, typeAsWorkItemType, name, averageCaseEstimate, worstCaseEstimate,
            importance, notes, color, excluded, workStreams, currentLocalDate());
        project.save();

        if ("print".equals(redirectTo)) {
            return new RedirectView("../print-items?printSelection=" + newId);
        }
        else {
            return new RedirectView("../" + board);
        }
    }

    /**
     * Responds to a form submission which passes an edited item
     * 
     * @param project
     * @param boardType
     * @return
     * @throws IOException
     * @throws ParseException
     */
    @RequestMapping("{board}/edit-item-action")
    public synchronized RedirectView editItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") int id,
                                                    @RequestParam("parentId") Integer parentId,
                                                    @RequestParam("name") String name,
                                                    @RequestParam("averageCaseEstimate") String averageCaseEstimateStr,
                                                    @RequestParam("worstCaseEstimate") String worstCaseEstimateStr,
                                                    @RequestParam("importance") String importanceStr,
                                                    @RequestParam("notes") String notes,
                                                    @RequestParam("color") String color,
                                                    @RequestParam(value = "excluded", required = false) String excludedStr,
                                                    @RequestParam(value = "workStreamsSelect", required = false) String workStreams,
                                                    @RequestParam(value = "redirectTo", required = false) String redirectTo,
                                                    HttpServletRequest request) throws IOException, ParseException {

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        @SuppressWarnings("unchecked")
        Map parameters = request.getParameterMap();

        int averageCaseEstimate = parseInteger(averageCaseEstimateStr, 0);
        int worstCaseEstimate = parseInteger(worstCaseEstimateStr, 0);
        int importance = parseInteger(importanceStr, 0);
        boolean excluded = parseBoolean(excludedStr);

        // Save all the updates
        workItem.setName(name);
        workItem.setAverageCaseEstimate(averageCaseEstimate);
        workItem.setWorstCaseEstimate(worstCaseEstimate);
        workItem.setImportance(importance);
        workItem.setNotes(notes);
        workItem.setExcluded(excluded);
        workItem.setColour(color);
        workItem.setWorkStreamsAsString(workStreams);

        // TODO Figure this out
        for (String phase : workItem.getType().getPhases()) {
            String key = "date-" + phase;
            String[] valueArray = parameters.get(key);
            if (valueArray != null) {
                if (valueArray[0].trim().isEmpty()) {
                    workItem.setDate(phase, null);
                    workItem.setDate(phase, parseConventionalNewZealandDate(valueArray[0]));
                }
            }
        }
        if (workItem.getParentId() != parentId) {
            project.getWorkItemTree().reparent(id, parentId);
        }

        // Save the whole project
        project.save();

        if ("print".equals(redirectTo)) {
            return new RedirectView("../print-items?printSelection=" + id);
        }
        else {
            return new RedirectView("../" + boardType);
        }
    }

    @RequestMapping(value = "print-items")
    public synchronized ModelAndView printItems(@ModelAttribute("project") KanbanProject project,
                                                @PathVariable("projectName") String projectName,
                                                @RequestParam("printSelection") String[] ids) throws IOException {

        List items = new ArrayList();

        for (String id : ids) {
            items.add(project.getWorkItemTree().getWorkItem(parseInteger(id, 0)));
        }

        return new ModelAndView("/printCards.jsp", "items", items);
    }

    @RequestMapping("{board}/move-item-action")
    public synchronized RedirectView moveItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") String id,
                                                    @RequestParam("targetId") String targetId,
                                                    @RequestParam("after") Boolean after,
                                                    @RequestParam("scrollTop") String scrollTop) throws IOException {
        int idAsInteger = parseInt(id);
        int targetIdAsInteger = parseInt(targetId);
        project.move(idAsInteger, targetIdAsInteger, after);
        project.save();
        return new RedirectView(includeScrollTopPosition(boardType, scrollTop));
    }

    @RequestMapping("{board}/reorder")
    public synchronized RedirectView reorder(@ModelAttribute("project") KanbanProject project,
                                             @PathVariable("board") String boardType,
                                             @RequestParam("id") Integer id, @RequestParam("ids") Integer[] ids,
                                             @RequestParam("scrollTop") String scrollTop) throws IOException {

        project.reorder(id, ids);
        project.save();

        return new RedirectView(includeScrollTopPosition(boardType, scrollTop));

    }

    @RequestMapping(value = "{board}/edit-item/{id}/name", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemName(@ModelAttribute("project") KanbanProject project,
                                                              @PathVariable("board") String boardType,
                                                              @PathVariable("id") int id,
                                                              @RequestParam("newValue") String newValue)
        throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setName(newValue);
        project.save();

        // Go home.
        return new ResponseEntity(
            String.format("Name change successfully.  New name: %s", workItem.getName()), HttpStatus.OK);
    }

    @RequestMapping(value = "{board}/edit-item/{id}/size", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemSize(@ModelAttribute("project") KanbanProject project,
                                                              @PathVariable("board") String boardType,
                                                              @PathVariable("id") int id,
                                                              @RequestParam("newValue") String newValue)
        throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setAverageCaseEstimate(parseInteger(newValue, 0));
        project.save();

        // Go home.
        return new ResponseEntity(
            String.format("Size change successfully.  New size: %s", workItem.getAverageCaseEstimate()), HttpStatus.OK);
    }

    @RequestMapping(value = "{board}/edit-item/{id}/importance", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemImportance(@ModelAttribute("project") KanbanProject project,
                                                                    @PathVariable("board") String boardType,
                                                                    @PathVariable("id") int id,
                                                                    @RequestParam("newValue") String newValue)
        throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setImportance(parseInteger(newValue, 0));
        project.save();

        // Go home.
        return new ResponseEntity(
            String.format("Importance change successfully.  New importance: %s", workItem.getImportance()),
            HttpStatus.OK);
    }

    @RequestMapping("edit-journal-action")
    public synchronized RedirectView editJournalAction(@ModelAttribute("project") KanbanProject project,
                                                       @RequestParam("journalText") String journalText,
                                                       HttpServletRequest request) throws IOException, ParseException {

        project.writeJournalText(journalText);
        return new RedirectView("journal");

    }

    /**
     * Responds to a request to delete an item
     * 
     * @param project
     * @param id
     *            - the id of the item you want to delete
     * @param board
     *            - the view you want to redirect to
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/delete-item-action")
    public synchronized RedirectView deleteWorkItem(@ModelAttribute("project") KanbanProject project,
                                                    @RequestParam("id") int id,
                                                    @PathVariable("board") String board) throws IOException {

        // Delete the workitem, save and redirect.
        project.deleteWorkItem(id);
        project.save();
        return new RedirectView("../" + board);
    }

    @RequestMapping("chart")
    public synchronized ModelAndView chart(@ModelAttribute("project") KanbanProject project,
                                           @RequestParam("chartName") String chartName,
                                           @RequestParam("workItemTypeName") String workItemTypeName,
                                           @PathVariable("projectName") String projectName,
                                           @RequestParam(value = "startDate", required = false) String startDate,
                                           @RequestParam(value = "endDate", required = false) String endDate) {

        Map model = initBoard("chart", projectName, null);

        model.put("workItemTypeName", workItemTypeName);
        model.put("imageName", chartName + ".png");
        model.put("startDate", startDate);
        model.put("endDate", endDate);
        model.put("kanbanJournal", project.getJournalText());
        model.put("chartName", chartName);

        return new ModelAndView("/chart.jsp", model);
    }


    // TODO check in this class for redundent model.put("kanban...

    @RequestMapping("cumulative-flow-chart.png")
    public synchronized void cumulativeFlowChartPng(@ModelAttribute("project") KanbanProject project,
        try {
                                                    @RequestParam("startDate") String startDate,
                                                    @RequestParam("endDate") String endDate,
                                                    @RequestParam("level") String level,
                                                    @RequestParam(value = "workStream", required = false) String workStream,
                                                    OutputStream outputStream) throws IOException {

        WorkItemType type;

        try {
            type = project.getWorkItemTypes().getByName(level);
        } catch (IllegalArgumentException e) {
            // TODO produce image with text from exception
            return;
        }

        List workItemList = project.getWorkItemTree().getWorkItemsOfType(type, workStream);

        LocalDate start = null;
        LocalDate end = null;

        try {
            start = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(startDate));
        } catch (ParseException e) {
            // keep start as null
        }
        try {
            end = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(endDate));
        } catch (ParseException e) {
            // keep end as null
        }

        // add start and end date params here
        CumulativeFlowChartBuilder builder = new CumulativeFlowChartBuilder(start, end);

        CategoryDataset dataset = builder.createDataset(type.getPhases(), workItemList);
        JFreeChart chart = builder.createChart(dataset);
        int width = dataset.getColumnCount() * 15;
        ChartUtilities.writeChartAsPNG(outputStream, chart, width < 800 ? 800 : width, 600);
    }

    @RequestMapping("cycle-time-chart.png")
    public synchronized void cycleTimeChartPng(@ModelAttribute("project") KanbanProject project,
                                               @RequestParam("startDate") String startDate,
                                               @RequestParam("endDate") String endDate,
                                               @RequestParam(value = "workStream", required = false) String workStream,
                                               @RequestParam("level") String level, OutputStream outputStream)
        throws IOException {

        WorkItemType type = project.getWorkItemTypes().getByName(level);
        CycleTimeChartBuilder builder = new CycleTimeChartBuilder();
        List workItemList = project.getWorkItemTree().getWorkItemsOfType(type, workStream);

        CategoryDataset dataset = builder.createDataset(builder
            .getCompletedWorkItemsInOrderOfCompletion(workItemList));
        JFreeChart chart = builder.createChart(dataset);
        int width = dataset.getColumnCount() * 15;
        ChartUtilities.writeChartAsPNG(outputStream, chart, width < 800 ? 800 : width, 600);
    }

    /**
     * Provides the variables of the current project to either a new project
     * form,
     * or to the edit project page, depending on createNewProject's boolean
     * value.
     * 
     * @param project
     * @param projectName
     * @param boardType
     * @param createNewProject
     *            - whether we want this to create a new project.
     * @return
     * @throws IOException
     */
    @RequestMapping("edit-project")
    public synchronized ModelAndView editProject(@ModelAttribute("project") KanbanProject project,
                                                 @PathVariable("projectName") String projectName,
                                                 @RequestParam("createNewProject") boolean createNewProject)
        throws IOException {

        Map model = buildModel(projectName, "wall");

        // Get the settings of this current project and pass it to the form.
        model.put("settings", kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString());
        // Create a new project if true
        if (createNewProject) {
            return new ModelAndView("/createProject.jsp", model);
        }
        // else edit the current project
        else {
            return new ModelAndView("/editProject.jsp", model);
        }
    }

    /**
     * Saves the content string and renames the project if
     * newProjectName does
     * not match projectName.
     * 
     * @param project
     *            the project
     * @param projectName
     *            the project name
     * @param newProjectName
     *            the new project name
     * @param content
     *            the content
     * @return the redirect view
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @RequestMapping("edit-project-action")
    public synchronized RedirectView editProjectAction(@ModelAttribute("project") KanbanProject project,
                                                       @PathVariable("projectName") String projectName,
                                                       @RequestParam("newProjectName") String newProjectName,
                                                       @RequestParam("content") String content) throws IOException {

        if (newProjectName != null && !newProjectName.equals(projectName)) {
            // edit project name
            kanbanService.renameProject(projectName, newProjectName);
        } else {
            // edit project
            kanbanService.editProject(newProjectName, content);
        }
        return openProject(projectName, "wall", newProjectName, null, null);
    }

    @RequestMapping("edit-wiplimit-action")
    public synchronized ModelAndView editWIPLimitAction(@ModelAttribute("project") KanbanProject project,
                                                        @PathVariable("projectName") String projectName,
                                                        @RequestParam("columnName") String columnName,
                                                        @RequestParam("columnType") String columnType,
                                                        @RequestParam("wipLimit") String wipLimit) throws IOException,
        ParseException {

        String content = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(content);
        String temp = "";
        String newContent = "";

        //Keep track of how many columns there are
        int totalColumns = 0;
        //This is set once,
        int insertAfterComma = -1;

        String wipLine = "workItemTypes." + columnType + ".wipLimit=";

        while (sc.hasNext()) {

            temp = sc.nextLine();
            //Find out where the new wipLimit should go
            if (temp.contains("workItemTypes." + columnType + ".phases=")) {
                String columns = temp.split("=")[1];
                for (String column : columns.split(",")) {
                    if (column.equals(columnName)) {
                        insertAfterComma = totalColumns;
                    }

                    totalColumns++;

                }
            }
            if (temp.contains(wipLine)) {
                String wipLimits = temp.split("=")[1];
                String[] limits = wipLimits.split(",");
                //limts.count should == totalColumns
                String newLimits = "";

                String limit = "";
                for (int i = 0; i < totalColumns; i++) {
                    //Get the old limit
                    try {
                        limit = limits[i];
                    } catch (ArrayIndexOutOfBoundsException e) {
                        //Set default if no limit
                        limit = "-1";
                    }
                    //Do we need to set it to a new limit?
                    if (i == insertAfterComma) {
                        //Set the new limit
                        limit = wipLimit;
                    }

                    newLimits += limit + ",";
                }
                temp = wipLine + newLimits;
            }
            newContent += temp + "\n";
        }

        kanbanService.editProject(projectName, newContent);

        return new ModelAndView("/admin.jsp", null);
    }

    @RequestMapping("create-project-action")
    public synchronized RedirectView createProjectAction(@ModelAttribute("project") KanbanProject project,
                                                         @PathVariable("projectName") String projectName,
                                                         @RequestParam("newProjectName") String newProjectName,
                                                         @RequestParam("content") String content) throws IOException {

        kanbanService.createProject(newProjectName, content);
        return openProject(projectName, "wall", newProjectName, null, null);
    }

    /**
     * Opens a project and goes to boardType (e.g. wall, backlog etc)
     * 
     * @param projectName
     * @param boardType
     * @param newProjectName
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/open-project")
    public synchronized RedirectView openProject(@PathVariable("projectName") String projectName,
                                                 @PathVariable("board") String boardType,
                                                 @RequestParam("newProjectName") String newProjectName,
                                                 @RequestParam(value = "chartName", required = false) String chartName,
                                                 @RequestParam(value = "workItemTypeName", required = false) String workItemTypeName)
        throws IOException {

        String params = "";

        if (!StringUtils.isEmpty(chartName) && !StringUtils.isEmpty(workItemTypeName)) {
            params = "?chartName=" + chartName + "&workItemTypeName=" + workItemTypeName;
        }
        return new RedirectView(
            "/projects/" + newProjectName + "/" + boardType + params, true);
    }

    /**
     * Models are a hashmap used to pass attributes to a view.
     * 
     * @param projectName
     * @param boardType
     * @return the model hashmap
     */
    private Map buildModel(String projectName, String boardType) {
        Map model = new HashMap();
        model.put("projectName", projectName);
        model.put("boardType", boardType);
        return model;
    }

    @RequestMapping("burn-up-chart.png")
    public void burnUpChartPng(@ModelAttribute("project") KanbanProject project,
                               @ModelAttribute("chartGenerator") BurnUpChartGenerator chartGenerator,
                               @RequestParam("startDate") String startDate,
                               @RequestParam("endDate") String endDate,
                               @RequestParam(value = "workStream", required = false) String workStream,
                               OutputStream outputStream) throws IOException {

        WorkItemTree tree = project.getWorkItemTree();
        WorkItemType type = project.getWorkItemTypes().getRoot().getValue();
        List topLevelWorkItems = tree.getWorkItemsOfType(type, workStream);

        LocalDate start = null;
        LocalDate end = null;
            start = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(startDate));
        } catch (ParseException e) {
            // keep start as null
        }
            end = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(endDate));
        } catch (ParseException e) {
            // keep end as null
        }

        chartGenerator.generateBurnUpChart(type, topLevelWorkItems, start,
            end, outputStream);
    }

    @RequestMapping("{board}/add-column-action")
    public synchronized RedirectView addColumn(@ModelAttribute("project") KanbanProject project,
                                               @PathVariable("projectName") String projectName,
                                               @PathVariable("board") String boardType,
                                               @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";
        boolean addedCol = false;

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains("phases") && !addedCol) {
                addedCol = true;
                String[] phases = temp.split(",");
                String last = phases[phases.length - 1];
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                }
                else {
                    last = name + "," + last;
                }

                for (int i = 0; i < phases.length - 1; i++) {
                    newContent += phases[i] + ",";
                }
                newContent += last + "\n";

            }
            else if (temp.contains("boards.wall")) {
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                    newContent += temp + "\n";
                }
                else {
                    temp += "," + name;
                    newContent += temp + "\n";
                }
            }
            else {
                newContent += temp + "\n";
            }

        }
        //For when Ok button is pressed and no input is entered
        if (newContent.length() < 10) {
            newContent = orig;
        }
        kanbanService.editProject(projectName, newContent);
        return new RedirectView(
            "/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/add-waitingcolumn-action")
    public synchronized RedirectView addWaitingColumn(@ModelAttribute("project") KanbanProject project,
                                                      @PathVariable("projectName") String projectName,
                                                      @PathVariable("board") String boardType,
                                                      @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains(name)) {
                String[] phases = temp.split(",|=");

                for (int i = 0; i < phases.length; i++) {
                    if (phases[i].equals(name)) {
                        newContent += "Pre - " + name + ",";
                    }
                    if (phases[i].contains(".")) {
                        newContent += phases[i] + "=";
                    }
                    else {
                        newContent += phases[i] + ",";
                    }

                }
                newContent += "\n";

            }
            else if (temp.contains("boards.wall")) {
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                    newContent += temp + "\n";
                }
                else {
                    temp += "," + name;
                    newContent += temp + "\n";
                }
            }

            else {
                newContent += temp + "\n";
            }

        }
        if (newContent.length() < 10) {
            newContent = orig;
        }

        kanbanService.editProject(projectName, newContent);

        return new RedirectView(
            "/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/delete-column-action")
    public synchronized RedirectView deleteColumn(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @PathVariable("board") String boardType,
                                                  @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains(name)) {
                String[] phases = temp.split(",|=");

                for (int i = 0; i <= phases.length - 1; i++) {

                    if (phases[i].equals(name)) {
                        //DO NOTHING
                    }
                    else {
                        if (i == 0) {
                            newContent += phases[i] + "=";
                        }
                        else if (i == phases.length - 1) {
                            newContent += phases[i];
                        }
                        else if (i == phases.length - 2 && name.equals(phases[phases.length - 1])) {
                            newContent += phases[i];
                        }
                        else {

                            newContent += phases[i] + ",";
                        }
                    }
                }
                newContent += "\n";
            }
            else {
                newContent += temp + "\n";
            }
        }

        if (newContent.length() < 10) {
            newContent = orig;
        }

        kanbanService.editProject(projectName, newContent);

        return new RedirectView("/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/set-work-stream")
    public RedirectView setWorkStream(@ModelAttribute("project") KanbanProject project,
                                      @PathVariable("projectName") String projectName,
                                      @PathVariable("board") String boardType,
                                      @RequestParam("workStream") String selectedWorkStream,
                                      @RequestParam(value = "chartName", required = false) String chartName,
                                      @RequestParam(value = "workItemTypeName", required = false) String workItemTypeName,
                                      @ModelAttribute("workStreams") Map workStreams) {

        workStreams.put(projectName, selectedWorkStream);

        String params = "";

        if (!StringUtils.isEmpty(chartName) && !StringUtils.isEmpty(workItemTypeName)) {
            params = "?chartName=" + chartName + "&workItemTypeName=" + workItemTypeName;
        }

        return new RedirectView("/projects/" + projectName + "/" + boardType + params, true);
    }

    public void setKanbanService(KanbanService kanbanService) {
        this.kanbanService = kanbanService;
    }

    private boolean parseBoolean(String excludedStr) {
        if (excludedStr == null) {
            return false;
        }
        if ("on".equals(excludedStr)) {
            return true;
        }
        return Boolean.parseBoolean(excludedStr);
    }

    private int parseInteger(String sizeStr, int defaultValue) {
        try {
            return Integer.parseInt(sizeStr);
        } catch (NumberFormatException nfe) {
        }
        return defaultValue;
    }

    @RequestMapping(value = "comment", method = RequestMethod.POST)
    public ResponseEntity addComment(@ModelAttribute("project") KanbanProject project,
                                             @RequestParam int id,
                                             @RequestParam String userName,
                                             @RequestParam String comment) throws IOException {

        WorkItemComment workItemComment = new WorkItemComment(userName, comment);
        WorkItem workItem = project.getWorkItemById(id);
        workItem.addComment(workItemComment);

        project.save();

        String body = gson.toJson(workItemComment);
        ResponseEntity response = new ResponseEntity(body, HttpStatus.OK);
        return response;
    }

    @RequestMapping("")
    public RedirectView redirectToWall(@PathVariable("projectName") String projectName) {
        return new RedirectView("/projects/" + projectName + "/wall", true);
    }
}
>>>>>>> 0342e6050d87037a1847934fcb9f3726cdd4f74d
Solution content
package com.metservice.kanban.web;

import static com.metservice.kanban.model.WorkItem.ROOT_WORK_ITEM_ID;
import static com.metservice.kanban.utils.DateUtils.currentLocalDate;
import static com.metservice.kanban.utils.DateUtils.parseConventionalNewZealandDate;
import static java.lang.Integer.parseInt;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.CategoryDataset;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
    }

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.metservice.kanban.KanbanService;
import com.metservice.kanban.charts.burnup.BurnUpChartGenerator;
import com.metservice.kanban.charts.burnup.DefaultBurnUpChartGenerator;
import com.metservice.kanban.charts.burnup.DefaultChartWriter;
import com.metservice.kanban.charts.cumulativeflow.CumulativeFlowChartBuilder;
import com.metservice.kanban.charts.cycletime.CycleTimeChartBuilder;
import com.metservice.kanban.model.BoardIdentifier;
import com.metservice.kanban.model.KanbanBoard;
import com.metservice.kanban.model.KanbanProject;
import com.metservice.kanban.model.WorkItem;
import com.metservice.kanban.model.WorkItemComment;
import com.metservice.kanban.model.WorkItemTree;
import com.metservice.kanban.model.WorkItemType;
import com.metservice.kanban.utils.DateUtils;
import com.metservice.kanban.utils.JsonLocalDateTimeConvertor;

//TODO This class needs more unit tests.

/**
 * @author Nicholas Malcolm - malcolnich - 300170288
 */
@Controller
@RequestMapping("{projectName}")
@SessionAttributes("workStreams")
public class KanbanBoardController {

    @Autowired
    private KanbanService kanbanService;
    private Gson gson;

    public KanbanBoardController() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(LocalDateTime.class, new JsonLocalDateTimeConvertor());
        this.gson = gsonBuilder.create();
    }

    @ModelAttribute("project")
    public synchronized KanbanProject populateProject(@PathVariable("projectName") String projectName)
        throws IOException {

        return kanbanService.getKanbanProject(projectName);
    }

    /*
        @ModelAttribute("boardName")
        public String populateBoardName(@PathVariable("board") String board) {
            return board;
        }
        */

    @ModelAttribute("projectName")
    public String populateProjectName(@PathVariable("projectName") String board) {
        return board;
    }

    @ModelAttribute("workStreams")
    public synchronized Map populateWorkStreams() {
        return new HashMap();
    }

    //    @ModelAttribute("redirectView")
    //    public synchronized RedirectView populateRedirectView(
    //                                                          @PathVariable("projectName") String projectName,
    //                                                          @PathVariable("board") String board) {
    //        return new RedirectView("/projects/" + projectName + "/" + board, true);
    //    }

    @ModelAttribute("chartGenerator")
    public synchronized BurnUpChartGenerator populateChartGenerator() {
        return new DefaultBurnUpChartGenerator(new DefaultChartWriter());
    }

    private Map initBoard(String boardType, String projectName, String error, String scrollTop) {
        //        boardType = cleanBoardType(boardType);

        Map model = buildModel(projectName, boardType);

        // TODO model used to have kanbanTransaction now it has kanban... need
        // to fix view

        model.put("scrollTop", scrollTop == null ? "0" : scrollTop);
        model.put("error", error);

        return model;

    private ModelAndView finishBoard(String boardType, String projectName, KanbanProject project,
                             Map workStreams,
                                     Map model,
                                     String view) {
        KanbanBoard board = project.getBoard(BoardIdentifier.valueOf(boardType.toUpperCase()),
            workStreams.get(projectName));

        model.put("board", board);

        return new ModelAndView(view, model);

    }

    @RequestMapping("wall")
    public synchronized ModelAndView wallBoard(@ModelAttribute("project") KanbanProject project,
                                               @PathVariable("projectName") String projectName,
                                               @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                               @RequestParam(value = "error", required = false) String error,
                                               @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        String boardType = "wall";

        Map model = initBoard("wall", projectName, error, scrollTop);



        if (boardType.equals("backlog")) {
            model.put("kanbanBacklog", project.getBacklog(workStreams.get(projectName)));
            model.put("type", project.getWorkItemTypes().getRoot().getValue());
            model.put("phase", project.getWorkItemTypes().getRoot().getValue()
                .getPhases().get(0));
            return new ModelAndView("/backlog.jsp", model);
        } else if (boardType.equals("completed")) {
            model.put("type", project.getWorkItemTypes().getRoot().getValue());
            List phases = project.getWorkItemTypes().getRoot()
                .getValue().getPhases();
            model.put("phase", phases.get(phases.size() - 1));

            KanbanBoard board = project.getBoard(BoardIdentifier.valueOf(boardType.toUpperCase()),
                workStreams.get(projectName));

            model.put("board", board);

            return new ModelAndView("/completed.jsp", model);
        }
        else if (boardType.equals("journal")) {
            model.put("kanbanJournal", project.getJournalText());
            return new ModelAndView("/journal.jsp", model);
        }

        return finishBoard("wall", projectName, project, workStreams, model, "/project.jsp");
    }

    @RequestMapping("backlog")
    public synchronized ModelAndView backlogBoard(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                  @RequestParam(value = "error", required = false) String error,
                                                  @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("backlog", projectName, error, scrollTop);

        model.put("kanbanBacklog", project.getBacklog(workStreams.get(projectName)));
        model.put("type", project.getWorkItemTypes().getRoot().getValue());
        model.put("phase", project.getWorkItemTypes().getRoot().getValue().getPhases().get(0));
        return new ModelAndView("/backlog.jsp", model);
    }

    @RequestMapping("journal")
    public synchronized ModelAndView journalBoard(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                  @RequestParam(value = "error", required = false) String error,
                                                  @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("journal", projectName, error, scrollTop);
        model.put("kanbanJournal", project.getJournalText());
        return new ModelAndView("/journal.jsp", model);
    }

    @RequestMapping("completed")
    public synchronized ModelAndView completedBoard(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("projectName") String projectName,
                                                    @RequestParam(value = "scrollTop", required = false) String scrollTop,
                                                    @RequestParam(value = "error", required = false) String error,
                                                    @ModelAttribute("workStreams") Map workStreams)
        throws IOException {

        Map model = initBoard("completed", projectName, error, scrollTop);

        model.put("type", project.getWorkItemTypes().getRoot().getValue());
        List phases = project.getWorkItemTypes().getRoot()
            .getValue().getPhases();
        model.put("phase", phases.get(phases.size() - 1));

        return finishBoard("completed", projectName, project, workStreams, model, "/completed.jsp");
    }

    //    private String cleanBoardType(String boardType) {
    //        int sep = boardType.indexOf(":");
    //        if (sep > -1) {
    //            return boardType.substring(0, sep);
    //        }
    //        return boardType;
    //    }

    //    private String extractScrollPositionInfoFromBoardType(String boardType) {
    //        int sep = boardType.indexOf(":");
    //        if (sep > -1) {
    //            return boardType.substring(sep + 1);
    //        }
    //        return null;
    //    }

    @RequestMapping(value = "{board}/advance-item-action")
    public synchronized RedirectView advanceItemAction(@ModelAttribute("project") KanbanProject project,
                                                       @PathVariable("board") String boardType,
                                                       @RequestParam("id") String id,
                                                       @RequestParam("phase") String phase) throws IOException {
        // check item hasn't already been advanced
        WorkItem workItem = project.getWorkItemById(Integer.parseInt(id));
        if (!workItem.getCurrentPhase().equals(phase)) {
            //TODO display error to user
            return new RedirectView("../" + boardType
                + "?error=Could not advance item to the next phase. Probably current board view was not valid.");
        }

        project.advance(parseInt(id), currentLocalDate());
        project.save();

        return new RedirectView("../" + boardType);
    }

    @RequestMapping(value = "{board}/stop-item-action", method = RequestMethod.POST)
    public synchronized RedirectView stopItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") String id) throws IOException {

        project.stop(parseInt(id));
        project.save();

        // Redirect
        return new RedirectView("../" + boardType);

        //return new RedirectView(includeScrollTopPosition(boardType, scrollTop));
    }

    private String includeScrollTopPosition(String boardType, String scrollTop) {
        return "../" + boardType + "?scrollTop=" + scrollTop;
    }

    /** Creates empty item model to display in add form with preset parent id. **/
    @RequestMapping("{board}/add-item")
    public synchronized ModelAndView addItem(@ModelAttribute("project") KanbanProject project,
                                             @PathVariable("projectName") String projectName,
                                             @RequestParam("id") int id) throws IOException {

        // Search for parent id
        WorkItem parent = project.getWorkItemTree().getWorkItem(id);

        WorkItemType type = project.getWorkItemTypes().getRoot().getValue();
        String parentName = "";
        int parentId = ROOT_WORK_ITEM_ID;
        String legend = "Add " + type;

        if (parent != null) {
            parentName = parent.getName();
            parentId = parent.getId();
            type = project.getWorkItemTypes().getTreeNode(parent.getType()).getChild(0).getValue();
            legend = "Add a " + type + " to " + parentName;
        }

        Map model = new HashMap();

        model.put("legend", legend);
        model.put("parentId", parentId);
        model.put("type", type);
        model.put("topLevel", true);

        return new ModelAndView("/add.jsp", model);
    }

    /**
     * Responds to edit-item request, passes the item in its current state to
     * the edit form
     *
     * @param project
     * @param projectName
     * @param boardType
     * @param id
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/edit-item")
    public synchronized ModelAndView editItem(@ModelAttribute("project") KanbanProject project,
                                              @PathVariable("board") String board,
                                              @PathVariable("projectName") String projectName,
                                              @RequestParam("id") Integer id) throws IOException {

        // Get a model ready to take some attributes
        Map model = new HashMap();
        //buildModel(projectName, boardType);

        // Fetch the item we want to edit
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        // Add some variables to the model hashmap
        model.put("workItem", workItem);
        model.put("children", project.getWorkItemTree().getChildren(id));
        model.put("parentAlternativesList", project.getWorkItemTree().getParentAlternatives(workItem));
        model.put("board", board);

        // TODO: Figure out what this does
        Map map = new LinkedHashMap();
        for (String phase : workItem.getType().getPhases()) {
            LocalDate date = workItem.getDate(phase);

            String dateString = "";
            if (date != null) {
                dateString = DateUtils.formatConventionalNewZealandDate(date);
            }
            map.put(phase, dateString);
        }
        model.put("phasesMap", map);

        // Pass the model to edit.jsp
        return new ModelAndView("/edit.jsp", model);
    }

    /**
     * Add item to Kanban project Needs a bunch of parameters in the request.
     * (Comes from add.jsp)
     **/
    @RequestMapping("{board}/add-item-action")
    public synchronized RedirectView addItemAction(@ModelAttribute("project") KanbanProject project,
                                                   @PathVariable("board") String board,
                                                   @RequestParam(value = "parentId", required = false) Integer parentId,
                                                   @RequestParam("type") String type,
                                                   @RequestParam("name") String name,
                                                   @RequestParam("averageCaseEstimate") String averageCaseEstimateStr,
                                                   @RequestParam(value = "worstCaseEstimate", required = false) String worstCaseEstimateStr,
                                                   @RequestParam("importance") String importanceStr,
                                                   @RequestParam("notes") String notes,
                                                   @RequestParam("color") String color,
                                                   @RequestParam(value = "excluded", required = false) String excludedStr,
                                                   @RequestParam(value = "redirectTo", required = false) String redirectTo,
                                                   @RequestParam(value = "workStreamsSelect", required = false) String workStreams) throws IOException {

        WorkItemType typeAsWorkItemType = project.getWorkItemTypes().getByName(type);

        if (parentId == null) {
            parentId = WorkItem.ROOT_WORK_ITEM_ID;
        }

        int averageCaseEstimate = parseInteger(averageCaseEstimateStr, 0);
        int importance = parseInteger(importanceStr, 0);
        boolean excluded = parseBoolean(excludedStr);
        int worstCaseEstimate = parseInteger(worstCaseEstimateStr, 0);

        // Add it and save it
        int newId = project.addWorkItem(parentId, typeAsWorkItemType, name, averageCaseEstimate, worstCaseEstimate,
            importance, notes, color, excluded, workStreams, currentLocalDate());
        project.save();

        if ("print".equals(redirectTo)) {
            return new RedirectView("../print-items?printSelection=" + newId);
        }
        else {
            return new RedirectView("../" + board);
        }
    }

    /**
     * Responds to a form submission which passes an edited item
     *
     * @param project
     * @param boardType
     * @return
     * @throws IOException
     * @throws ParseException
     */
    @RequestMapping("{board}/edit-item-action")
    public synchronized RedirectView editItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") int id,
                                                    @RequestParam("parentId") Integer parentId,
                                                    @RequestParam("name") String name,
                                                    @RequestParam("averageCaseEstimate") String averageCaseEstimateStr,
                                                    @RequestParam("worstCaseEstimate") String worstCaseEstimateStr,
                                                    @RequestParam("importance") String importanceStr,
                                                    @RequestParam("notes") String notes,
                                                    @RequestParam("color") String color,
                                                    @RequestParam(value = "excluded", required = false) String excludedStr,
                                                    @RequestParam(value = "workStreamsSelect", required = false) String workStreams,
                                                    @RequestParam(value = "redirectTo", required = false) String redirectTo,
                                                    HttpServletRequest request) throws IOException, ParseException {
    
        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);
    
        @SuppressWarnings("unchecked")
        Map parameters = request.getParameterMap();
    
        int averageCaseEstimate = parseInteger(averageCaseEstimateStr, 0);
        int worstCaseEstimate = parseInteger(worstCaseEstimateStr, 0);
        int importance = parseInteger(importanceStr, 0);
        boolean excluded = parseBoolean(excludedStr);
    
        // Save all the updates
        workItem.setName(name);
        workItem.setAverageCaseEstimate(averageCaseEstimate);
        workItem.setWorstCaseEstimate(worstCaseEstimate);
        workItem.setImportance(importance);
        workItem.setNotes(notes);
        workItem.setExcluded(excluded);
        workItem.setColour(color);
        workItem.setWorkStreamsAsString(workStreams);
    
        // TODO Figure this out
        for (String phase : workItem.getType().getPhases()) {
            String key = "date-" + phase;
            String[] valueArray = parameters.get(key);
            if (valueArray != null) {
                if (valueArray[0].trim().isEmpty()) {
                    workItem.setDate(phase, null);
                } else {
                    workItem.setDate(phase, parseConventionalNewZealandDate(valueArray[0]));
                }
            }
        }
    
        // If it's changed parent, reset the parent.
        if (workItem.getParentId() != parentId) {
            project.getWorkItemTree().reparent(id, parentId);
        }
    
        // Save the whole project
        project.save();
    
        if ("print".equals(redirectTo)) {
            return new RedirectView("../print-items?printSelection=" + id);
        }
        else {
            return new RedirectView("../" + boardType);
        }
    }

    @RequestMapping(value = "print-items")
    public synchronized ModelAndView printItems(@ModelAttribute("project") KanbanProject project,
                                                @PathVariable("projectName") String projectName,
                                                @RequestParam("printSelection") String[] ids) throws IOException {

        List items = new ArrayList();

        for (String id : ids) {
            items.add(project.getWorkItemTree().getWorkItem(parseInteger(id, 0)));
        }

        return new ModelAndView("/printCards.jsp", "items", items);
    }

    @RequestMapping("{board}/move-item-action")
    public synchronized RedirectView moveItemAction(@ModelAttribute("project") KanbanProject project,
                                                    @PathVariable("board") String boardType,
                                                    @RequestParam("id") String id,
                                                    @RequestParam("targetId") String targetId,
                                                    @RequestParam("after") Boolean after,
                                                    @RequestParam("scrollTop") String scrollTop) throws IOException {
        int idAsInteger = parseInt(id);
        int targetIdAsInteger = parseInt(targetId);
        project.move(idAsInteger, targetIdAsInteger, after);
        project.save();
        return new RedirectView(includeScrollTopPosition(boardType, scrollTop));
    }

    @RequestMapping("{board}/reorder")
    public synchronized RedirectView reorder(@ModelAttribute("project") KanbanProject project,
                                             @PathVariable("board") String boardType,
                                             @RequestParam("id") Integer id, @RequestParam("ids") Integer[] ids,
                                             @RequestParam("scrollTop") String scrollTop) throws IOException {

        project.reorder(id, ids);
        project.save();

        return new RedirectView(includeScrollTopPosition(boardType, scrollTop));

    }

    @RequestMapping(value = "{board}/edit-item/{id}/name", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemName(@ModelAttribute("project") KanbanProject project,
                                                              @PathVariable("board") String boardType,
                                                              @PathVariable("id") int id,
                                                              @RequestParam("newValue") String newValue)
                    throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setName(newValue);
        project.save();

        // Go home.
        return new ResponseEntity(
                String.format("Name change successfully.  New name: %s", workItem.getName()), HttpStatus.OK);
    }

    @RequestMapping(value = "{board}/edit-item/{id}/size", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemSize(@ModelAttribute("project") KanbanProject project,
                                                              @PathVariable("board") String boardType,
                                                              @PathVariable("id") int id,
                                                              @RequestParam("newValue") String newValue)
                    throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);


        workItem.setAverageCaseEstimate(parseInteger(newValue, 0));
        project.save();

        // Go home.
        return new ResponseEntity(
                String.format("Size change successfully.  New size: %s", workItem.getAverageCaseEstimate()), HttpStatus.OK);
    }

    @RequestMapping(value = "{board}/edit-item/{id}/importance", method = RequestMethod.POST)
    public synchronized ResponseEntity updateItemImportance(@ModelAttribute("project") KanbanProject project,
                                                                    @PathVariable("board") String boardType,
                                                                    @PathVariable("id") int id,
                                                                    @RequestParam("newValue") String newValue)
                    throws IOException {
        // why are these methods marked as synchronized?!?

        // Get the item which is being edited
        WorkItem workItem = project.getWorkItemTree().getWorkItem(id);

        workItem.setImportance(parseInteger(newValue, 0));
        project.save();

        // Go home.
        return new ResponseEntity(
            String.format("Importance change successfully.  New importance: %s", workItem.getImportance()),
            HttpStatus.OK);
    }

    @RequestMapping("edit-journal-action")
    public synchronized RedirectView editJournalAction(@ModelAttribute("project") KanbanProject project,
                                                       @RequestParam("journalText") String journalText,
                                                       HttpServletRequest request) throws IOException, ParseException {

        project.writeJournalText(journalText);
        return new RedirectView("journal");

    }

    /**
     * Responds to a request to delete an item
     *
     * @param project
     * @param id
            }
     *            - the id of the item you want to delete
     * @param board
     *            - the view you want to redirect to
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/delete-item-action")
    public synchronized RedirectView deleteWorkItem(@ModelAttribute("project") KanbanProject project,
                                            @RequestParam("id") int id,
                                            @PathVariable("board") String board) throws IOException {

        // Delete the workitem, save and redirect.
        project.deleteWorkItem(id);
        project.save();
        return new RedirectView("../" + board);
    }

    @RequestMapping("chart")
    public synchronized ModelAndView chart(@ModelAttribute("project") KanbanProject project,
                                           @RequestParam("chartName") String chartName,
                                           @RequestParam("workItemTypeName") String workItemTypeName,
                                           @PathVariable("projectName") String projectName,
                                           @RequestParam(value = "error", required = false) String error,
                                           @RequestParam(value = "startDate", required = false) String startDate,
                                           @RequestParam(value = "endDate", required = false) String endDate) {

        Map model = initBoard("chart", projectName, error, null);

        model.put("workItemTypeName", workItemTypeName);
        model.put("imageName", chartName + ".png");
        model.put("startDate", startDate);
        model.put("endDate", endDate);
        model.put("kanbanJournal", project.getJournalText());
        model.put("chartName", chartName);

        return new ModelAndView("/chart.jsp", model);
    }

    // TODO check in this class for redundent model.put("kanban...

    @RequestMapping("cumulative-flow-chart.png")
    public synchronized void cumulativeFlowChartPng(@ModelAttribute("project") KanbanProject project,
                    }
                                                    @RequestParam("startDate") String startDate,
                                                    @RequestParam("endDate") String endDate,
                                                    @RequestParam("level") String level,
                                                    @RequestParam(value = "workStream", required = false) String workStream,
                                                    OutputStream outputStream) throws IOException {

        WorkItemType type;

        try {
            type = project.getWorkItemTypes().getByName(level);
        } catch (IllegalArgumentException e) {
            // TODO produce image with text from exception
            return;
        }

        List workItemList = project.getWorkItemTree().getWorkItemsOfType(type, workStream);

        LocalDate start = null;
        LocalDate end = null;

        try {
            start = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(startDate));
        } catch (ParseException e) {
            // keep start as null
        }
        try {
            end = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(endDate));
        } catch (ParseException e) {
            // keep end as null
        }

        // add start and end date params here
        CumulativeFlowChartBuilder builder = new CumulativeFlowChartBuilder(start, end);

        CategoryDataset dataset = builder.createDataset(type.getPhases(), workItemList);
        JFreeChart chart = builder.createChart(dataset);
        int width = dataset.getColumnCount() * 15;
        ChartUtilities.writeChartAsPNG(outputStream, chart, width < 800 ? 800 : width, 600);
    }

    @RequestMapping("cycle-time-chart.png")
    public synchronized void cycleTimeChartPng(@ModelAttribute("project") KanbanProject project,
            else if (temp.contains("boards.wall")) {
                                               @RequestParam("startDate") String startDate,
                                               @RequestParam("endDate") String endDate,
                                               @RequestParam(value = "workStream", required = false) String workStream,
                                               @RequestParam("level") String level, OutputStream outputStream)
        throws IOException {

        WorkItemType type = project.getWorkItemTypes().getByName(level);
        CycleTimeChartBuilder builder = new CycleTimeChartBuilder();
        List workItemList = project.getWorkItemTree().getWorkItemsOfType(type, workStream);

        CategoryDataset dataset = builder.createDataset(builder
            .getCompletedWorkItemsInOrderOfCompletion(workItemList));
        JFreeChart chart = builder.createChart(dataset);
        int width = dataset.getColumnCount() * 15;
        ChartUtilities.writeChartAsPNG(outputStream, chart, width < 800 ? 800 : width, 600);
    }

    /**
     * Provides the variables of the current project to either a new project
     * form,
     * or to the edit project page, depending on createNewProject's boolean
     * value.
     *
     * @param project
     * @param projectName
     * @param boardType
     * @param createNewProject
     *            - whether we want this to create a new project.
     * @return
     * @throws IOException
     */
    @RequestMapping("edit-project")
    public synchronized ModelAndView editProject(@ModelAttribute("project") KanbanProject project,
                                                 @PathVariable("projectName") String projectName,
                                                 @RequestParam("createNewProject") boolean createNewProject)
        throws IOException {

        Map model = buildModel(projectName, "wall");

        // Get the settings of this current project and pass it to the form.
        model.put("settings", kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString());
        // Create a new project if true
        if (createNewProject) {
            return new ModelAndView("/createProject.jsp", model);
        }
        // else edit the current project
        else {
            return new ModelAndView("/editProject.jsp", model);
        }
    }

    /**
     * Saves the content string and renames the project if
     * newProjectName does
     * not match projectName.
     * 
     * @param project
     *            the project
     * @param projectName
     *            the project name
     * @param newProjectName
     *            the new project name
     * @param content
     *            the content
     * @return the redirect view
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @RequestMapping("edit-project-action")
    public synchronized RedirectView editProjectAction(@ModelAttribute("project") KanbanProject project,
                                                       @PathVariable("projectName") String projectName,
                                                       @RequestParam("newProjectName") String newProjectName,
                                                       @RequestParam("content") String content) throws IOException {

        if (newProjectName != null && !newProjectName.equals(projectName)) {
            // edit project name
            kanbanService.renameProject(projectName, newProjectName);
        } else {
            // edit project
            kanbanService.editProject(newProjectName, content);
        }
        return openProject(projectName, "wall", newProjectName, null, null);
    }

    @RequestMapping("edit-wiplimit-action")
    public synchronized ModelAndView editWIPLimitAction(@ModelAttribute("project") KanbanProject project,
                                                        @PathVariable("projectName") String projectName,
                                                        @RequestParam("columnName") String columnName,
                                                        @RequestParam("columnType") String columnType,
                                                        @RequestParam("wipLimit") String wipLimit) throws IOException,
        ParseException {

        String content = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(content);
        String temp = "";
        String newContent = "";

        //Keep track of how many columns there are
        int totalColumns = 0;
        //This is set once,
        int insertAfterComma = -1;

        String wipLine = "workItemTypes." + columnType + ".wipLimit=";

        while (sc.hasNext()) {

            temp = sc.nextLine();
            //Find out where the new wipLimit should go
            if (temp.contains("workItemTypes." + columnType + ".phases=")) {
                String columns = temp.split("=")[1];
                for (String column : columns.split(",")) {
                    if (column.equals(columnName)) {
                        insertAfterComma = totalColumns;
                    }

                    totalColumns++;

                }
            }
            if (temp.contains(wipLine)) {
                String wipLimits = temp.split("=")[1];
                String[] limits = wipLimits.split(",");
                //limts.count should == totalColumns
                String newLimits = "";

                String limit = "";
                for (int i = 0; i < totalColumns; i++) {
                    //Get the old limit
                    try {
                        limit = limits[i];
                    } catch (ArrayIndexOutOfBoundsException e) {
                        //Set default if no limit
                        limit = "-1";
                    }
                    //Do we need to set it to a new limit?
                    if (i == insertAfterComma) {
                        //Set the new limit
                        limit = wipLimit;
                    }

                    newLimits += limit + ",";
                }
                temp = wipLine + newLimits;
            }
            newContent += temp + "\n";
        }

        kanbanService.editProject(projectName, newContent);

        return new ModelAndView("/admin.jsp", null);
    }

    @RequestMapping("create-project-action")
    public synchronized RedirectView createProjectAction(@ModelAttribute("project") KanbanProject project,
                                                         @PathVariable("projectName") String projectName,
                                                         @RequestParam("newProjectName") String newProjectName,
                                                         @RequestParam("content") String content) throws IOException {

        kanbanService.createProject(newProjectName, content);
        return openProject(projectName, "wall", newProjectName, null, null);
    }

    /**
     * Opens a project and goes to boardType (e.g. wall, backlog etc)
     *
     * @param projectName
     * @param boardType
     * @param newProjectName
     * @return
     * @throws IOException
     */
    @RequestMapping("{board}/open-project")
    public synchronized RedirectView openProject(@PathVariable("projectName") String projectName,
                                                 @PathVariable("board") String boardType,
                                                 @RequestParam("newProjectName") String newProjectName,
                                                 @RequestParam(value = "chartName", required = false) String chartName,
                                                 @RequestParam(value = "workItemTypeName", required = false) String workItemTypeName)
        throws IOException {

        String params = "";

        if (!StringUtils.isEmpty(chartName) && !StringUtils.isEmpty(workItemTypeName)) {
            params = "?chartName=" + chartName + "&workItemTypeName=" + workItemTypeName;
        }
        return new RedirectView(
            "/projects/" + newProjectName + "/" + boardType + params, true);
    }

    /**
     * Models are a hashmap used to pass attributes to a view.
     *
     * @param projectName
     * @param boardType
     * @return the model hashmap
     */
    private Map buildModel(String projectName, String boardType) {
        Map model = new HashMap();
        model.put("projectName", projectName);
        model.put("boardType", boardType);
        return model;
    }

    @RequestMapping("burn-up-chart.png")
    public void burnUpChartPng(@ModelAttribute("project") KanbanProject project,
                               @ModelAttribute("chartGenerator") BurnUpChartGenerator chartGenerator,
                               @RequestParam("startDate") String startDate,
                               @RequestParam("endDate") String endDate,
                               @RequestParam(value = "workStream", required = false) String workStream,
                               OutputStream outputStream) throws IOException {

        WorkItemTree tree = project.getWorkItemTree();
        WorkItemType type = project.getWorkItemTypes().getRoot().getValue();
        List topLevelWorkItems = tree.getWorkItemsOfType(type, workStream);

        LocalDate start = null;
        LocalDate end = null;

        try {
            start = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(startDate));
        } catch (ParseException e) {
            // keep start as null
        }
        try {
            end = LocalDate.fromDateFields(DateFormat.getDateInstance().parse(endDate));
        } catch (ParseException e) {
            // keep end as null
        }

        chartGenerator.generateBurnUpChart(type, topLevelWorkItems, start,
            end, outputStream);
    }

    @RequestMapping("{board}/add-column-action")
    public synchronized RedirectView addColumn(@ModelAttribute("project") KanbanProject project,
                                               @PathVariable("projectName") String projectName,
                                               @PathVariable("board") String boardType,
                                               @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";
        boolean addedCol = false;

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains("phases") && !addedCol) {
                addedCol = true;
                String[] phases = temp.split(",");
                String last = phases[phases.length - 1];
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                }
                else {
                    last = name + "," + last;
                }

                for (int i = 0; i < phases.length - 1; i++) {
                    newContent += phases[i] + ",";
                }
                newContent += last + "\n";
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                    newContent += temp + "\n";
                }
                else {
                    temp += "," + name;
                    newContent += temp + "\n";
                }
            }
            else {
                newContent += temp + "\n";
            }

        }
        //For when Ok button is pressed and no input is entered
        if (newContent.length() < 10) {
            newContent = orig;
        }
        kanbanService.editProject(projectName, newContent);
        return new RedirectView(
            "/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/add-waitingcolumn-action")
    public synchronized RedirectView addWaitingColumn(@ModelAttribute("project") KanbanProject project,
                                                      @PathVariable("projectName") String projectName,
                                                      @PathVariable("board") String boardType,
                                                      @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains(name)) {
                String[] phases = temp.split(",|=");

                for (int i = 0; i < phases.length; i++) {

                    if (phases[i].equals(name)) {
                        newContent += "Pre - " + name + ",";
                    if (phases[i].contains(".")) {
                        newContent += phases[i] + "=";
                    }
                    else {
                        newContent += phases[i] + ",";
                    }

                }
                newContent += "\n";

            }
            else if (temp.contains("boards.wall")) {
                //FOr when cancel is hit on the add column button
                if (name.equals("null")) {
                    newContent += temp + "\n";
                }
                else {
                    temp += "," + name;
                    newContent += temp + "\n";
                }
            }

            else {
                newContent += temp + "\n";
            }

        }
        if (newContent.length() < 10) {
            newContent = orig;
        }

        kanbanService.editProject(projectName, newContent);

        return new RedirectView(
            "/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/delete-column-action")
    public synchronized RedirectView deleteColumn(@ModelAttribute("project") KanbanProject project,
                                                  @PathVariable("projectName") String projectName,
                                                  @PathVariable("board") String boardType,
                                                  @RequestParam("name") String name) throws IOException {

        String orig = kanbanService
            .getProjectConfiguration(projectName).getKanbanPropertiesFile()
            .getContentAsString();

        Scanner sc = new Scanner(orig);
        String temp = "";
        String newContent = "";

        while (sc.hasNext() && name != null && name != "") {

            temp = sc.nextLine();
            if (temp.contains(name)) {
                String[] phases = temp.split(",|=");

                for (int i = 0; i <= phases.length - 1; i++) {

                    if (phases[i].equals(name)) {
                        //DO NOTHING
                    }
                    else {
                        if (i == 0) {
                            newContent += phases[i] + "=";
                        }
                        else if (i == phases.length - 1) {
                            newContent += phases[i];
                        }
                        else if (i == phases.length - 2 && name.equals(phases[phases.length - 1])) {
                            newContent += phases[i];
                        }
                        else {

                            newContent += phases[i] + ",";
                        }
                    }
                }
                newContent += "\n";
            }
            else {
                newContent += temp + "\n";
            }
        }

        if (newContent.length() < 10) {
            newContent = orig;
        }

        kanbanService.editProject(projectName, newContent);

        return new RedirectView("/projects/" + projectName + "/" + boardType, true);
    }

    @RequestMapping("{board}/set-work-stream")
    public RedirectView setWorkStream(@ModelAttribute("project") KanbanProject project,
                                      @PathVariable("projectName") String projectName,
                                      @PathVariable("board") String boardType,
                                      @RequestParam("workStream") String selectedWorkStream,
                                      @RequestParam(value = "chartName", required = false) String chartName,
                                      @RequestParam(value = "workItemTypeName", required = false) String workItemTypeName,
                                      @ModelAttribute("workStreams") Map workStreams) {

        workStreams.put(projectName, selectedWorkStream);

        String params = "";

        if (!StringUtils.isEmpty(chartName) && !StringUtils.isEmpty(workItemTypeName)) {
            params = "?chartName=" + chartName + "&workItemTypeName=" + workItemTypeName;
        }

        return new RedirectView("/projects/" + projectName + "/" + boardType + params, true);
    }

    public void setKanbanService(KanbanService kanbanService) {
        this.kanbanService = kanbanService;
    }

    private boolean parseBoolean(String excludedStr) {
        if (excludedStr == null) {
            return false;
        }
        if ("on".equals(excludedStr)) {
            return true;
        }
        return Boolean.parseBoolean(excludedStr);
    }

    private int parseInteger(String sizeStr, int defaultValue) {
        try {
            return Integer.parseInt(sizeStr);
        } catch (NumberFormatException nfe) {
        }
        return defaultValue;
    }

    @RequestMapping(value = "comment", method = RequestMethod.POST)
    public ResponseEntity addComment(@ModelAttribute("project") KanbanProject project,
            @RequestParam int id,
            @RequestParam String userName,
            @RequestParam String comment) throws IOException {

        WorkItemComment workItemComment = new WorkItemComment(userName, comment);
        WorkItem workItem = project.getWorkItemById(id);
        workItem.addComment(workItemComment);

        project.save();

        String body = gson.toJson(workItemComment);
        ResponseEntity response = new ResponseEntity(body, HttpStatus.OK);
        return response;
    }

    @RequestMapping("")
    public RedirectView redirectToWall(@PathVariable("projectName") String projectName) {
        return new RedirectView("/projects/" + projectName + "/wall", true);
    }
}
File
KanbanBoardController.java
Developer's decision
Manual
Kind of conflict
Annotation
Class declaration
Comment
Import
Package declaration