diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/FilterImplementationQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/FilterImplementationQuickFix.java index 43ac52c03..6eb21697c 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/FilterImplementationQuickFix.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/FilterImplementationQuickFix.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2023 Red Hat Inc. and others. + * Copyright (c) 2020, 2024 Red Hat Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,15 +16,27 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; import com.intellij.psi.util.PsiTreeUtil; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.ExtendedCodeAction; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.IJavaCodeActionParticipant; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ImplementInterfaceProposal; import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4mp.commons.CodeActionResolveData; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * QuickFix for fixing HttpServlet extension error by providing the code actions @@ -36,27 +48,48 @@ * @author Credit to Angelo ZERR * */ -public class FilterImplementationQuickFix { +public class FilterImplementationQuickFix implements IJavaCodeActionParticipant { + private static final Logger LOGGER = Logger.getLogger(FilterImplementationQuickFix.class.getName()); + + @Override + public String getParticipantId() { + return FilterImplementationQuickFix.class.getName(); + } public List getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic) { List codeActions = new ArrayList<>(); PsiElement node = context.getCoveredNode(); PsiClass parentType = getBinding(node); if (parentType != null) { - // Create code action - // interface - ChangeCorrectionProposal proposal = new ImplementInterfaceProposal( - null, parentType, context.getASTRoot(), - "jakarta.servlet.Filter", 0, context.getSource().getCompilationUnit()); - CodeAction codeAction = context.convertToCodeAction(proposal, diagnostic); - - if (codeAction != null) { - codeActions.add(codeAction); - } + String title = Messages.getMessage("LetClassImplement", + parentType.getName(), + ServletConstants.FILTER); + codeActions.add(JDTUtils.createCodeAction(context, diagnostic, title, getParticipantId())); } return codeActions; } + @Override + public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) { + + final CodeAction toResolve = context.getUnresolved(); + final PsiElement node = context.getCoveredNode(); + final PsiClass parentType = getBinding(node); + + assert parentType != null; + ChangeCorrectionProposal proposal = new ImplementInterfaceProposal( + context.getCompilationUnit(), parentType, context.getASTRoot(), + "jakarta.servlet.Filter", 0, context.getSource().getCompilationUnit()); + try { + WorkspaceEdit we = context.convertToWorkspaceEdit(proposal); + toResolve.setEdit(we); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Unable to create workspace edit for code action to filter implementation", e); + } + return toResolve; + } + + private PsiClass getBinding(PsiElement node) { return PsiTreeUtil.getParentOfType(node, PsiClass.class); } diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/ListenerImplementationQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/ListenerImplementationQuickFix.java index 1b54e40dc..28dd026fd 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/ListenerImplementationQuickFix.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/servlet/ListenerImplementationQuickFix.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2023 Red Hat Inc. and others. + * Copyright (c) 2020, 2024 Red Hat Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,15 +17,25 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; import com.intellij.psi.util.PsiTreeUtil; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.ExtendedCodeAction; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.IJavaCodeActionParticipant; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ImplementInterfaceProposal; import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4mp.commons.CodeActionResolveData; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * QuickFix for fixing HttpServlet extension error by providing the code actions @@ -38,48 +48,69 @@ * */ -public class ListenerImplementationQuickFix { +public class ListenerImplementationQuickFix implements IJavaCodeActionParticipant { + + private static final Logger LOGGER = Logger.getLogger(ListenerImplementationQuickFix.class.getName()); + + private final static String INTERFACE_NAME_KEY = "interface"; + + @Override + public String getParticipantId() { + return ListenerImplementationQuickFix.class.getName(); + } + public List getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic) { List codeActions = new ArrayList<>(); - // Create code action - // interface - - setUpCodeAction(codeActions, diagnostic, context, ServletConstants.SERVLET_CONTEXT_LISTENER, - "jakarta.servlet.ServletContextListener"); - setUpCodeAction(codeActions, diagnostic, context, - ServletConstants.SERVLET_CONTEXT_ATTRIBUTE_LISTENER, - "jakarta.servlet.ServletContextAttributeListener"); - setUpCodeAction(codeActions, diagnostic, context, ServletConstants.SERVLET_REQUEST_LISTENER, - "jakarta.servlet.ServletRequestListener"); - setUpCodeAction(codeActions, diagnostic, context, - ServletConstants.SERVLET_REQUEST_ATTRIBUTE_LISTENER, - "jakarta.servlet.ServletRequestAttributeListener"); - setUpCodeAction(codeActions, diagnostic, context, ServletConstants.HTTP_SESSION_LISTENER, - "jakarta.servlet.http.HttpSessionListener"); - setUpCodeAction(codeActions, diagnostic, context, - ServletConstants.HTTP_SESSION_ATTRIBUTE_LISTENER, - "jakarta.servlet.http.HttpSessionAttributeListener"); - setUpCodeAction(codeActions, diagnostic, context, ServletConstants.HTTP_SESSION_ID_LISTENER, - "jakarta.servlet.http.HttpSessionIdListener"); + PsiElement node = context.getCoveredNode(); + PsiClass parentType = getBinding(node); + + String[] listenerConstants = { + ServletConstants.SERVLET_CONTEXT_LISTENER_FQ_NAME, + ServletConstants.SERVLET_CONTEXT_ATTRIBUTE_LISTENER_FQ_NAME, + ServletConstants.SERVLET_REQUEST_LISTENER_FQ_NAME, + ServletConstants.SERVLET_REQUEST_ATTRIBUTE_LISTENER_FQ_NAME, + ServletConstants.HTTP_SESSION_LISTENER_FQ_NAME, + ServletConstants.HTTP_SESSION_ATTRIBUTE_LISTENER_FQ_NAME, + ServletConstants.HTTP_SESSION_ID_LISTENER_FQ_NAME + }; + + for (String interfaceType : listenerConstants) { + Map extendedData = new HashMap<>(); + extendedData.put(INTERFACE_NAME_KEY, interfaceType); + String title = getLabel(interfaceType, parentType); + codeActions.add(JDTUtils.createCodeAction(context, diagnostic, title, getParticipantId(), extendedData)); + } return codeActions; } + @Override + public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) { + final CodeAction toResolve = context.getUnresolved(); + final PsiElement node = context.getCoveredNode(); + final PsiClass parentType = getBinding(node); + CodeActionResolveData data = (CodeActionResolveData) toResolve.getData(); + String interfaceType = (String) data.getExtendedDataEntry(INTERFACE_NAME_KEY); + + assert parentType != null; + ChangeCorrectionProposal proposal = new ImplementInterfaceProposal( + context.getCompilationUnit(), parentType, context.getASTRoot(), + interfaceType, 0, context.getSource().getCompilationUnit()); + try { + WorkspaceEdit we = context.convertToWorkspaceEdit(proposal); + toResolve.setEdit(we); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Unable to create workspace edit for code action to listener implementation", e); + } + return toResolve; + } + private PsiClass getBinding(PsiElement node) { return PsiTreeUtil.getParentOfType(node, PsiClass.class); } - private void setUpCodeAction(List codeActions, Diagnostic diagnostic, JavaCodeActionContext sourceContext, - String interfaceName, String interfaceType) { - JavaCodeActionContext targetContext = sourceContext.copy(); - PsiElement node = targetContext.getCoveredNode(); // find covered node in the new context - PsiClass targetType = getBinding(node); - if (targetType != null) { - ChangeCorrectionProposal proposal = new ImplementInterfaceProposal( - null, targetType, targetContext.getASTRoot(), interfaceType, 0, - sourceContext.getCompilationUnit()); - CodeAction codeAction = targetContext.convertToCodeAction(proposal, diagnostic); - codeActions.add(codeAction); - } + private static String getLabel(String fqAnnotation, PsiClass parentType) { + String annotationName = fqAnnotation.substring(fqAnnotation.lastIndexOf('.') + 1, fqAnnotation.length()); + return Messages.getMessage("LetClassImplement", parentType.getName(), annotationName); } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index e615d397c..994071043 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -210,7 +210,7 @@ - + + + + + diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/servlet/JakartaServletTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/servlet/JakartaServletTest.java index d9eb6828e..c900e6360 100644 --- a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/servlet/JakartaServletTest.java +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/servlet/JakartaServletTest.java @@ -219,4 +219,104 @@ public void RemoveDuplicateWebFilterAttributes() throws Exception { CodeAction ca2 = JakartaForJavaAssert.ca(uri, "Remove the `value` attribute from @WebFilter", d, te2); JakartaForJavaAssert.assertJavaCodeAction(codeActionParams, utils, ca1, ca2); } + + @Test + public void implementFilter() throws Exception { + Module module = createMavenModule(new File("src/test/resources/projects/maven/jakarta-sample")); + IPsiUtils utils = PsiUtilsLSImpl.getInstance(myProject); + + VirtualFile javaFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(ModuleUtilCore.getModuleDirPath(module) + + "/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementFilter.java"); + String uri = VfsUtilCore.virtualToIoFile(javaFile).toURI().toString(); + + JakartaDiagnosticsParams diagnosticsParams = new JakartaDiagnosticsParams(); + diagnosticsParams.setUris(Arrays.asList(uri)); + + Diagnostic d = JakartaForJavaAssert.d(5, 13, 32, "Annotated classes with @WebFilter must implement the Filter interface.", + DiagnosticSeverity.Error, "jakarta-servlet", "ImplementFilter"); + + JakartaForJavaAssert.assertJavaDiagnostics(diagnosticsParams, utils, d); + + //TODO: this condition will be enabled when all quickfixes are refactored. +// if (CHECK_CODE_ACTIONS) { + // test associated quick-fix code action + JakartaJavaCodeActionParams codeActionParams = JakartaForJavaAssert.createCodeActionParams(uri, d); + String newText = "package io.openliberty.sample.jakarta.servlet;" + + "\n\nimport jakarta.servlet.Filter;\nimport jakarta.servlet.annotation.WebFilter;" + + "\n\n@WebFilter(urlPatterns = {\"/filter\"})\npublic class DontImplementFilter implements " + + "Filter {\n\n}"; + TextEdit te = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + + CodeAction ca = JakartaForJavaAssert.ca(uri, "Let 'DontImplementFilter' implement 'Filter'", d, te); + JakartaForJavaAssert.assertJavaCodeAction(codeActionParams, utils, ca); +// } + } + + @Test + public void implementListener() throws Exception { + Module module = createMavenModule(new File("src/test/resources/projects/maven/jakarta-sample")); + IPsiUtils utils = PsiUtilsLSImpl.getInstance(myProject); + + VirtualFile javaFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(ModuleUtilCore.getModuleDirPath(module) + + "/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementListener.java"); + String uri = VfsUtilCore.virtualToIoFile(javaFile).toURI().toString(); + + JakartaDiagnosticsParams diagnosticsParams = new JakartaDiagnosticsParams(); + diagnosticsParams.setUris(Arrays.asList(uri)); + + Diagnostic d = JakartaForJavaAssert.d(5, 13, 34, "Annotated classes with @WebListener must implement one or more of the following interfaces: ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener, HttpSessionListener, HttpSessionAttributeListener, or HttpSessionIdListener.", + DiagnosticSeverity.Error, "jakarta-servlet", "ImplementListener"); + + JakartaForJavaAssert.assertJavaDiagnostics(diagnosticsParams, utils, d); + + //TODO: this condition will be enabled when all quickfixes are refactored. +// if (CHECK_CODE_ACTIONS) { + // test associated quick-fix code action + JakartaJavaCodeActionParams codeActionParams = JakartaForJavaAssert.createCodeActionParams(uri, d); + + String newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet.annotation." + + "WebListener;\nimport jakarta.servlet.http.HttpSessionAttributeListener;\n\n@WebListener\n" + + "public class DontImplementListener implements HttpSessionAttributeListener {\n\n}"; + TextEdit te1 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca1 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'HttpSessionAttributeListener'", d, te1); + + newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet.annotation." + + "WebListener;\nimport jakarta.servlet.http.HttpSessionIdListener;\n\n@WebListener\n" + + "public class DontImplementListener implements HttpSessionIdListener {\n\n}"; + TextEdit te2 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca2 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'HttpSessionIdListener'", d, te2); + + newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet.annotation." + + "WebListener;\nimport jakarta.servlet.http.HttpSessionListener;\n\n@WebListener\npublic" + + " class DontImplementListener implements HttpSessionListener {\n\n}"; + TextEdit te3 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca3 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'HttpSessionListener'", d, te3); + + newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet." + + "ServletContextAttributeListener;\nimport jakarta.servlet.annotation.WebListener;\n\n" + + "@WebListener\npublic class DontImplementListener implements ServletContextAttributeListener {\n\n}"; + TextEdit te4 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca4 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'ServletContextAttributeListener'", d, te4); + + newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet.ServletContextListener;" + + "\nimport jakarta.servlet.annotation.WebListener;\n\n@WebListener\npublic class DontImplementListener" + + " implements ServletContextListener {\n\n}"; + TextEdit te5 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca5 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'ServletContextListener'", d, te5); + + newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet." + + "ServletRequestAttributeListener;\nimport jakarta.servlet.annotation.WebListener;\n\n" + + "@WebListener\npublic class DontImplementListener implements ServletRequestAttributeListener {\n\n}"; + TextEdit te6 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca6 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'ServletRequestAttributeListener'", d, te6); + + newText = "package io.openliberty.sample.jakarta.servlet;\n\nimport jakarta.servlet.ServletRequestListener;" + + "\nimport jakarta.servlet.annotation.WebListener;\n\n@WebListener\npublic class DontImplementListener" + + " implements ServletRequestListener {\n\n}"; + TextEdit te7 = JakartaForJavaAssert.te(0, 0, 7, 1, newText); + CodeAction ca7 = JakartaForJavaAssert.ca(uri, "Let 'DontImplementListener' implement 'ServletRequestListener'", d, te7); + + JakartaForJavaAssert.assertJavaCodeAction(codeActionParams, utils, ca1, ca2, ca3, ca4, ca5, ca6, ca7); + } +// } } diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementFilter.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementFilter.java new file mode 100644 index 000000000..4f00c71e5 --- /dev/null +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementFilter.java @@ -0,0 +1,8 @@ +package io.openliberty.sample.jakarta.servlet; + +import jakarta.servlet.annotation.WebFilter; + +@WebFilter(urlPatterns = { "/filter" }) +public class DontImplementFilter { + +} \ No newline at end of file diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementListener.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementListener.java new file mode 100644 index 000000000..0d2383687 --- /dev/null +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/servlet/DontImplementListener.java @@ -0,0 +1,8 @@ +package io.openliberty.sample.jakarta.servlet; + +import jakarta.servlet.annotation.WebListener; + +@WebListener +public class DontImplementListener { + +} \ No newline at end of file