| /******************************************************************************* |
| * 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 |
| * Sebastian Davids <sdavids@gmx.de> - bug 48696 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.junit.ui; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| |
| import org.eclipse.core.resources.IFile; |
| |
| import org.eclipse.text.edits.MalformedTreeException; |
| import org.eclipse.text.edits.TextEdit; |
| |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.CompositeChange; |
| import org.eclipse.ltk.core.refactoring.PerformChangeOperation; |
| import org.eclipse.ltk.core.refactoring.RefactoringCore; |
| import org.eclipse.ltk.core.refactoring.TextFileChange; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.BodyDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.IAnnotationBinding; |
| import org.eclipse.jdt.core.dom.IMethodBinding; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.MarkerAnnotation; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; |
| |
| import org.eclipse.jdt.internal.junit.BasicElementLabels; |
| import org.eclipse.jdt.internal.junit.JUnitCorePlugin; |
| import org.eclipse.jdt.internal.junit.Messages; |
| import org.eclipse.jdt.internal.junit.util.ExceptionHandler; |
| import org.eclipse.jdt.internal.junit.util.JUnitStubUtility; |
| |
| import org.eclipse.jdt.ui.CodeStyleConfiguration; |
| import org.eclipse.jdt.ui.ISharedImages; |
| import org.eclipse.jdt.ui.JavaUI; |
| import org.eclipse.jdt.ui.PreferenceConstants; |
| import org.eclipse.jdt.ui.text.java.ClasspathFixProcessor; |
| import org.eclipse.jdt.ui.text.java.ClasspathFixProcessor.ClasspathFixProposal; |
| import org.eclipse.jdt.ui.text.java.IInvocationContext; |
| import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; |
| import org.eclipse.jdt.ui.text.java.IProblemLocation; |
| import org.eclipse.jdt.ui.text.java.IQuickFixProcessor; |
| |
| public class JUnitQuickFixProcessor implements IQuickFixProcessor { |
| |
| private static final HashSet<String> ASSERT_METHOD_NAMES= new HashSet<>(); |
| |
| public JUnitQuickFixProcessor() { |
| ASSERT_METHOD_NAMES.add("fail"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertTrue"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertFalse"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertEquals"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertNotNull"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertNull"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertSame"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("assertNotSame"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("failNotEquals"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("failSame"); //$NON-NLS-1$ |
| ASSERT_METHOD_NAMES.add("failNotSame"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public boolean hasCorrections(ICompilationUnit unit, int problemId) { |
| return problemId == IProblem.UndefinedType || problemId == IProblem.UndefinedMethod; |
| } |
| |
| @Override |
| public IJavaCompletionProposal[] getCorrections(final IInvocationContext context, IProblemLocation[] locations) { |
| ArrayList<IJavaCompletionProposal> res= null; |
| for (IProblemLocation problem : locations) { |
| int id= problem.getProblemId(); |
| if (IProblem.UndefinedType == id) { |
| res= getAddJUnitToBuildPathProposals(context, problem, res); |
| } else if (id == IProblem.UndefinedMethod) { |
| String currentPreferenceValue= PreferenceConstants.getPreferenceStore().getString(PreferenceConstants.CODEASSIST_FAVORITE_STATIC_MEMBERS); |
| if (!currentPreferenceValue.contains("org.junit.Assert.*")) { //$NON-NLS-1$ |
| res= getAddAssertImportProposals(context, problem, res); |
| } |
| } |
| } |
| if (res == null || res.isEmpty()) { |
| return null; |
| } |
| return res.toArray(new IJavaCompletionProposal[res.size()]); |
| } |
| |
| private ArrayList<IJavaCompletionProposal> getAddAssertImportProposals(IInvocationContext context, IProblemLocation problem, ArrayList<IJavaCompletionProposal> proposals) { |
| String[] args= problem.getProblemArguments(); |
| if (args.length > 1) { |
| String methodName= args[1]; |
| if (ASSERT_METHOD_NAMES.contains(methodName) && isInsideJUnit4Test(context)) { |
| if (proposals == null) { |
| proposals= new ArrayList<>(); |
| } |
| proposals.add(new AddAssertProposal(context.getASTRoot(), methodName, 9)); |
| proposals.add(new AddAssertProposal(context.getASTRoot(), "*", 10)); //$NON-NLS-1$ |
| } |
| } |
| return proposals; |
| } |
| |
| private ArrayList<IJavaCompletionProposal> getAddJUnitToBuildPathProposals(IInvocationContext context, IProblemLocation location, ArrayList<IJavaCompletionProposal> proposals) { |
| try { |
| ICompilationUnit unit= context.getCompilationUnit(); |
| String qualifiedName= null; |
| String qualifiedName1= null; |
| |
| String s= unit.getBuffer().getText(location.getOffset(), location.getLength()); |
| switch (s) { |
| case "TestCase": //$NON-NLS-1$ |
| case "TestSuite": //$NON-NLS-1$ |
| qualifiedName= "junit.framework." + s; //$NON-NLS-1$ |
| break; |
| case "RunWith": //$NON-NLS-1$ |
| qualifiedName= "org.junit.runner.RunWith"; //$NON-NLS-1$ |
| break; |
| case "Test": //$NON-NLS-1$ |
| ASTNode node= location.getCoveredNode(context.getASTRoot()); |
| if (node != null && node.getLocationInParent() == MarkerAnnotation.TYPE_NAME_PROPERTY) { |
| qualifiedName= "org.junit.Test"; //$NON-NLS-1$ |
| qualifiedName1= "org.junit.jupiter.api.Test"; //$NON-NLS-1$ |
| } else { |
| qualifiedName= "junit.framework.Test"; //$NON-NLS-1$ |
| } |
| break; |
| case "TestFactory": //$NON-NLS-1$ |
| qualifiedName= "org.junit.jupiter.api.TestFactory"; //$NON-NLS-1$ |
| break; |
| case "Testable": //$NON-NLS-1$ |
| qualifiedName= "org.junit.platform.commons.annotation.Testable"; //$NON-NLS-1$ |
| break; |
| case "TestTemplate": //$NON-NLS-1$ |
| qualifiedName= "org.junit.jupiter.api.TestTemplate"; //$NON-NLS-1$ |
| break; |
| case "ParameterizedTest": //$NON-NLS-1$ |
| qualifiedName= "org.junit.jupiter.params.ParameterizedTest"; //$NON-NLS-1$ |
| break; |
| case "RepeatedTest": //$NON-NLS-1$ |
| qualifiedName= "org.junit.jupiter.api.RepeatedTest"; //$NON-NLS-1$ |
| break; |
| default: |
| break; |
| } |
| IJavaProject javaProject= unit.getJavaProject(); |
| if (!(foundInProjectClasspath(javaProject, qualifiedName) && foundInProjectClasspath(javaProject, qualifiedName1))) { |
| if (qualifiedName != null) { |
| proposals= addProposal(context, proposals, qualifiedName, javaProject); |
| } |
| if (qualifiedName1 != null) { |
| proposals= addProposal(context, proposals, qualifiedName1, javaProject); |
| } |
| } else { |
| return proposals; |
| } |
| } catch (JavaModelException e) { |
| JUnitPlugin.log(e.getStatus()); |
| } |
| return proposals; |
| } |
| |
| private ArrayList<IJavaCompletionProposal> addProposal(IInvocationContext context, ArrayList<IJavaCompletionProposal> proposals, String qualifiedName, IJavaProject javaProject) { |
| ClasspathFixProposal[] fixProposals= ClasspathFixProcessor.getContributedFixImportProposals(javaProject, qualifiedName, null); |
| for (ClasspathFixProposal fixProposal : fixProposals) { |
| if (proposals == null) |
| proposals= new ArrayList<>(); |
| proposals.add(new JUnitClasspathFixCorrectionProposal(javaProject, fixProposal, getImportRewrite(context.getASTRoot(), qualifiedName))); |
| } |
| return proposals; |
| } |
| |
| private boolean foundInProjectClasspath(IJavaProject javaProject, String qualifiedName) throws JavaModelException { |
| return qualifiedName != null && javaProject.findType(qualifiedName) != null; |
| } |
| |
| private ImportRewrite getImportRewrite(CompilationUnit astRoot, String typeToImport) { |
| if (typeToImport != null) { |
| ImportRewrite importRewrite= CodeStyleConfiguration.createImportRewrite(astRoot, true); |
| importRewrite.addImport(typeToImport); |
| return importRewrite; |
| } |
| return null; |
| } |
| |
| private boolean isInsideJUnit4Test(IInvocationContext context) { |
| if (!JUnitStubUtility.is50OrHigher(context.getCompilationUnit().getJavaProject())) { |
| return false; |
| } |
| |
| ASTNode node= context.getCoveringNode(); |
| while (node != null && !(node instanceof BodyDeclaration)) { |
| node= node.getParent(); |
| } |
| if (node instanceof MethodDeclaration) { |
| IMethodBinding binding= ((MethodDeclaration) node).resolveBinding(); |
| if (binding != null) { |
| IAnnotationBinding[] annotations= binding.getAnnotations(); |
| for (IAnnotationBinding annotation : annotations) { |
| final ITypeBinding annotationType= annotation.getAnnotationType(); |
| if (annotationType != null && JUnitCorePlugin.JUNIT4_ANNOTATION_NAME.equals(annotationType.getQualifiedName())) |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static class JUnitClasspathFixCorrectionProposal implements IJavaCompletionProposal { |
| |
| private final ClasspathFixProposal fClasspathFixProposal; |
| private final ImportRewrite fImportRewrite; |
| private final IJavaProject fJavaProject; |
| |
| public JUnitClasspathFixCorrectionProposal(IJavaProject javaProject, ClasspathFixProposal cpfix, ImportRewrite rewrite) { |
| fJavaProject= javaProject; |
| fClasspathFixProposal= cpfix; |
| fImportRewrite= rewrite; |
| } |
| |
| protected Change createChange() throws CoreException { |
| Change change= fClasspathFixProposal.createChange(null); |
| if (fImportRewrite != null) { |
| TextFileChange cuChange= new TextFileChange("Add import", (IFile) fImportRewrite.getCompilationUnit().getResource()); //$NON-NLS-1$ |
| cuChange.setEdit(fImportRewrite.rewriteImports(null)); |
| |
| CompositeChange composite= new CompositeChange(getDisplayString()); |
| composite.add(change); |
| composite.add(cuChange); |
| return composite; |
| } |
| return change; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| try { |
| PlatformUI.getWorkbench().getActiveWorkbenchWindow().run(false, true, new IRunnableWithProgress() { |
| @Override |
| public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { |
| try { |
| Change change= createChange(); |
| change.initializeValidationData(new NullProgressMonitor()); |
| PerformChangeOperation op= new PerformChangeOperation(change); |
| op.setUndoManager(RefactoringCore.getUndoManager(), getDisplayString()); |
| op.setSchedulingRule(fJavaProject.getProject().getWorkspace().getRoot()); |
| op.run(monitor); |
| } catch (CoreException e) { |
| throw new InvocationTargetException(e); |
| } catch (OperationCanceledException e) { |
| throw new InterruptedException(); |
| } |
| } |
| }); |
| } catch (InvocationTargetException e) { |
| ExceptionHandler.handle(e, JUnitPlugin.getActiveWorkbenchShell(), JUnitMessages.JUnitQuickFixProcessor_apply_problem_title, JUnitMessages.JUnitQuickFixProcessor_apply_problem_description); |
| } catch (InterruptedException e) { |
| |
| } |
| } |
| |
| @Override |
| public String getAdditionalProposalInfo() { |
| return fClasspathFixProposal.getAdditionalProposalInfo(); |
| } |
| |
| @Override |
| public int getRelevance() { |
| return fClasspathFixProposal.getRelevance(); |
| } |
| |
| @Override |
| public IContextInformation getContextInformation() { |
| return null; |
| } |
| |
| @Override |
| public String getDisplayString() { |
| return fClasspathFixProposal.getDisplayString(); |
| } |
| |
| @Override |
| public Image getImage() { |
| return fClasspathFixProposal.getImage(); |
| } |
| |
| @Override |
| public Point getSelection(IDocument document) { |
| return null; |
| } |
| } |
| |
| private static class AddAssertProposal implements IJavaCompletionProposal { |
| |
| private final CompilationUnit fAstRoot; |
| private final String fMethodName; |
| private final int fRelevance; |
| |
| public AddAssertProposal(CompilationUnit astRoot, String methodName, int relevance) { |
| fAstRoot= astRoot; |
| fMethodName= methodName; |
| fRelevance= relevance; |
| } |
| |
| @Override |
| public int getRelevance() { |
| return fRelevance; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| try { |
| ImportRewrite rewrite= CodeStyleConfiguration.createImportRewrite(fAstRoot, true); |
| rewrite.addStaticImport("org.junit.Assert", fMethodName, true); //$NON-NLS-1$ |
| TextEdit edit= rewrite.rewriteImports(null); |
| edit.apply(document); |
| } catch (MalformedTreeException e) { |
| } catch (CoreException e) { |
| } catch (BadLocationException e) { |
| } |
| } |
| |
| @Override |
| public String getAdditionalProposalInfo() { |
| return Messages.format(JUnitMessages.JUnitQuickFixProcessor_add_assert_info, BasicElementLabels.getJavaElementName("org.junit.Assert." + fMethodName)); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public IContextInformation getContextInformation() { |
| return null; |
| } |
| |
| @Override |
| public String getDisplayString() { |
| return Messages.format(JUnitMessages.JUnitQuickFixProcessor_add_assert_description, BasicElementLabels.getJavaElementName("org.junit.Assert." + fMethodName)); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public Image getImage() { |
| return JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_IMPDECL); |
| } |
| |
| @Override |
| public Point getSelection(IDocument document) { |
| return null; |
| } |
| } |
| |
| } |