Skip to content
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021, 2024 IBM Corporation and others.
* Copyright (c) 2021, 2025 IBM Corporation 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
Expand All @@ -13,8 +13,9 @@

package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations;

import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.search.GlobalSearchScope;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.AbstractDiagnosticsCollector;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
import org.eclipse.lsp4j.Diagnostic;
Expand Down Expand Up @@ -148,6 +149,13 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
if (isMatchedAnnotation(annotation, AnnotationConstants.POST_CONSTRUCT_FQ_NAME)) {
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod) element;
if (isCheckedExceptionPresent(method)) {
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
"@PostConstruct");
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_POSTCONSTRUCT_EXCEPTION, null,
DiagnosticSeverity.Error));
}
if (method.getParameters().length != 0) {
String diagnosticMessage = Messages.getMessage("MethodMustNotHaveParameters",
"@PostConstruct");
Expand All @@ -163,18 +171,17 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
AnnotationConstants.DIAGNOSTIC_CODE_POSTCONSTRUCT_RETURN_TYPE, null,
DiagnosticSeverity.Error));
}

if (method.getThrowsTypes().length != 0) {
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
"@PostConstruct");
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_POSTCONSTRUCT_EXCEPTION, null,
DiagnosticSeverity.Warning));
}
}
} else if (isMatchedAnnotation(annotation, AnnotationConstants.PRE_DESTROY_FQ_NAME)) {
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod) element;
if (isCheckedExceptionPresent(method)) {
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
"@PreDestroy");
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_PREDESTROY_EXCEPTION, null,
DiagnosticSeverity.Error));
}
if (method.getParameters().length != 0) {
String diagnosticMessage = Messages.getMessage("MethodMustNotHaveParameters",
"@PreDestroy");
Expand All @@ -190,14 +197,6 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
AnnotationConstants.DIAGNOSTIC_CODE_PREDESTROY_STATIC, method.getName(),
DiagnosticSeverity.Error));
}

if (method.getThrowsTypes().length != 0) {
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
"@PreDestroy");
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_PREDESTROY_EXCEPTION, null,
DiagnosticSeverity.Warning));
}
}
}
}
Expand All @@ -224,4 +223,58 @@ private static boolean isValidAnnotation(String annotationName, String[] validAn
}
return false;
}

/**
* isCheckedExceptionPresent
* This method scans the exception signatures to identify if any checked exceptions are declared.
*
* @param method
* @return
*/
private boolean isCheckedExceptionPresent(PsiMethod method) {
for (PsiClassType type : method.getThrowsList().getReferencedTypes()) {
PsiClass exceptionClass = type.resolve();
if (exceptionClass != null && extendsException(exceptionClass) && notExtendsRuntimeException(exceptionClass)) {
return true;
}
}
return false;
}

/**
* extendsException
*
* @param exceptionClass The root type of which the super-types are checked.
* @return true if Exception is the superType of the given exception type.
*/
private static boolean extendsException(PsiClass exceptionClass) {
return inheritsFrom(exceptionClass, "java.lang.Exception");
}

/**
* notExtendsRuntimeException
*
* @param exceptionClass
* @return true if RuntimeException is not the superType of the given exception type.
*/
private static boolean notExtendsRuntimeException(PsiClass exceptionClass) {
return !inheritsFrom(exceptionClass, "java.lang.RuntimeException");
}

/**
* inheritsFrom
* Check if specified superType is present or not in the type hierarchy
*
* @param clazz
* @param fqSuperType
* @return
*/
private static boolean inheritsFrom(PsiClass clazz, String fqSuperType) {
Project project = clazz.getProject();
PsiClass superClass = JavaPsiFacade.getInstance(project)
.findClass(fqSuperType, GlobalSearchScope.allScope(project));
return superClass != null &&
(clazz.isEquivalentTo(superClass) || clazz.isInheritor(superClass, true));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,72 +52,85 @@ public void GeneratedAnnotation() throws Exception {

// expected Diagnostics

Diagnostic d1 = d(15, 19, 31, "A method with the @PostConstruct annotation must be void.",
Diagnostic d1 = d(19, 19, 31, "A method with the @PostConstruct annotation must be void.",
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructReturnType");

Diagnostic d2 = d(20, 16, 28, "A method with the @PostConstruct annotation must not have any parameters.",
Diagnostic d2 = d(24, 16, 28, "A method with the @PostConstruct annotation must not have any parameters.",
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructParams");

Diagnostic d3 = d(25, 16, 28, "A method with the @PostConstruct annotation must not throw checked exceptions.",
DiagnosticSeverity.Warning, "jakarta-annotations", "PostConstructException");
Diagnostic d3 = d(28, 16, 28, "A method with the @PostConstruct annotation must not throw checked exceptions.",
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructException");

assertJavaDiagnostics(diagnosticsParams, utils, d1, d2, d3);
Diagnostic d4 = d(43, 16, 32, "A method with the @PostConstruct annotation must not throw checked exceptions.",
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructException");

Diagnostic d5 = d(48, 16, 31, "A method with the @PostConstruct annotation must not throw checked exceptions.",
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructException");

assertJavaDiagnostics(diagnosticsParams, utils, d1, d2, d3, d4, d5);

// Starting codeAction tests.
String newText = "package io.openliberty.sample.jakarta.annotations;\n\n" +
"import jakarta.annotation.PostConstruct;\n" +
"import jakarta.annotation.Resource;\n\n" +
"@Resource(type = Object.class, name = \"aa\")\n" +
"public class PostConstructAnnotation {\n\n" +
" private Integer studentId;\n\n private boolean isHappy;\n\n" +
" private boolean isSad;\n\n @PostConstruct()\n" +
" public void getStudentId() {\n return this.studentId;\n" +
" }\n\n @PostConstruct\n public void getHappiness(String type) {\n\n" +
" }\n\n @PostConstruct\n" +
" public void throwTantrum() throws Exception {\n" +
" System.out.println(\"I'm sad\");\n }\n\n" +
" private String emailAddress;\n\n}";
String newText = "package io.openliberty.sample.jakarta.annotations;\n\nimport jakarta.annotation.PostConstruct;\n" +
"import jakarta.annotation.Resource;\n\nimport java.io.IOException;\n\n@Resource(type = Object.class, name = \"aa\")\n" +
"public class PostConstructAnnotation {\n\n private Integer studentId;\n\n private boolean isHappy;\n\n " +
"private boolean isSad;\n\n private String emailAddress;\n\n @PostConstruct()\n " +
"public void getStudentId() {\n return this.studentId;\n }\n\n @PostConstruct\n " +
"public void getHappiness(String type) {\n }\n\n @PostConstruct\n " +
"public void throwTantrum() throws Exception {\n System.out.println(\"I'm sad\");\n }\n\n " +
"@PostConstruct\n public void throwRuntimeException() throws RuntimeException {\n " +
"System.out.println(\"RuntimeException\");\n }\n\n @PostConstruct\n " +
"public void throwNullPointerException() throws NullPointerException {\n " +
"System.out.println(\"NullPointerException\");\n }\n\n @PostConstruct\n " +
"public void throwIOException() throws IOException {\n System.out.println(\"IOException\");\n }\n\n " +
"@PostConstruct\n public void throwExceptions() throws CustomCheckedException, CustomUncheckedException, IOException {\n " +
"System.out.println(\"throwExceptions\");\n }\n\n @PostConstruct\n " +
"public void throwCustomUnCheckedException() throws CustomUncheckedException {\n " +
"System.out.println(\"CustomUncheckedException\");\n }\n\n " +
"@PostConstruct\n public void throwError() throws Error {\n System.out.println(\"throwError\");\n }\n\n}";

JakartaJavaCodeActionParams codeActionParams2 = createCodeActionParams(uri, d1);
TextEdit te3 = te(0, 0, 31, 1, newText);
TextEdit te3 = te(0, 0, 62, 1, newText);
CodeAction ca3 = ca(uri, "Change return type to void", d1, te3);
assertJavaCodeAction(codeActionParams2, utils, ca3);

String newText1 = "package io.openliberty.sample.jakarta.annotations;\n\n" +
"import jakarta.annotation.PostConstruct;\n" +
"import jakarta.annotation.Resource;\n\n" +
"@Resource(type = Object.class, name = \"aa\")\n" +
"public class PostConstructAnnotation {\n\n" +
" private Integer studentId;\n\n private boolean isHappy;\n\n" +
" private boolean isSad;\n\n @PostConstruct()\n" +
" public Integer getStudentId() {\n return this.studentId;\n" +
" }\n\n public void getHappiness(String type) {\n\n" +
" }\n\n @PostConstruct\n" +
" public void throwTantrum() throws Exception {\n" +
" System.out.println(\"I'm sad\");\n }\n\n" +
" private String emailAddress;\n\n}";

String newText2 = "package io.openliberty.sample.jakarta.annotations;\n\n" +
"import jakarta.annotation.PostConstruct;\n" +
"import jakarta.annotation.Resource;\n\n" +
"@Resource(type = Object.class, name = \"aa\")\n" +
"public class PostConstructAnnotation {\n\n " +
"private Integer studentId;\n\n " +
"private boolean isHappy;\n\n " +
"private boolean isSad;\n\n " +
"@PostConstruct()\n " +
"public Integer getStudentId() {\n " +
"return this.studentId;\n }\n\n " +
"@PostConstruct\n " +
"public void getHappiness() {\n\n }\n\n " +
"@PostConstruct\n " +
"public void throwTantrum() throws Exception {\n " +
"System.out.println(\"I'm sad\");\n }\n\n " +
"private String emailAddress;\n\n}";
String newText1 = "package io.openliberty.sample.jakarta.annotations;\n\nimport jakarta.annotation.PostConstruct;\n" +
"import jakarta.annotation.Resource;\n\nimport java.io.IOException;\n\n@Resource(type = Object.class, name = \"aa\")\n" +
"public class PostConstructAnnotation {\n\n private Integer studentId;\n\n private boolean isHappy;\n\n " +
"private boolean isSad;\n\n private String emailAddress;\n\n @PostConstruct()\n " +
"public Integer getStudentId() {\n return this.studentId;\n }\n\n public void getHappiness(String type) {\n }\n\n " +
"@PostConstruct\n public void throwTantrum() throws Exception {\n System.out.println(\"I'm sad\");\n }\n\n " +
"@PostConstruct\n public void throwRuntimeException() throws RuntimeException {\n " +
"System.out.println(\"RuntimeException\");\n }\n\n " +
"@PostConstruct\n public void throwNullPointerException() throws NullPointerException {\n " +
"System.out.println(\"NullPointerException\");\n }\n\n @PostConstruct\n " +
"public void throwIOException() throws IOException {\n System.out.println(\"IOException\");\n }\n\n " +
"@PostConstruct\n public void throwExceptions() throws CustomCheckedException, CustomUncheckedException, IOException {\n " +
"System.out.println(\"throwExceptions\");\n }\n\n @PostConstruct\n " +
"public void throwCustomUnCheckedException() throws CustomUncheckedException {\n " +
"System.out.println(\"CustomUncheckedException\");\n }\n\n @PostConstruct\n public void throwError() throws Error {\n " +
"System.out.println(\"throwError\");\n }\n\n}";

String newText2 = "package io.openliberty.sample.jakarta.annotations;\n\nimport jakarta.annotation.PostConstruct;\n" +
"import jakarta.annotation.Resource;\n\nimport java.io.IOException;\n\n@Resource(type = Object.class, name = \"aa\")\n" +
"public class PostConstructAnnotation {\n\n private Integer studentId;\n\n private boolean isHappy;\n\n " +
"private boolean isSad;\n\n private String emailAddress;\n\n @PostConstruct()\n " +
"public Integer getStudentId() {\n return this.studentId;\n }\n\n @PostConstruct\n " +
"public void getHappiness() {\n }\n\n @PostConstruct\n public void throwTantrum() throws Exception {\n " +
"System.out.println(\"I'm sad\");\n }\n\n @PostConstruct\n public void throwRuntimeException() throws RuntimeException {\n " +
"System.out.println(\"RuntimeException\");\n }\n\n @PostConstruct\n " +
"public void throwNullPointerException() throws NullPointerException {\n " +
"System.out.println(\"NullPointerException\");\n }\n\n " +
"@PostConstruct\n public void throwIOException() throws IOException {\n " +
"System.out.println(\"IOException\");\n }\n\n " +
"@PostConstruct\n public void throwExceptions() throws CustomCheckedException, CustomUncheckedException, IOException {\n " +
"System.out.println(\"throwExceptions\");\n }\n\n @PostConstruct\n " +
"public void throwCustomUnCheckedException() throws CustomUncheckedException {\n " +
"System.out.println(\"CustomUncheckedException\");\n }\n\n " +
"@PostConstruct\n public void throwError() throws Error {\n System.out.println(\"throwError\");\n }\n\n}";

JakartaJavaCodeActionParams codeActionParams1 = createCodeActionParams(uri, d2);
TextEdit te = te(0, 0, 31, 1, newText1);
TextEdit te1 = te(0, 0, 31, 1, newText2);
TextEdit te = te(0, 0, 62, 1, newText1);
TextEdit te1 = te(0, 0, 62, 1, newText2);
CodeAction ca = ca(uri, "Remove @PostConstruct", d2, te);
CodeAction ca1 = ca(uri, "Remove all parameters", d2, te1);
assertJavaCodeAction(codeActionParams1, utils, ca, ca1);
Expand Down
Loading