/** * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved. * This file is licensed under the terms of the Modified BSD License. */ package abs.backend.java.scheduling; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import abs.backend.java.lib.runtime.ABSRuntime; import abs.backend.java.lib.runtime.COG; import abs.backend.java.lib.runtime.Logging; import abs.backend.java.scheduling.SimpleTaskScheduler.TaskInfo; import abs.backend.java.utils.ColorUtils; public class InteractiveScheduler implements TotalSchedulingStrategy { private static final Logger log = Logging.getLogger(ABSRuntime.class.getName()); private final SchedulerGUI gui; private final SchedulerGUISwing guiSwingWrapper; private final SchedulingObserver obs; // synchronized by this private TotalSchedulingStrategy directScheduler; public InteractiveScheduler() { gui = new SchedulerGUI(this); guiSwingWrapper = SwingWrapperProxy.newBlockingInstance(gui, SchedulerGUISwing.class); obs = SwingWrapperProxy.newAsyncInstance(gui, SchedulingObserver.class); } public synchronized void setDirectScheduler(TotalSchedulingStrategy s) { directScheduler = s; } public synchronized TotalSchedulingStrategy getDirectScheduler() { return directScheduler; } private volatile ScheduleOptions lastOptions; @Override public ScheduleAction choose(ScheduleOptions options) { log.finest("showing options..."); ScheduleAction a = null; lastOptions = options; TotalSchedulingStrategy ds = getDirectScheduler(); if (ds != null) { a = ds.choose(options); } else { guiSwingWrapper.showOptions(options); log.finest("awaiting GUI action..."); a = awaitGUIAction(); } obs.choosedAction(a); return a; } public ScheduleOptions getLastOptions() { return lastOptions; } private ScheduleAction nextAction; public synchronized ScheduleAction awaitGUIAction() { while (nextAction == null) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } ScheduleAction res = nextAction; System.out.println("Chosen action " + nextAction); nextAction = null; return res; } public synchronized void setNextAction(ScheduleAction a) { nextAction = a; notify(); } @Override public TaskInfo schedule(TaskScheduler scheduler, List<TaskInfo> scheduableTasks) { TotalSchedulingStrategy ds = getDirectScheduler(); TaskInfo task = null; if (ds != null) { task = ds.schedule(scheduler, scheduableTasks); } else { guiSwingWrapper.chooseTask(scheduler, scheduableTasks); task = awaitGUITaskChoose(); } obs.activatedTask(task); return task; } public synchronized void setNextTask(TaskInfo i) { if (i == null) throw new IllegalArgumentException("i is null"); choosenTask = i; notify(); } private TaskInfo choosenTask; private synchronized TaskInfo awaitGUITaskChoose() { while (choosenTask == null) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } TaskInfo t = choosenTask; choosenTask = null; return t; } } interface SchedulerGUISwing { void showOptions(ScheduleOptions options); void chooseTask(TaskScheduler scheduler, List<TaskInfo> scheduableTasks); } class InteractiveOptionPnl extends JPanel implements SchedulerGUISwing { private GridBagLayout layout; final InteractiveScheduler scheduler; private JFrame frame; public InteractiveOptionPnl(JFrame frame, InteractiveScheduler s) { this.frame = frame; scheduler = s; layout = new GridBagLayout(); setLayout(layout); } class BtnLine { final COG cog; final JLabel label; final ChooseBtn btn; boolean isEnabled = true; private JDialog taskBtnFrame; BtnLine(ScheduleAction a) { cog = a.getCOG(); label = new JLabel("COG " + cog.getID()); label.setFont(label.getFont().deriveFont(Font.PLAIN)); btn = new ChooseBtn(a); btn.setFont(btn.getFont().deriveFont(Font.PLAIN)); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.anchor = GridBagConstraints.WEST; c.insets = new Insets(5, 5, 5, 5); add(label); add(btn); layout.setConstraints(label, c); c.gridx = 1; layout.setConstraints(btn, c); } public void setScheduleTaskBtns(List<TaskInfo> scheduableTasks) { enableBtns(false); taskBtnFrame = new TaskBtnDialog(scheduableTasks); taskBtnFrame.setVisible(true); } class TaskBtnDialog extends JDialog { public TaskBtnDialog(List<TaskInfo> scheduableTasks) { super(frame, "Schedule Task of COG " + cog.getID(), false); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); GridBagLayout layout = new GridBagLayout(); setLayout(layout); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.insets = new Insets(5, 5, 5, 5); c.gridx = 0; // btn.setVisible(false); for (TaskInfo i : scheduableTasks) { ChooseTaskBtn btn = new ChooseTaskBtn(i); add(btn); layout.setConstraints(btn, c); } pack(); setLocation(frame.getX() + frame.getWidth(), frame.getY()); } } class ChooseTaskBtn extends JButton implements ActionListener { private TaskInfo info; public ChooseTaskBtn(TaskInfo i) { this.info = i; this.addActionListener(this); setText("Activate Task " + i.task.getID() + " (" + i.task.getCall().methodName() + ")"); } @Override public void actionPerformed(ActionEvent arg0) { taskBtnFrame.dispose(); scheduler.setNextTask(info); enableBtns(true); } } } final Map<COG, BtnLine> btnLines = new HashMap<COG, BtnLine>(); @Override public synchronized void showOptions(ScheduleOptions options) { // System.out.println("SchedulerGUI: showing options..."); int i = 0; for (BtnLine l : btnLines.values()) { l.btn.updateAction(null); } for (ScheduleAction a : options.allOptions()) { i++; COG c = a.getCOG(); BtnLine line = btnLines.get(c); if (line == null) { line = new BtnLine(a); btnLines.put(c, line); } else { line.btn.updateAction(a); } } // / System.out.println("SchedulerGUI: "+i+" options showed"); } void enableBtns(boolean b) { for (BtnLine line : btnLines.values()) { line.btn.setEnabled(b && line.btn.action != null); } } void chooseBtnPressed(ScheduleAction a) { scheduler.setNextAction(a); } class ChooseBtn extends JButton implements ActionListener { ScheduleAction action; ChooseBtn(ScheduleAction a) { this.addActionListener(this); updateAction(a); setPreferredSize(new Dimension(200, getPreferredSize().height)); } public void updateAction(ScheduleAction a) { action = a; updateBtn(); } public void updateBtn() { if (action == null) { setEnabled(false); setText(" - - - - "); } else { String text = "Schedule Next Task"; if (action instanceof StepTask) { StepTask st = (StepTask) action; text = "Step Task " + st.getTask().getID() + " (" + st.getTask().getMethodName() + ")"; } setText(text); setEnabled(true); } } @Override public void actionPerformed(ActionEvent arg0) { ScheduleAction a = action; action = null; updateBtn(); chooseBtnPressed(a); } } @Override public void chooseTask(TaskScheduler scheduler, List<TaskInfo> scheduableTasks) { if (scheduableTasks.size() == 1) { this.scheduler.setNextTask(scheduableTasks.get(0)); } else { BtnLine l = btnLines.get(scheduler.getCOG()); l.setScheduleTaskBtns(scheduableTasks); } } } class SchedulerChoosePnl extends JPanel implements ItemListener { public static String INTERACTIVE = "Interactive"; public static String REPLAY = "Replay"; public static String RANDOM = "Random"; private JComboBox schedulerComboBox; private JPanel choosePnl; private SchedulerGUI schedulerGUI; public SchedulerChoosePnl(SchedulerGUI gui) { schedulerGUI = gui; setLayout(new BorderLayout()); createChoosePnl(); } private void createChoosePnl() { choosePnl = new JPanel(); choosePnl.setLayout(new BorderLayout()); choosePnl.setBorder(BorderFactory.createTitledBorder( BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY), BorderFactory.createEmptyBorder(5, 5, 5, 5)), "Scheduler")); schedulerComboBox = new JComboBox(); choosePnl.add(schedulerComboBox, BorderLayout.CENTER); schedulerComboBox.addItem(INTERACTIVE); schedulerComboBox.addItem(REPLAY); schedulerComboBox.addItem(RANDOM); schedulerComboBox.addItemListener(this); add(choosePnl, BorderLayout.NORTH); } @Override public void itemStateChanged(ItemEvent e) { if (e.getItem() == RANDOM) { schedulerGUI.setRandomScheduler(); } else if (e.getItem() == REPLAY) { schedulerGUI.setReplayScheduler(); } else { schedulerGUI.setInteractiveScheduler(); } } } class HistoryPnl extends JPanel { JTextArea historyArea; List<ScheduleAction> history = new ArrayList<ScheduleAction>(); JButton saveHistory; private final SchedulerGUI schedulerGUI; public HistoryPnl(SchedulerGUI gui) { this.schedulerGUI = gui; setLayout(new BorderLayout()); historyArea = new JTextArea(); add(new JScrollPane(historyArea), BorderLayout.CENTER); setPreferredSize(new Dimension(300, 400)); setBorder(BorderFactory.createTitledBorder( BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY), BorderFactory.createEmptyBorder(5, 5, 5, 5)), "Scheduling History")); Box btnBox = new Box(BoxLayout.LINE_AXIS); add(btnBox, BorderLayout.SOUTH); btnBox.add(Box.createHorizontalGlue()); saveHistory = new JButton("Save History"); saveHistory.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveHistoryBtnPressed(); } }); btnBox.add(saveHistory); } void saveHistoryBtnPressed() { final JFileChooser fc = new JFileChooser(new File(System.getProperty("user.dir"))); int returnVal = fc.showSaveDialog(schedulerGUI.frame); if (returnVal == JFileChooser.APPROVE_OPTION) { File f = fc.getSelectedFile(); if (f.exists()) { int ans = JOptionPane.showConfirmDialog(schedulerGUI.frame, "File '" + f.getName() + "' already exists, do you want to override it?"); if (ans != JOptionPane.YES_OPTION) { return; } } saveHistory(f); } } private void saveHistory(File f) { try { PrintWriter p = new PrintWriter(new FileOutputStream(f)); for (ScheduleAction a : history) { p.append(a.shortString()); p.append("\n"); } p.close(); JOptionPane.showMessageDialog(schedulerGUI.frame, "History saved"); } catch (FileNotFoundException e) { JOptionPane.showMessageDialog(schedulerGUI.frame, "Could not save history: " + e.getMessage()); } } public void activatedTask(TaskInfo task) { historyArea.append("Activate Task " + task.task.getID() + "\n"); history.add(new ActivateTask(task.task.getCOG(), task.task)); } public void choosedAction(ScheduleAction a) { historyArea.append(a.toString() + "\n"); history.add(a); } } interface SchedulingObserver { void choosedAction(ScheduleAction a); void activatedTask(TaskInfo task); } class ReplayOptionPnl extends JPanel implements SchedulerGUISwing, TotalSchedulingStrategy { JButton loadHistoryBtn; private InteractiveScheduler scheduler; private List<HistoryItem> history = new ArrayList<HistoryItem>(0); private ScheduleOptions options; public ReplayOptionPnl(InteractiveScheduler scheduler) { this.scheduler = scheduler; this.setLayout(new BorderLayout()); loadHistoryBtn = new JButton("Replay History"); loadHistoryBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { loadHistoryBtnPressed(); } }); add(loadHistoryBtn, BorderLayout.PAGE_START); } void loadHistoryBtnPressed() { final JFileChooser fc = new JFileChooser(new File(System.getProperty("user.dir"))); if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { File f = fc.getSelectedFile(); loadHistory(f); } } private void loadHistory(File f) { try { history = HistoryItem.loadHistory(f); scheduler.setDirectScheduler(this); scheduler.setNextAction(getOptionByHistory()); } catch (FileNotFoundException e) { JOptionPane.showMessageDialog(this, "File " + f + " does not exist"); } catch (IOException e) { JOptionPane.showMessageDialog(this, "Error while reading history: " + e.getMessage()); } } @Override public synchronized void showOptions(ScheduleOptions options) { this.options = options; } private ScheduleAction getOptionByHistory() { HistoryItem item = history.remove(0); for (ScheduleAction a : options.allOptions()) { if (item.matches(a)) { return a; } } JOptionPane.showMessageDialog(this, "Illegal History!"); return null; } @Override public void chooseTask(TaskScheduler s, List<TaskInfo> scheduableTasks) { TaskInfo i = getNextTask(scheduableTasks); if (i != null) scheduler.setNextTask(i); } private TaskInfo getNextTask(List<TaskInfo> scheduableTasks) { if (!history.isEmpty()) { try { HistoryItem at = history.remove(0); if (at.action != HistoryAction.ACTIVATE) throw new IllegalStateException("Task action action expected!"); for (TaskInfo i : scheduableTasks) { if (i.task.getID() == at.taskid && i.task.getCOG().getID() == at.cogId) { return i; } } throw new IllegalStateException("Task " + at.taskid + " cannot be scheduled."); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, "Illegal History! " + e.getMessage()); } } return null; } @Override public ScheduleAction choose(ScheduleOptions options) { ScheduleAction a = getOptionByHistory(); if (history.isEmpty()) { scheduler.setDirectScheduler(null); } return a; } @Override public TaskInfo schedule(TaskScheduler taskScheduler, List<TaskInfo> scheduableTasks) { TaskInfo i = getNextTask(scheduableTasks); if (history.isEmpty()) { scheduler.setDirectScheduler(null); } return i; } } class RandomOptionPnl extends JPanel implements SchedulerGUISwing, TotalSchedulingStrategy { private final JButton nextStepBtn; private final JButton nextNStepBtn; private final JButton runBtn; private long seed = System.nanoTime(); private final Random r = new Random(seed); private final RandomSchedulingStrategy globalStrat = new RandomSchedulingStrategy(r); private final RandomSchedulingStrategy taskStrat = globalStrat; private final InteractiveScheduler scheduler; private TaskInfo nextTask; private GridBagLayout gridBagLayout; private JTextField seedField; private boolean isRunning; RandomOptionPnl(InteractiveScheduler s) { this.scheduler = s; gridBagLayout = new GridBagLayout(); setLayout(gridBagLayout); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.anchor = GridBagConstraints.EAST; c.insets = new Insets(5, 5, 5, 5); JLabel lbl = new JLabel("Random Seed: "); lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); add(lbl); gridBagLayout.setConstraints(lbl, c); seedField = new JTextField(); add(seedField); seedField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { seedChanged(); } @Override public void removeUpdate(DocumentEvent e) { seedChanged(); } @Override public void changedUpdate(DocumentEvent e) { seedChanged(); } }); c.gridx = 1; c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.HORIZONTAL; gridBagLayout.setConstraints(seedField, c); seedField.setPreferredSize(new Dimension(150, seedField.getPreferredSize().height)); seedField.setText("" + seed); nextStepBtn = new JButton("Single Random Step"); nextStepBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { nextStepClicked(); } }); add(nextStepBtn); c.gridx = 1; c.gridy = 1; gridBagLayout.setConstraints(nextStepBtn, c); nextNStepBtn = new JButton("N Random Steps"); nextNStepBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { nextNStepClicked(); } }); add(nextNStepBtn); c.gridy++; gridBagLayout.setConstraints(nextNStepBtn, c); runBtn = new JButton("Run"); runBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { runClicked(); } }); add(runBtn); c.gridy++; gridBagLayout.setConstraints(runBtn, c); } private void runClicked() { isRunning = true; nextStepClicked(); } private void seedChanged() { try { long s = Long.parseLong(seedField.getText()); seed = s; r.setSeed(seed); System.out.println("Seed Changed to " + seedField.getText()); seedField.setBackground(Color.WHITE); } catch (Exception e) { seedField.setBackground(ColorUtils.setSaturation(Color.RED, 0.5f)); } } long remaindingSteps = 0; private ScheduleOptions options; private void nextNStepClicked() { String steps = JOptionPane.showInputDialog(this, "Number of steps"); try { remaindingSteps = Long.parseLong(steps); nextStepClicked(); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, steps + " is not a legal number"); } } private void nextStepClicked() { nextNStepBtn.setEnabled(false); nextStepBtn.setEnabled(false); runBtn.setEnabled(false); ScheduleAction a = nextAction(); if (a != null) { scheduler.setDirectScheduler(this); scheduler.setNextAction(a); } } private synchronized ScheduleAction nextAction() { if (remaindingSteps > 0) remaindingSteps--; if (!options.isEmpty()) { return globalStrat.choose(options); } return null; } @Override public synchronized void showOptions(ScheduleOptions options) { this.options = options; if (isRunning || remaindingSteps > 0) { nextStepClicked(); } else { nextStepBtn.setEnabled(true); nextNStepBtn.setEnabled(true); runBtn.setEnabled(true); } } @Override public synchronized void chooseTask(TaskScheduler s, List<TaskInfo> scheduableTasks) { nextTask = taskStrat.schedule(s, scheduableTasks); scheduler.setNextTask(nextTask); } @Override public synchronized ScheduleAction choose(ScheduleOptions options) { ScheduleAction a = nextAction(); if (remaindingSteps == 0 && !isRunning) { scheduler.setDirectScheduler(null); } return a; } @Override public synchronized TaskInfo schedule(TaskScheduler scheduler, List<TaskInfo> scheduableTasks) { return taskStrat.schedule(scheduler, scheduableTasks); } } class SchedulerGUI implements SchedulerGUISwing, SchedulingObserver { final JFrame frame; final InteractiveScheduler scheduler; JPanel btnPnl; HistoryPnl historyPnl; private InteractiveOptionPnl interactiveOptionPnl; JPanel optionsPnl; SchedulerGUISwing currentScheduler; SchedulerChoosePnl schedulerChoosePnl; private CardLayout cardLayout; private RandomOptionPnl randomOptionPnl; private ReplayOptionPnl replayOptionPnl; SchedulerGUI(InteractiveScheduler s) { scheduler = s; frame = new JFrame("ABS Interactive Scheduler"); if (ABSRuntime.runsInOwnProcess()) frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); else frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.setLayout(new BorderLayout()); schedulerChoosePnl = new SchedulerChoosePnl(this); frame.add(schedulerChoosePnl, BorderLayout.NORTH); optionsPnl = new JPanel(); cardLayout = new CardLayout(); optionsPnl.setLayout(cardLayout); optionsPnl.setBorder(BorderFactory.createTitledBorder( BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY), BorderFactory.createEmptyBorder(5, 5, 5, 5)), "Scheduling Options")); // optionsPnl.setPreferredSize(new Dimension(300,300)); frame.add(optionsPnl, BorderLayout.CENTER); interactiveOptionPnl = new InteractiveOptionPnl(frame, scheduler); optionsPnl.add(interactiveOptionPnl, SchedulerChoosePnl.INTERACTIVE); replayOptionPnl = new ReplayOptionPnl(scheduler); optionsPnl.add(replayOptionPnl, SchedulerChoosePnl.REPLAY); randomOptionPnl = new RandomOptionPnl(scheduler); optionsPnl.add(randomOptionPnl, SchedulerChoosePnl.RANDOM); setInteractiveScheduler(); historyPnl = new HistoryPnl(this); frame.add(historyPnl, BorderLayout.SOUTH); frame.setBounds(0, 0, 100, 50); frame.setVisible(true); frame.pack(); } void setRandomScheduler() { setScheduler(randomOptionPnl, SchedulerChoosePnl.RANDOM); } private void setScheduler(SchedulerGUISwing swingScheduler, String name) { cardLayout.show(optionsPnl, name); currentScheduler = swingScheduler; ScheduleOptions lastOptions = scheduler.getLastOptions(); if (lastOptions != null) currentScheduler.showOptions(lastOptions); frame.pack(); } void setReplayScheduler() { setScheduler(replayOptionPnl, SchedulerChoosePnl.REPLAY); } void setInteractiveScheduler() { setScheduler(interactiveOptionPnl, SchedulerChoosePnl.INTERACTIVE); } @Override public void activatedTask(TaskInfo task) { historyPnl.activatedTask(task); } @Override public void choosedAction(ScheduleAction a) { historyPnl.choosedAction(a); } @Override public void showOptions(ScheduleOptions options) { currentScheduler.showOptions(options); frame.pack(); } @Override public void chooseTask(TaskScheduler scheduler, List<TaskInfo> scheduableTasks) { currentScheduler.chooseTask(scheduler, scheduableTasks); } } // needed to allow for access to package members class SwingWrapperProxy implements InvocationHandler { final Object target; volatile Object result; private boolean block; public SwingWrapperProxy(Object target, boolean block) { this.target = target; this.block = block; } @Override public synchronized Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { Runnable r = new Runnable() { @Override public void run() { try { result = method.invoke(target, args); } catch (Exception e) { e.printStackTrace(); } } }; if (block) { SwingUtilities.invokeAndWait(r); return result; } else { SwingUtilities.invokeLater(r); return null; } } public static <V> V newInstance(V target, Class<?> interfce, boolean block) { return (V) Proxy.newProxyInstance(SwingWrapperProxy.class.getClassLoader(), new Class[] { interfce }, new SwingWrapperProxy(target, block)); } public static <V> V newBlockingInstance(V target, Class<?> interfce) { return newInstance(target, interfce, true); } public static <V> V newAsyncInstance(V target, Class<?> interfce) { return newInstance(target, interfce, false); } }