| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * SAP SE - Support hyperlinks for stack entries with method signature |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.debug.ui.console; |
| |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.model.IProcess; |
| import org.eclipse.debug.ui.IDebugModelPresentation; |
| import org.eclipse.debug.ui.IDebugUIConstants; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; |
| import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; |
| import org.eclipse.jdt.internal.debug.ui.actions.OpenTypeAction; |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.console.IHyperlink; |
| import org.eclipse.ui.console.TextConsole; |
| import org.eclipse.ui.progress.UIJob; |
| import org.eclipse.ui.texteditor.IDocumentProvider; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| /** |
| * A hyper-link from a stack trace line of the form "*(*.java:*)" |
| */ |
| public class JavaStackTraceHyperlink implements IHyperlink { |
| |
| private TextConsole fConsole; |
| |
| /** |
| * Constructor |
| * @param console the {@link TextConsole} this link detector is attached to |
| */ |
| public JavaStackTraceHyperlink(TextConsole console) { |
| fConsole = console; |
| } |
| |
| /** |
| * @see org.eclipse.debug.ui.console.IConsoleHyperlink#linkEntered() |
| */ |
| @Override |
| public void linkEntered() { |
| } |
| |
| /** |
| * @see org.eclipse.debug.ui.console.IConsoleHyperlink#linkExited() |
| */ |
| @Override |
| public void linkExited() { |
| } |
| |
| /** |
| * @see org.eclipse.debug.ui.console.IConsoleHyperlink#linkActivated() |
| */ |
| @Override |
| public void linkActivated() { |
| String typeName; |
| int lineNumber; |
| try { |
| String linkText = getLinkText(); |
| typeName = getTypeName(linkText); |
| lineNumber = getLineNumber(linkText); |
| } catch (CoreException e1) { |
| ErrorDialog.openError(JDIDebugUIPlugin.getActiveWorkbenchShell(), ConsoleMessages.JavaStackTraceHyperlink_Error, ConsoleMessages.JavaStackTraceHyperlink_Error, e1.getStatus()); |
| return; |
| } |
| |
| // documents start at 0 |
| if (lineNumber > 0) { |
| lineNumber--; |
| } |
| startSourceSearch(typeName, lineNumber); |
| } |
| |
| /** |
| * Starts a search for the type with the given name. Reports back to 'searchCompleted(...)'. |
| * |
| * @param typeName the type to search for |
| * @param lineNumber the line number to open the editor on |
| */ |
| protected void startSourceSearch(final String typeName, final int lineNumber) { |
| Job search = new Job(ConsoleMessages.JavaStackTraceHyperlink_2) { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| ILaunch launch = getLaunch(); |
| Object result = null; |
| try { |
| // search for the type in the workspace |
| result = OpenTypeAction.findTypeInWorkspace(typeName, true); |
| if (result == null && launch != null) { |
| result = JavaDebugUtils.resolveSourceElement(JavaDebugUtils.generateSourceName(typeName), getLaunch()); |
| } |
| if (result == null) { |
| // search for any type in the workspace |
| result = OpenTypeAction.findTypeInWorkspace(typeName, false); |
| } |
| searchCompleted(result, typeName, lineNumber, null); |
| } catch (CoreException e) { |
| searchCompleted(null, typeName, lineNumber, e.getStatus()); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| }; |
| search.schedule(); |
| } |
| |
| /** |
| * Reported back to from {@link JavaStackTraceHyperlink#startSourceSearch(String, int)} when results are found |
| * |
| * @param source the source object |
| * @param typeName the fully qualified type name |
| * @param lineNumber the line number in the type |
| * @param status the error status or <code>null</code> if none |
| */ |
| protected void searchCompleted(final Object source, final String typeName, final int lineNumber, final IStatus status) { |
| UIJob job = new UIJob("link search complete") { //$NON-NLS-1$ |
| @Override |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| if (source == null) { |
| if (status == null) { |
| // did not find source |
| MessageDialog.openInformation(JDIDebugUIPlugin.getActiveWorkbenchShell(), ConsoleMessages.JavaStackTraceHyperlink_Information_1, NLS.bind(ConsoleMessages.JavaStackTraceHyperlink_Source_not_found_for__0__2, new String[] {typeName})); |
| } else { |
| JDIDebugUIPlugin.statusDialog(ConsoleMessages.JavaStackTraceHyperlink_3, status); |
| } |
| } else { |
| processSearchResult(source, typeName, lineNumber); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setSystem(true); |
| job.schedule(); |
| } |
| |
| /** |
| * The search succeeded with the given result |
| * |
| * @param source resolved source object for the search |
| * @param typeName type name searched for |
| * @param lineNumber line number on link |
| */ |
| protected void processSearchResult(Object source, String typeName, int lineNumber) { |
| IDebugModelPresentation presentation = JDIDebugUIPlugin.getDefault().getModelPresentation(); |
| IEditorInput editorInput = presentation.getEditorInput(source); |
| if (editorInput != null) { |
| String editorId = presentation.getEditorId(editorInput, source); |
| if (editorId != null) { |
| try { |
| IEditorPart editorPart = JDIDebugUIPlugin.getActivePage().openEditor(editorInput, editorId); |
| if (editorPart instanceof ITextEditor && lineNumber >= 0) { |
| ITextEditor textEditor = (ITextEditor)editorPart; |
| IDocumentProvider provider = textEditor.getDocumentProvider(); |
| provider.connect(editorInput); |
| IDocument document = provider.getDocument(editorInput); |
| try { |
| IRegion line = document.getLineInformation(lineNumber); |
| textEditor.selectAndReveal(line.getOffset(), line.getLength()); |
| } catch (BadLocationException e) { |
| MessageDialog.openInformation(JDIDebugUIPlugin.getActiveWorkbenchShell(), ConsoleMessages.JavaStackTraceHyperlink_0, NLS.bind("{0}{1}{2}", new String[] {(lineNumber+1)+"", ConsoleMessages.JavaStackTraceHyperlink_1, typeName})); //$NON-NLS-2$ //$NON-NLS-1$ |
| } |
| provider.disconnect(editorInput); |
| } |
| } catch (CoreException e) { |
| JDIDebugUIPlugin.statusDialog(e.getStatus()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the launch associated with this hyper-link, or |
| * <code>null</code> if none |
| * |
| * @return the launch associated with this hyper-link, or |
| * <code>null</code> if none |
| */ |
| private ILaunch getLaunch() { |
| IProcess process = (IProcess) getConsole().getAttribute(IDebugUIConstants.ATTR_CONSOLE_PROCESS); |
| if (process != null) { |
| return process.getLaunch(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the fully qualified name of the type to open |
| * |
| * @param linkText |
| * the complete text of the link to be parsed |
| * @return fully qualified type name |
| * @exception CoreException |
| * if unable to parse the type name |
| */ |
| protected String getTypeName(String linkText) throws CoreException { |
| int start = linkText.lastIndexOf('('); |
| int end = linkText.indexOf(':'); |
| if (start >= 0 && end > start) { |
| //linkText could be something like packageA.TypeB(TypeA.java:45) |
| //need to look in packageA.TypeA for line 45 since TypeB is defined |
| //in TypeA.java |
| //Inner classes can be ignored because we're using file and line number |
| |
| // get File name (w/o .java) |
| String typeName = linkText.substring(start + 1, end); |
| typeName = JavaCore.removeJavaLikeExtension(typeName); |
| |
| String qualifier = linkText.substring(0, start); |
| // remove the method name |
| start = qualifier.lastIndexOf('.'); |
| |
| if (start >= 0) { |
| // remove the class name |
| start = new String((String) qualifier.subSequence(0, start)).lastIndexOf('.'); |
| if (start == -1) { |
| start = 0; // default package |
| } |
| } |
| |
| if (start >= 0) { |
| qualifier = qualifier.substring(0, start); |
| } |
| |
| if (qualifier.length() > 0) { |
| typeName = qualifier + "." + typeName; //$NON-NLS-1$ |
| } |
| // Remove the module name if exists |
| int index = typeName.lastIndexOf('/'); |
| if (index != -1) { |
| typeName = typeName.substring(index + 1); |
| } |
| return typeName; |
| } |
| IStatus status = new Status(IStatus.ERROR, JDIDebugUIPlugin.getUniqueIdentifier(), 0, ConsoleMessages.JavaStackTraceHyperlink_Unable_to_parse_type_name_from_hyperlink__5, null); |
| throw new CoreException(status); |
| } |
| |
| /** |
| * Returns the line number associated with the stack trace or -1 if none. |
| * |
| * @param linkText the complete text of the link to be parsed |
| * @return the line number for the stack trace or -1 if one cannot be computed or has not been provided |
| * @exception CoreException if unable to parse the number |
| */ |
| protected int getLineNumber(String linkText) throws CoreException { |
| int index = linkText.lastIndexOf(':'); |
| if (index >= 0) { |
| String numText = linkText.substring(index + 1); |
| index = numText.indexOf(')'); |
| if (index >= 0) { |
| numText = numText.substring(0, index); |
| } |
| try { |
| return Integer.parseInt(numText); |
| } catch (NumberFormatException e) { |
| IStatus status = new Status(IStatus.ERROR, JDIDebugUIPlugin.getUniqueIdentifier(), 0, ConsoleMessages.JavaStackTraceHyperlink_Unable_to_parse_line_number_from_hyperlink__6, e); |
| throw new CoreException(status); |
| } |
| } |
| IStatus status = new Status(IStatus.ERROR, JDIDebugUIPlugin.getUniqueIdentifier(), 0, ConsoleMessages.JavaStackTraceHyperlink_Unable_to_parse_line_number_from_hyperlink__6, null); |
| throw new CoreException(status); |
| } |
| |
| /** |
| * Returns the console this link is contained in. |
| * |
| * @return console |
| */ |
| protected TextConsole getConsole() { |
| return fConsole; |
| } |
| |
| /** |
| * Returns this link's text |
| * |
| * @return the complete text of the link, never <code>null</code> |
| * @exception CoreException if unable to retrieve the text |
| */ |
| protected String getLinkText() throws CoreException { |
| try { |
| IDocument document = getConsole().getDocument(); |
| IRegion region = getConsole().getRegion(this); |
| int regionOffset = region.getOffset(); |
| |
| int lineNumber = document.getLineOfOffset(regionOffset); |
| IRegion lineInformation = document.getLineInformation(lineNumber); |
| int lineOffset = lineInformation.getOffset(); |
| String line = document.get(lineOffset, lineInformation.getLength()); |
| |
| int regionOffsetInLine = regionOffset - lineOffset; |
| |
| int linkEnd = line.indexOf(')', regionOffsetInLine); |
| int linkStart = line.lastIndexOf(' ', regionOffsetInLine); |
| if (linkStart == -1) { |
| linkStart = line.lastIndexOf('\t', regionOffsetInLine); |
| } |
| |
| return line.substring(linkStart==-1?0:linkStart+1,linkEnd+1).trim(); |
| } catch (BadLocationException e) { |
| IStatus status = new Status(IStatus.ERROR, JDIDebugUIPlugin.getUniqueIdentifier(), 0, ConsoleMessages.JavaStackTraceHyperlink_Unable_to_retrieve_hyperlink_text__8, e); |
| throw new CoreException(status); |
| } |
| } |
| |
| } |