/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.montecarlo;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.libreplan.web.common.Util;
import org.libreplan.web.montecarlo.MonteCarloGraphController.IOnClose;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.zkoss.ganttz.util.LongOperationFeedback;
import org.zkoss.ganttz.util.LongOperationFeedback.IBackGroundOperation;
import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdate;
import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdatesEmitter;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Progressmeter;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Rows;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.api.Window;
/**
* Controller for MonteCarlo graphic generation
*
* @author Diego Pino Garcia <dpino@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MonteCarloController extends GenericForwardComposer {
@Autowired
private IMonteCarloModel monteCarloModel;
private final Integer DEFAULT_ITERATIONS = Integer.valueOf(10000);
private final Integer MAX_NUMBER_ITERATIONS = Integer.valueOf(100000);
private final RowRenderer gridCriticalPathTasksRender = new CriticalPathTasksRender();
private Grid gridCriticalPathTasks;
private Intbox ibIterations;
private Button btnRunMonteCarlo;
private Checkbox cbGroupByWeeks;
private Listbox lbCriticalPaths;
private Progressmeter progressMonteCarloCalculation;
private Window monteCarloChartWindow;
public MonteCarloController() {
}
@Override
public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception {
super.doAfterCompose(comp);
ibIterations.setValue(DEFAULT_ITERATIONS);
lbCriticalPaths.addEventListener(Events.ON_SELECT, new EventListener() {
@Override
public void onEvent(Event event) {
reloadGridCritialPathTasks();
}
});
btnRunMonteCarlo.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) {
validateRowsPercentages();
IBackGroundOperation<IDesktopUpdate> operation = new IBackGroundOperation<IDesktopUpdate>() {
@Override
public void doOperation(
IDesktopUpdatesEmitter<IDesktopUpdate> desktopUpdateEmitter) {
executeMontecarlo(desktopUpdateEmitter);
}
};
LongOperationFeedback.progressive(self.getDesktop(), operation);
}
private void executeMontecarlo(
IDesktopUpdatesEmitter<IDesktopUpdate> updatesEmitter) {
try {
updatesEmitter.doUpdate(disableButton(true));
int iterations = getIterations();
final Map<LocalDate, BigDecimal> monteCarloData = monteCarloModel
.calculateMonteCarlo(getSelectedCriticalPath(),
iterations,
percentageCompletedNotifier(updatesEmitter));
updatesEmitter.doUpdate(showCalculatedData(monteCarloData));
} finally {
updatesEmitter.doUpdate(disableButton(false));
}
}
private IDesktopUpdate disableButton(final boolean disable) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
btnRunMonteCarlo.setDisabled(disable);
}
};
}
private int getIterations() {
int iterations = ibIterations.getValue() != null ? ibIterations
.getValue().intValue() : 0;
if (iterations == 0) {
throw new WrongValueException(ibIterations,
_("cannot be empty"));
}
if (iterations < 0 || iterations > MAX_NUMBER_ITERATIONS) {
throw new WrongValueException(ibIterations,
_("Number of iterations should be between 1 and {0}",
MAX_NUMBER_ITERATIONS));
}
return iterations;
}
private void validateRowsPercentages() {
Intbox intbox;
int page = 0;
int counter = 0;
Rows rows = gridCriticalPathTasks.getRows();
for (Object each : rows.getChildren()) {
Row row = (Row) each;
List<org.zkoss.zk.ui.Component> children = row
.getChildren();
Integer sum = 0;
intbox = (Intbox) children.get(3);
sum += intbox.getValue();
intbox = (Intbox) children.get(5);
sum += intbox.getValue();
intbox = (Intbox) children.get(7);
sum += intbox.getValue();
if (sum != 100) {
gridCriticalPathTasks.setActivePage(page);
throw new WrongValueException(row,
_("Percentages should sum 100"));
}
counter++;
if (counter % gridCriticalPathTasks.getPageSize() == 0) {
page++;
}
}
}
private IDesktopUpdatesEmitter<Integer> percentageCompletedNotifier(
final IDesktopUpdatesEmitter<IDesktopUpdate> updatesEmitter) {
return new IDesktopUpdatesEmitter<Integer>() {
@Override
public void doUpdate(final Integer percentage) {
updatesEmitter
.doUpdate(showCompletedPercentage(percentage));
}
private IDesktopUpdate showCompletedPercentage(
final Integer value) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
progressMonteCarloCalculation.setValue(value);
}
};
}
};
}
private IDesktopUpdate showCalculatedData(
final Map<LocalDate, BigDecimal> monteCarloData) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
showMonteCarloGraph(monteCarloData);
}
};
}
private void showMonteCarloGraph(Map<LocalDate, BigDecimal> data) {
monteCarloChartWindow = createMonteCarloGraphWindow(data);
try {
monteCarloChartWindow.setMode("modal");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private Window createMonteCarloGraphWindow(
Map<LocalDate, BigDecimal> data) {
HashMap<String, Object> args = new HashMap<String, Object>();
args.put("monteCarloGraphController",
new MonteCarloGraphController());
Window result = (Window) Executions.createComponents(
"/planner/montecarlo_function.zul", self, args);
MonteCarloGraphController controller = (MonteCarloGraphController) result
.getVariable("monteCarloGraphController", true);
final String orderName = monteCarloModel.getOrderName();
final boolean groupByWeeks = cbGroupByWeeks.isChecked();
controller.generateMonteCarloGraph(orderName, data,
groupByWeeks, new IOnClose() {
@Override
public void montecarloGraphClosed() {
progressMonteCarloCalculation.setValue(0);
}
});
return result;
}
});
}
private void feedCriticalPathsList() {
lbCriticalPaths.setModel(new SimpleListModel(monteCarloModel
.getCriticalPathNames()));
if (!lbCriticalPaths.getChildren().isEmpty()) {
lbCriticalPaths.setSelectedIndex(0);
}
}
private void reloadGridCritialPathTasks() {
List<MonteCarloTask> selectedCriticalPath = getSelectedCriticalPath();
if (selectedCriticalPath != null) {
gridCriticalPathTasks.setModel(new SimpleListModel(
selectedCriticalPath));
}
if (gridCriticalPathTasks.getRowRenderer() == null) {
gridCriticalPathTasks.setRowRenderer(gridCriticalPathTasksRender);
gridCriticalPathTasks.renderAll();
}
}
public List<MonteCarloTask> getSelectedCriticalPath() {
Listitem selectedItem = lbCriticalPaths.getSelectedItem();
String selectedPath = selectedItem != null ? selectedItem.getLabel()
: null;
return monteCarloModel.getCriticalPath(selectedPath);
}
public void setCriticalPath(List criticalPath) {
monteCarloModel.setCriticalPath(criticalPath);
if (lbCriticalPaths != null) {
feedCriticalPathsList();
reloadGridCritialPathTasks();
}
btnRunMonteCarlo.setDisabled(monteCarloModel.getCriticalPathNames()
.isEmpty());
}
private static class CriticalPathTasksRender implements RowRenderer {
@Override
public void render(Row row, Object data) {
row.setValue(data);
MonteCarloTask task = (MonteCarloTask) data;
row.appendChild(taskName(task));
row.appendChild(duration(task));
row.appendChild(optimisticDuration(task));
row.appendChild(optimisticDurationPercentage(task));
row.appendChild(normalDuration(task));
row.appendChild(normalDurationPercentage(task));
row.appendChild(pessimisticDuration(task));
row.appendChild(pessimisticDurationPercentage(task));
}
private Label taskName(final MonteCarloTask task) {
return new Label(task.getTaskName());
}
private Label duration(final MonteCarloTask task) {
Double duration = Double.valueOf(task.getDuration().doubleValue());
return new Label(duration.toString());
}
private Decimalbox pessimisticDuration(final MonteCarloTask task) {
Decimalbox result = new Decimalbox();
Util.bind(result, new Util.Getter<BigDecimal>() {
@Override
public BigDecimal get() {
return task.getPessimisticDuration();
}
}, new Util.Setter<BigDecimal>() {
@Override
public void set(BigDecimal value) {
task.setPessimisticDuration(value);
}
});
return result;
}
private Intbox pessimisticDurationPercentage(
final MonteCarloTask task) {
Intbox result = new Intbox();
Util.bind(result, new Util.Getter<Integer>() {
@Override
public Integer get() {
return task.getPessimisticDurationPercentage();
}
}, new Util.Setter<Integer>() {
@Override
public void set(Integer value) {
task.setPessimisticDurationPercentage(value);
}
});
return result;
}
private Decimalbox normalDuration(final MonteCarloTask task) {
Decimalbox result = new Decimalbox();
Util.bind(result, new Util.Getter<BigDecimal>() {
@Override
public BigDecimal get() {
return task.getNormalDuration();
}
}, new Util.Setter<BigDecimal>() {
@Override
public void set(BigDecimal value) {
task.setNormalDuration(value);
}
});
return result;
}
private Intbox normalDurationPercentage(final MonteCarloTask task) {
Intbox result = new Intbox();
Util.bind(result, new Util.Getter<Integer>() {
@Override
public Integer get() {
return task.getNormalDurationPercentage();
}
}, new Util.Setter<Integer>() {
@Override
public void set(Integer value) {
task.setNormalDurationPercentage(value);
}
});
return result;
}
private Decimalbox optimisticDuration(final MonteCarloTask task) {
Decimalbox result = new Decimalbox();
Util.bind(result, new Util.Getter<BigDecimal>() {
@Override
public BigDecimal get() {
return task.getOptimisticDuration();
}
}, new Util.Setter<BigDecimal>() {
@Override
public void set(BigDecimal value) {
task.setOptimisticDuration(value);
}
});
return result;
}
private Intbox optimisticDurationPercentage(final MonteCarloTask task) {
Intbox result = new Intbox();
Util.bind(result, new Util.Getter<Integer>() {
@Override
public Integer get() {
return task.getOptimisticDurationPercentage();
}
}, new Util.Setter<Integer>() {
@Override
public void set(Integer value) {
task.setOptimisticDurationPercentage(value);
}
});
return result;
}
}
}