package com.redhat.ceylon.test.eclipse.plugin.ui; import static com.redhat.ceylon.eclipse.core.launch.CeylonPatternMatchListenerDelegate.gotoFileAndLine; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.COMPARE; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.STACK_TRACE; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.STACK_TRACE_FILTER; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.STACK_TRACE_LINE; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.TEST_ERROR; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.TEST_FAILED; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry.getImage; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestMessages.compareValuesActionLabel; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestMessages.stackTraceCopyLabel; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestMessages.stackTraceFilterLabel; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestMessages.stackTraceLabel; import static com.redhat.ceylon.test.eclipse.plugin.CeylonTestPlugin.PREF_STACK_TRACE_FILTER; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.internal.debug.ui.console.JavaStackTraceHyperlink; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.IConsoleDocumentPartitioner; import org.eclipse.ui.console.TextConsole; import com.redhat.ceylon.test.eclipse.plugin.CeylonTestImageRegistry; import com.redhat.ceylon.test.eclipse.plugin.CeylonTestPlugin; import com.redhat.ceylon.test.eclipse.plugin.model.TestElement; import com.redhat.ceylon.test.eclipse.plugin.model.TestElement.State; import com.redhat.ceylon.test.eclipse.plugin.model.TestRun; @SuppressWarnings("restriction") public class StackTracePanel extends Composite { private static final String[] STACK_TRACE_FILTER_PATTERNS = new String[] { "com.redhat.ceylon.*", "ceylon.modules.*", "org.jboss.modules.*", "java.lang.reflect.Method.invoke", "sun.reflect.*", }; private TestRun testRun; private TestElement selectedTestElement; private Label panelIcon; private Label panelLabel; private ToolBar toolBar; private ToolBarManager toolBarManager; private TableViewer stackTraceTable; private StackTraceFilterAction stackTraceFilterAction; private StackTraceCopyAction stackTraceCopyAction; private CompareValuesAction compareValuesAction; public StackTracePanel(Composite parent) { super(parent, SWT.NONE); GridLayout gridLayout = new GridLayout(3, false); gridLayout.marginTop = 10; gridLayout.marginWidth = 0; gridLayout.marginHeight = 0; gridLayout.verticalSpacing = 0; setLayout(gridLayout); createHeader(); createToolBar(); createStackTraceTable(); } public void setSelectedTestElement(TestRun currentTestRun, TestElement selectedTestElement) { this.testRun = currentTestRun; this.selectedTestElement = selectedTestElement; updateView(); } private void createHeader() { panelIcon = new Label(this, SWT.NONE); panelIcon.setImage(getImage(STACK_TRACE)); panelLabel = new Label(this, SWT.NONE); panelLabel.setText(stackTraceLabel); } private void createToolBar() { stackTraceFilterAction = new StackTraceFilterAction(); stackTraceCopyAction = new StackTraceCopyAction(); compareValuesAction = new CompareValuesAction(); toolBar = new ToolBar(this, SWT.FLAT | SWT.WRAP); toolBar.setLayoutData(GridDataFactory.swtDefaults().align(SWT.RIGHT, SWT.CENTER).grab(true, false).create()); toolBarManager = new ToolBarManager(toolBar); toolBarManager.add(stackTraceFilterAction); toolBarManager.add(compareValuesAction); toolBarManager.add(stackTraceCopyAction); toolBarManager.update(true); } private void createStackTraceTable() { stackTraceTable = new TableViewer(this, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL); stackTraceTable.getTable().setLayoutData(GridDataFactory.swtDefaults().span(3, 1).align(SWT.FILL, SWT.FILL).grab(true, true).create()); stackTraceTable.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { handleDoubleClick(event); } }); } private void handleDoubleClick(DoubleClickEvent event) { TableItem[] selectedLines = stackTraceTable.getTable().getSelection(); if (selectedLines != null && selectedLines.length == 1 && selectedLines[0] != null && selectedLines[0].getText() != null) { if( stackTraceTable.getTable().getSelectionIndex() == 0 && isCompareResultAvailable() ) { compareValuesAction.run(); } else { String text = selectedLines[0].getText(); gotoStackTraceLine(testRun, text); } } } public static void gotoStackTraceLine(TestRun testRun, String stackTraceLine) { if( testRun.isJs() ) { return; } int i = stackTraceLine.indexOf("("); int j = stackTraceLine.indexOf(":"); int k = stackTraceLine.indexOf("at "); if (i == -1 || j == -1 || k == -1) { return; } String file = stackTraceLine.substring(i + 1, j); String line = stackTraceLine.substring(j + 1, stackTraceLine.length() - 1); String pack = stackTraceLine.substring(k + 3, i); if (file.endsWith(".ceylon")) { gotoFileAndLine(file, line, pack); } else if (file.endsWith(".java")) { new InternalJavaStackTraceHyperlink(stackTraceLine.substring(k + 3)).linkActivated(); } } public static List<String> parseStackTraceLine(String stackTrace) { List<String> lines = new ArrayList<String>(); StringReader stringReader = new StringReader(stackTrace); BufferedReader bufferedReader = new BufferedReader(stringReader); try { String line; while ((line = bufferedReader.readLine()) != null) { lines.add(line); } } catch (IOException e) { CeylonTestPlugin.logError("", e); } return lines; } private void createStackTraceLines(String stackTrace) { List<String> lines = parseStackTraceLine(stackTrace); boolean isFirstLine = true; for (String line : lines) { if (!isStackTraceLineFiltred(isFirstLine, line)) { createStackTraceLine(isFirstLine, line); } if (isFirstLine) { isFirstLine = false; } } } private void createStackTraceLine(boolean isFirstLine, String line) { String text = line.replace("\t", " "); Image image = null; if (isFirstLine || text.startsWith("Caused by: ")) { if (selectedTestElement.getState() == State.FAILURE) { image = getImage(TEST_FAILED); } else { image = getImage(TEST_ERROR); } } else if (text.startsWith(" at ")) { image = getImage(STACK_TRACE_LINE); } TableItem tableItem = new TableItem(stackTraceTable.getTable(), SWT.NONE); tableItem.setText(text); tableItem.setImage(image); } private void updateView() { updateStackTraceTable(); updateActionState(); } private void updateStackTraceTable() { stackTraceTable.getTable().setRedraw(false); stackTraceTable.getTable().removeAll(); if (isStackTraceAvailable()) { createStackTraceLines(selectedTestElement.getException()); } stackTraceTable.getTable().setRedraw(true); } private void updateActionState() { stackTraceCopyAction.setEnabled(isStackTraceAvailable()); compareValuesAction.setEnabled(isCompareResultAvailable()); } private boolean isStackTraceAvailable() { if (selectedTestElement != null && selectedTestElement.getException() != null && selectedTestElement.getState().canShowStackTrace()) { return true; } return false; } private boolean isCompareResultAvailable() { if( selectedTestElement != null && selectedTestElement.getState() == State.FAILURE && selectedTestElement.getActualValue() != null && selectedTestElement.getExpectedValue() != null ) { return true; } return false; } private boolean isStackTraceLineFiltred(boolean isFirstLine, String line) { if( !isFirstLine && stackTraceFilterAction.isChecked() ) { for (String pattern : STACK_TRACE_FILTER_PATTERNS) { if (pattern.charAt(pattern.length() - 1) == '*') { // strip trailing * from a package filter pattern = pattern.substring(0, pattern.length() - 1); } else if (Character.isUpperCase(pattern.charAt(0))) { // class in the default package pattern = "at " + pattern + '.'; } else { // class names start w/ an uppercase letter after the . int lastDotIndex = pattern.lastIndexOf('.'); if ((lastDotIndex != -1) && (lastDotIndex != pattern.length() - 1) && Character.isUpperCase(pattern.charAt(lastDotIndex + 1))) { pattern += '.'; // append . to a class filter } } if (line.indexOf(pattern) > 0) { return true; } } } return false; } private class StackTraceFilterAction extends Action { public StackTraceFilterAction() { super(stackTraceFilterLabel, AS_CHECK_BOX); setDescription(stackTraceFilterLabel); setToolTipText(stackTraceFilterLabel); setImageDescriptor(CeylonTestImageRegistry.getImageDescriptor(STACK_TRACE_FILTER)); IPreferenceStore preferenceStore = CeylonTestPlugin.getDefault().getPreferenceStore(); setChecked(preferenceStore.getBoolean(PREF_STACK_TRACE_FILTER)); } @Override public void run() { IPreferenceStore preferenceStore = CeylonTestPlugin.getDefault().getPreferenceStore(); preferenceStore.setValue(PREF_STACK_TRACE_FILTER, isChecked()); updateView(); } } private class StackTraceCopyAction extends Action { public StackTraceCopyAction() { super(stackTraceCopyLabel); setDescription(stackTraceCopyLabel); setToolTipText(stackTraceCopyLabel); setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY)); setEnabled(false); } @Override public void run() { if( isStackTraceAvailable() ) { String stackTrace = selectedTestElement.getException(); Clipboard clipboard = new Clipboard(getDisplay()); clipboard.setContents(new String[] { stackTrace }, new Transfer[] { TextTransfer.getInstance() }); clipboard.dispose(); } } } private class CompareValuesAction extends Action { private CompareValuesDialog dlg; public CompareValuesAction() { super(compareValuesActionLabel); setDescription(compareValuesActionLabel); setToolTipText(compareValuesActionLabel); setImageDescriptor(CeylonTestImageRegistry.getImageDescriptor(COMPARE)); setEnabled(false); } @Override public void run() { if (dlg == null) { dlg = new CompareValuesDialog(StackTracePanel.this.getShell()); dlg.create(); dlg.getShell().addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { dlg = null; } }); dlg.setTestElement(selectedTestElement); dlg.open(); } else { dlg.setTestElement(selectedTestElement); dlg.getShell().setActive(); } } } private static class InternalJavaStackTraceHyperlink extends JavaStackTraceHyperlink { private final String linkText; public InternalJavaStackTraceHyperlink(String linkText) { super(InternalTextConsole.INSTANCE); this.linkText = linkText; } @Override protected String getLinkText() throws CoreException { return linkText; } } private static class InternalTextConsole extends TextConsole { private static final InternalTextConsole INSTANCE = new InternalTextConsole(); public InternalTextConsole() { super("", "", null, false); } @Override protected IConsoleDocumentPartitioner getPartitioner() { return null; } } }