| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.unscramble; |
| |
| import com.intellij.codeInsight.highlighting.HighlightManager; |
| import com.intellij.execution.ui.ConsoleView; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.DataManager; |
| import com.intellij.notification.NotificationGroup; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.ide.CopyPasteManager; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.project.DumbAwareAction; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.MessageType; |
| import com.intellij.openapi.ui.Splitter; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.openapi.wm.ToolWindowId; |
| import com.intellij.ui.*; |
| import com.intellij.ui.components.JBList; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.ui.UIUtil; |
| |
| import javax.swing.*; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.ListSelectionEvent; |
| import javax.swing.event.ListSelectionListener; |
| import java.awt.*; |
| import java.awt.datatransfer.StringSelection; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| import static com.intellij.icons.AllIcons.Debugger.ThreadStates.*; |
| |
| /** |
| * @author Jeka |
| * @author Konstantin Bulenkov |
| */ |
| public class ThreadDumpPanel extends JPanel { |
| private static final Icon PAUSE_ICON_DAEMON = new LayeredIcon(Paused, Daemon_sign); |
| private static final Icon LOCKED_ICON_DAEMON = new LayeredIcon(Locked, Daemon_sign); |
| private static final Icon RUNNING_ICON_DAEMON = new LayeredIcon(Running, Daemon_sign); |
| private static final Icon SOCKET_ICON_DAEMON = new LayeredIcon(Socket, Daemon_sign); |
| private static final Icon IDLE_ICON_DAEMON = new LayeredIcon(Idle, Daemon_sign); |
| private static final Icon EDT_BUSY_ICON_DAEMON = new LayeredIcon(EdtBusy, Daemon_sign); |
| private static final Icon IO_ICON_DAEMON = new LayeredIcon(IO, Daemon_sign); |
| private final JBList myThreadList; |
| private final List<ThreadState> myThreadDump; |
| private final JPanel myFilterPanel; |
| private final SearchTextField myFilterField; |
| |
| public ThreadDumpPanel(final Project project, final ConsoleView consoleView, final DefaultActionGroup toolbarActions, final List<ThreadState> threadDump) { |
| super(new BorderLayout()); |
| myThreadDump = threadDump; |
| |
| myFilterField = new SearchTextField(); |
| myFilterField.addDocumentListener(new DocumentAdapter() { |
| @Override |
| protected void textChanged(DocumentEvent e) { |
| updateThreadList(); |
| } |
| }); |
| |
| myFilterPanel = new JPanel(new BorderLayout()); |
| myFilterPanel.add(new JLabel("Filter:"), BorderLayout.WEST); |
| myFilterPanel.add(myFilterField); |
| myFilterPanel.setVisible(false); |
| |
| myThreadList = new JBList(new DefaultListModel()); |
| myThreadList.setCellRenderer(new ThreadListCellRenderer()); |
| myThreadList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
| myThreadList.addListSelectionListener(new ListSelectionListener() { |
| public void valueChanged(final ListSelectionEvent e) { |
| int index = myThreadList.getSelectedIndex(); |
| if (index >= 0) { |
| ThreadState selection = (ThreadState)myThreadList.getModel().getElementAt(index); |
| AnalyzeStacktraceUtil.printStacktrace(consoleView, selection.getStackTrace()); |
| } |
| else { |
| AnalyzeStacktraceUtil.printStacktrace(consoleView, ""); |
| } |
| myThreadList.repaint(); |
| } |
| }); |
| |
| FilterAction filterAction = new FilterAction(); |
| filterAction.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_FIND).getShortcutSet(), myThreadList); |
| toolbarActions.add(filterAction); |
| toolbarActions.add(new CopyToClipboardAction(threadDump, project)); |
| toolbarActions.add(new SortThreadsAction()); |
| add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false).getComponent(), BorderLayout.WEST); |
| |
| JPanel leftPanel = new JPanel(new BorderLayout()); |
| leftPanel.add(myFilterPanel, BorderLayout.NORTH); |
| leftPanel.add(ScrollPaneFactory.createScrollPane(myThreadList, SideBorder.LEFT | SideBorder.RIGHT), BorderLayout.CENTER); |
| |
| final Splitter splitter = new Splitter(false, 0.3f); |
| splitter.setFirstComponent(leftPanel); |
| splitter.setSecondComponent(consoleView.getComponent()); |
| add(splitter, BorderLayout.CENTER); |
| |
| new ListSpeedSearch(myThreadList).setComparator(new SpeedSearchComparator(false, true)); |
| |
| updateThreadList(); |
| |
| final Editor editor = CommonDataKeys.EDITOR.getData(DataManager.getInstance().getDataContext(consoleView.getPreferredFocusableComponent())); |
| if (editor != null) { |
| editor.getDocument().addDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() { |
| @Override |
| public void documentChanged(com.intellij.openapi.editor.event.DocumentEvent e) { |
| String filter = myFilterField.getText(); |
| if (StringUtil.isNotEmpty(filter)) { |
| highlightOccurrences(filter, project, editor); |
| } |
| } |
| }, consoleView); |
| } |
| } |
| |
| private void updateThreadList() { |
| String text = myFilterPanel.isVisible() ? myFilterField.getText() : ""; |
| DefaultListModel model = (DefaultListModel)myThreadList.getModel(); |
| model.clear(); |
| for (ThreadState state : myThreadDump) { |
| if (StringUtil.containsIgnoreCase(state.getStackTrace(), text) || StringUtil.containsIgnoreCase(state.getName(), text)) { |
| //noinspection unchecked |
| model.addElement(state); |
| } |
| } |
| if (!model.isEmpty()) { |
| myThreadList.setSelectedIndex(0); |
| } |
| myThreadList.revalidate(); |
| myThreadList.repaint(); |
| } |
| |
| private static void highlightOccurrences(String filter, Project project, Editor editor) { |
| final HighlightManager highlightManager = HighlightManager.getInstance(project); |
| EditorColorsManager colorManager = EditorColorsManager.getInstance(); |
| final TextAttributes attributes = colorManager.getGlobalScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES); |
| String documentText = editor.getDocument().getText(); |
| int i = -1; |
| while (true) { |
| int nextOccurrence = StringUtil.indexOfIgnoreCase(documentText, filter, i + 1); |
| if (nextOccurrence < 0) { |
| break; |
| } |
| i = nextOccurrence; |
| highlightManager.addOccurrenceHighlight(editor, i, i + filter.length(), attributes, |
| HighlightManager.HIDE_BY_TEXT_CHANGE, null, null); |
| } |
| } |
| |
| private static Icon getThreadStateIcon(final ThreadState threadState) { |
| final boolean daemon = threadState.isDaemon(); |
| if (threadState.isSleeping()) { |
| return daemon ? PAUSE_ICON_DAEMON : Paused; |
| } |
| if (threadState.isWaiting()) { |
| return daemon ? LOCKED_ICON_DAEMON : Locked; |
| } |
| if (threadState.getOperation() == ThreadOperation.Socket) { |
| return daemon ? SOCKET_ICON_DAEMON : Socket; |
| } |
| if (threadState.getOperation() == ThreadOperation.IO) { |
| return daemon ? IO_ICON_DAEMON : IO; |
| } |
| if (threadState.isEDT()) { |
| if ("idle".equals(threadState.getThreadStateDetail())) { |
| return daemon ? IDLE_ICON_DAEMON : Idle; |
| } |
| return daemon ? EDT_BUSY_ICON_DAEMON : EdtBusy; |
| } |
| return daemon ? RUNNING_ICON_DAEMON : Running; |
| } |
| |
| private enum StateCode {RUN, RUN_IO, RUN_SOCKET, PAUSED, LOCKED, EDT, IDLE} |
| private static StateCode getThreadStateCode(final ThreadState state) { |
| if (state.isSleeping()) return StateCode.PAUSED; |
| if (state.isWaiting()) return StateCode.LOCKED; |
| if (state.getOperation() == ThreadOperation.Socket) return StateCode.RUN_SOCKET; |
| if (state.getOperation() == ThreadOperation.IO) return StateCode.RUN_IO; |
| if (state.isEDT()) { |
| return "idle".equals(state.getThreadStateDetail()) ? StateCode.IDLE : StateCode.EDT; |
| } |
| return StateCode.RUN; |
| } |
| |
| private static SimpleTextAttributes getAttributes(final ThreadState threadState) { |
| if (threadState.isSleeping()) { |
| return SimpleTextAttributes.GRAY_ATTRIBUTES; |
| } |
| if (threadState.isEmptyStackTrace() || ThreadDumpParser.isKnownJdkThread(threadState)) { |
| return new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, Color.GRAY.brighter()); |
| } |
| if (threadState.isEDT()) { |
| return SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; |
| } |
| return SimpleTextAttributes.REGULAR_ATTRIBUTES; |
| } |
| |
| private static class ThreadListCellRenderer extends ColoredListCellRenderer { |
| |
| protected void customizeCellRenderer(final JList list, final Object value, final int index, final boolean selected, final boolean hasFocus) { |
| ThreadState threadState = (ThreadState) value; |
| setIcon(getThreadStateIcon(threadState)); |
| if (!selected) { |
| ThreadState selectedThread = (ThreadState)list.getSelectedValue(); |
| if (threadState.isDeadlocked()) { |
| setBackground(LightColors.RED); |
| } |
| else if (selectedThread != null && threadState.isAwaitedBy(selectedThread)) { |
| setBackground(JBColor.YELLOW); |
| } |
| else { |
| setBackground(UIUtil.getListBackground()); |
| } |
| } |
| SimpleTextAttributes attrs = getAttributes(threadState); |
| append(threadState.getName() + " (", attrs); |
| String detail = threadState.getThreadStateDetail(); |
| if (detail == null) { |
| detail = threadState.getState(); |
| } |
| if (detail.length() > 30) { |
| detail = detail.substring(0, 30) + "..."; |
| } |
| append(detail, attrs); |
| append(")", attrs); |
| if (threadState.getExtraState() != null) { |
| append(" [" + threadState.getExtraState() + "]", attrs); |
| } |
| } |
| } |
| |
| public void selectStackFrame(int index) { |
| myThreadList.setSelectedIndex(index); |
| } |
| |
| private class SortThreadsAction extends DumbAwareAction { |
| private final Comparator<ThreadState> BY_TYPE = new Comparator<ThreadState>() { |
| public int compare(ThreadState o1, ThreadState o2) { |
| final int s1 = getThreadStateCode(o1).ordinal(); |
| final int s2 = getThreadStateCode(o2).ordinal(); |
| if (s1 == s2) { |
| return o1.getName().compareToIgnoreCase(o2.getName()); |
| } else { |
| return s1 < s2 ? - 1 : 1; |
| } |
| } |
| }; |
| |
| private final Comparator<ThreadState> BY_NAME = new Comparator<ThreadState>() { |
| public int compare(ThreadState o1, ThreadState o2) { |
| return o1.getName().compareToIgnoreCase(o2.getName()); |
| } |
| }; |
| private Comparator<ThreadState> COMPARATOR = BY_TYPE; |
| private static final String TYPE_LABEL = "Sort threads by type"; |
| private static final String NAME_LABEL = "Sort threads by name"; |
| public SortThreadsAction() { |
| super(TYPE_LABEL); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final DefaultListModel model = (DefaultListModel)myThreadList.getModel(); |
| final ThreadState selected = (ThreadState)myThreadList.getSelectedValue(); |
| ArrayList<ThreadState> states = new ArrayList<ThreadState>(); |
| for (int i = 0; i < model.getSize(); i++) { |
| states.add((ThreadState)model.getElementAt(i)); |
| } |
| Collections.sort(states, COMPARATOR); |
| int selectedIndex = 0; |
| for (int i = 0; i < states.size(); i++) { |
| final ThreadState state = states.get(i); |
| model.setElementAt(state, i); |
| if (state == selected) { |
| selectedIndex = i; |
| } |
| } |
| myThreadList.setSelectedIndex(selectedIndex); |
| COMPARATOR = COMPARATOR == BY_TYPE ? BY_NAME : BY_TYPE; |
| update(e); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| e.getPresentation().setIcon(COMPARATOR == BY_TYPE ? AllIcons.ObjectBrowser.SortByType : AllIcons.Icons.Inspector.SortByName); |
| e.getPresentation().setText(COMPARATOR == BY_TYPE ? TYPE_LABEL : NAME_LABEL); |
| } |
| } |
| private static class CopyToClipboardAction extends DumbAwareAction { |
| private static final NotificationGroup GROUP = NotificationGroup.toolWindowGroup("Analyze thread dump", ToolWindowId.RUN, false); |
| private final List<ThreadState> myThreadDump; |
| private final Project myProject; |
| |
| public CopyToClipboardAction(List<ThreadState> threadDump, Project project) { |
| super("Copy to Clipboard", "Copy whole thread dump to clipboard", PlatformIcons.COPY_ICON); |
| myThreadDump = threadDump; |
| myProject = project; |
| } |
| |
| public void actionPerformed(AnActionEvent e) { |
| final StringBuilder buf = new StringBuilder(); |
| buf.append("Full thread dump").append("\n\n"); |
| for (ThreadState state : myThreadDump) { |
| buf.append(state.getStackTrace()).append("\n\n"); |
| } |
| CopyPasteManager.getInstance().setContents(new StringSelection(buf.toString())); |
| |
| GROUP.createNotification("Full thread dump was successfully copied to clipboard", MessageType.INFO).notify(myProject); |
| } |
| } |
| |
| private class FilterAction extends ToggleAction implements DumbAware { |
| |
| FilterAction() { |
| super("Filter", "Show only threads containing a specific string", AllIcons.General.Filter); |
| } |
| |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return myFilterPanel.isVisible(); |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| myFilterPanel.setVisible(state); |
| if (state) { |
| IdeFocusManager.getInstance(getEventProject(e)).requestFocus(myFilterField, true); |
| myFilterField.selectText(); |
| } |
| updateThreadList(); |
| } |
| } |
| } |