| /******************************************************************************* |
| * 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 |
| * Andreas Schmid, service@aaschmid.de - Locate test method even if it contains parameters - http://bugs.eclipse.org/343935 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.junit.ui; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| |
| import org.eclipse.jface.dialogs.MessageDialog; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IMethod; |
| import org.eclipse.jdt.core.ISourceRange; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.ITypeHierarchy; |
| import org.eclipse.jdt.core.JavaConventions; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.Signature; |
| import org.eclipse.jdt.core.search.IJavaSearchConstants; |
| import org.eclipse.jdt.core.search.IJavaSearchScope; |
| import org.eclipse.jdt.core.search.SearchEngine; |
| import org.eclipse.jdt.core.search.SearchMatch; |
| import org.eclipse.jdt.core.search.SearchParticipant; |
| import org.eclipse.jdt.core.search.SearchPattern; |
| import org.eclipse.jdt.core.search.SearchRequestor; |
| |
| import org.eclipse.jdt.internal.corext.util.JavaConventionsUtil; |
| import org.eclipse.jdt.internal.junit.BasicElementLabels; |
| import org.eclipse.jdt.internal.junit.Messages; |
| import org.eclipse.jdt.internal.junit.model.TestCaseElement; |
| import org.eclipse.jdt.internal.junit.model.TestElement; |
| |
| import org.eclipse.jdt.internal.ui.actions.SelectionConverter; |
| |
| /** |
| * Open a class on a Test method. |
| */ |
| public class OpenTestAction extends OpenEditorAction { |
| |
| private String fMethodName; |
| private String[] fMethodParamTypes; |
| private IMethod fMethod; |
| private int fLineNumber= -1; |
| |
| private IType fType; |
| |
| public OpenTestAction(TestRunnerViewPart testRunnerPart, TestCaseElement testCase, String[] methodParamTypes) { |
| this(testRunnerPart, testCase.getClassName(), extractRealMethodName(testCase), methodParamTypes, true); |
| String trace= testCase.getTrace(); |
| if (trace != null) { |
| String rawClassName= TestElement.extractRawClassName(testCase.getTestName()); |
| rawClassName= rawClassName.replaceAll("\\.", "\\\\."); //$NON-NLS-1$//$NON-NLS-2$ |
| rawClassName= rawClassName.replaceAll("\\$", "\\\\\\$"); //$NON-NLS-1$//$NON-NLS-2$ |
| Pattern pattern= Pattern.compile(FailureTrace.FRAME_PREFIX |
| + rawClassName + '.' + fMethodName |
| + "\\(.*:(\\d+)\\)" //$NON-NLS-1$ |
| ); |
| Matcher matcher= pattern.matcher(trace); |
| if (matcher.find()) { |
| try { |
| fLineNumber= Integer.parseInt(matcher.group(1)); |
| } catch (NumberFormatException e) { |
| // continue |
| } |
| } |
| } |
| } |
| |
| public OpenTestAction(TestRunnerViewPart testRunner, String className) { |
| this(testRunner, className, null, null, true); |
| } |
| |
| public OpenTestAction(TestRunnerViewPart testRunner, String className, String method, String[] methodParamTypes, boolean activate) { |
| super(testRunner, className, activate); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJUnitHelpContextIds.OPENTEST_ACTION); |
| fMethodName= method; |
| fMethodParamTypes= methodParamTypes; |
| } |
| |
| private static String extractRealMethodName(TestCaseElement testCase) { |
| //workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=334864 : |
| if (testCase.isIgnored() && JavaConventions.validateJavaTypeName(testCase.getTestName(), JavaCore.VERSION_1_5, JavaCore.VERSION_1_5, null).getSeverity() != IStatus.ERROR) { |
| return null; |
| } |
| |
| //workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=275308 : |
| String testMethodName= testCase.getTestMethodName(); |
| for (int i= 0; i < testMethodName.length(); i++) { |
| if (!Character.isJavaIdentifierPart(testMethodName.charAt(i))) { |
| return testMethodName.substring(0, i); |
| } |
| } |
| return testMethodName; |
| } |
| |
| @Override |
| protected IJavaElement findElement(IJavaProject project, String className) throws JavaModelException { |
| IType type= findType(project, className); |
| if (type == null) |
| return null; |
| |
| if (fMethodName == null) { |
| fType= type; |
| return type; |
| } |
| |
| IMethod method= null; |
| try { |
| method= findMethod(type); |
| if (method == null) { |
| ITypeHierarchy typeHierarchy= type.newSupertypeHierarchy(null); |
| IType[] supertypes= typeHierarchy.getAllSupertypes(type); |
| for (IType supertype : supertypes) { |
| method= findMethod(supertype); |
| if (method != null) |
| break; |
| } |
| } |
| } catch (OperationCanceledException e) { |
| // user cancelled the selection dialog - ignore and proceed |
| } |
| if (method == null) { |
| if (fLineNumber < 0) { |
| String title= JUnitMessages.OpenTestAction_dialog_title; |
| String message= Messages.format(JUnitMessages.OpenTestAction_error_methodNoFound, BasicElementLabels.getJavaElementName(fMethodName)); |
| MessageDialog.openInformation(getShell(), title, message); |
| } |
| return type; |
| } |
| |
| fMethod= method; |
| return method; |
| } |
| |
| private IMethod findMethod(IType type) { |
| IStatus status= JavaConventionsUtil.validateMethodName(fMethodName, type); |
| if (! status.isOK()) |
| return null; |
| |
| List<IMethod> foundMethods= new ArrayList<>(); |
| try { |
| PlatformUI.getWorkbench().getProgressService().busyCursorWhile(monitor -> { |
| String methodPattern= type.getFullyQualifiedName('.') + '.' + fMethodName; |
| if (fMethodParamTypes != null && fMethodParamTypes.length > 0) { |
| String paramTypes= Arrays.stream(fMethodParamTypes).map(paramType -> { |
| try { |
| return paramType= Signature.toString(paramType); |
| } catch (IllegalArgumentException e1) { |
| // return the paramType as it is |
| } |
| return paramType.replace('$', '.'); // for nested classes... See OpenEditorAction#findType also. |
| }).collect(Collectors.joining(", ", "(", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| methodPattern+= paramTypes; |
| } else { |
| methodPattern+= "()"; //$NON-NLS-1$ |
| } |
| int matchRule= SearchPattern.R_ERASURE_MATCH | SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE; |
| SearchPattern searchPattern= SearchPattern.createPattern(methodPattern, IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, matchRule); |
| if (searchPattern == null) { |
| return; |
| } |
| SearchRequestor requestor= new SearchRequestor() { |
| @Override |
| public void acceptSearchMatch(SearchMatch match) throws CoreException { |
| Object element= match.getElement(); |
| if (element instanceof IMethod) { |
| foundMethods.add((IMethod) element); |
| } |
| } |
| }; |
| SearchParticipant[] participants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }; |
| IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaElement[] { type }); |
| try { |
| new SearchEngine().search(searchPattern, participants, scope, requestor, monitor); |
| } catch (CoreException e2) { |
| JUnitPlugin.log(e2); |
| } |
| }); |
| } catch (InvocationTargetException e) { |
| JUnitPlugin.log(e); |
| } catch (InterruptedException e) { |
| // user cancelled |
| } |
| |
| if (foundMethods.size() == 1) { |
| return foundMethods.get(0); |
| } else if (foundMethods.size() > 1) { |
| IMethod method= openSelectionDialog(foundMethods); |
| if (method == null) { |
| throw new OperationCanceledException(); |
| } |
| return method; |
| } |
| |
| // search just by name and number of parameters, if method not found yet |
| try { |
| for (IMethod method : type.getMethods()) { |
| String methodName= method.getElementName(); |
| if (fMethodName.equals(methodName)) { |
| int numOfParams= method.getNumberOfParameters(); |
| int requiredNumOfParams= 0; |
| if (fMethodParamTypes != null) { |
| requiredNumOfParams= fMethodParamTypes.length; |
| } |
| if (numOfParams == requiredNumOfParams) { |
| foundMethods.add(method); |
| } |
| } |
| } |
| if (foundMethods.isEmpty()) { |
| return null; |
| } else if (foundMethods.size() > 1) { |
| IMethod method= openSelectionDialog(foundMethods); |
| if (method == null) { |
| throw new OperationCanceledException(); |
| } |
| return method; |
| } else { |
| return foundMethods.get(0); |
| } |
| } catch (JavaModelException e) { |
| // if type does not exist or if an exception occurs while accessing its resource => ignore (no method found) |
| } |
| |
| return null; |
| } |
| |
| private IMethod openSelectionDialog(List<IMethod> foundMethods) { |
| IMethod[] elements= foundMethods.toArray(new IMethod[foundMethods.size()]); |
| String title= JUnitMessages.OpenTestAction_dialog_title; |
| String message= JUnitMessages.OpenTestAction_select_element; |
| return (IMethod) SelectionConverter.selectJavaElement(elements, getShell(), title, message); |
| } |
| |
| @Override |
| protected void reveal(ITextEditor textEditor) { |
| if (fLineNumber >= 0) { |
| try { |
| IDocument document= textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); |
| int lineOffset= document.getLineOffset(fLineNumber-1); |
| int lineLength= document.getLineLength(fLineNumber-1); |
| if (fMethod != null) { |
| try { |
| ISourceRange sr= fMethod.getSourceRange(); |
| if (sr == null || sr.getOffset() == -1 |
| || lineOffset < sr.getOffset() |
| || sr.getOffset() + sr.getLength() < lineOffset + lineLength) { |
| throw new BadLocationException(); |
| } |
| } catch (JavaModelException e) { |
| // not a problem |
| } |
| } |
| textEditor.selectAndReveal(lineOffset, lineLength); |
| return; |
| } catch (BadLocationException x) { |
| // marker refers to invalid text position -> do nothing |
| } |
| } |
| if (fMethod != null) { |
| try { |
| ISourceRange range= fMethod.getNameRange(); |
| if (range != null && range.getOffset() >= 0) |
| textEditor.selectAndReveal(range.getOffset(), range.getLength()); |
| return; |
| } catch (JavaModelException e) { |
| // not a problem |
| } |
| } |
| if (fType != null) { |
| try { |
| ISourceRange range= fType.getNameRange(); |
| if (range != null && range.getOffset() >= 0) |
| textEditor.selectAndReveal(range.getOffset(), range.getLength()); |
| } catch (JavaModelException e) { |
| // not a problem |
| } |
| } |
| } |
| |
| } |