All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooq.debug.console.DebuggerPane Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2009-2013, Lukas Eder, [email protected]
 *                          Christopher Deckers, [email protected]
 * All rights reserved.
 *
 * This software is licensed to you under the Apache License, Version 2.0
 * (the "License"); You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * . Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * . Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * . Neither the name "jOOQ" nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package org.jooq.debug.console;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.jooq.debug.Breakpoint;
import org.jooq.debug.BreakpointHit;
import org.jooq.debug.BreakpointHitHandler;
import org.jooq.debug.Debugger;
import org.jooq.debug.console.misc.CheckBoxNode;
import org.jooq.debug.console.misc.CheckBoxNodeEditor;
import org.jooq.debug.console.misc.CheckBoxNodeRenderer;
import org.jooq.debug.console.misc.InvisibleSplitPane;
import org.jooq.debug.console.misc.TreeDataTip;

/**
 * @author Christopher Deckers
 */
@SuppressWarnings({"serial", "hiding"})
public class DebuggerPane extends JPanel {

    private final ImageIcon BREAKPOINT_ON_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointOn16.png"));
    private final ImageIcon BREAKPOINT_OFF_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointOff16.png"));
    private final ImageIcon BREAKPOINT_HIT_BEFORE_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointHit16.png"));
    private final ImageIcon BREAKPOINT_HIT_AFTER_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointHitAfter16.png"));
    private final ImageIcon STACK_TRACE_ELEMENT_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/StackTraceElement16.png"));

    private Debugger debugger;
    private DefaultMutableTreeNode rootNode;
    private JTree breakpointTree;
    private DefaultTreeModel breakpointTreeModel;
    private JPanel eastPane;

    public DebuggerPane(Debugger debugger) {
        super(new BorderLayout());
        setBorder(BorderFactory.createEmptyBorder(2, 5, 5, 5));
        setOpaque(false);
        this.debugger = debugger;
        JPanel westPane = new JPanel(new BorderLayout());
        westPane.setBorder(BorderFactory.createTitledBorder("Breakpoints"));
        westPane.setOpaque(false);
        JPanel breakpointAddPane = new JPanel(new GridBagLayout());
        breakpointAddPane.setOpaque(false);
        breakpointAddPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 2, 0));
        breakpointAddPane.add(new JLabel("Name "), new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
        final JTextField addBreakpointTextField = new JTextField(7);
        breakpointAddPane.add(addBreakpointTextField, new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 0), 0, 0));
        final JButton addBreakpointButton = new JButton("Add");
        addBreakpointButton.setOpaque(false);
        addBreakpointButton.setEnabled(false);
        breakpointAddPane.add(addBreakpointButton, new GridBagConstraints(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 0), 0, 0));
        addBreakpointTextField.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void removeUpdate(DocumentEvent e) {
                adjustStates();
            }
            @Override
            public void insertUpdate(DocumentEvent e) {
                adjustStates();
            }
            @Override
            public void changedUpdate(DocumentEvent e) {
                adjustStates();
            }
            private void adjustStates() {
                // TODO: restrict to unique names?
                boolean isEnabled = addBreakpointTextField.getText().length() > 0;
                addBreakpointButton.setEnabled(isEnabled);
            }
        });
        ActionListener addBreakpointActionListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String name = addBreakpointTextField.getText();
                if(name.length() == 0) {
                    return;
                }
                addBreakpointTextField.setText("");
                addBreakpoint(name);
            }
        };
        addBreakpointTextField.addActionListener(addBreakpointActionListener);
        addBreakpointButton.addActionListener(addBreakpointActionListener);
        westPane.add(breakpointAddPane, BorderLayout.NORTH);
        rootNode = new DefaultMutableTreeNode();
        breakpointTreeModel = new DefaultTreeModel(rootNode) {
            @Override
            public void valueForPathChanged(TreePath path, Object newValue) {
                if(newValue instanceof CheckBoxNode) {
                    CheckBoxNode node = (CheckBoxNode)path.getLastPathComponent();
                    boolean isSelected = ((CheckBoxNode) newValue).isSelected();
                    node.setSelected(isSelected);
                    Breakpoint breakpoint = (Breakpoint)node.getUserObject();
                    super.valueForPathChanged(path, breakpoint);
                    if(isSelected) {
                        DebuggerPane.this.debugger.setBreakpointHitHandler(breakpointHitHandler);
                        DebuggerPane.this.debugger.addBreakpoint(breakpoint);
                    } else {
                        DebuggerPane.this.debugger.removeBreakpoint(breakpoint);
                        if(DebuggerPane.this.debugger.getBreakpoints() == null) {
                            DebuggerPane.this.debugger.setBreakpointHitHandler(null);
                        }
                    }
                }
            }
        };
        breakpointTree = new JTree(breakpointTreeModel);
        breakpointTree.setRootVisible(false);
        breakpointTree.setShowsRootHandles(true);
        breakpointTree.setCellRenderer(new DefaultTreeCellRenderer() {
            @Override
            public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
                Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
                if(c instanceof JLabel) {
                    Icon icon = null;
                    if(value instanceof CheckBoxNode) {
                        icon = ((CheckBoxNode) value).isSelected()? BREAKPOINT_ON_ICON: BREAKPOINT_OFF_ICON;
                    } else if(value instanceof BreakpointHitNode) {
                        if(((BreakpointHitNode) value).getUserObject().isBeforeExecution()) {
                            icon = BREAKPOINT_HIT_BEFORE_ICON;
                        } else {
                            icon = BREAKPOINT_HIT_AFTER_ICON;
                        }
                    } else if(value instanceof StackTraceElementNode) {
                        icon = STACK_TRACE_ELEMENT_ICON;
                    }
                    ((JLabel) c).setIcon(icon);
                }
                return c;
            }
        });
        // Order matters because we want the editor to have the old cell renderer as a base.
        breakpointTree.setCellEditor(new CheckBoxNodeEditor(breakpointTree));
        breakpointTree.setCellRenderer(new CheckBoxNodeRenderer(breakpointTree));
        breakpointTree.setEditable(true);
        TreeDataTip.activate(breakpointTree);
        JScrollPane breakpointTreeScrollPane = new JScrollPane(breakpointTree);
        breakpointTreeScrollPane.setPreferredSize(new Dimension(200, 200));
        westPane.add(breakpointTreeScrollPane, BorderLayout.CENTER);
        JPanel breakpointRemovePane = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        breakpointRemovePane.setOpaque(false);
        breakpointRemovePane.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
        final JButton removeBreakpointButton = new JButton("Remove");
        removeBreakpointButton.setOpaque(false);
        removeBreakpointButton.setEnabled(false);
        removeBreakpointButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                TreePath[] paths = breakpointTree.getSelectionPaths();
                boolean isValid = paths != null && paths.length > 0;
                if(isValid) {
                    for(int i=0; i breakpointList = new ArrayList();
                    List breakpointHitNodeList = new ArrayList();
                    // TODO: list of after exec
                    breakpointTree.cancelEditing();
                    for(int i=0; i 0;
                if(isValid) {
                    for(int i=0; i= 0 && !breakpointTree.isRowSelected(row)) {
                        breakpointTree.setSelectionRow(row);
                    }
                }
                maybeShowPopup(e);
            }
            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }
            private void maybeShowPopup(MouseEvent e) {
                if(e.isPopupTrigger()) {
                    TreePath[] selectionPaths = breakpointTree.getSelectionPaths();
                    if(selectionPaths != null && selectionPaths.length > 0) {
                        JPopupMenu popupMenu = new JPopupMenu();
                        {
                            final List nodeList = new ArrayList();
                            for(TreePath selectionPath: selectionPaths) {
                                Object o = selectionPath.getLastPathComponent();
                                if(!(o instanceof BreakpointHitNode)) {
                                    nodeList.clear();
                                    break;
                                }
                                nodeList.add((BreakpointHitNode)o);
                            }
                            if(!nodeList.isEmpty()) {
                                JMenuItem proceedMenuItem = new JMenuItem("Proceed");
                                proceedMenuItem.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        for(BreakpointHitNode node: nodeList) {
                                            proceedBreakpointHit(node);
                                        }
                                    }
                                });
                                popupMenu.add(proceedMenuItem);
                            }
                        }
                        if(selectionPaths.length == 1) {
                            Object o = selectionPaths[0].getLastPathComponent();
                            if(o instanceof BreakpointHitNode) {
                                final BreakpointHit breakpointHit = ((BreakpointHitNode) o).getUserObject();
                                JMenuItem copyStackToClipboardMenuItem = new JMenuItem("Copy Call Stack to Clipboard");
                                copyStackToClipboardMenuItem.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        StringWriter sw = new StringWriter();
                                        Throwable throwable = new Exception("Statement Stack trace");
                                        throwable.setStackTrace(breakpointHit.getStackTrace());
                                        throwable.printStackTrace(new PrintWriter(sw));
                                        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                                        clipboard.setContents(new StringSelection(sw.toString()), null);
                                    }
                                });
                                popupMenu.add(copyStackToClipboardMenuItem);
                                JMenuItem dumpStackToConsoleMenuItem = new JMenuItem("Dump Call Stack");
                                dumpStackToConsoleMenuItem.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        Throwable throwable = new Exception("Statement Stack trace");
                                        throwable.setStackTrace(breakpointHit.getStackTrace());
                                        throwable.printStackTrace();
                                    }
                                });
                                popupMenu.add(dumpStackToConsoleMenuItem);
                            }
                        }
                        if(popupMenu.getComponentCount() > 0) {
                            popupMenu.show(e.getComponent(), e.getX(), e.getY());
                        }
                    }
                }
            }
        });
        breakpointRemovePane.add(removeBreakpointButton);
        westPane.add(breakpointRemovePane, BorderLayout.SOUTH);
        eastPane = new JPanel(new BorderLayout());
        eastPane.setOpaque(false);
        add(new InvisibleSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, westPane, eastPane), BorderLayout.CENTER);
    }

    private void addBreakpoint(String name) {
        breakpointTree.cancelEditing();
        Breakpoint breakpoint = new Breakpoint();
        CheckBoxNode breakpointNode = new CheckBoxNode(breakpoint, name, true);
        rootNode.add(breakpointNode);
        breakpointTreeModel.nodesWereInserted(rootNode, new int[] { rootNode.getIndex(breakpointNode) });
        breakpointTree.expandPath(new TreePath(rootNode));
        debugger.addBreakpoint(breakpoint);
        DebuggerPane.this.debugger.setBreakpointHitHandler(breakpointHitHandler);
        breakpointTree.setSelectionPath(new TreePath(new Object[] { rootNode, breakpointNode }));
    }

    void modifyBreakpoint(Breakpoint breakpoint) {
        int childCount = rootNode.getChildCount();

        for (int i = 0; i < childCount; i++) {
            CheckBoxNode checkBoxNode = (CheckBoxNode) rootNode.getChildAt(i);
            Breakpoint b = (Breakpoint) checkBoxNode.getUserObject();

            if (b.equals(breakpoint)) {
                checkBoxNode.setUserObject(breakpoint);

                if (checkBoxNode.isSelected()) {
                    debugger.addBreakpoint(breakpoint);
                }

                break;
            }
        }
    }

    private class StackTraceElementNode extends DefaultMutableTreeNode {
        private String name;
        public StackTraceElementNode(StackTraceElement stackTraceElement) {
            super(stackTraceElement);
            String className = stackTraceElement.getClassName();
            className = className.substring(className.lastIndexOf('.') + 1);
            String fileName = stackTraceElement.getFileName();
            int lineNumber = stackTraceElement.getLineNumber();
            String fileInfo = fileName != null && lineNumber >= 0? fileName + ":" + lineNumber: "";
            name = className + '.' + stackTraceElement.getMethodName() + "(" + fileInfo + ")";
        }
        @Override
        public String toString() {
            return name;
        }
    }

    class BreakpointHitNode extends DefaultMutableTreeNode {
        public BreakpointHitNode(BreakpointHit breakpointHit) {
            super(breakpointHit);
            StackTraceElement[] stackTrace = breakpointHit.getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                add(new StackTraceElementNode(stackTraceElement));
            }
        }
        @Override
        public BreakpointHit getUserObject() {
            return (BreakpointHit)super.getUserObject();
        }
        private boolean isLocked = true;
        public void proceed() {
            synchronized (this) {
                isLocked = false;
                notifyAll();
            }
        }
        public boolean isLocked() {
            synchronized (this) {
                return isLocked;
            }
        }
        @Override
        public String toString() {
            return "Thread [" + getUserObject().getThreadName() + "]";
        }
    }

    void proceedBreakpointHit(BreakpointHitNode breakpointHitNode) {
        CheckBoxNode parentNode = (CheckBoxNode)breakpointHitNode.getParent();
        int index = parentNode.getIndex(breakpointHitNode);
        parentNode.remove(index);
        breakpointTreeModel.nodesWereRemoved(parentNode, new int[] {index}, new Object[] {breakpointHitNode});
        breakpointHitNode.proceed();
    }

    private BreakpointHitHandler breakpointHitHandler = new BreakpointHitHandler() {
        @Override
        public void processBreakpointBeforeExecutionHit(final BreakpointHit breakpointHit) {
            if(SwingUtilities.isEventDispatchThread()) {
                new IllegalStateException("Breakpoint triggered from UI thread: cannot break because the debugger needs a live UI thread!").printStackTrace();
                return;
            }
            handleBreakpoint(breakpointHit);
        }
        @Override
        public void processBreakpointAfterExecutionHit(BreakpointHit breakpointHit) {
            if(SwingUtilities.isEventDispatchThread()) {
                new IllegalStateException("Breakpoint triggered from UI thread: cannot break because the debugger needs a live UI thread!").printStackTrace();
                return;
            }
            handleBreakpoint(breakpointHit);
        }
        private void handleBreakpoint(final BreakpointHit breakpointHit) {
            final BreakpointHitNode node = new BreakpointHitNode(breakpointHit);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    int childCount = rootNode.getChildCount();
                    boolean isFound = false;
                    for(int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy