LoggingSprintsSimulationListener.java

package de.slothsoft.sprintsim.simulation;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;

import de.slothsoft.sprintsim.Member;
import de.slothsoft.sprintsim.Task;
import de.slothsoft.sprintsim.execution.SprintRetro;
import de.slothsoft.sprintsim.generation.SprintPlanning;
import de.slothsoft.sprintsim.io.ComponentWriter;
import de.slothsoft.sprintsim.io.ComponentWriter.TableInfo;
import de.slothsoft.sprintsim.io.TaskWriter;
import de.slothsoft.sprintsim.io.TextComponentWriter;

/**
 * This {@link SimulationListener} logs the information from multiple sprints in a nice
 * little table. If you have only one sprint consider using
 * {@link LoggingOneSprintSimulationListener}.
 */

class LoggingSprintsSimulationListener implements SimulationListener {

	private Member[] members;
	private ComponentWriter componentWriter = new TextComponentWriter(System.out::println);
	private Function<Task, String> taskNameSupplier = TaskWriter.DEFAULT_TASK_NAME_SUPPLIER;
	private boolean printTasksOverview = true;

	private SprintPlanning lastSprintPlanning;

	@Override
	public void simulationStarted(SimulationInfo simulationInfo) {
		this.componentWriter.writeTitle(Messages.getString("TeamMembersTitle")); //$NON-NLS-1$

		this.members = simulationInfo.getMembers();
		for (final Member member : this.members) {
			this.componentWriter.writeLine(MessageFormat.format(Messages.getString("TeamMemberPattern"), //$NON-NLS-1$
					member.getUserData(LoggingSimulationListener.MEMBER_DATA_NAME), member.getWorkPerformance(),
					String.valueOf(member.getWorkHoursPerDay())));
		}
		this.componentWriter.writeEmpty();

		this.componentWriter.writeTitle(Messages.getString("SprintOverviewTitle")); //$NON-NLS-1$

		this.componentWriter.startTable(new TableInfo().columnRatios(2, 2, 2, 2));
		this.componentWriter.writeTableHeader(Messages.getString("EstimatedHours"), //$NON-NLS-1$
				Messages.getString("EstimatedAdditionalHours"), //$NON-NLS-1$
				Messages.getString("RemainingHours"), //$NON-NLS-1$
				Messages.getString("AdditionalNecessaryHours")); //$NON-NLS-1$
	}

	@Override
	public void sprintPlanned(SprintPlanning sprintPlanning) {
		this.lastSprintPlanning = sprintPlanning;
	}

	@Override
	public void sprintExecuted(SprintRetro sprintRetro) {
		this.componentWriter.writeTableLine(Double.valueOf(this.lastSprintPlanning.getEstimatedHours()),
				Double.valueOf(this.lastSprintPlanning.getEstimatedAdditionalHours()),
				Double.valueOf(sprintRetro.getRemainingHours()),
				Double.valueOf(sprintRetro.getNecessaryAdditionalHours()));
	}

	@Override
	public void simulationFinished(SimulationResult simulationResult) {
		this.componentWriter.writeTableSeparatorLine(4);
		this.componentWriter.writeTableLine(
				Double.valueOf(calculateAverage(simulationResult.getPlannings(), SprintPlanning::getEstimatedHours)),
				Double.valueOf(
						calculateAverage(simulationResult.getPlannings(), SprintPlanning::getEstimatedAdditionalHours)),
				Double.valueOf(calculateAverage(simulationResult.getRetros(), SprintRetro::getRemainingHours)),
				Double.valueOf(
						calculateAverage(simulationResult.getRetros(), SprintRetro::getNecessaryAdditionalHours)));
		this.componentWriter.endTable();

		if (this.printTasksOverview) {
			this.componentWriter.writeEmpty();

			this.componentWriter.writeTitle(Messages.getString("TaskOverviewTitle")); //$NON-NLS-1$

			final TaskWriter taskWriter = new TaskWriter(this.componentWriter);
			taskWriter.setMemberNameSupplier(
					index -> (String) this.members[index].getUserData(LoggingSimulationListener.MEMBER_DATA_NAME));
			taskWriter.setTaskNameSupplier(this.taskNameSupplier);
			taskWriter.writeExecutionInfo(true).setWriteEstimationInfo(true);
			taskWriter.writeTasks(
					Arrays.stream(simulationResult.retros).flatMap(r -> Arrays.stream(r.getSprint().getTasks()))
							.sorted(Comparator.comparing(this.taskNameSupplier)).toArray(Task[]::new));
		}
	}

	private static <T> double calculateAverage(T[] values, ToDoubleFunction<T> valueGetter) {
		return Arrays.stream(values).mapToDouble(valueGetter).average().getAsDouble();
	}

	public Function<Task, String> getTaskNameSupplier() {
		return this.taskNameSupplier;
	}

	public LoggingSprintsSimulationListener taskNameSupplier(Function<Task, String> newTaskNameSupplier) {
		setTaskNameSupplier(newTaskNameSupplier);
		return this;
	}

	public void setTaskNameSupplier(Function<Task, String> taskNameSupplier) {
		this.taskNameSupplier = Objects.requireNonNull(taskNameSupplier);
	}

	public boolean isPrintTasksOverview() {
		return this.printTasksOverview;
	}

	public LoggingSprintsSimulationListener printTasksOverview(boolean newPrintTasksOverview) {
		setPrintTasksOverview(newPrintTasksOverview);
		return this;
	}

	public void setPrintTasksOverview(boolean printTasksOverview) {
		this.printTasksOverview = printTasksOverview;
	}

	public ComponentWriter getComponentWriter() {
		return this.componentWriter;
	}

	public LoggingSprintsSimulationListener componentWriter(ComponentWriter newComponentWriter) {
		setComponentWriter(newComponentWriter);
		return this;
	}

	public void setComponentWriter(ComponentWriter componentWriter) {
		this.componentWriter = Objects.requireNonNull(componentWriter);
	}

}