1 /******************************************************************************* 2 * Copyright (c) 2007, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.debug.ui.actions; 15 16 import org.eclipse.core.runtime.CoreException; 17 import org.eclipse.core.runtime.IAdapterManager; 18 import org.eclipse.core.runtime.Platform; 19 import org.eclipse.debug.core.DebugEvent; 20 import org.eclipse.debug.core.DebugException; 21 import org.eclipse.debug.core.DebugPlugin; 22 import org.eclipse.debug.core.IDebugEventSetListener; 23 import org.eclipse.debug.core.model.IStackFrame; 24 import org.eclipse.debug.core.model.IThread; 25 import org.eclipse.debug.ui.actions.IRunToLineTarget; 26 import org.eclipse.jdt.core.ICodeAssist; 27 import org.eclipse.jdt.core.IJavaElement; 28 import org.eclipse.jdt.core.IMember; 29 import org.eclipse.jdt.core.IMethod; 30 import org.eclipse.jdt.core.IType; 31 import org.eclipse.jdt.core.JavaCore; 32 import org.eclipse.jdt.core.JavaModelException; 33 import org.eclipse.jdt.core.ToolFactory; 34 import org.eclipse.jdt.core.compiler.IScanner; 35 import org.eclipse.jdt.core.compiler.ITerminalSymbols; 36 import org.eclipse.jdt.core.compiler.InvalidInputException; 37 import org.eclipse.jdt.debug.core.IJavaStackFrame; 38 import org.eclipse.jdt.debug.core.IJavaThread; 39 import org.eclipse.jdt.internal.debug.ui.EvaluationContextManager; 40 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; 41 import org.eclipse.jdt.ui.JavaUI; 42 import org.eclipse.jface.text.BadLocationException; 43 import org.eclipse.jface.text.IDocument; 44 import org.eclipse.jface.text.IRegion; 45 import org.eclipse.jface.text.ITextSelection; 46 import org.eclipse.ui.IEditorInput; 47 import org.eclipse.ui.IEditorPart; 48 import org.eclipse.ui.IWorkbenchPage; 49 import org.eclipse.ui.texteditor.IDocumentProvider; 50 import org.eclipse.ui.texteditor.IEditorStatusLine; 51 import org.eclipse.ui.texteditor.ITextEditor; 52 53 /** 54 * Utility class for aiding step into selection actions and hyper-linking 55 * 56 * @see StepIntoSelectionActionDelegate 57 * @see StepIntoSelectionHyperlinkDetector 58 * 59 * @since 3.3 60 */ 61 public class StepIntoSelectionUtils { 62 63 64 /** 65 * gets the <code>IJavaElement</code> from the editor input 66 * @param input the current editor input 67 * @return the corresponding <code>IJavaElement</code> 68 */ getJavaElement(IEditorInput input)69 public static IJavaElement getJavaElement(IEditorInput input) { 70 IJavaElement je = JavaUI.getEditorInputJavaElement(input); 71 if(je != null) { 72 return je; 73 } 74 return JavaUI.getWorkingCopyManager().getWorkingCopy(input); 75 } 76 77 /** 78 * Returns the <code>IMethod</code> from the given selection within the given <code>IJavaElement</code>, 79 * or <code>null</code> if the selection does not container or is not an <code>IMethod</code> 80 * @param selection 81 * @param element 82 * @return the corresponding <code>IMethod</code> from the selection within the provided <code>IJavaElement</code> 83 * @throws JavaModelException 84 */ getMethod(ITextSelection selection, IJavaElement element)85 public static IMethod getMethod(ITextSelection selection, IJavaElement element) throws JavaModelException { 86 if(element != null && element instanceof ICodeAssist) { 87 return resolveMethod(selection.getOffset(), selection.getLength(), (ICodeAssist)element); 88 } 89 return null; 90 } 91 92 /** 93 * @param offset selection offset 94 * @param length selection length 95 * @param codeAssist context 96 * @return the method at the given position, or <code>null</code> if no method could be resolved 97 * @throws JavaModelException 98 */ resolveMethod(int offset, int length, ICodeAssist codeAssist)99 private static IMethod resolveMethod(int offset, int length, ICodeAssist codeAssist) throws JavaModelException { 100 IJavaElement[] elements = codeAssist.codeSelect(offset, length); 101 for (int i = 0; i < elements.length; i++) { 102 if (elements[i] instanceof IMethod) { 103 return (IMethod)elements[i]; 104 } 105 } 106 return null; 107 } 108 109 /** 110 * @param offset 111 * @param activeEditor 112 * @param element 113 * @return the first method found at or after <code>offset</code> on the same line 114 * @throws JavaModelException 115 */ getFirstMethodOnLine(int offset, IEditorPart activeEditor, IJavaElement element)116 public static IMethod getFirstMethodOnLine(int offset, IEditorPart activeEditor, IJavaElement element) throws JavaModelException { 117 if (! (activeEditor instanceof ITextEditor) || ! (element instanceof ICodeAssist)) { 118 return null; 119 } 120 ITextEditor editor = (ITextEditor)activeEditor; 121 IDocumentProvider documentProvider = editor.getDocumentProvider(); 122 if (documentProvider == null) { 123 return null; 124 } 125 IDocument document = documentProvider.getDocument(editor.getEditorInput()); 126 if (document == null) { 127 return null; 128 } 129 try { 130 IRegion lineInfo = document.getLineInformationOfOffset(offset); 131 String line = document.get(lineInfo.getOffset(), lineInfo.getLength()); 132 IScanner scanner = ToolFactory.createScanner(false, false, false, null, JavaCore.VERSION_1_5); 133 scanner.setSource(line.toCharArray()); 134 scanner.resetTo(offset - lineInfo.getOffset(), lineInfo.getLength()); 135 int token = scanner.getNextToken(); 136 while (token != ITerminalSymbols.TokenNameEOF) { 137 if (token == ITerminalSymbols.TokenNameIdentifier) { 138 int methodStart = scanner.getCurrentTokenStartPosition(); 139 token = scanner.getNextToken(); 140 if (token == ITerminalSymbols.TokenNameLPAREN) { 141 return resolveMethod(lineInfo.getOffset() + methodStart, 0, (ICodeAssist)element); 142 } 143 } 144 else { 145 token = scanner.getNextToken(); 146 } 147 } 148 } 149 catch (BadLocationException e) { 150 return null; 151 } 152 catch (InvalidInputException e) { 153 return null; 154 } 155 return null; 156 } 157 158 /** 159 * Steps into the selection described by the given {@link IRegion} 160 * 161 * @param region the region of the selection or <code>null</code> if we should use the page's selection service to compute 162 * the selection 163 * 164 * @since 3.6.200 165 */ stepIntoSelection(ITextSelection selection)166 public static void stepIntoSelection(ITextSelection selection) { 167 IWorkbenchPage page = JDIDebugUIPlugin.getActiveWorkbenchWindow().getActivePage(); 168 if(page != null) { 169 IEditorPart editor = page.getActiveEditor(); 170 if(editor instanceof ITextEditor) { 171 IJavaStackFrame frame = EvaluationContextManager.getEvaluationContext(editor); 172 if (frame == null || !frame.isSuspended()) { 173 // no longer suspended - unexpected 174 return; 175 } 176 if(selection == null) { 177 //grab it from the provider, either the passed region was null or we failed to get it 178 selection = (ITextSelection)editor.getEditorSite().getSelectionProvider().getSelection(); 179 } 180 try { 181 IJavaElement javaElement= StepIntoSelectionUtils.getJavaElement(editor.getEditorInput()); 182 IMethod method = StepIntoSelectionUtils.getMethod(selection, javaElement); 183 if (method == null) { 184 method = StepIntoSelectionUtils.getFirstMethodOnLine(selection.getOffset(), editor, javaElement); 185 } 186 IType callingType = getType(selection); 187 if (method == null || callingType == null) { 188 return; 189 } 190 int lineNumber = frame.getLineNumber(); 191 String callingTypeName = stripInnerNamesAndParameterType(callingType.getFullyQualifiedName()); 192 String frameName = stripInnerNamesAndParameterType(frame.getDeclaringTypeName()); 193 // debug line numbers are 1 based, document line numbers are 0 based 194 if (selection.getStartLine() == (lineNumber - 1) && callingTypeName.equals(frameName)) { 195 doStepIn(editor, frame, method); 196 } else { 197 // not on current line 198 runToLineBeforeStepIn(editor, callingTypeName, selection, frame.getThread(), method); 199 return; 200 } 201 } 202 catch (DebugException e) { 203 showErrorMessage(editor, e.getStatus().getMessage()); 204 return; 205 } 206 catch(JavaModelException jme) { 207 showErrorMessage(editor, jme.getStatus().getMessage()); 208 return; 209 } 210 } 211 } 212 } 213 214 /** 215 * When the user chooses to "step into selection" on a line other than 216 * the currently executing one, first perform a "run to line" to get to 217 * the desired location, then perform a "step into selection." 218 */ runToLineBeforeStepIn(final IEditorPart editor, final String typeName, ITextSelection textSelection, final IThread thread, final IMethod method)219 static void runToLineBeforeStepIn(final IEditorPart editor, final String typeName, ITextSelection textSelection, final IThread thread, final IMethod method) { 220 final int line = textSelection.getStartLine() + 1; 221 if (typeName == null || line == -1) { 222 return; 223 } 224 // see bug 65489 - get the run-to-line adapter from the editor 225 IRunToLineTarget runToLineAction = null; 226 if (editor != null) { 227 runToLineAction = editor.getAdapter(IRunToLineTarget.class); 228 if (runToLineAction == null) { 229 IAdapterManager adapterManager = Platform.getAdapterManager(); 230 if (adapterManager.hasAdapter(editor, IRunToLineTarget.class.getName())) { 231 runToLineAction = (IRunToLineTarget) adapterManager.loadAdapter(editor,IRunToLineTarget.class.getName()); 232 } 233 } 234 } 235 // if no adapter exists, use the Java adapter 236 if (runToLineAction == null) { 237 runToLineAction = new RunToLineAdapter(); 238 } 239 final IDebugEventSetListener listener = new IDebugEventSetListener() { 240 /** 241 * @see IDebugEventSetListener#handleDebugEvents(DebugEvent[]) 242 */ 243 @Override 244 public void handleDebugEvents(DebugEvent[] events) { 245 for (int i = 0; i < events.length; i++) { 246 DebugEvent event = events[i]; 247 switch (event.getKind()) { 248 case DebugEvent.SUSPEND : 249 handleSuspendEvent(event); 250 break; 251 case DebugEvent.TERMINATE : 252 handleTerminateEvent(event); 253 break; 254 default : 255 break; 256 } 257 } 258 } 259 /** 260 * Listen for the completion of the "run to line." When the thread 261 * suspends at the correct location, perform a "step into selection" 262 * @param event the debug event 263 */ 264 private void handleSuspendEvent(DebugEvent event) { 265 Object source = event.getSource(); 266 if (source instanceof IJavaThread) { 267 try { 268 final IJavaStackFrame frame= (IJavaStackFrame) ((IJavaThread) source).getTopStackFrame(); 269 if (isExpectedFrame(frame)) { 270 DebugPlugin plugin = DebugPlugin.getDefault(); 271 plugin.removeDebugEventListener(this); 272 plugin.asyncExec(new Runnable() { 273 @Override 274 public void run() { 275 try { 276 doStepIn(editor, frame, method); 277 } catch (DebugException e) { 278 showErrorMessage(editor, e.getStatus().getMessage()); 279 } 280 } 281 }); 282 } 283 } catch (DebugException e) { 284 return; 285 } 286 } 287 } 288 /** 289 * Returns whether the given frame is the frame that this action is expecting. 290 * This frame is expecting a stack frame for the suspension of the "run to line". 291 * @param frame the given stack frame or <code>null</code> 292 * @return whether the given stack frame is the expected frame 293 * @throws DebugException 294 */ 295 private boolean isExpectedFrame(IJavaStackFrame frame) throws DebugException { 296 return frame != null && 297 line == frame.getLineNumber() && 298 frame.getReceivingTypeName().equals(typeName); 299 } 300 /** 301 * When the debug target we're listening for terminates, stop listening 302 * to debug events. 303 * @param event the debug event 304 */ 305 private void handleTerminateEvent(DebugEvent event) { 306 Object source = event.getSource(); 307 if (thread.getDebugTarget() == source) { 308 DebugPlugin.getDefault().removeDebugEventListener(this); 309 } 310 } 311 }; 312 DebugPlugin.getDefault().addDebugEventListener(listener); 313 try { 314 runToLineAction.runToLine(editor, textSelection, thread); 315 } catch (CoreException e) { 316 DebugPlugin.getDefault().removeDebugEventListener(listener); 317 showErrorMessage(editor, ActionMessages.StepIntoSelectionActionDelegate_4); 318 JDIDebugUIPlugin.log(e.getStatus()); 319 } 320 } 321 322 /** 323 * Steps into the given method in the given stack frame 324 * 325 * @param editor 326 * @param frame the frame in which the step should begin 327 * @param method the method to step into 328 * @throws DebugException 329 */ doStepIn(IEditorPart editor, IJavaStackFrame frame, IMethod method)330 static void doStepIn(IEditorPart editor, IJavaStackFrame frame, IMethod method) throws DebugException { 331 // ensure top stack frame 332 IStackFrame tos = frame.getThread().getTopStackFrame(); 333 if (tos == null) { 334 return; 335 } 336 if (!tos.equals(frame)) { 337 showErrorMessage(editor, ActionMessages.StepIntoSelectionActionDelegate_Step_into_selection_only_available_in_top_stack_frame__3); 338 return; 339 } 340 StepIntoSelectionHandler handler = new StepIntoSelectionHandler((IJavaThread)frame.getThread(), frame, method); 341 handler.step(); 342 } 343 344 /** 345 * Displays an error message in the status area 346 * 347 * @param editor 348 * @param message 349 */ showErrorMessage(IEditorPart editor, String message)350 static void showErrorMessage(IEditorPart editor, String message) { 351 if (editor != null) { 352 IEditorStatusLine statusLine = editor.getAdapter(IEditorStatusLine.class); 353 if (statusLine != null) { 354 statusLine.setMessage(true, message, null); 355 } 356 } 357 } 358 359 /** 360 * Return the type containing the selected text, or <code>null</code> if the 361 * selection is not in a type. 362 */ getType(ITextSelection textSelection)363 static IType getType(ITextSelection textSelection) { 364 IMember member= ActionDelegateHelper.getDefault().getCurrentMember(textSelection); 365 IType type= null; 366 if (member instanceof IType) { 367 type = (IType)member; 368 } else if (member != null) { 369 type= member.getDeclaringType(); 370 } 371 return type; 372 } 373 374 /** 375 * Strips inner class names and parameterized type information from the given type name. 376 * 377 * @param fullyQualifiedName 378 */ stripInnerNamesAndParameterType(String fullyQualifiedName)379 static String stripInnerNamesAndParameterType(String fullyQualifiedName) { 380 // ignore inner class qualification, as the compiler generated names and java model names can be different 381 String sig = fullyQualifiedName; 382 int index = sig.indexOf('$'); 383 if (index > 0) { 384 sig = sig.substring(0, index); 385 } 386 //also ignore erasure 387 index = sig.indexOf('<'); 388 if (index > 0){ 389 sig = sig.substring(0, index); 390 } 391 return sig; 392 } 393 } 394