| /* |
| * 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.execution.ui.ConsoleView; |
| import com.intellij.execution.ui.RunContentDescriptor; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.ide.util.PropertiesComponent; |
| import com.intellij.openapi.actionSystem.DefaultActionGroup; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileChooser.FileChooser; |
| import com.intellij.openapi.fileChooser.FileChooserDescriptor; |
| import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; |
| import com.intellij.openapi.help.HelpManager; |
| import com.intellij.openapi.options.ConfigurationException; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vcs.ProjectLevelVcsManager; |
| import com.intellij.openapi.vcs.configurable.VcsContentAnnotationConfigurable; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.ui.GuiUtils; |
| import com.intellij.ui.ListCellRendererWrapper; |
| import com.intellij.ui.TextFieldWithHistory; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.text.CharArrayUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * @author cdr |
| */ |
| public class UnscrambleDialog extends DialogWrapper { |
| @NonNls private static final String PROPERTY_LOG_FILE_HISTORY_URLS = "UNSCRAMBLE_LOG_FILE_URL"; |
| @NonNls private static final String PROPERTY_LOG_FILE_LAST_URL = "UNSCRAMBLE_LOG_FILE_LAST_URL"; |
| @NonNls private static final String PROPERTY_UNSCRAMBLER_NAME_USED = "UNSCRAMBLER_NAME_USED"; |
| private static final Condition<ThreadState> DEADLOCK_CONDITION = new Condition<ThreadState>() { |
| @Override |
| public boolean value(ThreadState state) { |
| return state.isDeadlocked(); |
| } |
| }; |
| |
| private final Project myProject; |
| private JPanel myEditorPanel; |
| private JPanel myLogFileChooserPanel; |
| private JComboBox myUnscrambleChooser; |
| private JPanel myPanel; |
| private TextFieldWithHistory myLogFile; |
| private JCheckBox myUseUnscrambler; |
| private JPanel myUnscramblePanel; |
| private JCheckBox myOnTheFly; |
| private JPanel myBottomPanel; |
| protected AnalyzeStacktraceUtil.StacktraceEditorPanel myStacktraceEditorPanel; |
| private VcsContentAnnotationConfigurable myConfigurable; |
| |
| public UnscrambleDialog(Project project) { |
| super(false); |
| myProject = project; |
| |
| populateRegisteredUnscramblerList(); |
| myUnscrambleChooser.addActionListener(new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| UnscrambleSupport unscrambleSupport = getSelectedUnscrambler(); |
| GuiUtils.enableChildren(myLogFileChooserPanel, unscrambleSupport != null); |
| } |
| }); |
| myUseUnscrambler.addActionListener(new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| useUnscramblerChanged(); |
| } |
| }); |
| myOnTheFly.setSelected(Registry.get("analyze.exceptions.on.the.fly").asBoolean()); |
| myOnTheFly.addActionListener(new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| Registry.get("analyze.exceptions.on.the.fly").setValue(myOnTheFly.isSelected()); |
| } |
| }); |
| createLogFileChooser(); |
| createEditor(); |
| reset(); |
| |
| setTitle(IdeBundle.message("unscramble.dialog.title")); |
| init(); |
| } |
| |
| private void useUnscramblerChanged() { |
| boolean selected = myUseUnscrambler.isSelected(); |
| GuiUtils.enableChildren(myUnscramblePanel, selected, myUseUnscrambler); |
| } |
| |
| private void reset() { |
| final List<String> savedUrls = getSavedLogFileUrls(); |
| myLogFile.setHistorySize(10); |
| myLogFile.setHistory(savedUrls); |
| |
| String lastUrl = getLastUsedLogUrl(); |
| if (lastUrl == null && !savedUrls.isEmpty()) { |
| lastUrl = savedUrls.get(savedUrls.size() - 1); |
| } |
| if (lastUrl != null) { |
| myLogFile.setText(lastUrl); |
| myLogFile.setSelectedItem(lastUrl); |
| } |
| final UnscrambleSupport selectedUnscrambler = getSavedUnscrambler(); |
| |
| final int count = myUnscrambleChooser.getItemCount(); |
| int index = 0; |
| if (selectedUnscrambler != null) { |
| for (int i = 0; i < count; i++) { |
| final UnscrambleSupport unscrambleSupport = (UnscrambleSupport)myUnscrambleChooser.getItemAt(i); |
| if (unscrambleSupport != null && Comparing.strEqual(unscrambleSupport.getPresentableName(), selectedUnscrambler.getPresentableName())) { |
| index = i; |
| break; |
| } |
| } |
| } |
| if (count > 0) { |
| myUseUnscrambler.setEnabled(true); |
| myUnscrambleChooser.setSelectedIndex(index); |
| myUseUnscrambler.setSelected(selectedUnscrambler != null); |
| } |
| else { |
| myUseUnscrambler.setEnabled(false); |
| } |
| |
| useUnscramblerChanged(); |
| myStacktraceEditorPanel.pasteTextFromClipboard(); |
| } |
| |
| private void createUIComponents() { |
| myBottomPanel = new JPanel(new BorderLayout()); |
| if (ProjectLevelVcsManager.getInstance(myProject).hasActiveVcss()) { |
| myConfigurable = new VcsContentAnnotationConfigurable(myProject); |
| myBottomPanel.add(myConfigurable.createComponent(), BorderLayout.CENTER); |
| myConfigurable.reset(); |
| } |
| } |
| |
| public static String getLastUsedLogUrl() { |
| return PropertiesComponent.getInstance().getValue(PROPERTY_LOG_FILE_LAST_URL); |
| } |
| |
| @Nullable |
| public static UnscrambleSupport getSavedUnscrambler() { |
| final List<UnscrambleSupport> registeredUnscramblers = getRegisteredUnscramblers(); |
| final String savedUnscramblerName = PropertiesComponent.getInstance().getValue(PROPERTY_UNSCRAMBLER_NAME_USED); |
| UnscrambleSupport selectedUnscrambler = null; |
| for (final UnscrambleSupport unscrambleSupport : registeredUnscramblers) { |
| if (Comparing.strEqual(unscrambleSupport.getPresentableName(), savedUnscramblerName)) { |
| selectedUnscrambler = unscrambleSupport; |
| } |
| } |
| return selectedUnscrambler; |
| } |
| |
| public static List<String> getSavedLogFileUrls() { |
| final List<String> res = new ArrayList<String>(); |
| final String savedUrl = PropertiesComponent.getInstance().getValue(PROPERTY_LOG_FILE_HISTORY_URLS); |
| final String[] strings = savedUrl == null ? ArrayUtil.EMPTY_STRING_ARRAY : savedUrl.split(":::"); |
| for (int i = 0; i != strings.length; ++i) { |
| res.add(strings[i]); |
| } |
| return res; |
| } |
| |
| @Nullable |
| private UnscrambleSupport getSelectedUnscrambler() { |
| if (!myUseUnscrambler.isSelected()) return null; |
| return (UnscrambleSupport)myUnscrambleChooser.getSelectedItem(); |
| } |
| |
| private void createEditor() { |
| myStacktraceEditorPanel = AnalyzeStacktraceUtil.createEditorPanel(myProject, myDisposable); |
| myEditorPanel.setLayout(new BorderLayout()); |
| myEditorPanel.add(myStacktraceEditorPanel, BorderLayout.CENTER); |
| } |
| |
| @NotNull |
| protected Action[] createActions(){ |
| return new Action[]{createNormalizeTextAction(), getOKAction(), getCancelAction(), getHelpAction()}; |
| } |
| |
| private void createLogFileChooser() { |
| myLogFile = new TextFieldWithHistory(); |
| JPanel panel = GuiUtils.constructFieldWithBrowseButton(myLogFile, new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(); |
| FileChooser.chooseFiles(descriptor, myProject, null, new Consumer<List<VirtualFile>>() { |
| @Override |
| public void consume(List<VirtualFile> files) { |
| myLogFile.setText(FileUtil.toSystemDependentName(files.get(files.size() - 1).getPath())); |
| } |
| }); |
| } |
| }); |
| myLogFileChooserPanel.setLayout(new BorderLayout()); |
| myLogFileChooserPanel.add(panel, BorderLayout.CENTER); |
| } |
| |
| private void populateRegisteredUnscramblerList() { |
| List<UnscrambleSupport> unscrambleComponents = getRegisteredUnscramblers(); |
| for (final UnscrambleSupport unscrambleSupport : unscrambleComponents) { |
| myUnscrambleChooser.addItem(unscrambleSupport); |
| } |
| myUnscrambleChooser.setRenderer(new ListCellRendererWrapper<UnscrambleSupport>() { |
| @Override |
| public void customize(JList list, UnscrambleSupport unscrambleSupport, int index, boolean selected, boolean hasFocus) { |
| setText(unscrambleSupport == null ? IdeBundle.message("unscramble.no.unscrambler.item") : unscrambleSupport.getPresentableName()); |
| } |
| }); |
| } |
| |
| private static List<UnscrambleSupport> getRegisteredUnscramblers() { |
| final UnscrambleSupport[] components = Extensions.getExtensions(UnscrambleSupport.EP_NAME); |
| return Arrays.asList(components); |
| } |
| |
| protected JComponent createCenterPanel() { |
| return myPanel; |
| } |
| |
| public void dispose() { |
| if (isOK()){ |
| final List list = myLogFile.getHistory(); |
| String res = null; |
| for (Object aList : list) { |
| final String s = (String)aList; |
| if (res == null) { |
| res = s; |
| } |
| else { |
| res = res + ":::" + s; |
| } |
| } |
| PropertiesComponent.getInstance().setValue(PROPERTY_LOG_FILE_HISTORY_URLS, res); |
| UnscrambleSupport selectedUnscrambler = getSelectedUnscrambler(); |
| PropertiesComponent.getInstance().setValue(PROPERTY_UNSCRAMBLER_NAME_USED, selectedUnscrambler == null ? null : selectedUnscrambler.getPresentableName()); |
| |
| PropertiesComponent.getInstance().setValue(PROPERTY_LOG_FILE_LAST_URL, myLogFile.getText()); |
| } |
| super.dispose(); |
| } |
| |
| public void setText(String trace) { |
| myStacktraceEditorPanel.setText(trace); |
| } |
| |
| public Action createNormalizeTextAction() { |
| return new NormalizeTextAction(); |
| } |
| |
| private final class NormalizeTextAction extends AbstractAction { |
| public NormalizeTextAction(){ |
| putValue(NAME, IdeBundle.message("unscramble.normalize.button")); |
| putValue(DEFAULT_ACTION, Boolean.FALSE); |
| } |
| |
| public void actionPerformed(ActionEvent e){ |
| String text = myStacktraceEditorPanel.getText(); |
| myStacktraceEditorPanel.setText(normalizeText(text)); |
| } |
| |
| } |
| |
| public static String normalizeText(@NonNls String text) { |
| StringBuilder builder = new StringBuilder(text.length()); |
| |
| text = text.replaceAll("(\\S[ \\t\\x0B\\f\\r]+)(at\\s+)", "$1\n$2"); |
| String[] lines = text.split("\n"); |
| |
| boolean first = true; |
| boolean inAuxInfo = false; |
| for (String line : lines) { |
| //noinspection HardCodedStringLiteral |
| if (!inAuxInfo && (line.startsWith("JNI global references") || line.trim().equals("Heap"))) { |
| builder.append("\n"); |
| inAuxInfo = true; |
| } |
| if (inAuxInfo) { |
| builder.append(trimSuffix(line)).append("\n"); |
| continue; |
| } |
| if (!first && mustHaveNewLineBefore(line)) { |
| builder.append("\n"); |
| if (line.startsWith("\"")) builder.append("\n"); // Additional line break for thread names |
| } |
| first = false; |
| int i = builder.lastIndexOf("\n"); |
| CharSequence lastLine = i == -1 ? builder : builder.subSequence(i + 1, builder.length()); |
| if (lastLine.toString().matches("\\s*at") && !line.matches("\\s+.*")) builder.append(" "); // separate 'at' from file name |
| builder.append(trimSuffix(line)); |
| } |
| return builder.toString(); |
| } |
| |
| private static String trimSuffix(final String line) { |
| int len = line.length(); |
| |
| while ((0 < len) && (line.charAt(len-1) <= ' ')) { |
| len--; |
| } |
| return (len < line.length()) ? line.substring(0, len) : line; |
| } |
| |
| private static boolean mustHaveNewLineBefore(String line) { |
| final int nonWs = CharArrayUtil.shiftForward(line, 0, " \t"); |
| if (nonWs < line.length()) { |
| line = line.substring(nonWs); |
| } |
| |
| if (line.startsWith("at")) return true; // Start of the new stack frame entry |
| if (line.startsWith("Caused")) return true; // Caused by message |
| if (line.startsWith("- locked")) return true; // "Locked a monitor" logging |
| if (line.startsWith("- waiting")) return true; // "Waiting for monitor" logging |
| if (line.startsWith("- parking to wait")) return true; |
| if (line.startsWith("java.lang.Thread.State")) return true; |
| if (line.startsWith("\"")) return true; // Start of the new thread (thread name) |
| |
| return false; |
| } |
| |
| protected void doOKAction() { |
| if (myConfigurable != null && myConfigurable.isModified()) { |
| try { |
| myConfigurable.apply(); |
| } |
| catch (ConfigurationException e) { |
| setText(e.getMessage()); |
| return; |
| } |
| } |
| if (performUnscramble()) { |
| myLogFile.addCurrentTextToHistory(); |
| close(OK_EXIT_CODE); |
| } |
| } |
| |
| public void doHelpAction() { |
| HelpManager.getInstance().invokeHelp("find.analyzeStackTrace"); |
| } |
| |
| private boolean performUnscramble() { |
| UnscrambleSupport selectedUnscrambler = getSelectedUnscrambler(); |
| return showUnscrambledText(selectedUnscrambler, myLogFile.getText(), myProject, myStacktraceEditorPanel.getText()) != null; |
| } |
| |
| @Nullable |
| static RunContentDescriptor showUnscrambledText(@Nullable UnscrambleSupport unscrambleSupport, |
| String logName, |
| Project project, |
| String textToUnscramble) { |
| String unscrambledTrace = unscrambleSupport == null ? textToUnscramble : unscrambleSupport.unscramble(project,textToUnscramble, logName); |
| if (unscrambledTrace == null) return null; |
| List<ThreadState> threadStates = ThreadDumpParser.parse(unscrambledTrace); |
| return addConsole(project, threadStates, unscrambledTrace); |
| } |
| |
| private static RunContentDescriptor addConsole(final Project project, final List<ThreadState> threadDump, String unscrambledTrace) { |
| Icon icon = null; |
| String message = IdeBundle.message("unscramble.unscrambled.stacktrace.tab"); |
| if (!threadDump.isEmpty()) { |
| message = IdeBundle.message("unscramble.unscrambled.threaddump.tab"); |
| icon = AllIcons.Debugger.ThreadStates.Threaddump; |
| } |
| else { |
| String name = getExceptionName(unscrambledTrace); |
| if (name != null) { |
| message = name; |
| icon = AllIcons.Debugger.ThreadStates.Exception; |
| } |
| } |
| if (ContainerUtil.find(threadDump, DEADLOCK_CONDITION) != null) { |
| message = IdeBundle.message("unscramble.unscrambled.deadlock.tab"); |
| icon = AllIcons.Debugger.KillProcess; |
| } |
| return AnalyzeStacktraceUtil.addConsole(project, threadDump.size() > 1 ? new AnalyzeStacktraceUtil.ConsoleFactory() { |
| public JComponent createConsoleComponent(ConsoleView consoleView, DefaultActionGroup toolbarActions) { |
| return new ThreadDumpPanel(project, consoleView, toolbarActions, threadDump); |
| } |
| } : null, message, unscrambledTrace, icon); |
| } |
| |
| protected String getDimensionServiceKey(){ |
| return "#com.intellij.unscramble.UnscrambleDialog"; |
| } |
| |
| public JComponent getPreferredFocusedComponent() { |
| return myStacktraceEditorPanel.getEditorComponent(); |
| } |
| |
| @Nullable |
| private static String getExceptionName(String unscrambledTrace) { |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") |
| BufferedReader reader = new BufferedReader(new StringReader(unscrambledTrace)); |
| for (int i = 0; i < 3; i++) { |
| try { |
| String line = reader.readLine(); |
| if (line == null) return null; |
| line = line.trim(); |
| String name = getExceptionAbbreviation(line); |
| if (name != null) return name; |
| } |
| catch (IOException e) { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static String getExceptionAbbreviation(String line) { |
| int lastDelimiter = 0; |
| for (int j = 0; j < line.length(); j++) { |
| char c = line.charAt(j); |
| if (c == '.' || c == '$') { |
| lastDelimiter = j; |
| continue; |
| } |
| if (!StringUtil.isJavaIdentifierPart(c)) { |
| return null; |
| } |
| } |
| String clazz = line.substring(lastDelimiter); |
| String abbreviate = abbreviate(clazz); |
| return abbreviate.length() > 1 ? abbreviate : clazz; |
| } |
| |
| private static String abbreviate(String s) { |
| StringBuilder builder = new StringBuilder(); |
| for (int i = 0; i < s.length(); i++) { |
| char c = s.charAt(i); |
| if (Character.isUpperCase(c)) { |
| builder.append(c); |
| } |
| } |
| return builder.toString(); |
| } |
| |
| } |