workcraft/workcraft

View on GitHub
workcraft/DfsPlugin/src/org/workcraft/plugins/dfs/tools/CycleAnalyserTool.java

Summary

Maintainability
A
1 hr
Test Coverage
package org.workcraft.plugins.dfs.tools;

import org.workcraft.dom.visual.SizeHelper;
import org.workcraft.gui.controls.WrapHeaderRenderer;
import org.workcraft.gui.tools.AbstractGraphEditorTool;
import org.workcraft.gui.tools.Decoration;
import org.workcraft.gui.tools.Decorator;
import org.workcraft.gui.tools.GraphEditor;
import org.workcraft.plugins.dfs.VisualDelayComponent;
import org.workcraft.plugins.dfs.VisualDfs;
import org.workcraft.shared.IntDocument;
import org.workcraft.utils.DirectedGraphUtils;
import org.workcraft.utils.GuiUtils;
import org.workcraft.utils.Hierarchy;
import org.workcraft.workspace.WorkspaceEntry;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.awt.event.*;
import java.text.DecimalFormat;
import java.util.List;
import java.util.*;

public class CycleAnalyserTool extends AbstractGraphEditorTool {

    public static final String INFINITY_SYMBOL = Character.toString((char) 0x221E);

    private static final int THROUGHPUT_COLUMN = 0;
    private static final int TOKEN_COLUMN = 1;
    private static final int DELAY_COLUMN = 2;
    private static final int CYCLE_COLUMN = 3;

    private VisualDfs dfs;
    private ArrayList<Cycle> cycles;
    private double minDelay;
    private double maxDelay;
    protected Cycle selectedCycle = null;
    private int cycleCount = 10;

    protected JPanel controlPanel;
    protected JScrollPane infoPanel;
    private JTable cycleTable;
    private JLabel cycleCountLabel;
    private JPanel panel;

    @Override
    public JPanel getControlsPanel(final GraphEditor editor) {
        if (panel != null) {
            return panel;
        }
        controlPanel = new JPanel();
        cycleTable = new JTable(new CycleTableModel());
        cycleTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        TableColumnModel columnModel = cycleTable.getColumnModel();
        TableColumn throughputColumn = columnModel.getColumn(THROUGHPUT_COLUMN);
        throughputColumn.setPreferredWidth(Math.round(throughputColumn.getPreferredWidth() * 1.6f));

        WrapHeaderRenderer wrapHeaderRenderer = new WrapHeaderRenderer();
        columnModel.getColumn(THROUGHPUT_COLUMN).setHeaderRenderer(wrapHeaderRenderer);
        columnModel.getColumn(TOKEN_COLUMN).setHeaderRenderer(wrapHeaderRenderer);
        columnModel.getColumn(DELAY_COLUMN).setHeaderRenderer(wrapHeaderRenderer);
        columnModel.getColumn(CYCLE_COLUMN).setHeaderRenderer(wrapHeaderRenderer);

        cycleTable.setRowHeight(SizeHelper.getComponentHeightFromFont(cycleTable.getFont()));
        cycleTable.setDefaultRenderer(Object.class, new CycleTableCellRenderer());
        cycleTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
        cycleTable.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int selectedRow = cycleTable.getSelectedRow();
                if ((cycles != null) && (selectedRow >= 0) && (selectedRow < cycles.size())) {
                    Cycle curCycle = cycles.get(selectedRow);
                    if (selectedCycle != curCycle) {
                        selectedCycle = curCycle;
                    } else {
                        selectedCycle = null;
                        cycleTable.clearSelection();
                    }
                    editor.repaint();
                    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) {
            }
        });
        infoPanel = new JScrollPane(cycleTable);

        final JTextField cycleCountText = new JTextField();
        Dimension dimension = cycleCountText.getPreferredSize();
        dimension.width = 40;
        cycleCountText.setPreferredSize(dimension);
        cycleCountText.setDocument(new IntDocument(3));
        cycleCountText.setText(String.valueOf(cycleCount));
        cycleCountText.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent arg0) {
                if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        cycleCount = Integer.parseInt(cycleCountText.getText());
                        resetSelectedCycle(editor);
                    } catch (NumberFormatException e) {
                        cycleCountText.setText(String.valueOf(cycleCount));
                    }
                } else if (arg0.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    cycleCountText.setText(String.valueOf(cycleCount));
                }
            }
            @Override
            public void keyReleased(KeyEvent arg0) {
            }
            @Override
            public void keyTyped(KeyEvent arg0) {
            }
        });

        cycleCountText.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent arg0) {
                cycleCountText.setText(String.valueOf(cycleCount));
            }

            @Override
            public void focusLost(FocusEvent arg0) {
                cycleCount = Integer.parseInt(cycleCountText.getText());
                resetSelectedCycle(editor);
            }
        });

        cycleCountLabel = new JLabel();
        cycleCountLabel.setText("Cycle count:");
        cycleCountLabel.setLabelFor(cycleCountText);

        controlPanel.setLayout(new FlowLayout(FlowLayout.LEADING));
        controlPanel.add(cycleCountLabel);
        controlPanel.add(cycleCountText);

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

    @Override
    public void activated(final GraphEditor editor) {
        super.activated(editor);
        dfs = (VisualDfs) editor.getModel();
        cycleTable.clearSelection();
        selectedCycle = null;
        cycles = findCycles();
        if (cycleCountLabel != null) {
            cycleCountLabel.setText("Cycle count (out of " + cycles.size() + "):");
        }
    }

    @Override
    public void deactivated(final GraphEditor editor) {
        super.deactivated(editor);
        cycles = null;
        selectedCycle = null;
        dfs = null;
        cycleTable.clearSelection();
    }

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

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

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

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

    @Override
    public Decorator getDecorator(final GraphEditor editor) {
        return node -> {
            if (node instanceof VisualDelayComponent) {
                if (selectedCycle == null) {
                    double delay = ((VisualDelayComponent) node).getReferencedComponent().getDelay();
                    double range = maxDelay - minDelay;
                    double offset = delay - minDelay;
                    final Color fgColor = (range > 0 &&  offset > 0.8 * range) ? Color.RED : null;
                    return new Decoration() {
                        @Override
                        public Color getColorisation() {
                            return fgColor;
                        }
                        @Override
                        public Color getBackground() {
                            return null;
                        }
                    };
                } else if (selectedCycle.components.contains(node)) {
                    double delay = selectedCycle.getEffectiveDelay((VisualDelayComponent) node);
                    double range = selectedCycle.maxDelay - selectedCycle.minDelay;
                    double offset = delay - selectedCycle.minDelay;
                    int bgIintencity = 150;
                    if (range > 0) {
                        bgIintencity = (int) (bgIintencity + (255 - bgIintencity) * offset / range);
                    }
                    final Color fgColor = (range > 0 &&  offset > 0.8 * range) ? Color.RED : null;
                    final Color bgColor = new Color(bgIintencity, 0, 0);
                    return new Decoration() {
                        @Override
                        public Color getColorisation() {
                            return fgColor;
                        }
                        @Override
                        public Color getBackground() {
                            return bgColor;
                        }
                    };
                }
            }
            return null;
        };
    }

    private ArrayList<Cycle> findCycles() {
        ArrayList<Cycle> result = new ArrayList<>();
        Map<VisualDelayComponent, Set<VisualDelayComponent>> graph = new HashMap<>();
        // Update global min and max delay values
        Collection<VisualDelayComponent> allComponents = Hierarchy.getDescendantsOfType(dfs.getRoot(), VisualDelayComponent.class);
        boolean first = true;
        for (VisualDelayComponent c: allComponents) {
            graph.put(c, dfs.getPostset(c, VisualDelayComponent.class));
            double delay = c.getReferencedComponent().getDelay();
            if (first || minDelay > delay) {
                minDelay = delay;
            }
            if (first || maxDelay < delay) {
                maxDelay = delay;
            }
            first = false;
        }
        for (List<VisualDelayComponent> cycle : DirectedGraphUtils.findSimpleCycles(graph)) {
            result.add(new Cycle(dfs, new LinkedHashSet<>(cycle)));
        }
        Collections.sort(result);
        return result;
    }

    private void resetSelectedCycle(final GraphEditor editor) {
        selectedCycle = null;
        cycleTable.tableChanged(null);
        editor.repaint();
    }

    @SuppressWarnings("serial")
    private final class CycleTableModel extends AbstractTableModel {
        @Override
        public int getColumnCount() {
            return 4;
        }

        @Override
        public String getColumnName(int column) {
            String result;
            switch (column) {
            case CYCLE_COLUMN:
                result = "Cycle";
                break;
            case TOKEN_COLUMN:
                result = "Spread tokens";
                break;
            case DELAY_COLUMN:
                result = "Delay";
                break;
            case THROUGHPUT_COLUMN:
                result = "Throughput";
                break;
            default:
                result = "";
                break;
            }
            return result;
        }

        @Override
        public int getRowCount() {
            if (cycles != null) {
                return Math.min(cycleCount, cycles.size());
            }
            return 0;
        }

        @Override
        public Object getValueAt(int row, int col) {
            Object result = null;
            Cycle cycle = cycles.get(row);
            if (cycle != null) {
                switch (col) {
                case THROUGHPUT_COLUMN:
                    if (cycle.totalDelay == 0) {
                        result = INFINITY_SYMBOL;
                    } else {
                        result = new DecimalFormat("#.###").format(cycle.throughput);
                    }
                    break;
                case TOKEN_COLUMN:
                    result = cycle.tokenCount;
                    break;
                case DELAY_COLUMN:
                    result = new DecimalFormat("#.###").format(cycle.totalDelay);
                    break;
                case CYCLE_COLUMN:
                    result = cycle.toString();
                    break;
                default:
                    result = null;
                    break;
                }
            }
            return result;
        }
    }

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

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

            label.setBorder(GuiUtils.getTableCellBorder());
            if ((cycles != null) && (row >= 0) && (row < cycles.size())) {
                label.setText(value.toString());
                label.setToolTipText(cycles.get(row).toString());
            }
            if (isSelected) {
                label.setBackground(table.getSelectionBackground());
            } else {
                label.setBackground(table.getBackground());
            }
            return label;
        }
    }

}