TimelineSimulationListener.java

package de.slothsoft.sprintsim.simulation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import de.slothsoft.sprintsim.Member;
import de.slothsoft.sprintsim.Task;
import de.slothsoft.sprintsim.execution.SprintExecutor;
import de.slothsoft.sprintsim.execution.SprintRetro;
import de.slothsoft.sprintsim.generation.SprintPlanning;
import de.slothsoft.sprintsim.simulation.TimelineEvent.Type;

/**
 * A {@link TimelineListener} that breaks the events of the sprints down so they happen
 * chronologically.
 */

class TimelineSimulationListener implements SimulationListener {

	static final int NO_DAY = -1;
	static final double NO_HOUR = Double.NaN;

	static final int START_DAY = 1;
	static final double START_HOUR = 0.0;

	private final List<TimelineListener> timelineListeners = new ArrayList<>();

	@Override
	public void simulationStarted(SimulationInfo simulationInfo) {
		fireListeners(new TimelineEvent(this, Type.SIMULATION_STARTED, NO_DAY, NO_HOUR, simulationInfo));
	}

	@Override
	public void sprintPlanned(SprintPlanning sprintPlanning) {
		fireListeners(new TimelineEvent(this, Type.SPRINT_STARTED, START_DAY, START_HOUR, sprintPlanning));
	}

	@Override
	public void sprintExecuted(SprintRetro sprintRetro) {
		final TaskAndTime lastTaskAndTime = fireTimelineEvents(sprintRetro.getMembers(),
				sprintRetro.getSprint().getTasks());
		fireListeners(
				new TimelineEvent(this, Type.SPRINT_FINISHED, lastTaskAndTime.day, lastTaskAndTime.hour, sprintRetro));
	}

	TaskAndTime fireTimelineEvents(Member[] members, Task[] tasks) {
		final List<Member> membersAsList = Arrays.asList(members);

		@SuppressWarnings("unchecked")
		final List<TaskAndTime>[] tasksByMember = new List[members.length];
		for (int i = 0; i < members.length; i++) {
			tasksByMember[i] = new ArrayList<>();
		}

		// sort the tasks by member and sum their times

		for (int taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
			final Task task = tasks[taskIndex];

			final Member member = (Member) task.getUserData(SprintExecutor.TASK_DATA_ASSIGNEE);
			final int membersIndex = membersAsList.indexOf(member);

			if (membersIndex < 0) throw new IllegalArgumentException("Could not find member " + member);

			final int taskIndexByMember = tasksByMember[membersIndex].size();

			final TaskAndTime taskAndTime;
			if (taskIndexByMember == 0) {
				// first task of that member
				taskAndTime = new TaskAndTime(task);
			} else {
				// follow up task
				taskAndTime = tasksByMember[membersIndex].get(taskIndexByMember - 1).createNext(task);
			}
			tasksByMember[membersIndex].add(taskAndTime);
		}

		// now add the last task of each member

		for (int i = 0; i < tasksByMember.length; i++) {
			final TaskAndTime lastTaskAndTime = tasksByMember[i].stream().max(Comparator.naturalOrder()).get();
			final TaskAndTime finishedTaskAndTime = lastTaskAndTime.createNext(null);
			tasksByMember[i].add(finishedTaskAndTime);
		}

		// sort the tasks by their time and fire events in correct order

		Arrays.stream(tasksByMember).flatMap(List::stream).sorted().forEach(taskAndTime -> {
			if (taskAndTime.previousTask != null) {
				fireListeners(new TimelineEvent(this, Type.TASK_FINISHED, taskAndTime.day, taskAndTime.hour,
						taskAndTime.previousTask));
			}
			if (taskAndTime.task != null) {
				fireListeners(new TimelineEvent(this, Type.TASK_STARTED, taskAndTime.day, taskAndTime.hour,
						taskAndTime.task));
			}
		});

		return Arrays.stream(tasksByMember).flatMap(List::stream).max(Comparator.naturalOrder()).get();
	}

	@Override
	public void simulationFinished(SimulationResult simulationResult) {
		fireListeners(new TimelineEvent(this, Type.SIMULATION_FINISHED, NO_DAY, NO_HOUR, simulationResult));

	}

	protected void fireListeners(TimelineEvent event) {
		for (final TimelineListener timelineListener : this.timelineListeners
				.toArray(new TimelineListener[this.timelineListeners.size()])) {
			timelineListener.eventHappened(event);
		}
	}

	public void addTimelineListener(TimelineListener timelineListener) {
		this.timelineListeners.add(timelineListener);
	}

	public void removeTimelineListener(TimelineListener timelineListener) {
		this.timelineListeners.remove(timelineListener);
	}

	/*
	 *
	 */

	static class TaskAndTime implements Comparable<TaskAndTime> {

		final Task task;
		int day;
		double hour;
		Task previousTask;

		public TaskAndTime(Task task) {
			this(task, START_DAY, START_HOUR, null);
		}

		private TaskAndTime(Task task, int day, double hour, Task previousTask) {
			this.task = task;
			this.day = day;
			this.hour = hour;
			this.previousTask = previousTask;
		}

		public TaskAndTime createNext(Task nextTask) {
			final Member member = (Member) this.task.getUserData(SprintExecutor.TASK_DATA_ASSIGNEE);
			final double taskTime = ((Double) this.task.getUserData(SprintExecutor.TASK_DATA_NECESSARY_HOURS))
					.doubleValue();

			int newDay = this.day;
			double newHour = this.hour + taskTime;

			while (newHour >= member.getWorkHoursPerDay()) {
				newHour -= member.getWorkHoursPerDay();
				newDay++;
			}
			return new TaskAndTime(nextTask, newDay, newHour, this.task);
		}

		@Override
		public int compareTo(TaskAndTime that) {
			final int compare = Integer.compare(this.day, that.day);
			if (compare != 0) return compare;
			return Double.compare(this.hour, that.hour);
		}

	}
}