/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.examples.taskassigning.swingui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.Timer;
import org.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.taskassigning.domain.Customer;
import org.optaplanner.examples.taskassigning.domain.Priority;
import org.optaplanner.examples.taskassigning.domain.Task;
import org.optaplanner.examples.taskassigning.domain.TaskAssigningSolution;
import org.optaplanner.examples.taskassigning.domain.TaskType;
import static org.optaplanner.examples.taskassigning.persistence.TaskAssigningGenerator.*;
public class TaskAssigningPanel extends SolutionPanel<TaskAssigningSolution> {
public static final String LOGO_PATH = "/org/optaplanner/examples/taskassigning/swingui/taskAssigningLogo.png";
private final TaskOverviewPanel taskOverviewPanel;
private JSpinner consumeRateField;
private AbstractAction consumeAction;
private Timer consumeTimer;
private JSpinner produceRateField;
private AbstractAction produceAction;
private Timer produceTimer;
private int consumedDurationInSeconds = 0;
private int previousConsumedDuration = 0; // In minutes
private int producedDurationInSeconds = 0;
private int previousProducedDuration = 0; // In minutes
private volatile Random producingRandom;
public TaskAssigningPanel() {
setLayout(new BorderLayout());
JPanel headerPanel = createHeaderPanel();
add(headerPanel, BorderLayout.NORTH);
taskOverviewPanel = new TaskOverviewPanel(this);
add(new JScrollPane(taskOverviewPanel), BorderLayout.CENTER);
}
private JPanel createHeaderPanel() {
JPanel headerPanel = new JPanel(new GridLayout(1, 0));
JPanel consumePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
consumePanel.add(new JLabel("Consume rate:"));
consumeRateField = new JSpinner(new SpinnerNumberModel(1000, 10, 3600, 10));
consumePanel.add(consumeRateField);
consumeTimer = new Timer(1000, e -> {
consumedDurationInSeconds += (Integer) consumeRateField.getValue();
consumeUpTo(consumedDurationInSeconds / 60);
repaint();
});
consumeAction = new AbstractAction("Consume") {
@Override
public void actionPerformed(ActionEvent e) {
if (!consumeTimer.isRunning()) {
consumeRateField.setEnabled(false);
consumeTimer.start();
} else {
consumeRateField.setEnabled(true);
consumeTimer.stop();
}
}
};
consumePanel.add(new JToggleButton(consumeAction));
headerPanel.add(consumePanel);
JPanel producePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
producePanel.add(new JLabel("Produce rate:"));
produceRateField = new JSpinner(new SpinnerNumberModel(1000, 10, 3600, 10));
producePanel.add(produceRateField);
produceTimer = new Timer(1000, e -> {
producedDurationInSeconds += (Integer) produceRateField.getValue();
produceUpTo(producedDurationInSeconds / 60);
repaint();
});
produceAction = new AbstractAction("Produce") {
@Override
public void actionPerformed(ActionEvent e) {
if (!produceTimer.isRunning()) {
produceRateField.setEnabled(false);
produceTimer.start();
} else {
produceRateField.setEnabled(true);
produceTimer.stop();
}
}
};
producePanel.add(new JToggleButton(produceAction));
headerPanel.add(producePanel);
return headerPanel;
}
/**
* @param consumedDuration in minutes, just like {@link Task#getStartTime()}
*/
public void consumeUpTo(final int consumedDuration) {
taskOverviewPanel.setConsumedDuration(consumedDuration);
if (consumedDuration <= previousConsumedDuration) {
// Occurs due to rounding down of consumedDurationInSeconds
return;
}
logger.debug("Scheduling consumption of all tasks up to {} minutes.", consumedDuration);
previousConsumedDuration = consumedDuration;
doProblemFactChange(scoreDirector -> {
TaskAssigningSolution solution = scoreDirector.getWorkingSolution();
for (Task task : solution.getTaskList()) {
if (!task.isLocked()) {
if (task.getStartTime() != null && task.getStartTime() < consumedDuration) {
scoreDirector.beforeProblemPropertyChanged(task);
task.setLocked(true);
scoreDirector.afterProblemPropertyChanged(task);
logger.trace("Consumed task ({}).", task);
} else if (task.getReadyTime() < consumedDuration) {
// Prevent a non-locked task from being assigned retroactively
scoreDirector.beforeProblemPropertyChanged(task);
task.setReadyTime(consumedDuration);
scoreDirector.afterProblemPropertyChanged(task);
}
}
}
scoreDirector.triggerVariableListeners();
});
}
/**
* @param producedDuration in minutes, just like {@link Task#getStartTime()}
*/
public void produceUpTo(final int producedDuration) {
if (producedDuration <= previousProducedDuration) {
// Occurs due to rounding down of producedDurationInSeconds
return;
}
final int baseDurationBudgetPerEmployee = (producedDuration - previousProducedDuration);
if (baseDurationBudgetPerEmployee < BASE_DURATION_AVERAGE) {
return;
}
final int newTaskCount = Math.max(1,
getSolution().getEmployeeList().size() * baseDurationBudgetPerEmployee / BASE_DURATION_AVERAGE);
logger.debug("Scheduling production of {} new tasks.", newTaskCount);
previousProducedDuration = producedDuration;
final int readyTime = previousConsumedDuration;
doProblemFactChange(scoreDirector -> {
TaskAssigningSolution solution = scoreDirector.getWorkingSolution();
List<TaskType> taskTypeList = solution.getTaskTypeList();
List<Customer> customerList = solution.getCustomerList();
Priority[] priorities = Priority.values();
List<Task> taskList = solution.getTaskList();
for (int i = 0; i < newTaskCount; i++) {
Task task = new Task();
TaskType taskType = taskTypeList.get(producingRandom.nextInt(taskTypeList.size()));
long nextTaskId = 0L;
int nextIndexInTaskType = 0;
for (Task other : taskList) {
if (nextTaskId <= other.getId()) {
nextTaskId = other.getId() + 1L;
}
if (taskType == other.getTaskType()) {
if (nextIndexInTaskType <= other.getIndexInTaskType()) {
nextIndexInTaskType = other.getIndexInTaskType() + 1;
}
}
}
task.setId(nextTaskId);
task.setTaskType(taskType);
task.setIndexInTaskType(nextIndexInTaskType);
task.setCustomer(customerList.get(producingRandom.nextInt(customerList.size())));
// Prevent the new task from being assigned retroactively
task.setReadyTime(readyTime);
task.setPriority(priorities[producingRandom.nextInt(priorities.length)]);
scoreDirector.beforeEntityAdded(task);
taskList.add(task);
scoreDirector.afterEntityAdded(task);
}
scoreDirector.triggerVariableListeners();
});
}
@Override
public boolean isWrapInScrollPane() {
return false;
}
@Override
public void resetPanel(TaskAssigningSolution taskAssigningSolution) {
consumedDurationInSeconds = 0;
previousConsumedDuration = 0;
producedDurationInSeconds = 0;
previousProducedDuration = 0;
producingRandom = new Random(0); // Random is thread safe
taskOverviewPanel.resetPanel(taskAssigningSolution);
taskOverviewPanel.setConsumedDuration(consumedDurationInSeconds / 60);
}
@Override
public void updatePanel(TaskAssigningSolution taskAssigningSolution) {
taskOverviewPanel.resetPanel(taskAssigningSolution);
}
}