Skip to content

Commit 6eb1b9c

Browse files
authored
Dependency Injection: Diagnostics for non-static inner class managed bean (#1419)
* Diagnostics for non-static inner class managed bean * Quick fix for non-static inner class managed bean * Added test resources to support the non-static inner class managed bean test cases * test cases * Update InnerClassInjectionTest.java * Update InsertModifierToNestedClassQuickFix.java * pr comments fixed * changd needsStaticModifier to needsModifier as the method is implemented for accepting any modifier
1 parent fbba6c6 commit 6eb1b9c

File tree

8 files changed

+403
-8
lines changed

8 files changed

+403
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 IBM Corporation and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBM Corporation - initial API and implementation
12+
*******************************************************************************/
13+
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix;
14+
15+
import com.intellij.psi.*;
16+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils;
17+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
18+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.ModifyModifiersProposal;
19+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.IJavaCodeActionParticipant;
20+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext;
21+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext;
22+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal;
23+
import io.openliberty.tools.intellij.util.ExceptionUtil;
24+
import org.eclipse.lsp4j.CodeAction;
25+
import org.eclipse.lsp4j.Diagnostic;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.logging.Logger;
30+
31+
/**
32+
* Quickfix for adding modifiers to the Nested Class.
33+
*/
34+
public abstract class InsertModifierToNestedClassQuickFix implements IJavaCodeActionParticipant {
35+
36+
/**
37+
* Logger object to record events for this class.
38+
*/
39+
private static final Logger LOGGER = Logger.getLogger(InsertModifierToNestedClassQuickFix.class.getName());
40+
/**
41+
* modifier to add.
42+
*/
43+
private final String modifier;
44+
45+
/**
46+
* Constructor.
47+
*
48+
* @param modifier The modifier to add.
49+
*/
50+
public InsertModifierToNestedClassQuickFix(String modifier) {
51+
this.modifier = modifier;
52+
}
53+
54+
/**
55+
* {@inheritDoc}
56+
*
57+
* @return
58+
*/
59+
@Override
60+
public List<? extends CodeAction> getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic) {
61+
List<CodeAction> codeActions = new ArrayList<>();
62+
codeActions.add(JDTUtils.createCodeAction(context, diagnostic, getLabel(modifier), getParticipantId()));
63+
return codeActions;
64+
}
65+
66+
/**
67+
* {@inheritDoc}
68+
* Resolves a code action by inserting the appropriate modifier into a nested class matching
69+
* the annotated field or method parameter type.
70+
*/
71+
@Override
72+
public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) {
73+
final CodeAction toResolve = context.getUnresolved();
74+
final PsiElement node = context.getCoveredNode();
75+
if (node.getParent() instanceof PsiField field) {
76+
if (field.getType() instanceof PsiClassType classType) {
77+
if (insertModifier(context, classType, modifier, toResolve)) {
78+
return toResolve;
79+
}
80+
}
81+
} else if (node.getParent() instanceof PsiMethod method) {
82+
for (PsiParameter param : method.getParameterList().getParameters()) {
83+
if (param.getType() instanceof PsiClassType classType) {
84+
if (insertModifier(context, classType, modifier, toResolve)) {
85+
return toResolve;
86+
}
87+
}
88+
}
89+
}
90+
return toResolve;
91+
}
92+
93+
private boolean insertModifier(
94+
JavaCodeActionResolveContext context,
95+
PsiClassType classType,
96+
String modifier,
97+
CodeAction toResolve
98+
) {
99+
PsiClass injectedClass = classType.resolve();
100+
if (injectedClass == null || !needsModifier(injectedClass)) {
101+
return false;
102+
}
103+
final String label = getLabel(modifier);
104+
final ChangeCorrectionProposal proposal = new ModifyModifiersProposal(
105+
label,
106+
context.getSource().getCompilationUnit(),
107+
context.getASTRoot(),
108+
injectedClass,
109+
0,
110+
injectedClass.getModifierList(),
111+
List.of(modifier)
112+
);
113+
ExceptionUtil.executeWithWorkspaceEditHandling(
114+
context, proposal, toResolve, LOGGER,
115+
"Unable to create workspace edit for code action " + label
116+
);
117+
return true;
118+
}
119+
120+
/**
121+
* needsModifier
122+
* It checks whether the modifier is applicable
123+
*
124+
* @param injectedClass
125+
* @return
126+
*/
127+
private boolean needsModifier(PsiClass injectedClass) {
128+
return injectedClass.getContainingClass() != null
129+
&& !injectedClass.hasModifierProperty(modifier);
130+
}
131+
132+
/**
133+
* Returns the label associated with the input modifier.
134+
*
135+
* @param modifier The modifier to add.
136+
* @return The label associated with the input modifier.
137+
*/
138+
protected String getLabel(String modifier) {
139+
return Messages.getMessage("InsertModifierToNestedClass", modifier);
140+
}
141+
}

src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/di/DependencyInjectionConstants.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2021, 2024 IBM Corporation.
2+
* Copyright (c) 2021, 2025 IBM Corporation.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -26,4 +26,5 @@ public class DependencyInjectionConstants {
2626
public static final String DIAGNOSTIC_CODE_INJECT_ABSTRACT = "RemoveInjectOrAbstract";
2727
public static final String DIAGNOSTIC_CODE_INJECT_STATIC = "RemoveInjectOrStatic";
2828
public static final String DIAGNOSTIC_CODE_INJECT_GENERIC = "RemoveInjectForGeneric";
29+
public static final String DIAGNOSTIC_CODE_INJECT_INNER_CLASS= "RemoveInjectForInnerClass";
2930
}

src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/di/DependencyInjectionDiagnosticsCollector.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2021, 2023 IBM Corporation and others.
2+
* Copyright (c) 2021, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -61,12 +61,19 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
6161
for (PsiClass type : alltypes) {
6262
PsiField[] allFields = type.getFields();
6363
for (PsiField field : allFields) {
64-
if (field.hasModifierProperty(PsiModifier.FINAL)
65-
&& containsAnnotation(type, field.getAnnotations(), INJECT_FQ_NAME)) {
66-
String msg = Messages.getMessage("InjectNoFinalField");
67-
diagnostics.add(createDiagnostic(field, unit, msg,
68-
DIAGNOSTIC_CODE_INJECT_FINAL, field.getType().getInternalCanonicalText(),
69-
DiagnosticSeverity.Error));
64+
if (containsAnnotation(type, field.getAnnotations(), INJECT_FQ_NAME)) {
65+
if (field.hasModifierProperty(PsiModifier.FINAL)) {
66+
String msg = Messages.getMessage("InjectNoFinalField");
67+
diagnostics.add(createDiagnostic(field, unit, msg,
68+
DIAGNOSTIC_CODE_INJECT_FINAL, field.getType().getInternalCanonicalText(),
69+
DiagnosticSeverity.Error));
70+
}
71+
if (isNonStaticInnerClass(type, field.getType())) {
72+
String msg = Messages.getMessage("InjectNonStaticInnerClass");
73+
diagnostics.add(createDiagnostic(field, unit, msg,
74+
DIAGNOSTIC_CODE_INJECT_INNER_CLASS, field.getType().getInternalCanonicalText(),
75+
DiagnosticSeverity.Error));
76+
}
7077
}
7178
}
7279

@@ -106,6 +113,15 @@ && containsAnnotation(type, field.getAnnotations(), INJECT_FQ_NAME)) {
106113
DIAGNOSTIC_CODE_INJECT_GENERIC, method.getReturnType().getInternalCanonicalText(),
107114
DiagnosticSeverity.Error));
108115
}
116+
117+
for (PsiParameter param : method.getParameterList().getParameters()) {
118+
if (isNonStaticInnerClass(type, param.getType())) {
119+
String msg = Messages.getMessage("InjectNonStaticInnerClass");
120+
diagnostics.add(createDiagnostic(method, unit, msg,
121+
DIAGNOSTIC_CODE_INJECT_INNER_CLASS, method.getReturnType().getInternalCanonicalText(),
122+
DiagnosticSeverity.Error));
123+
}
124+
}
109125
}
110126
}
111127

@@ -120,6 +136,29 @@ && containsAnnotation(type, field.getAnnotations(), INJECT_FQ_NAME)) {
120136
}
121137
}
122138

139+
/**
140+
* isNonStaticInnerClass
141+
* This will check whether the parent class contains any nested class that has no static field matching the field’s type.
142+
*
143+
* @param outerClass
144+
* @param injectedType
145+
* @return
146+
*/
147+
private boolean isNonStaticInnerClass(PsiClass outerClass, PsiType injectedType) {
148+
PsiClass injectedClass = injectedType instanceof PsiClassType
149+
? ((PsiClassType) injectedType).resolve()
150+
: null;
151+
152+
PsiClass parentClass = injectedClass != null
153+
? injectedClass.getContainingClass()
154+
: null;
155+
156+
return injectedClass != null
157+
&& outerClass.equals(parentClass)
158+
&& !injectedClass.hasModifierProperty(PsiModifier.STATIC);
159+
}
160+
161+
123162
private boolean containsAnnotation(PsiClass type, PsiAnnotation[] annotations, String annotationFQName) {
124163
return Stream.of(annotations).anyMatch(annotation -> {
125164
return isMatchedJavaElement(type, annotation.getQualifiedName(), annotationFQName);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 IBM Corporation and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*
11+
* Contributors:
12+
* IBM Corporation - initial API and implementation
13+
*******************************************************************************/
14+
15+
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.di;
16+
17+
import com.intellij.psi.PsiModifier;
18+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.InsertModifierToNestedClassQuickFix;
19+
20+
public class InsertStaticModifierQuickFix extends InsertModifierToNestedClassQuickFix {
21+
22+
public InsertStaticModifierQuickFix() {
23+
super(PsiModifier.STATIC);
24+
}
25+
26+
@Override
27+
public String getParticipantId() {
28+
return InsertStaticModifierQuickFix.class.getName();
29+
}
30+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@
317317
group="jakarta"
318318
targetDiagnostic="jakarta-di#RemoveInject"
319319
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.RemoveInjectAnnotationQuickFix"/>
320+
<javaCodeActionParticipant kind="quickfix"
321+
group="jakarta"
322+
targetDiagnostic="jakarta-di#RemoveInjectForInnerClass"
323+
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.di.InsertStaticModifierQuickFix"/>
324+
<javaCodeActionParticipant kind="quickfix"
325+
group="jakarta"
326+
targetDiagnostic="jakarta-di#RemoveInjectForInnerClass"
327+
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.RemoveInjectAnnotationQuickFix"/>
320328

321329
<!-- JSON-B -->
322330
<javaCodeActionParticipant kind="quickfix"

src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ AddAtoB = Add {0} to {1}
3131
# InsertAnnotationMissingQuickFix
3232
InsertItem = Insert {0}
3333

34+
# InsertModifierToNestedClass
35+
InsertModifierToNestedClass = Add ''{0}'' modifier to the nested class
36+
3437
# RemoveAnnotationConflictQuickFix
3538
RemoveItem = Remove {0}
3639

@@ -101,6 +104,7 @@ InjectNoAbstractMethod = The @Inject annotation must not define an abstract meth
101104
InjectNoStaticMethod = The @Inject annotation must not define a static method.
102105
InjectNoGenericMethod = The @Inject annotation must not define a generic method.
103106
InjectMoreThanOneConstructor = The @Inject annotation must not define more than one constructor.
107+
InjectNonStaticInnerClass = Cannot inject non-static inner class. Injection target must be a top-level or static nested class.
104108

105109
# Jax_RSClassDiagnosticsCollector
106110
RootResourceClasses = Root resource classes are instantiated by the JAX-RS runtime and MUST have a public constructor.

0 commit comments

Comments
 (0)