workcraft/workcraft

View on GitHub
workcraft/SonPlugin/src/org/workcraft/plugins/son/tools/SONSimulationTool.java

Summary

Maintainability
C
7 hrs
Test Coverage
package org.workcraft.plugins.son.tools;

import org.workcraft.Framework;
import org.workcraft.dom.Container;
import org.workcraft.dom.Node;
import org.workcraft.dom.visual.HitMan;
import org.workcraft.dom.visual.VisualGroup;
import org.workcraft.dom.visual.VisualPage;
import org.workcraft.gui.MainWindow;
import org.workcraft.gui.events.GraphEditorMouseEvent;
import org.workcraft.gui.layouts.WrapLayout;
import org.workcraft.gui.tools.*;
import org.workcraft.plugins.builtin.settings.SimulationDecorationSettings;
import org.workcraft.plugins.son.BlockConnector;
import org.workcraft.plugins.son.SON;
import org.workcraft.plugins.son.SONSettings;
import org.workcraft.plugins.son.VisualSON;
import org.workcraft.plugins.son.algorithm.*;
import org.workcraft.plugins.son.elements.*;
import org.workcraft.plugins.son.exception.InvalidStructureException;
import org.workcraft.plugins.son.exception.UnboundedException;
import org.workcraft.plugins.son.gui.ParallelSimDialog;
import org.workcraft.plugins.son.util.Phase;
import org.workcraft.plugins.son.util.Step;
import org.workcraft.plugins.son.util.StepRef;
import org.workcraft.plugins.son.util.Trace;
import org.workcraft.utils.GuiUtils;
import org.workcraft.workspace.WorkspaceEntry;

import javax.swing.Timer;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.util.*;

public class SONSimulationTool extends AbstractGraphEditorTool implements ClipboardOwner {

    protected RelationAlgorithm relationAlg;
    protected BSONAlg bsonAlg;
    protected SimulationAlg simuAlg;
    private ErrorTracingAlg    errAlg;

    protected Collection<Path> sync;
    protected Map<Condition, Collection<Phase>> phases;
    protected Map<PlaceNode, Boolean> initialMarking;

    protected JPanel panel;
    protected JTable traceTable;

    protected JSlider speedSlider;
    protected JButton playButton;
    protected JButton stopButton;
    protected JButton backwardButton;
    protected JButton forwardButton;
    protected JButton reverseButton;
    protected JButton errorButton;
    protected JButton copyStateButton;
    protected JButton pasteStateButton;
    protected JButton mergeTraceButton;
    protected JToggleButton autoSimuButton;

    protected HashMap<Container, Boolean> excitedContainers = new HashMap<>();

    private static final int SLIDER_RANGE = 10;
    private static final double BASE_SPEED = 300;
    private static final double INCREMENT_SPEED = 10;

    protected final Trace mainTrace = new Trace();
    protected final Trace branchTrace = new Trace();

    protected boolean isRev;

    protected Timer timer = null;

    @Override
    public String getHintText(final GraphEditor editor) {
        return "Click on a highlighted node to fire it.";
    }

    @Override
    public String getLabel() {
        return "Simulation";
    }

    @Override
    public int getHotKeyCode() {
        return KeyEvent.VK_M;
    }

    @Override
    public Icon getIcon() {
        return GuiUtils.createIconFromSVG("images/son-tool-simulation.svg");
    }

    @Override
    public JPanel getControlsPanel(final GraphEditor editor) {
        if (panel != null) {
            return panel;
        }
        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();

        playButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-play.svg"),
                "Automatic trace playback");
        stopButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-stop.svg"),
                "Reset trace playback");
        backwardButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-backward.svg"),
                "Step backward");
        forwardButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-forward.svg"),
                "Step forward");
        reverseButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-progress.svg"),
                "Switch to reverse simulation");
        autoSimuButton = GuiUtils.createIconToggleButton(GuiUtils.createIconFromSVG("images/son-simulation-auto.svg"),
                "Automatic simulation");
        errorButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-trace-error.svg"),
                "Enable/Disable error tracing");

        speedSlider = new JSlider(-SLIDER_RANGE, SLIDER_RANGE, 0);
        speedSlider.setToolTipText("Simulation playback speed");
        speedSlider.setMajorTickSpacing(SLIDER_RANGE);
        speedSlider.setMinorTickSpacing(1);
        speedSlider.setPaintTicks(true);

        copyStateButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-trace-copy.svg"),
                "Copy trace to clipboard");
        pasteStateButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-trace-paste.svg"),
                "Paste trace from clipboard");
        mergeTraceButton = GuiUtils.createIconButton(GuiUtils.createIconFromSVG("images/son-simulation-trace-merge.svg"),
                "Merge branch into trace");

        int buttonWidth = (int) Math.round(playButton.getPreferredSize().getWidth() + 5);
        int buttonHeight = (int) Math.round(playButton.getPreferredSize().getHeight() + 5);
        Dimension panelSize = new Dimension(buttonWidth * 7, buttonHeight);

        JPanel simulationControl = new JPanel();
        simulationControl.setLayout(new FlowLayout());
        simulationControl.setPreferredSize(panelSize);
        simulationControl.setMaximumSize(panelSize);
        simulationControl.add(playButton);
        simulationControl.add(stopButton);
        simulationControl.add(backwardButton);
        simulationControl.add(forwardButton);
        simulationControl.add(reverseButton);
        simulationControl.add(autoSimuButton);
        simulationControl.add(errorButton);

        JPanel speedControl = new JPanel();
        speedControl.setLayout(new BorderLayout());
        speedControl.setPreferredSize(panelSize);
        speedControl.setMaximumSize(panelSize);
        speedControl.add(speedSlider, BorderLayout.CENTER);

        JPanel traceControl = new JPanel();
        traceControl.setLayout(new FlowLayout());
        traceControl.setPreferredSize(panelSize);
        traceControl.add(new JSeparator());
        traceControl.add(copyStateButton);
        traceControl.add(pasteStateButton);
        traceControl.add(mergeTraceButton);

        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new WrapLayout());
        controlPanel.add(simulationControl);
        controlPanel.add(speedControl);
        controlPanel.add(traceControl);

        traceTable = new JTable(new TraceTableModel());
        traceTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        JScrollPane tablePanel = new JScrollPane(traceTable);
        tablePanel.setPreferredSize(new Dimension(1, 1));

        speedSlider.addChangeListener(e -> {
            if (timer != null) {
                timer.stop();
                timer.setInitialDelay(getAnimationDelay());
                timer.setDelay(getAnimationDelay());
                timer.start();
            }
            updateState(editor);
        });

        playButton.addActionListener(event -> {
            if (timer == null) {
                timer = new Timer(getAnimationDelay(), event1 -> step(editor));
                timer.start();
            } else {
                timer.stop();
                timer = null;
            }
            updateState(editor);
            editor.requestFocus();
        });

        stopButton.addActionListener(event -> {
            reset(editor);
            setDecoration(editor, simuAlg.getEnabledNodes(sync, phases, isRev));
        });

        backwardButton.addActionListener(event -> stepBack(editor));

        forwardButton.addActionListener(event -> step(editor));

        reverseButton.addActionListener(event -> {
            Map<PlaceNode, Boolean> currentMarking = readSONMarking(net);
            setReverse(editor, !isRev);
            writeModelState(currentMarking);
            setDecoration(editor, simuAlg.getEnabledNodes(sync, phases, isRev));
            excitedContainers.clear();
            updateState(editor);
            editor.requestFocus();
        });

        autoSimuButton.addActionListener(event -> {
            if (autoSimuButton.isSelected()) {
                if (!acyclicChecker(editor)) {
                    autoSimuButton.setSelected(false);
                    try {
                        throw new InvalidStructureException("Cyclic structure error");
                    } catch (InvalidStructureException e1) {
                        errorMsg(e1.getMessage(), editor);
                    }
                } else {
                    autoSimulator(editor);
                }
            }
        });

        errorButton.addActionListener(event -> {
            SONSettings.setErrorTracing(!SONSettings.isErrorTracing());
            editor.repaint();
        });

        copyStateButton.addActionListener(event -> copyState(editor));

        pasteStateButton.addActionListener(event -> pasteState(editor));

        mergeTraceButton.addActionListener(event -> mergeTrace(editor));

        traceTable.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int column = traceTable.getSelectedColumn();
                int row = traceTable.getSelectedRow();
                if (column == 0) {
                    if (row < mainTrace.size()) {
                        boolean work = true;
                        while (work && (branchTrace.getPosition() > 0)) {
                            work = quietStepBack(editor);
                        }
                        while (work && (mainTrace.getPosition() > row)) {
                            work = quietStepBack(editor);
                        }
                        while (work && (mainTrace.getPosition() < row)) {
                            work = quietStep(editor);
                        }
                    }
                } else {
                    if ((row >= mainTrace.getPosition()) && (row < mainTrace.getPosition() + branchTrace.size())) {
                        boolean work = true;
                        while (work && (mainTrace.getPosition() + branchTrace.getPosition() > row)) {
                            work = quietStepBack(editor);
                        }
                        while (work && (mainTrace.getPosition() + branchTrace.getPosition() < row)) {
                            work = quietStep(editor);
                        }
                    }
                }
                updateState(editor);
                editor.requestFocus();
            }

            @Override
            public void mouseEntered(MouseEvent arg0) {
            }

            @Override
            public void mouseExited(MouseEvent arg0) {
            }

            @Override
            public void mousePressed(MouseEvent arg0) {
            }

            @Override
            public void mouseReleased(MouseEvent arg0) {
            }
        });
        traceTable.setDefaultRenderer(Object.class, new TraceTableCellRendererImplementation());

        panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(controlPanel, BorderLayout.NORTH);
        panel.add(tablePanel, BorderLayout.CENTER);
        panel.setPreferredSize(new Dimension(0, 0));
        return panel;
    }

    @Override
    public void activated(final GraphEditor editor) {
        super.activated(editor);
        editor.getWorkspaceEntry().captureMemento();

        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        BlockConnector.blockBoundingConnector(visualNet);

        errAlg = new ErrorTracingAlg(net);
        initialise(editor);
        reset(editor);
        setDecoration(editor, simuAlg.getEnabledNodes(sync, phases, isRev));

        if (SONSettings.isErrorTracing()) {
            net.resetConditionErrStates();
        }
        updateState(editor);
        editor.forceRedraw();
    }

    @Override
    public void deactivated(final GraphEditor editor) {
        super.deactivated(editor);
        if (timer != null) {
            timer.stop();
            timer = null;
        }
        editor.getWorkspaceEntry().cancelMemento();
        mainTrace.clear();
        branchTrace.clear();
        isRev = false;
    }

    @Override
    public void setPermissions(final GraphEditor editor) {
        WorkspaceEntry we = editor.getWorkspaceEntry();
        we.setCanModify(false);
        we.setCanSelect(true);
        we.setCanCopy(true);
    }

    protected void initialise(final GraphEditor editor) {
        final SON net = (SON) editor.getModel().getMathModel();
        relationAlg = new RelationAlgorithm(net);
        bsonAlg = new BSONAlg(net);
        simuAlg = new SimulationAlg(net);
        initialMarking = simuAlg.getInitialMarking();
        sync = getSyncEventCycles(net);
        phases = bsonAlg.getAllPhases();
    }

    protected Collection<Path> getSyncEventCycles(final SON net) {
        HashSet<Node> nodes = new HashSet<>();
        nodes.addAll(net.getTransitionNodes());
        nodes.addAll(net.getChannelPlaces());
        CSONCycleAlg cycleAlg = new CSONCycleAlg(net);

        return cycleAlg.syncEventCycleTask(nodes);
    }

    public void updateState(final GraphEditor editor) {
        if (timer == null) {
            playButton.setIcon(GuiUtils.createIconFromSVG("images/son-simulation-play.svg"));
        } else {
            if (branchTrace.canProgress() || (branchTrace.isEmpty() && mainTrace.canProgress())) {
                playButton.setIcon(GuiUtils.createIconFromSVG("images/son-simulation-pause.svg"));
                timer.setDelay(getAnimationDelay());
            } else {
                playButton.setIcon(GuiUtils.createIconFromSVG("images/son-simulation-play.svg"));
                timer.stop();
                timer = null;
            }
        }
        playButton.setEnabled(branchTrace.canProgress() || (branchTrace.isEmpty() && mainTrace.canProgress()));
        stopButton.setEnabled(!mainTrace.isEmpty() || !branchTrace.isEmpty());
        backwardButton.setEnabled((mainTrace.getPosition() > 0) || (branchTrace.getPosition() > 0));
        forwardButton.setEnabled(branchTrace.canProgress() || (branchTrace.isEmpty() && mainTrace.canProgress()));
        traceTable.tableChanged(new TableModelEvent(traceTable.getModel()));
        if (!isRev) {
            reverseButton.setIcon(GuiUtils.createIconFromSVG("images/son-simulation-progress.svg"));
            reverseButton.setToolTipText("Switch to reverse simulation");
        } else {
            reverseButton.setIcon(GuiUtils.createIconFromSVG("images/son-simulation-reverse.svg"));
            reverseButton.setToolTipText("Switch to forward simulation");
        }
        editor.repaint();
    }

    private int getAnimationDelay() {
        double power = (double) -speedSlider.getValue() / SLIDER_RANGE;
        return (int) (BASE_SPEED * Math.pow(INCREMENT_SPEED, power));
    }

    @SuppressWarnings("serial")
    protected class TraceTableModel extends AbstractTableModel {
        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(int column) {
            if (column == 0) return "Trace";
            return "Branch";
        }

        @Override
        public int getRowCount() {
            return Math.max(mainTrace.size(), mainTrace.getPosition() + branchTrace.size());
        }

        @Override
        public Object getValueAt(int row, int column) {
            if (column == 0) {
                if (!mainTrace.isEmpty() && (row < mainTrace.size())) {
                    return mainTrace.get(row);
                }
            } else {
                if (!branchTrace.isEmpty() && (row >= mainTrace.getPosition()) && (row < mainTrace.getPosition() + branchTrace.size())) {
                    return branchTrace.get(row - mainTrace.getPosition());
                }
            }
            return "";
        }
    }

    protected void errorMsg(String message, final GraphEditor editor) {
        final Framework framework = Framework.getInstance();
        MainWindow mainWindow = framework.getMainWindow();
        JOptionPane.showMessageDialog(mainWindow, message,
                "Invalid structure", JOptionPane.WARNING_MESSAGE);

        reset(editor);
    }

    protected Map<PlaceNode, Boolean> readSONMarking(final SON net) {
        HashMap<PlaceNode, Boolean> result = new HashMap<>();
        for (PlaceNode c : net.getPlaceNodes()) {
            result.put(c, c.isMarked());
        }

        return result;
    }

    protected boolean quietStep(final GraphEditor editor) {
        excitedContainers.clear();
        boolean result = false;
        Step step = null;
        int mainInc = 0;
        int branchInc = 0;
        if (branchTrace.canProgress()) {
            StepRef stepRef = branchTrace.getCurrent();
            step = getStep(editor, stepRef);
            setReverse(editor, stepRef);
            branchInc = 1;
        } else if (mainTrace.canProgress()) {
            StepRef stepRef = mainTrace.getCurrent();
            step = getStep(editor, stepRef);
            setReverse(editor, stepRef);
            mainInc = 1;
        }

        if (step != null) {
            try {
                simuAlg.setMarking(step, phases, isRev);
            } catch (UnboundedException e) {
            }
            setErrNum(step, isRev);
            mainTrace.incPosition(mainInc);
            branchTrace.incPosition(branchInc);
            setDecoration(editor, simuAlg.getEnabledNodes(sync, phases, isRev));
            result = true;
        }

        return result;
    }

    protected boolean step(final GraphEditor editor) {
        boolean ret = quietStep(editor);
        updateState(editor);
        return ret;
    }

    private boolean stepBack(final GraphEditor editor) {
        boolean ret = quietStepBack(editor);
        updateState(editor);
        return ret;
    }

    protected boolean quietStepBack(final GraphEditor editor) {
        excitedContainers.clear();
        boolean result = false;
        Step step = null;
        int mainDec = 0;
        int branchDec = 0;
        if (branchTrace.getPosition() > 0) {
            StepRef stepRef = branchTrace.get(branchTrace.getPosition() - 1);
            step = getStep(editor, stepRef);
            setReverse(editor, stepRef);
            branchDec = 1;
        } else if (mainTrace.getPosition() > 0) {
            StepRef stepRef = mainTrace.get(mainTrace.getPosition() - 1);
            step = getStep(editor, stepRef);
            setReverse(editor, stepRef);
            mainDec = 1;
        }

        if (step != null) {
            try {
                simuAlg.setMarking(step, phases, !isRev);
            } catch (UnboundedException e) {
                e.printStackTrace();
            }
            mainTrace.decPosition(mainDec);
            branchTrace.decPosition(branchDec);
            if ((branchTrace.getPosition() == 0) && !mainTrace.isEmpty()) {
                branchTrace.clear();
            }
            setDecoration(editor, simuAlg.getEnabledNodes(sync, phases, isRev));
            result = true;
            this.setErrNum(step, !isRev);
        }

        return result;
    }

    private void reset(final GraphEditor editor) {
        writeModelState(initialMarking);
        isRev = false;
        mainTrace.clear();
        branchTrace.clear();

        if (timer != null) {
            timer.stop();
            timer = null;
        }
        updateState(editor);
    }

    private void copyState(final GraphEditor editor) {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        StringSelection stringSelection = new StringSelection(mainTrace.toString() + '\n' + branchTrace.toString() + '\n');
        clip.setContents(stringSelection, this);
        updateState(editor);
    }

    private void pasteState(final GraphEditor editor) {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable contents = clip.getContents(null);
        boolean hasTransferableText = (contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor);
        String str = "";
        if (hasTransferableText) {
            try {
                str = (String) contents.getTransferData(DataFlavor.stringFlavor);
            } catch (UnsupportedFlavorException | IOException ex) {
                System.out.println(ex);
                ex.printStackTrace();
            }
        }

        writeModelState(initialMarking);
        mainTrace.clear();
        branchTrace.clear();
        boolean first = true;
        for (String s: str.split("\n")) {
            if (first) {
                mainTrace.fromString(s);
                int mainTracePosition = mainTrace.getPosition();
                mainTrace.setPosition(0);
                boolean work = true;
                while (work && (mainTrace.getPosition() < mainTracePosition)) {
                    work = quietStep(editor);
                }
            } else {
                branchTrace.fromString(s);
                int branchTracePosition = branchTrace.getPosition();
                branchTrace.setPosition(0);
                boolean work = true;
                while (work && (branchTrace.getPosition() < branchTracePosition)) {
                    work = quietStep(editor);
                }
                break;
            }
            first = false;
        }
        updateState(editor);
    }

    public void mergeTrace(final GraphEditor editor) {
        if (!branchTrace.isEmpty()) {
            while (mainTrace.getPosition() < mainTrace.size()) {
                mainTrace.removeCurrent();
            }
            mainTrace.addAll(branchTrace);
            mainTrace.incPosition(branchTrace.getPosition());
            branchTrace.clear();
        }
        updateState(editor);
    }

    private void setErrNum(Step step, boolean isRev) {
        if (SONSettings.isErrorTracing()) {
            Collection<TransitionNode> upperEvents = new ArrayList<>();

            // Get high level events
            for (TransitionNode node : step) {
                if (bsonAlg.isUpperEvent(node)) {
                    upperEvents.add(node);
                }
            }
            // Get low level events
            step.removeAll(upperEvents);

            if (!isRev) {
                // Set error number for upper events
                errAlg.setErrNum(upperEvents, sync, phases, false);
                // Set error number for lower events
                errAlg.setErrNum(step, sync, phases, true);
            } else {
                errAlg.setRevErrNum(upperEvents, sync, phases, false);
                errAlg.setRevErrNum(step, sync, phases, true);
            }
        }
    }

    protected void writeModelState(Map<PlaceNode, Boolean> marking) {
        for (PlaceNode c: marking.keySet()) {
            c.setMarked(marking.get(c));
        }
    }

    protected void autoSimulator(final GraphEditor editor) {
        if (!acyclicChecker(editor)) {
            autoSimuButton.setSelected(false);
        } else {
            autoSimulationTask(editor);
        }
    }

    protected boolean acyclicChecker(final GraphEditor editor) {
        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        SONCycleAlg cycle = new SONCycleAlg(net, phases);
        if (!cycle.cycleTask(net.getComponents()).isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    protected void autoSimulationTask(final GraphEditor editor) {
        Step step = simuAlg.getEnabledNodes(sync, phases, isRev);
        if (step.isEmpty()) {
            autoSimuButton.setSelected(false);
            return;
        }
        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        step = conflictFilter(net, step);
        if (!step.isEmpty()) {
            step = simuAlg.getMinFire(step.iterator().next(), sync, step, isRev);
            executeEvents(editor, step);
            autoSimulationTask(editor);
        }
    }

    protected Step conflictFilter(final SON net, Step step) {
        Step result = new Step();
        result.addAll(step);

        for (PlaceNode c : readSONMarking(net).keySet()) {
            Collection<TransitionNode> conflict = new ArrayList<>();
            if (c.isMarked()) {
                if (!isRev) {
                    conflict.addAll(relationAlg.getPostConflictEvents(c));
                } else {
                    conflict.addAll(relationAlg.getPreConflictEvents(c));
                }
            }

            if (!conflict.isEmpty()) {
                for (TransitionNode e : conflict) {
                    result.removeAll(simuAlg.getMinFire(e, sync, step, isRev));
                    result.removeAll(simuAlg.getMinFire(e, sync, step, !isRev));
                }
            }
        }
        return result;
    }

    public Map<PlaceNode, Boolean> reachabilitySimulator(final GraphEditor editor,
            Collection<String> causalPredecessorRefs, Collection<String> markingRefs) {

        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        Collection<TransitionNode> causalPredecessors = new ArrayList<>();
        for (String ref : causalPredecessorRefs) {
            Node node = net.getNodeByReference(ref);
            if (node instanceof TransitionNode) {
                causalPredecessors.add((TransitionNode) net.getNodeByReference(ref));
            }
        }
        return reachabilitySimulationTask(editor, causalPredecessors, markingRefs);
    }

    private Map<PlaceNode, Boolean> reachabilitySimulationTask(final GraphEditor editor,
            Collection<TransitionNode> causalPredecessors, Collection<String> markingRefs) {

        Step enabled = simuAlg.getEnabledNodes(sync, phases, isRev);

        Step step = new Step();
        for (Node node : relationAlg.getCommonElements(enabled, causalPredecessors)) {
            if (node instanceof TransitionNode) {
                step.add((TransitionNode) node);
            }
        }

        if (!step.isEmpty()) {
            executeEvents(editor, step);
            reachabilitySimulationTask(editor, causalPredecessors, markingRefs);
        }

        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        for (String ref : markingRefs) {
            Node node = net.getNodeByReference(ref);
            if (node instanceof PlaceNode) {
                ((PlaceNode) node).setForegroundColor(Color.BLUE);
                ((PlaceNode) node).setTokenColor(Color.BLUE);
            }
        }

        return readSONMarking(net);
    }

    public void executeEvents(final GraphEditor editor, Step step) {
        if (step.isEmpty()) return;
        Step traceList = new Step();
        // If clicked on the trace event, do the step forward
        if (branchTrace.isEmpty() && !mainTrace.isEmpty() && (mainTrace.getPosition() < mainTrace.size())) {
            StepRef stepRef = mainTrace.get(mainTrace.getPosition());
            traceList = getStep(editor, stepRef);
        }
        // Otherwise form/use the branch trace
        if (!branchTrace.isEmpty() && (branchTrace.getPosition() < branchTrace.size())) {
            StepRef stepRef = branchTrace.get(branchTrace.getPosition());
            traceList = getStep(editor, stepRef);
        }
        if (!traceList.isEmpty() && traceList.containsAll(step) && step.containsAll(traceList)) {
            step(editor);
            return;
        }
        while (branchTrace.getPosition() < branchTrace.size()) {
            branchTrace.removeCurrent();
        }

        StepRef newStep = new StepRef();
        if (!isRev) {
            newStep.add(">");
        } else {
            newStep.add("<");
        }

        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        for (TransitionNode e : step) {
            newStep.add(net.getNodeReference(e));
        }

        branchTrace.add(newStep);
        step(editor);
    }

    protected Step getStep(final GraphEditor editor, StepRef stepRef) {
        Step result = new Step();
        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        for (int i = 0; i < stepRef.size(); i++) {
            final Node node = net.getNodeByReference(stepRef.get(i));
            if (node instanceof TransitionNode) {
                result.add((TransitionNode) node);
            }
        }
        return result;
    }

    @SuppressWarnings("serial")
    protected final class TraceTableCellRendererImplementation implements TableCellRenderer {
        private final JLabel label = new JLabel() {
            @Override
            public void paint(Graphics g) {
                g.setColor(getBackground());
                g.fillRect(0, 0, getWidth() - 1, getHeight() - 1);
                super.paint(g);
            }
        };

        private boolean isActive(int row, int column) {
            if (column == 0) {
                if (!mainTrace.isEmpty() && branchTrace.isEmpty()) {
                    return row == mainTrace.getPosition();
                }
            } else {
                if (!branchTrace.isEmpty() && (row >= mainTrace.getPosition()) && (row < mainTrace.getPosition() + branchTrace.size())) {
                    return row == mainTrace.getPosition() + branchTrace.getPosition();
                }
            }
            return false;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {

            if (!(value instanceof StepRef)) return null;

            label.setText(value.toString());

            if (isActive(row, column)) {
                label.setBackground(Color.YELLOW);
            } else {
                label.setBackground(Color.WHITE);
            }

            return label;
        }
    }

    protected void setDecoration(final GraphEditor editor, Step enabled) {
        final VisualSON visualNet = (VisualSON) editor.getModel();
        final SON net = visualNet.getMathModel();
        net.refreshAllColor();

        for (TransitionNode e : enabled) {
            e.setForegroundColor(SimulationDecorationSettings.getExcitedComponentColor());
        }

        StepRef refStep = null;
        if (branchTrace.canProgress()) {
            refStep = branchTrace.get(branchTrace.getPosition());
        } else if (branchTrace.isEmpty() && mainTrace.canProgress()) {
            refStep = mainTrace.get(mainTrace.getPosition());
        }

        if (refStep != null) {
            if (refStep.isReverse() == isRev) {
                for (String ref : refStep) {
                    Node n = net.getNodeByReference(ref);
                    if (n != null) {
                        if (n instanceof TransitionNode) {
                            ((TransitionNode) n).setFillColor(new Color(255, 228, 181));
                        }
                    }
                }
            }
        }
    }

    private boolean isEnabled(Node e, Step step) {
        if (step.contains(e)) {
            return true;
        }
        return false;
    }

    @Override
    public void mousePressed(GraphEditorMouseEvent e) {

        Node deepestNode = HitMan.hitDeepest(e.getPosition(), e.getModel().getRoot(),
                node -> {
                    if (node instanceof VisualTransitionNode) {
                        TransitionNode transitionNode = ((VisualTransitionNode) node).getMathTransitionNode();
                        Step enabled = simuAlg.getEnabledNodes(sync, phases, isRev);
                        return isEnabled(transitionNode, enabled);
                    }
                    return false;
                });

        final Framework framework = Framework.getInstance();
        final MainWindow mainWindow = framework.getMainWindow();

        if (deepestNode instanceof VisualTransitionNode) {

            Step enabled = null;
            TransitionNode select = ((VisualTransitionNode) deepestNode).getMathTransitionNode();

            enabled = simuAlg.getEnabledNodes(sync, phases, isRev);

            Step minFire = simuAlg.getMinFire(select, sync, enabled, isRev);
            Step possibleFire = simuAlg.getMinFire(select, sync, enabled, !isRev);
            //remove select node and directed sync cycle
            possibleFire.removeAll(minFire);
            possibleFire.remove(select);

            Step step = new Step();
            step.addAll(minFire);

            minFire.remove(select);

            GraphEditor editor = e.getEditor();
            if (possibleFire.isEmpty()) {
                executeEvents(editor, step);
            } else {
                editor.requestFocus();
                final VisualSON visualNet = (VisualSON) editor.getModel();
                final SON net = visualNet.getMathModel();
                ParallelSimDialog dialog = new ParallelSimDialog(mainWindow,
                        net, possibleFire, minFire, select, isRev, sync);
                dialog.setVisible(true);
                if (dialog.getModalResult() == 1) {
                    step.addAll(dialog.getSelectedEvent());
                    executeEvents(editor, step);
                }
                if (dialog.getModalResult() == 2) {
                    setDecoration(editor, enabled);
                    return;
                }
            }

            if ((autoSimuButton != null) && autoSimuButton.isSelected()) {
                autoSimulator(editor);
            }
        }
    }

    protected boolean isContainerExcited(Container container) {
        if (excitedContainers.containsKey(container)) return excitedContainers.get(container);

        boolean ret = false;

        for (Node node: container.getChildren()) {
            Step enabled = null;
            enabled = simuAlg.getEnabledNodes(sync, phases, isRev);

            if (node instanceof VisualTransitionNode) {
                TransitionNode event = ((VisualTransitionNode) node).getMathTransitionNode();
                ret = ret || isEnabled(event, enabled);
            }

            if (node instanceof Container) {
                ret = ret || isContainerExcited((Container) node);
            }

            if (ret) break;
        }

        excitedContainers.put(container, ret);
        return ret;
    }

    public void setReverse(final GraphEditor editor, boolean rev) {
        this.isRev = rev;
        updateState(editor);
    }

    public void setReverse(final GraphEditor editor, StepRef stepRef) {

        if (stepRef.contains(">")) {
            this.setReverse(editor, false);
        } else {
            this.setReverse(editor, true);
        }
    }

    @Override
    public Decorator getDecorator(final GraphEditor editor) {

        return new Decorator() {
            @Override
            public Decoration getDecoration(Node node) {
                if ((node instanceof VisualPage && !(node instanceof VisualBlock)) || node instanceof VisualGroup) {
                    final boolean ret = isContainerExcited((Container) node);

                    return new ContainerDecoration() {

                        @Override
                        public Color getColorisation() {
                            return null;
                        }

                        @Override
                        public Color getBackground() {
                            return null;
                        }

                        @Override
                        public boolean isContainerExcited() {
                            return ret;
                        }
                    };
                }

                return null;
            }
        };
    }

    @Override
    public void lostOwnership(Clipboard clipboard, Transferable contents) {
    }

}