com.aurel.track.report.dashboard.StatusOverTimeGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.aurel.track.report.dashboard.StatusOverTimeGraph.java

Source

/**
 * Genji Scrum Tool and Issue Tracker
 * Copyright (C) 2015 Steinbeis GmbH & Co. KG Task Management Solutions
    
 * <a href="http://www.trackplus.com">Genji Scrum Tool</a>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/* $Id:$ */

package com.aurel.track.report.dashboard;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.aurel.track.Constants;
import com.aurel.track.admin.customize.category.filter.FilterBL;
import com.aurel.track.admin.customize.category.filter.QNode;
import com.aurel.track.admin.customize.category.filter.TreeFilterFacade;
import com.aurel.track.admin.customize.category.filter.execute.TreeFilterExecuterFacade;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.FilterUpperConfigUtil;
import com.aurel.track.admin.customize.category.filter.execute.loadItems.TooManyItemsToLoadException;
import com.aurel.track.admin.customize.category.filter.tree.design.FilterUpperFromQNodeTransformer;
import com.aurel.track.admin.customize.category.filter.tree.design.FilterUpperTO;
import com.aurel.track.admin.customize.category.filter.tree.design.TreeFilterLoaderBL;
import com.aurel.track.admin.customize.lists.systemOption.IssueTypeBL;
import com.aurel.track.admin.customize.lists.systemOption.StatusBL;
import com.aurel.track.admin.customize.projectType.ProjectTypesBL;
import com.aurel.track.beans.ILabelBean;
import com.aurel.track.beans.TListTypeBean;
import com.aurel.track.beans.TPersonBean;
import com.aurel.track.beans.TQueryRepositoryBean;
import com.aurel.track.beans.TStateBean;
import com.aurel.track.beans.TWorkItemBean;
import com.aurel.track.beans.TQueryRepositoryBean.QUERY_PURPOSE;
import com.aurel.track.fieldType.constants.SystemFields;
import com.aurel.track.fieldType.runtime.base.LookupContainer;
import com.aurel.track.item.history.HistorySelectValues;
import com.aurel.track.item.history.HistoryTransactionBL;
import com.aurel.track.json.JSONUtility;
import com.aurel.track.plugin.DashboardDescriptor;
import com.aurel.track.report.dashboard.DataSourceDashboardBL.CONFIGURATION_PARAMETERS;
import com.aurel.track.report.datasource.statusOverTime.StatusOverTimeDatasource;
import com.aurel.track.resources.LocalizeUtil;
import com.aurel.track.util.CalendarUtil;
import com.aurel.track.util.DateTimeUtils;
import com.aurel.track.util.GeneralUtils;
import com.aurel.track.util.IntegerStringBean;
import com.aurel.track.util.StringArrayParameterUtils;
import com.opensymphony.xwork2.config.Configuration;

/**
 * Status over time graph
 * @author Tamas
 *
 */
public class StatusOverTimeGraph extends TimePeriodDashboardView {
    private static final Logger LOGGER = LogManager.getLogger(StatusOverTimeGraph.class);

    /**
     * When no status is needed (new workItems in a period) then the complete map structure is still built
     * in order to not duplicate the code
     */
    public static Integer ENTITY_PLACEHOLDER = Integer.valueOf(0);

    //Configuration page constants
    public static interface CONFIGURATION_PARAMETERS {

        static String STATUSES = "statuses";
        static String SELECTED_STATUS = "selectedStatus";
        static String SELECTED_STATUS_SECOND = "selectedStatusSecond";
        static String SELECTED_STATUS_THIRD = "selectedStatusThird";
        static String TIME_INTERVALS = "timeIntervals";
        static String SELECTED_TIME_INTERVAL = "selectedTimeInterval";
        static String CALCULATION_MODES = "calculationModes";
        static String SELECTED_CALCULATION_MODE = "selectedCalculationMode";
        static String DISABLE_STATUS = "disableStatus";
        static String FILLED = "filled";
        static String Y_AXE = "yAxe";
        static String SELECTED_CHART_TYPE = "selectedChartType";
        static String GROUPING = "grouping";
        static String GROUP_FIRST_NAME = "groupFirstName";
        static String GROUP_SECOND_NAME = "groupSecondName";
        static String GROUP_THIRD_NAME = "groupThirdName";
        static String ISSUE_TYPE = "issueType";
        static String SELECTED_ISSUE_TYPE = "selectedIssueType";
    }

    protected int DEFAULT_HEIGHT = 250;
    protected int MIN_HEIGHT = 100;
    protected int MAX_HEIGHT = 900;

    private static interface SESSION_PARAMETERS {
        static String PROVIDER_LINK = "providerLink";
        static String PROVIDER_PARAMS = "providerParams";
    }

    public static interface PERIOD_TYPE {
        static int FROM_TO = 1;
        static int DAYS_BEFORE = 2;
    }

    public static interface TIME_INTERVAL {
        static int DAY = 1;
        static int WEEK = 2;
        static int MONTH = 3;
    }

    public static interface CHART_TYPE {
        static int LINE = 1;
        static int STACKED = 2;
    }

    public static interface ISSUE_TYPE {
        static int GENERAL = 1;
        static int DOCUMENT = 2;
    }

    public static interface CALCULATION_MODE {
        static int NEW = 1; //no status considered
        static int ACTUAL_SAMPLE = 2; //counted if the selected status is the last status at the end if time interval
        static int ACTUAL_ACTIVITY = 3; //counted if the selected status was selected at any time in the time interval
        static int ACCUMULATED_ACTIVITY = 4; //ACTUAL_ACTIVITY accumulated
    }

    protected boolean isUseConfig() {
        return true;
    }

    @Override
    protected String encodeJSONExtraData(Integer dashboardID, Map<String, Object> session,
            Map<String, String> parameters, Integer projectID, Integer releaseID, Map<String, String> ajaxParams)
            throws TooManyItemsToLoadException {
        TPersonBean user = (TPersonBean) session.get(Constants.USER_KEY);
        Locale locale = user.getLocale();

        DashboardDescriptor dashboardDescriptor = getDescriptor();
        String bundleName = dashboardDescriptor.getBundleName();

        StringBuilder sb = new StringBuilder();
        String sessionPrefix = "dashboard" + "." + dashboardID;

        //the name where the ImageProvider implementation is stored in the session
        String theProvider = sessionPrefix;
        //the name where configParameters are stored
        String theParams = sessionPrefix + "Params";

        //to avoid caching if IE
        Long actualTime = new Long(new Date().getTime());

        Integer selectedCalculationMode = parseInteger(parameters,
                CONFIGURATION_PARAMETERS.SELECTED_CALCULATION_MODE, CALCULATION_MODE.NEW);
        //for ftl
        String userEnteredTitle = parameters.get("title");
        if (userEnteredTitle == null || "".equals(userEnteredTitle.trim())) {
            //title="statusOverTime.title." + getCalculationModeSuffix(selectedCalculationMode));
        }

        //      int width=BasePluginDashboardBL.parseIntegerValue(parameters, CONFIGURATION_PARAMETERS.X_AXE, DEFAULT_WIDTH);
        int height = parseInteger(parameters, CONFIGURATION_PARAMETERS.Y_AXE, DEFAULT_HEIGHT);

        Map<String, Object> configParameters = new HashMap<String, Object>();
        Iterator<String> it = parameters.keySet().iterator();
        while (it.hasNext()) {
            String key = it.next();
            configParameters.put(key, parameters.get(key));
        }
        //used in preparing the locale specific chart
        configParameters.put("locale", locale);
        configParameters.put("personBean", user);
        configParameters.put("projectID", projectID);
        configParameters.put("releaseID", releaseID);
        configParameters.put(SESSION_PARAMETERS.PROVIDER_LINK, theProvider);
        configParameters.put(SESSION_PARAMETERS.PROVIDER_PARAMS, theParams);

        //to avoid caching if IE
        configParameters.put("actualTime", new Long(new Date().getTime()));

        //      configParameters.put("width",width);
        configParameters.put("height", height);

        //save the theProvider ImageProvider and the configParameters map into the session
        //they will be used in the .ftl to call the corresponding image provider action
        session.put(theProvider, this);
        session.put(theParams, configParameters);

        String url = "imageServerAction!loadJSON.action?imageProviderKey=" + theProvider + "&imageProviderParams="
                + theParams + "&date=" + actualTime;

        JSONUtility.appendStringValue(sb, "url", url);
        //      JSONUtility.appendIntegerValue(sb, "width",width);
        JSONUtility.appendIntegerValue(sb, "height", height);
        if ((configParameters.get("dateTo") != null && configParameters.get("dateTo") != null
                && !configParameters.get("dateTo").equals("null")
                && !configParameters.get("dateFrom").equals("null"))
                || (configParameters.get("daysBefore") != null)) {
            JSONUtility.appendBooleanValue(sb, "empty", false);
            String JSONResponse = generateJSONResponse(configParameters);
            JSONUtility.appendJSONValue(sb, "chartData", JSONResponse);

            if (configParameters.get("daysBefore") != null) {
                SimpleDateFormat dateFormat;
                dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                Calendar calendar = Calendar.getInstance();
                int daysBefore = BasePluginDashboardBL.parseIntegerValue(configParameters,
                        TIMEPERIOD_PARAMETERS.DAYS_BEFORE, 0);
                Date dateTo = calendar.getTime();
                Date dateFrom = calendar.getTime();
                if (daysBefore != 0) {
                    calendar.add(Calendar.DATE, -daysBefore);
                    dateFrom = calendar.getTime();
                }
                JSONUtility.appendStringValue(sb, "dateTo", dateFormat.format(dateTo).toString());
                JSONUtility.appendStringValue(sb, "dateFrom", dateFormat.format(dateFrom).toString());
            } else {
                JSONUtility.appendStringValue(sb, "dateTo", configParameters.get("dateTo").toString());
                JSONUtility.appendStringValue(sb, "dateFrom", configParameters.get("dateFrom").toString());
            }

            String xAxesLabel = "";
            if (configParameters.get("selectedTimeInterval").toString().equals("2")) {
                xAxesLabel = LocalizeUtil.getLocalizedText("statusOverTime.xAxesLabelMonthly", locale, bundleName);
            } else {
                xAxesLabel = LocalizeUtil.getLocalizedText("statusOverTime.xAxesLabel", locale, bundleName);
            }
            String yAxesLabel = LocalizeUtil.getLocalizedText(
                    "statusOverTime.yAxesLabel." + getCalculationModeSuffix(selectedCalculationMode), locale,
                    bundleName);

            JSONUtility.appendStringValue(sb, "xAxesLabel", xAxesLabel);
            JSONUtility.appendStringValue(sb, "yAxesLabel", yAxesLabel);
            JSONUtility.appendStringValue(sb, "selectedTimeInterval",
                    configParameters.get("selectedTimeInterval").toString());
            JSONUtility.appendStringValue(sb, "locale", locale.toString());

            if (configParameters.get("selectedStatus") != null) {
                JSONUtility.appendStringValue(sb, "selectedStatus",
                        configParameters.get("selectedStatus").toString());
            }

            if (configParameters.get("selectedStatusSecond") != null) {
                JSONUtility.appendStringValue(sb, "selectedStatusSecond",
                        configParameters.get("selectedStatusSecond").toString());
            }

            if (configParameters.get("selectedStatusThird") != null) {
                JSONUtility.appendStringValue(sb, "selectedStatusThird",
                        configParameters.get("selectedStatusThird").toString());
            }
            JSONUtility.appendILabelBeanList(sb, CONFIGURATION_PARAMETERS.STATUSES, StatusBL.loadAll(locale));
            LOGGER.debug(("Status over time config parameters: " + configParameters.toString()));
            LOGGER.debug(("Status over time  JSON Response: " + JSONResponse));
        } else {
            JSONUtility.appendBooleanValue(sb, "empty", true);
            LOGGER.debug("Status over time chart not configured yet!");
        }
        JSONUtility.appendIntegerValue(sb, CONFIGURATION_PARAMETERS.SELECTED_CHART_TYPE,
                parseInteger(parameters, CONFIGURATION_PARAMETERS.SELECTED_CHART_TYPE, CHART_TYPE.LINE));

        if (parameters.get(CONFIGURATION_PARAMETERS.FILLED) != null) {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.FILLED, true);
        } else {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.FILLED, false);
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUPING) != null) {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.GROUPING, true);
        } else {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.GROUPING, false);
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUP_FIRST_NAME) != null) {
            JSONUtility.appendStringValue(sb, CONFIGURATION_PARAMETERS.GROUP_FIRST_NAME,
                    configParameters.get(CONFIGURATION_PARAMETERS.GROUP_FIRST_NAME).toString());
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUP_SECOND_NAME) != null) {
            JSONUtility.appendStringValue(sb, CONFIGURATION_PARAMETERS.GROUP_SECOND_NAME,
                    configParameters.get(CONFIGURATION_PARAMETERS.GROUP_SECOND_NAME).toString());
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUP_THIRD_NAME) != null) {
            JSONUtility.appendStringValue(sb, CONFIGURATION_PARAMETERS.GROUP_THIRD_NAME,
                    configParameters.get(CONFIGURATION_PARAMETERS.GROUP_THIRD_NAME).toString());
        }

        return sb.toString();
    }

    /*fill config windows items */
    @Override
    public String encodeJSONExtraDataConfig(Map<String, String> parameters, TPersonBean user, Integer entityId,
            Integer entityType) {
        StringBuilder sb = new StringBuilder();

        Locale locale = user.getLocale();
        //DateTimeUtils dateTimeUtils = DateTimeUtils.getInstance();
        DashboardDescriptor dashboardDescriptor = getDescriptor();
        sb.append(getDatasourceConfig(parameters, entityId, entityType, locale));
        //DataSourceDashboardBL.appendJSONExtraDataConfig_DataSource(sb,dashboardDescriptor,parameters,user,entityId,entityType);
        sb.append(getTimePeriodConfig(parameters, locale));

        JSONUtility.appendILabelBeanList(sb, CONFIGURATION_PARAMETERS.STATUSES, StatusBL.loadAll(locale));

        JSONUtility.appendStringList(sb, CONFIGURATION_PARAMETERS.SELECTED_STATUS,
                StringArrayParameterUtils.splitSelection(parameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS)));

        JSONUtility.appendStringList(sb, CONFIGURATION_PARAMETERS.SELECTED_STATUS_SECOND, StringArrayParameterUtils
                .splitSelection(parameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS_SECOND)));

        JSONUtility.appendStringList(sb, CONFIGURATION_PARAMETERS.SELECTED_STATUS_THIRD, StringArrayParameterUtils
                .splitSelection(parameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS_THIRD)));

        JSONUtility.appendIntegerStringBeanList(sb, CONFIGURATION_PARAMETERS.TIME_INTERVALS,
                possibleTimeIntervals(locale));

        int selectedTimeInterval = parseInteger(parameters, CONFIGURATION_PARAMETERS.SELECTED_TIME_INTERVAL,
                TIME_INTERVAL.MONTH);
        JSONUtility.appendIntegerValue(sb, CONFIGURATION_PARAMETERS.SELECTED_TIME_INTERVAL, selectedTimeInterval);

        JSONUtility.appendIntegerStringBeanList(sb, CONFIGURATION_PARAMETERS.CALCULATION_MODES,
                possibleCalculationModes(locale));

        int selectedCalculationMode = parseInteger(parameters, CONFIGURATION_PARAMETERS.SELECTED_CALCULATION_MODE,
                CALCULATION_MODE.NEW);
        JSONUtility.appendIntegerValue(sb, CONFIGURATION_PARAMETERS.SELECTED_CALCULATION_MODE,
                new Integer(selectedCalculationMode));

        JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.DISABLE_STATUS,
                new Boolean(selectedCalculationMode == CALCULATION_MODE.NEW));

        JSONUtility.appendIntegerValue(sb, CONFIGURATION_PARAMETERS.Y_AXE,
                parseInteger(parameters, CONFIGURATION_PARAMETERS.Y_AXE, DEFAULT_HEIGHT));

        int chartType = parseInteger(parameters, CONFIGURATION_PARAMETERS.SELECTED_CHART_TYPE, CHART_TYPE.LINE);
        JSONUtility.appendIntegerValue(sb, CONFIGURATION_PARAMETERS.SELECTED_CHART_TYPE,
                parseInteger(parameters, CONFIGURATION_PARAMETERS.SELECTED_CHART_TYPE, CHART_TYPE.LINE));

        JSONUtility.appendIntegerValue(sb, CONFIGURATION_PARAMETERS.SELECTED_ISSUE_TYPE,
                parseInteger(parameters, CONFIGURATION_PARAMETERS.ISSUE_TYPE, ISSUE_TYPE.GENERAL));
        boolean userHasWiki = false;
        if (user.getLicensedFeaturesMap().get("wiki") != null) {
            userHasWiki = user.getLicensedFeaturesMap().get("wiki");
        }
        JSONUtility.appendBooleanValue(sb, "userHasWiki", userHasWiki);

        if (parameters.get(CONFIGURATION_PARAMETERS.FILLED) != null) {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.FILLED, true);
        } else {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.FILLED, false);
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUPING) != null) {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.GROUPING, true);
        } else {
            JSONUtility.appendBooleanValue(sb, CONFIGURATION_PARAMETERS.GROUPING, false);
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUP_FIRST_NAME) != null) {
            JSONUtility.appendStringValue(sb, CONFIGURATION_PARAMETERS.GROUP_FIRST_NAME,
                    parameters.get(CONFIGURATION_PARAMETERS.GROUP_FIRST_NAME));
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUP_SECOND_NAME) != null) {
            JSONUtility.appendStringValue(sb, CONFIGURATION_PARAMETERS.GROUP_SECOND_NAME,
                    parameters.get(CONFIGURATION_PARAMETERS.GROUP_SECOND_NAME));
        }

        if (parameters.get(CONFIGURATION_PARAMETERS.GROUP_THIRD_NAME) != null) {
            JSONUtility.appendStringValue(sb, CONFIGURATION_PARAMETERS.GROUP_THIRD_NAME,
                    parameters.get(CONFIGURATION_PARAMETERS.GROUP_THIRD_NAME));
        }

        int prefWidth = 880;
        int prefHeight = 665;
        JSONUtility.appendIntegerValue(sb, "prefWidth", prefWidth);
        JSONUtility.appendIntegerValue(sb, "prefHeight", prefHeight);
        return sb.toString();
    }

    /**
     * Creates a JSON String, consisting of series data.
     * @throws TooManyItemsToLoadException
     */
    public String generateJSONResponse(Map configParameters) throws TooManyItemsToLoadException {
        Locale locale = (Locale) configParameters.get("locale");
        TPersonBean personBean = (TPersonBean) configParameters.get("personBean");

        String statuses = "";
        if (configParameters.get(CONFIGURATION_PARAMETERS.GROUPING) != null) {
            statuses = (String) configParameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS) + ','
                    + (String) configParameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS_SECOND) + ","
                    + (String) configParameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS_THIRD.toString());
            if (statuses.indexOf("null") != -1) {
                statuses = null;
            }
        } else {
            statuses = (String) configParameters.get(CONFIGURATION_PARAMETERS.SELECTED_STATUS);
        }

        List<Integer> statusIDs;
        statusIDs = splitSelectedEntities(statuses);

        if (statusIDs.isEmpty()) {
            statusIDs = GeneralUtils.createListFromIntArr(GeneralUtils.getBeanIDs(StatusBL.loadAll()));
        }
        Date dateFrom = null;
        Date dateTo = null;
        Calendar calendar = Calendar.getInstance();
        if (BasePluginDashboardBL.parseIntegerValue(configParameters, TIMEPERIOD_PARAMETERS.SELECTED_PERIOD_TYPE,
                PERIOD_TYPE.FROM_TO) == PERIOD_TYPE.FROM_TO) {
            String strDateFrom = (String) configParameters.get(TIMEPERIOD_PARAMETERS.DATE_FROM);
            String strDateTo = (String) configParameters.get(TIMEPERIOD_PARAMETERS.DATE_TO);
            dateFrom = DateTimeUtils.getInstance().parseISODate(strDateFrom);
            dateTo = DateTimeUtils.getInstance().parseISODate(strDateTo);
        } else {
            int daysBefore = BasePluginDashboardBL.parseIntegerValue(configParameters,
                    TIMEPERIOD_PARAMETERS.DAYS_BEFORE, 0);
            dateTo = calendar.getTime();
            if (daysBefore != 0) {
                calendar.add(Calendar.DATE, -daysBefore);
                dateFrom = calendar.getTime();
            }
        }
        if (dateFrom != null) {
            calendar.setTime(dateFrom);
            CalendarUtil.clearTime(calendar);
            //the beginning of fromDate day
            dateFrom = calendar.getTime();
        }
        if (dateTo != null) {
            calendar.setTime(dateTo);
            CalendarUtil.clearTime(calendar);
            calendar.add(Calendar.DATE, 1);
            //the end of toDate day (actually the beginning of the day after toDate)
            dateTo = calendar.getTime();
        }

        int selectedTimeInterval = parseInteger(configParameters, CONFIGURATION_PARAMETERS.SELECTED_TIME_INTERVAL,
                TIME_INTERVAL.MONTH);
        int selectedCalculationMode = parseInteger(configParameters,
                CONFIGURATION_PARAMETERS.SELECTED_CALCULATION_MODE, CALCULATION_MODE.NEW);

        SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToEntityIDToNumbersMap = new TreeMap<Integer, SortedMap<Integer, Map<Integer, Integer>>>();
        Map<Integer, TStateBean> statusMap = null;

        configParameters.put(DataSourceDashboardBL.INCLUDE_CLOSED, Boolean.toString(true));
        int issueType = parseInteger(configParameters, CONFIGURATION_PARAMETERS.ISSUE_TYPE, ISSUE_TYPE.GENERAL);
        boolean loadOnlyDocuments;

        if (issueType == ISSUE_TYPE.GENERAL) {
            loadOnlyDocuments = false;
        } else {
            loadOnlyDocuments = true;
        }
        List<TWorkItemBean> workItemBeans = this.getWorkItemBeans(configParameters, personBean, locale,
                loadOnlyDocuments);//DataSourceDashboardBL.getGeneralOrDocumentWorkItems(projectOrReleaseIDs, datasourceType, personBean, locale, loadOnlyDocuments, isRelease, isQuery);
        String value;
        List<TStateBean> statusList;
        Set<String> statusOrder;
        switch (selectedCalculationMode) {
        case CALCULATION_MODE.NEW:
            yearToPeriodToEntityIDToNumbersMap = calculateNewWorkItems(workItemBeans, dateFrom, dateTo,
                    selectedTimeInterval);
            value = generateAxesValues(yearToPeriodToEntityIDToNumbersMap, null, selectedTimeInterval, false, null);
            return value;
        case CALCULATION_MODE.ACTUAL_SAMPLE:
            yearToPeriodToEntityIDToNumbersMap = calculateTotalInStatus(getWorkItemIDs(workItemBeans), dateFrom,
                    dateTo, statusIDs, selectedTimeInterval, locale);
            statusList = LocalizeUtil.localizeDropDownList(StatusBL.loadAll(), locale);
            statusMap = GeneralUtils.createMapFromList(statusList);
            statusOrder = createStatusOrder(statusList);
            value = generateAxesValues(yearToPeriodToEntityIDToNumbersMap, statusMap, selectedTimeInterval, true,
                    statusOrder);
            return value;
        case CALCULATION_MODE.ACTUAL_ACTIVITY:
            yearToPeriodToEntityIDToNumbersMap = calculateStatus(getWorkItemIDs(workItemBeans), dateFrom, dateTo,
                    statusIDs, selectedTimeInterval, locale);
            statusList = LocalizeUtil.localizeDropDownList(StatusBL.loadAll(), locale);
            statusMap = GeneralUtils.createMapFromList(statusList);
            statusOrder = createStatusOrder(statusList);
            value = generateAxesValues(yearToPeriodToEntityIDToNumbersMap, statusMap, selectedTimeInterval, false,
                    statusOrder);
            return value;
        case CALCULATION_MODE.ACCUMULATED_ACTIVITY:
            yearToPeriodToEntityIDToNumbersMap = calculateStatus(getWorkItemIDs(workItemBeans), dateFrom, dateTo,
                    statusIDs, selectedTimeInterval, locale);
            statusList = LocalizeUtil.localizeDropDownList(StatusBL.loadAll(), locale);
            statusMap = GeneralUtils.createMapFromList(statusList);
            statusOrder = createStatusOrder(statusList);
            value = generateAxesValues(yearToPeriodToEntityIDToNumbersMap, statusMap, selectedTimeInterval, true,
                    statusOrder);
            return value;
        }
        return null;
    }

    private List<TWorkItemBean> getWorkItemBeans(Map configParameters, TPersonBean personBean, Locale locale,
            boolean loadOnlyDocuments) {
        boolean isRelease = true;
        boolean isQuery;
        int datasourceType = BasePluginDashboardBL.parseIntegerValue(configParameters,
                DataSourceDashboardBL.CONFIGURATION_PARAMETERS.SELECTED_DATASOURCE_TYPE,
                DataSourceDashboardBL.DATASOURCE_TYPE.PROJECT_RELEASE);
        ArrayList<Integer> projectOrReleaseIDs = new ArrayList<Integer>();
        Integer selectedProjectOrRelease = null;

        if (configParameters.get("projectID") != null || configParameters.get("releaseID") != null) {
            isQuery = false;
            if (configParameters.get("releaseID") != null) {
                isRelease = true;
                selectedProjectOrRelease = Integer.valueOf(configParameters.get("releaseID").toString());
            } else {
                selectedProjectOrRelease = Integer.valueOf(configParameters.get("projectID").toString());
                isRelease = false;
            }
            datasourceType = DATASOURCE_TYPE.PROJECT_RELEASE;
            projectOrReleaseIDs.add(selectedProjectOrRelease);
        } else {
            selectedProjectOrRelease = parseInteger(configParameters, "selectedProjectOrRelease", 0);
            if (datasourceType == DATASOURCE_TYPE.PROJECT_RELEASE) {
                isQuery = false;
                if (selectedProjectOrRelease < 0) {
                    isRelease = false;
                    selectedProjectOrRelease = selectedProjectOrRelease * (-1);
                }
                projectOrReleaseIDs.add(selectedProjectOrRelease);
            } else {
                Integer selectedQueryID = parseInteger(configParameters, "selectedQueryID", 0);
                isQuery = true;
                projectOrReleaseIDs.add(selectedQueryID);
            }
        }

        List<TWorkItemBean> workItems = new ArrayList<TWorkItemBean>();
        try {
            workItems = DataSourceDashboardBL.getGeneralOrDocumentWorkItems(projectOrReleaseIDs, datasourceType,
                    personBean, locale, loadOnlyDocuments, isRelease, isQuery);
        } catch (Exception ex) {
            LOGGER.error("Error while getting workitems: " + ExceptionUtils.getStackTrace(ex));
        }
        return workItems;
    }

    private static Set<String> createStatusOrder(List<TStateBean> statList) {
        Set<String> statusOrder = new LinkedHashSet<String>();
        for (TStateBean aBean : statList) {
            statusOrder.add(aBean.getLabel().toString());
        }
        return statusOrder;
    }

    public static int[] getWorkItemIDs(List workItemBeans) {
        return GeneralUtils.createIntArrFromIntegerArr(GeneralUtils.getBeanIDs(workItemBeans));
    }

    /**
     * Computes the hierarchical data for new issues
        
     * @return
     */
    public static SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> calculateNewWorkItems(
            List<TWorkItemBean> workItemBeans, Date dateFrom, Date dateTo, int selectedTimeInterval) {
        SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToProjectIDToWorkItemNumbersMap = new TreeMap<Integer, SortedMap<Integer, Map<Integer, Integer>>>();

        if (workItemBeans == null || workItemBeans.isEmpty()) {
            LOGGER.debug("No workItems in datasource");
            return yearToPeriodToProjectIDToWorkItemNumbersMap;
        }
        SortedMap<Integer, SortedMap<Integer, List<TWorkItemBean>>> periodNewWorkItems = getNewWorkItemsMap(
                workItemBeans, selectedTimeInterval, dateFrom, dateTo);
        List entityList = new ArrayList();
        //for new WorkItems we have a single entity (a single graphic), hardcoded with Integer(0)
        //Integer hardCodedentityID = Integer.valueOf(0);
        entityList.add(ENTITY_PLACEHOLDER);

        Iterator<Integer> yearIterator = periodNewWorkItems.keySet().iterator();
        while (yearIterator.hasNext()) {
            Integer year = (Integer) yearIterator.next();
            SortedMap<Integer, List<TWorkItemBean>> intervalToStatusChangeBeans = periodNewWorkItems.get(year);
            Iterator<Integer> periodIterator = intervalToStatusChangeBeans.keySet().iterator();
            while (periodIterator.hasNext()) {
                Integer period = (Integer) periodIterator.next();
                List<TWorkItemBean> workItemBeansForInterval = intervalToStatusChangeBeans.get(period);
                if (workItemBeansForInterval != null) {
                    Iterator<TWorkItemBean> workItemBeansIterator = workItemBeansForInterval.iterator();
                    while (workItemBeansIterator.hasNext()) {
                        TWorkItemBean workItemBean = (TWorkItemBean) workItemBeansIterator.next();
                        if (workItemBean != null) {
                            setCount(yearToPeriodToProjectIDToWorkItemNumbersMap, year, period, ENTITY_PLACEHOLDER,
                                    1);
                        }
                    }
                }
            }
        }
        addZerosForEmptyIntervals(dateFrom, dateTo, selectedTimeInterval,
                yearToPeriodToProjectIDToWorkItemNumbersMap, entityList);
        //addTimeSeries(timeSeriesCollection, yearToPeriodToProjectIDToWorkItemNumbersMap, null, selectedTimeInterval, accumulated);
        return yearToPeriodToProjectIDToWorkItemNumbersMap;
    }

    /**
     * Computes the hierarchical data for status changes
     *
     * @return
     */
    public static SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> calculateTotalInStatus(
            int[] workItemIDs, Date dateFrom, Date dateTo, List<Integer> statusIDs, int selectedTimeInterval,
            Locale locale) {
        SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToStatusIDToStatusNumbersMap = new TreeMap<Integer, SortedMap<Integer, Map<Integer, Integer>>>();

        if (statusIDs != null && statusIDs.isEmpty()) {
            LOGGER.warn("No status specified");
            return yearToPeriodToStatusIDToStatusNumbersMap;
        }
        Set<Integer> statusIDsSet = GeneralUtils.createIntegerSetFromIntegerList(statusIDs);
        if (workItemIDs == null || workItemIDs.length == 0) {
            // LOGGER.warn("No issues satisfy the filtering condition (read right revoked, project/release deleted?)");
            return yearToPeriodToStatusIDToStatusNumbersMap;
        }

        Map<Integer, Integer> statusForWorkItems = new HashMap<Integer, Integer>();
        List<HistorySelectValues> historySelectValuesBefore = null;
        if (dateFrom != null) {
            //get all status changes till the beginning of the reporting period
            //include all statuses (not just the selected ones)
            //because we are interested only in the status at the end of each period
            historySelectValuesBefore = HistoryTransactionBL.getByWorkItemsFieldNewValuesDates(workItemIDs,
                    SystemFields.INTEGER_STATE, null, null, dateFrom);
        }
        //get all status changes for the reporting period
        //include all statuses (not just the selected ones)
        //because we are interested only in the status at the end of each period
        List<HistorySelectValues> historySelectValuesReportingPeriod = HistoryTransactionBL
                .getByWorkItemsFieldNewValuesDates(workItemIDs, SystemFields.INTEGER_STATE, null, dateFrom, dateTo);
        SortedMap<Integer, SortedMap<Integer, List<HistorySelectValues>>> periodStatusChangesReportingPeriod = getStatusChangesMap(
                historySelectValuesReportingPeriod, selectedTimeInterval, true/*, statusIDs*/);

        Integer year = null;
        Integer period = null;
        Iterator yearIterator;

        //calculate the values for the beginning of the first reporting period
        if (historySelectValuesBefore != null) {
            //get the first year and period
            if (dateFrom != null) {
                //explicit dateFrom specified by user
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(dateFrom);
                year = Integer.valueOf(calendar.get(Calendar.YEAR));
                int calendarInterval = getCalendarInterval(selectedTimeInterval);
                period = Integer.valueOf(calendar.get(calendarInterval));
            } else {
                //no explicit dateFrom specified by the user, get the first found entry in the history
                yearIterator = periodStatusChangesReportingPeriod.keySet().iterator();
                if (yearIterator.hasNext()) {
                    year = (Integer) yearIterator.next();
                    SortedMap<Integer, List<HistorySelectValues>> intervalToStatusChangeBeans = periodStatusChangesReportingPeriod
                            .get(year);
                    Iterator<Integer> periodIterator = intervalToStatusChangeBeans.keySet().iterator();
                    period = periodIterator.next();
                }
            }

            if (year == null || period == null) {
                //nothing found
                return yearToPeriodToStatusIDToStatusNumbersMap;
            }

            Iterator<HistorySelectValues> iterator = historySelectValuesBefore.iterator();
            while (iterator.hasNext()) {
                //count the workItems in status till the beginning of the reporting period
                HistorySelectValues historySelectValues = iterator.next();
                Integer workItemID = historySelectValues.getWorkItemID();
                Integer statusID = historySelectValues.getNewValue();
                if (statusForWorkItems.get(workItemID) == null) {
                    //take into account only the last stateChange for the workItem
                    statusForWorkItems.put(workItemID, statusID);
                    if (statusIDsSet.contains(statusID)) {
                        //count only if selected status
                        setCount(yearToPeriodToStatusIDToStatusNumbersMap, year, period, statusID, 1);
                    }
                }
            }
        }
        yearIterator = periodStatusChangesReportingPeriod.keySet().iterator();
        while (yearIterator.hasNext()) {
            year = (Integer) yearIterator.next();
            SortedMap intervalToStatusChangeBeans = periodStatusChangesReportingPeriod.get(year);
            Iterator<Integer> periodIterator = intervalToStatusChangeBeans.keySet().iterator();
            while (periodIterator.hasNext()) {
                period = periodIterator.next();
                List statusChangeBeansForInterval = (List) intervalToStatusChangeBeans.get(period);
                if (statusChangeBeansForInterval != null) {
                    Iterator statusChangeBeansIterator = statusChangeBeansForInterval.iterator();
                    while (statusChangeBeansIterator.hasNext()) {
                        HistorySelectValues historySelectValues = (HistorySelectValues) statusChangeBeansIterator
                                .next();
                        Integer workItemID = historySelectValues.getWorkItemID();
                        Integer nextStatusID = historySelectValues.getNewValue();
                        Integer previousStatus = statusForWorkItems.get(workItemID);
                        if (previousStatus == null) {
                            //probably the item was created in the actual period
                            statusForWorkItems.put(workItemID, nextStatusID);
                            if (statusIDsSet.contains(nextStatusID)) {
                                setCount(yearToPeriodToStatusIDToStatusNumbersMap, year, period, nextStatusID, 1);
                            }
                        } else {
                            if (!previousStatus.equals(nextStatusID)) {
                                statusForWorkItems.put(workItemID, nextStatusID);
                                //add as new status
                                if (statusIDsSet.contains(nextStatusID)) {
                                    setCount(yearToPeriodToStatusIDToStatusNumbersMap, year, period, nextStatusID,
                                            1);
                                }
                                //decrement the count for the previous status
                                if (statusIDsSet.contains(previousStatus)) {
                                    setCount(yearToPeriodToStatusIDToStatusNumbersMap, year, period, previousStatus,
                                            -1);
                                }
                            }
                        }
                    }
                }
            }
        }
        addZerosForEmptyIntervals(dateFrom, dateTo, selectedTimeInterval, yearToPeriodToStatusIDToStatusNumbersMap,
                statusIDs);
        //addTimeSeries(timeSeriesCollection, yearToPeriodToStatusIDToStatusNumbersMap, statusMap, selectedTimeInterval, true);
        return yearToPeriodToStatusIDToStatusNumbersMap;
    }

    /**
     * Computes the hierarchical data for status changes
     * @return
     */
    public static SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> calculateStatus(int[] workItemIDs,
            Date dateFrom, Date dateTo, List<Integer> statusIDs, int selectedTimeInterval, Locale locale) {
        SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToStatusIDToStatusNumbersMap = new TreeMap<Integer, SortedMap<Integer, Map<Integer, Integer>>>();

        if (statusIDs != null && statusIDs.isEmpty()) {
            LOGGER.debug("No status specified");
            return yearToPeriodToStatusIDToStatusNumbersMap;
        }
        if (workItemIDs == null || workItemIDs.length == 0) {
            // LOGGER.warn("No issues satisfy the filtering condition (read right revoked, project/release deleted?)");
            return yearToPeriodToStatusIDToStatusNumbersMap;
        }
        List<HistorySelectValues> historySelectValuesList = HistoryTransactionBL.getByWorkItemsFieldNewValuesDates(
                workItemIDs, SystemFields.INTEGER_STATE, statusIDs, dateFrom, dateTo);
        SortedMap<Integer, SortedMap<Integer, List<HistorySelectValues>>> periodStatusChanges = getStatusChangesMap(
                historySelectValuesList, selectedTimeInterval, false/*, statusIDs*/);
        Iterator<Integer> yearIterator = periodStatusChanges.keySet().iterator();
        while (yearIterator.hasNext()) {
            Integer year = yearIterator.next();
            SortedMap<Integer, List<HistorySelectValues>> intervalToStatusChangeBeans = periodStatusChanges
                    .get(year);
            Iterator<Integer> periodIterator = intervalToStatusChangeBeans.keySet().iterator();
            while (periodIterator.hasNext()) {
                Integer period = periodIterator.next();
                List<HistorySelectValues> statusChangeBeansForInterval = intervalToStatusChangeBeans.get(period);
                if (statusChangeBeansForInterval != null) {
                    Iterator statusChangeBeansIterator = statusChangeBeansForInterval.iterator();
                    while (statusChangeBeansIterator.hasNext()) {
                        HistorySelectValues stateChangeBean = (HistorySelectValues) statusChangeBeansIterator
                                .next();
                        Integer statusID = stateChangeBean.getNewValue();
                        setCount(yearToPeriodToStatusIDToStatusNumbersMap, year, period, statusID, 1);
                    }
                }
            }
        }
        addZerosForEmptyIntervals(dateFrom, dateTo, selectedTimeInterval, yearToPeriodToStatusIDToStatusNumbersMap,
                statusIDs);
        //addTimeSeries(timeSeriesCollection, yearToPeriodToStatusIDToStatusNumbersMap, statusMap, selectedTimeInterval, accumulated);
        return yearToPeriodToStatusIDToStatusNumbersMap;
    }

    /**
     * Set 0 values for the empty time intervals
     * @param dateFrom
     * @param dateTo
     * @param selectedTimeInterval
     * @param yearToPeriodToEntityNumbersMap
     * @param entityIDs
     */
    private static void addZerosForEmptyIntervals(Date dateFrom, Date dateTo, int selectedTimeInterval,
            SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToEntityNumbersMap,
            List entityIDs) {
        if (entityIDs == null || entityIDs.isEmpty()) {
            return;
        }
        int calendarInterval = getCalendarInterval(selectedTimeInterval);
        Calendar calendarFrom = StatusOverTimeDatasource.calculateDateFrom(dateFrom,
                (SortedMap) yearToPeriodToEntityNumbersMap, calendarInterval);
        if (calendarFrom == null) {
            return;
        }
        Calendar calendarTo = StatusOverTimeDatasource.calculateDateTo(dateTo,
                (SortedMap) yearToPeriodToEntityNumbersMap, calendarInterval);
        if (calendarTo == null) {
            return;
        }
        //previous-next issue: to avoid adding the first week of the new year as the first week of the old year,
        //because it can be that the year is the old one but the last days of the year belong to the first week of the next year
        //and that would add an entry with the first week of the old year
        int previousYearValue = calendarFrom.get(Calendar.YEAR);
        int nextYearValue = previousYearValue;
        int previousCalendarIntervalValue = calendarFrom.get(calendarInterval);
        int nextCalendarIntervalValue = previousCalendarIntervalValue;
        boolean jumpYearFirstTime = false;
        if (selectedTimeInterval == TIME_INTERVAL.WEEK && calendarFrom.get(Calendar.MONTH) == 11
                && nextCalendarIntervalValue == 1) {
            jumpYearFirstTime = true;
        }
        while (calendarFrom.before(calendarTo)) {
            if ((nextCalendarIntervalValue < previousCalendarIntervalValue && nextYearValue == previousYearValue)
                    || jumpYearFirstTime) {
                nextYearValue += 1;
            }
            jumpYearFirstTime = false;
            addZerosForInterval(new Integer(nextYearValue), new Integer(nextCalendarIntervalValue),
                    yearToPeriodToEntityNumbersMap, entityIDs);
            previousYearValue = nextYearValue;
            previousCalendarIntervalValue = nextCalendarIntervalValue;
            calendarFrom.add(calendarInterval, 1);
            nextYearValue = calendarFrom.get(Calendar.YEAR);
            nextCalendarIntervalValue = calendarFrom.get(calendarInterval);
        }
    }

    /**
     * Set 0 value for a time interval
     * @param year
     * @param periode
     * @param yearToPeriodToEntityNumbersMap
     * @param entityIDs
     */
    private static void addZerosForInterval(Integer year, Integer periode, SortedMap yearToPeriodToEntityNumbersMap,
            List entityIDs) {
        Iterator iterator = entityIDs.iterator();
        while (iterator.hasNext()) {
            Integer entityID = (Integer) iterator.next();
            setCount(yearToPeriodToEntityNumbersMap, year, periode, entityID, 0);
        }
    }

    /**
     * Add the time series to the timeSeriesCollection
     * SortedMap at first and second level (year and period)
     * (Sorted because the accumulated should be calculated in the right order)
     */
    private static String generateAxesValues(
            SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToEntityIDToWorkItemNumbersMap,
            Map entityMap, int selectedTimeInterval, boolean accumulated, Set<String> stateNameOrder) {

        Map timeSeriesMap = new HashMap();
        Map accumulatedMap = new HashMap();
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        Map<String, String> valuesMap;
        Iterator yearIterator = yearToPeriodToEntityIDToWorkItemNumbersMap.keySet().iterator();
        while (yearIterator.hasNext()) {
            Integer year = (Integer) yearIterator.next();
            SortedMap<Integer, Map<Integer, Integer>> intervalToStatusChangeBeans = yearToPeriodToEntityIDToWorkItemNumbersMap
                    .get(year);
            Iterator periodIterator = intervalToStatusChangeBeans.keySet().iterator();
            while (periodIterator.hasNext()) {
                Integer period = (Integer) periodIterator.next();
                Map<Integer, Integer> entityIDToWorkItemNumbersMap = intervalToStatusChangeBeans.get(period);
                if (entityIDToWorkItemNumbersMap != null) {
                    Iterator entityIDIterator = entityIDToWorkItemNumbersMap.keySet().iterator();
                    sb.append("{");
                    valuesMap = new HashMap<String, String>();
                    JSONUtility.appendStringValue(sb, "date", createRegularTimePeriodForJSON(period.intValue(),
                            year.intValue(), selectedTimeInterval));
                    while (entityIDIterator.hasNext()) {
                        Integer entityID = (Integer) entityIDIterator.next();
                        Integer numberOfStates = entityIDToWorkItemNumbersMap.get(entityID);
                        if (numberOfStates != null) {
                            String sb1 = (String) timeSeriesMap.get(entityID);
                            Integer accumuletedValueForEntity = (Integer) accumulatedMap.get(entityID);
                            String label = "";
                            if (sb1 == null) {
                                ILabelBean iLabelBean = null;
                                if (entityMap != null) {
                                    iLabelBean = (ILabelBean) entityMap.get(entityID);
                                }

                                if (iLabelBean != null) {
                                    label = iLabelBean.getLabel();
                                }
                                String axeValue = new String();
                                timeSeriesMap.put(entityID, axeValue);
                                accumulatedMap.put(entityID, Integer.valueOf(0));
                                accumuletedValueForEntity = (Integer) accumulatedMap.get(entityID);
                            }

                            Integer timeSeriesValue;
                            if (accumulated) {
                                accumulatedMap.put(entityID, Integer
                                        .valueOf(accumuletedValueForEntity.intValue() + numberOfStates.intValue()));
                                timeSeriesValue = (Integer) accumulatedMap.get(entityID);
                            } else {
                                timeSeriesValue = numberOfStates;
                            }
                            String status = "";
                            ILabelBean iLabelBeanJSON = null;
                            if (entityMap != null) {
                                iLabelBeanJSON = (ILabelBean) entityMap.get(entityID);
                            }
                            if (iLabelBeanJSON != null) {
                                status = iLabelBeanJSON.getLabel();
                            }
                            if (status.equals("")) {
                                status = "opened";
                            }
                            //                            JSONUtility.appendStringValue(sb, status, timeSeriesValue.toString(), true);
                            //                            sb.append(",");
                            valuesMap.put(status, timeSeriesValue.toString());
                            timeSeriesMap.put(entityID, sb.toString());
                        }
                    }

                    if (entityMap != null) {
                        for (String stateName : stateNameOrder) {
                            if (valuesMap.get(stateName) != null) {
                                JSONUtility.appendStringValue(sb, stateName, valuesMap.get(stateName), true);
                                sb.append(",");
                            }
                        }
                    } else {
                        if (valuesMap.get("opened") != null) {
                            JSONUtility.appendStringValue(sb, "opened", valuesMap.get("opened"), true);
                            sb.append(",");
                        }
                    }
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append("},");
                }
            }
        }
        sb.deleteCharAt(sb.length() - 1);
        if (sb.length() != 0) {
            sb.append("]");
        } else {
            sb.append("[{}]");
        }
        return sb.toString();
    }

    /**
    * Increment the value in the corresponding map
    * @param yearToPeriodToEntityIDToEntityCountMap
    * @param year
    * @param period
    * @param entityID
    */
    private static void setCount(
            SortedMap<Integer, SortedMap<Integer, Map<Integer, Integer>>> yearToPeriodToEntityIDToEntityCountMap,
            Integer year, Integer period, Integer entityID, int value) {
        if (yearToPeriodToEntityIDToEntityCountMap == null || year == null || period == null || entityID == null) {
            return;
        }
        SortedMap<Integer, Map<Integer, Integer>> periodToEntityIDToWorkItemNumbersMap = yearToPeriodToEntityIDToEntityCountMap
                .get(year);
        if (periodToEntityIDToWorkItemNumbersMap == null) {
            yearToPeriodToEntityIDToEntityCountMap.put(year, new TreeMap());
            periodToEntityIDToWorkItemNumbersMap = yearToPeriodToEntityIDToEntityCountMap.get(year);
        }
        Map<Integer, Integer> entityIDToWorkItemNumbersMap = periodToEntityIDToWorkItemNumbersMap.get(period);
        if (entityIDToWorkItemNumbersMap == null) {
            periodToEntityIDToWorkItemNumbersMap.put(period, new HashMap());
            entityIDToWorkItemNumbersMap = periodToEntityIDToWorkItemNumbersMap.get(period);
        }
        Integer previousValue = entityIDToWorkItemNumbersMap.get(entityID);
        if (previousValue == null) {
            entityIDToWorkItemNumbersMap.put(entityID, new Integer(value));
        } else {
            entityIDToWorkItemNumbersMap.put(entityID, new Integer(previousValue.intValue() + value));
        }
    }

    /**
    * Create a map of hierarchical data with TStateChangeBeans for the workItems in the periods
    * -   key: year
    * -   value: map
    *          -   key: period
    *          -   Set of HistorySelectValues
    * @param statusChangeList
    * @param timeInterval
    * @param sample sample (only last from the period) or action (all statuses during a period)
    * @return
    */
    private static SortedMap<Integer, SortedMap<Integer, List<HistorySelectValues>>> getStatusChangesMap(
            List<HistorySelectValues> statusChangeList, int timeInterval, boolean sample) {
        SortedMap<Integer, SortedMap<Integer, List<HistorySelectValues>>> yearToIntervalToStatusChangeBeans = new TreeMap<Integer, SortedMap<Integer, List<HistorySelectValues>>>();
        Map yearToIntervalToWorkItemIDs = new HashMap();
        int yearValue;
        int intervalValue;
        Integer workItemID;
        if (statusChangeList != null && !statusChangeList.isEmpty()) {
            Calendar calendarLastModified = Calendar.getInstance();
            //get them in the reverse order (the later first)
            Iterator iterator = statusChangeList.iterator();
            int calendarInterval = getCalendarInterval(timeInterval);
            Set workItemsIDsForInterval = new HashSet();
            while (iterator.hasNext()) {
                HistorySelectValues stateChangeBean = (HistorySelectValues) iterator.next();
                workItemID = stateChangeBean.getWorkItemID();
                calendarLastModified.setTime(stateChangeBean.getLastEdit());
                yearValue = calendarLastModified.get(Calendar.YEAR);
                intervalValue = calendarLastModified.get(calendarInterval);
                if (Calendar.WEEK_OF_YEAR == calendarInterval) {
                    //avoid adding the first week of the new year as the first week of the old year,
                    //because it can be that the year is the old one but the last days of the year belong to the first week of the next year
                    //and that would add an entry with the first week of the old year
                    int monthValue = calendarLastModified.get(Calendar.MONTH);
                    if (monthValue >= 11 && intervalValue == 1) {
                        yearValue = yearValue + 1;
                    }
                }
                if (sample) {
                    Map intervalToWorkItems = (Map) yearToIntervalToWorkItemIDs.get(new Integer(yearValue));
                    if (intervalToWorkItems == null) {
                        yearToIntervalToWorkItemIDs.put(new Integer(yearValue), new HashMap());
                        intervalToWorkItems = (Map) yearToIntervalToWorkItemIDs.get(new Integer(yearValue));
                    }
                    workItemsIDsForInterval = (Set) intervalToWorkItems.get(new Integer(intervalValue));
                    if (workItemsIDsForInterval == null) {
                        intervalToWorkItems.put(new Integer(intervalValue), new HashSet());
                        workItemsIDsForInterval = (Set) intervalToWorkItems.get(new Integer(intervalValue));
                    }
                }
                //add the stateChangeBean if:
                //1. if not sample add all stateChangeBeans
                //2. if sample: add only one (the latest i.e. the first one from the list) for each period
                if (!sample || !workItemsIDsForInterval.contains(workItemID)) {
                    if (sample) {
                        //add workItemID to forbid adding the same workItemID again for the same period
                        workItemsIDsForInterval.add(workItemID);
                        //the last state change bean for the period for the workItem is
                        //a change to a status which is not selected to be shown, so simply neglect it
                        /*if (!statusIDsSet.contains(stateChangeBean.getNewValue())) {
                           continue;
                        }*/
                    }
                    //workItemIDsSet.add(workItemID);
                    SortedMap<Integer, List<HistorySelectValues>> intervalToStatusChangeBeans = yearToIntervalToStatusChangeBeans
                            .get(new Integer(yearValue));
                    if (intervalToStatusChangeBeans == null) {
                        yearToIntervalToStatusChangeBeans.put(new Integer(yearValue),
                                new TreeMap<Integer, List<HistorySelectValues>>());
                        intervalToStatusChangeBeans = yearToIntervalToStatusChangeBeans.get(new Integer(yearValue));
                    }
                    List<HistorySelectValues> statusChangeBeansForInterval = intervalToStatusChangeBeans
                            .get(new Integer(intervalValue));
                    if (statusChangeBeansForInterval == null) {
                        intervalToStatusChangeBeans.put(new Integer(intervalValue), new ArrayList());
                        statusChangeBeansForInterval = intervalToStatusChangeBeans.get(new Integer(intervalValue));
                    }
                    statusChangeBeansForInterval.add(stateChangeBean);
                }
            }
        }
        return yearToIntervalToStatusChangeBeans;
    }

    /**
    * Create a map of hierarchical data with TWorkItemBeans in the periods
    * -   key: year
    * -   value: map
    *          -   key: period
    *          -   Set of TStateChangeBeans, one for each workItem
    * @param timeInterval
    * @return
    */
    private static SortedMap<Integer, SortedMap<Integer, List<TWorkItemBean>>> getNewWorkItemsMap(List workItemList,
            int timeInterval, Date dateFrom, Date dateTo) {
        SortedMap<Integer, SortedMap<Integer, List<TWorkItemBean>>> yearToIntervalToWorkItemBeans = new TreeMap();
        int yearValue;
        int intervalValue;
        if (workItemList != null) {
            Calendar calendarCreated = Calendar.getInstance();
            Iterator iterator = workItemList.iterator();
            int calendarInterval = getCalendarInterval(timeInterval);
            while (iterator.hasNext()) {
                TWorkItemBean workItemBean = (TWorkItemBean) iterator.next();
                Date createDate = workItemBean.getCreated();
                if (createDate == null) {
                    continue;
                }
                if (dateFrom != null && dateFrom.after(createDate) || dateTo != null && dateTo.before(createDate)) {
                    continue;
                }
                calendarCreated.setTime(workItemBean.getCreated());
                yearValue = calendarCreated.get(Calendar.YEAR);
                intervalValue = calendarCreated.get(calendarInterval);
                if (Calendar.WEEK_OF_YEAR == calendarInterval) {
                    //avoid adding the first week of the new year as the first week of the old year,
                    //because it can be that the year is the old one but the last days of the year belong to the first week of the next year
                    //and that would add an entry with the first week of the old year
                    int monthValue = calendarCreated.get(Calendar.MONTH);
                    if (monthValue >= 11 && intervalValue == 1) {
                        yearValue = yearValue + 1;
                    }
                }
                SortedMap<Integer, List<TWorkItemBean>> intervalToWorkItemBeans = yearToIntervalToWorkItemBeans
                        .get(new Integer(yearValue));
                if (intervalToWorkItemBeans == null) {
                    yearToIntervalToWorkItemBeans.put(new Integer(yearValue), new TreeMap());
                    intervalToWorkItemBeans = yearToIntervalToWorkItemBeans.get(new Integer(yearValue));
                }
                List<TWorkItemBean> workItemBeansForInterval = intervalToWorkItemBeans
                        .get(new Integer(intervalValue));
                if (workItemBeansForInterval == null) {
                    intervalToWorkItemBeans.put(new Integer(intervalValue), new ArrayList());
                    workItemBeansForInterval = intervalToWorkItemBeans.get(new Integer(intervalValue));
                }
                workItemBeansForInterval.add(workItemBean);
            }
        }
        return yearToIntervalToWorkItemBeans;
    }

    /**
     * Get the corresponding calendar constant
     * @param timeInterval
     * @return
     */
    private static int getCalendarInterval(int timeInterval) {
        switch (timeInterval) {
        case TIME_INTERVAL.DAY:
            return Calendar.DAY_OF_YEAR;
        case TIME_INTERVAL.WEEK:
            return Calendar.WEEK_OF_YEAR;
        default:
            return Calendar.MONTH;
        }
    }

    /*
      Transforming the period (into date) for the JSON response.
     */
    public static String createRegularTimePeriodForJSON(int period, int year, int timeInterval) {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat dateFormat;
        switch (timeInterval) {
        case TIME_INTERVAL.DAY:
            calendar.setLenient(true);
            calendar.clone();
            calendar.set(Calendar.YEAR, year);
            calendar.set(Calendar.DAY_OF_YEAR, period);
            dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            return dateFormat.format(calendar.getTime());
        case TIME_INTERVAL.WEEK:
            return period + "/" + year;
        default:
            period++;
            if (period < 10) {
                return year + "-0" + period;
            } else {
                return year + "-" + period;
            }
        }
    }

    /**
     * Transform the string with comma separated values
     * into a list of integers for further processing
     * @param entitySelection
     * @return
     */
    private static List<Integer> splitSelectedEntities(String entitySelection) {
        List<Integer> entityIDsList = new ArrayList<Integer>();
        if (entitySelection == null || "".equals(entitySelection)) {
            return entityIDsList;
        }
        String[] entityArr = entitySelection.split(",");
        if (entityArr == null || entityArr.length == 0) {
            return entityIDsList;
        }
        for (int i = 0; i < entityArr.length; ++i) {
            try {
                if (entityArr[i] != null) {
                    entityIDsList.add(Integer.valueOf(entityArr[i].trim()));
                }
            } catch (Exception e) {
                LOGGER.warn("The " + i + "'th component " + entityArr[i] + " can't be converted to Integer "
                        + e.getMessage(), e);
            }
        }
        return entityIDsList;
    }

    /**
     * The suffix for the title and Y-Axes label resource keys
     * @param selectedCalculationMode
     * @return
     */
    public static String getCalculationModeSuffix(int selectedCalculationMode) {
        switch (selectedCalculationMode) {
        case CALCULATION_MODE.NEW:
            return "new";
        case CALCULATION_MODE.ACTUAL_SAMPLE:
            return "actualSample";
        case CALCULATION_MODE.ACTUAL_ACTIVITY:
            return "actualActivity";
        case CALCULATION_MODE.ACCUMULATED_ACTIVITY:
            return "accumulatedActivity";
        }
        return null;
    }

    /**
     * Localized list of possible time intervals
     * @param locale
     * @return
     */
    private List<IntegerStringBean> possibleTimeIntervals(Locale locale) {
        List<IntegerStringBean> timeIntervals = new ArrayList<IntegerStringBean>();
        timeIntervals.add(new IntegerStringBean(localizeText("statusOverTime.timeInterval.day", locale),
                new Integer(TIME_INTERVAL.DAY)));
        timeIntervals.add(new IntegerStringBean(localizeText("statusOverTime.timeInterval.week", locale),
                new Integer(TIME_INTERVAL.WEEK)));
        timeIntervals.add(new IntegerStringBean(localizeText("statusOverTime.timeInterval.month", locale),
                new Integer(TIME_INTERVAL.MONTH)));
        return timeIntervals;
    }

    /**
     * Localized list of possible calculation modes
     * @param locale
     * @return
     */
    private List<IntegerStringBean> possibleCalculationModes(Locale locale) {
        List<IntegerStringBean> timeIntervals = new ArrayList<IntegerStringBean>();
        timeIntervals.add(new IntegerStringBean(
                localizeText("statusOverTime.calculationMode." + getCalculationModeSuffix(CALCULATION_MODE.NEW),
                        locale),
                new Integer(CALCULATION_MODE.NEW)));
        timeIntervals.add(new IntegerStringBean(
                localizeText("statusOverTime.calculationMode."
                        + getCalculationModeSuffix(CALCULATION_MODE.ACTUAL_SAMPLE), locale),
                new Integer(CALCULATION_MODE.ACTUAL_SAMPLE)));
        timeIntervals.add(new IntegerStringBean(
                localizeText("statusOverTime.calculationMode."
                        + getCalculationModeSuffix(CALCULATION_MODE.ACTUAL_ACTIVITY), locale),
                new Integer(CALCULATION_MODE.ACTUAL_ACTIVITY)));
        timeIntervals.add(new IntegerStringBean(
                localizeText("statusOverTime.calculationMode."
                        + getCalculationModeSuffix(CALCULATION_MODE.ACCUMULATED_ACTIVITY), locale),
                new Integer(CALCULATION_MODE.ACCUMULATED_ACTIVITY)));
        return timeIntervals;
    }

}