Skip to content

Commit

Permalink
Dependency Injection DiagnosticsCollector and Quick Fix for Injectabl…
Browse files Browse the repository at this point in the history
…e field (non final) (eclipse#167) (eclipse#175)

* Added DiagnosticCollector for Dependency Injection

* Added Inject final field conflict quick fix

Remove Inject quick fix

* Added removing modifier quickfix

* Added javadoc comment for DI diagnostics collector

* Added entity type in RemoveModifierQuickFix message

* formatting/identation fixes

* removed unnecessary Apache 2.0 comment
  • Loading branch information
himanshuc71 committed Oct 26, 2021
1 parent ae9394a commit edfeb8f
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
import org.eclipse.lsp4jakarta.jdt.core.cdi.ManagedBeanConstructorQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.cdi.ManagedBeanQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.cdi.ScopeDeclarationQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.di.DependencyInjectionConstants;
import org.eclipse.lsp4jakarta.jdt.core.di.RemoveFinalModifierQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.di.RemoveInjectAnnotationQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.servlet.CompleteFilterAnnotationQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.servlet.CompleteServletAnnotationQuickFix;
import org.eclipse.lsp4jakarta.jdt.core.servlet.FilterImplementationQuickFix;
Expand Down Expand Up @@ -99,6 +102,9 @@ public List<CodeAction> codeAction(JakartaJavaCodeActionParams params, JDTUtils
ManagedBeanConstructorQuickFix ManagedBeanConstructorQuickFix = new ManagedBeanConstructorQuickFix();
JsonbAnnotationQuickFix JsonbAnnotationQuickFix = new JsonbAnnotationQuickFix();
ScopeDeclarationQuickFix ScopeDeclarationQuickFix = new ScopeDeclarationQuickFix();
RemoveInjectAnnotationQuickFix RemoveInjectAnnotationQuickFix = new RemoveInjectAnnotationQuickFix();
RemoveFinalModifierQuickFix RemoveFinalModifierQuickFix = new RemoveFinalModifierQuickFix();


for (Diagnostic diagnostic : params.getContext().getDiagnostics()) {
try {
Expand Down Expand Up @@ -167,6 +173,10 @@ public List<CodeAction> codeAction(JakartaJavaCodeActionParams params, JDTUtils
if(diagnostic.getCode().getLeft().equals(ManagedBeanConstants.DIAGNOSTIC_CODE_SCOPEDECL)) {
codeActions.addAll(ScopeDeclarationQuickFix.getCodeActions(context, diagnostic, monitor));
}
if(diagnostic.getCode().getLeft().equals(DependencyInjectionConstants.DIAGNOSTIC_CODE_INJECT_FINAL)) {
codeActions.addAll(RemoveInjectAnnotationQuickFix.getCodeActions(context, diagnostic, monitor));
codeActions.addAll(RemoveFinalModifierQuickFix.getCodeActions(context, diagnostic, monitor));
}
} catch (CoreException e) {
e.printStackTrace();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*******************************************************************************
* Copyright (c) 2021 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
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Himanshu Chotwani - initial API and implementation
*******************************************************************************/

package org.eclipse.lsp4jakarta.jdt.codeAction.proposal.quickfix;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4jakarta.jdt.codeAction.IJavaCodeActionParticipant;
import org.eclipse.lsp4jakarta.jdt.codeAction.JavaCodeActionContext;
import org.eclipse.lsp4jakarta.jdt.codeAction.proposal.ModifyModifiersProposal;
import org.eclipse.lsp4jakarta.jdt.core.di.DependencyInjectionConstants;


/**
* QuickFix for removing modifiers.
*
* @author Himanshu Chotwani
*
*/
public class RemoveModifierConflictQuickFix implements IJavaCodeActionParticipant {

private final String[] modifiers;

protected final boolean generateOnlyOneCodeAction;


/**
* Constructor for remove modifier quick fix.
*
* <p>
* The participant will generate a CodeAction per modifier.
* </p>
*
* @param modifiers list of modifiers to remove.
*/
public RemoveModifierConflictQuickFix(String... modifiers) {
this(false, modifiers);
}


/**
* Constructor for remove modifiers quick fix.
*
* @param generateOnlyOneCodeAction true if the participant must generate a
* CodeAction which remove the list of
* modifiers and false otherwise.
* @param modifiers list of modifiers to remove.
*/
public RemoveModifierConflictQuickFix(boolean generateOnlyOneCodeAction, String... modifiers) {
this.generateOnlyOneCodeAction = generateOnlyOneCodeAction;
this.modifiers = modifiers;
}


@Override
public List<? extends CodeAction> getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic,
IProgressMonitor monitor) throws CoreException {
ASTNode node = context.getCoveredNode();
IBinding parentType = getBinding(node);

List<CodeAction> codeActions = new ArrayList<>();
removeModifiers(diagnostic, context, parentType, codeActions);

return codeActions;
}

protected void removeModifiers(Diagnostic diagnostic, JavaCodeActionContext context, IBinding parentType,
List<CodeAction> codeActions) throws CoreException {
if (generateOnlyOneCodeAction) {
removeModifier(diagnostic, context, parentType, codeActions, modifiers);
} else {
for (String modifier : modifiers) {
removeModifier(diagnostic, context, parentType, codeActions, modifier);
}
}
}


/**
* use setData() API with diagnostic to pass in ElementType in diagnostic collector class.
*
*/
private void removeModifier(Diagnostic diagnostic, JavaCodeActionContext context, IBinding parentType,
List<CodeAction> codeActions, String... modifier) throws CoreException {
// Remove the modifier and the proper import by using JDT Core Manipulation
// API
ASTNode coveredNode = context.getCoveredNode().getParent();
String type = "";
if (diagnostic.getData().equals(String.valueOf(IJavaElement.LOCAL_VARIABLE))){
type = "variable";
} else if (diagnostic.getData().toString().equals(String.valueOf(IJavaElement.FIELD))) {
type = "field";
} else if (diagnostic.getData().equals(String.valueOf(IJavaElement.METHOD))) {
type = "method";
} else if (diagnostic.getData().equals(String.valueOf(IJavaElement.CLASS_FILE))) {
type = "class";
}

String name = "Remove " + modifier[0] + " modifier from this ";
name = name.concat(type);
ModifyModifiersProposal proposal = new ModifyModifiersProposal(name, context.getCompilationUnit(),
context.getASTRoot(), parentType, 0, coveredNode, new ArrayList<>(), Arrays.asList(modifier));
CodeAction codeAction = context.convertToCodeAction(proposal, diagnostic);

if (codeAction != null) {
codeActions.add(codeAction);
}
}


protected IBinding getBinding(ASTNode node) {
if (node.getParent() instanceof VariableDeclarationFragment) {
return ((VariableDeclarationFragment) node.getParent()).resolveBinding();
}
return Bindings.getBindingOfParentType(node);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.eclipse.lsp4jakarta.jdt.core.jax_rs.RootResourceClassDiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.jsonb.JsonbCreatorDiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.cdi.ManagedBeanDiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.di.DependencyInjectionDiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.persistence.PersistenceEntityDiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.persistence.PersistenceMapKeyDiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.servlet.FilterDiagnosticsCollector;
Expand Down Expand Up @@ -75,6 +76,7 @@ private JDTServicesManager() {
diagnosticsCollectors.add(new RootResourceClassDiagnosticsCollector());
diagnosticsCollectors.add(new JsonbCreatorDiagnosticsCollector());
diagnosticsCollectors.add(new ManagedBeanDiagnosticsCollector());
diagnosticsCollectors.add(new DependencyInjectionDiagnosticsCollector());
this.codeActionHandler = new CodeActionHandler();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2021 IBM Corporation.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Himanshu Chotwani
*******************************************************************************/

package org.eclipse.lsp4jakarta.jdt.core.di;

import org.eclipse.lsp4j.DiagnosticSeverity;

public class DependencyInjectionConstants {
/* Annotation Constants */
public static final String PRODUCES = "Produces";
public static final String INJECT = "Inject";
public static final String QUALIFIER = "Qualifier";
public static final String NAMED = "Named";

/* Diagnostics fields constants */
public static final String DIAGNOSTIC_SOURCE = "jakarta-di";
public static final String DIAGNOSTIC_CODE_INJECT_FINAL = "RemoveInjectOrFinal";

public static final DiagnosticSeverity SEVERITY = DiagnosticSeverity.Error;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2021 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
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation, Himanshu Chotwani - initial API and implementation
*******************************************************************************/

package org.eclipse.lsp4jakarta.jdt.core.di;

import static org.eclipse.lsp4jakarta.jdt.core.di.DependencyInjectionConstants.DIAGNOSTIC_SOURCE;
import static org.eclipse.lsp4jakarta.jdt.core.di.DependencyInjectionConstants.SEVERITY;

import java.util.Arrays;
import java.util.List;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4jakarta.jdt.core.DiagnosticsCollector;
import org.eclipse.lsp4jakarta.jdt.core.JDTUtils;
import org.eclipse.lsp4jakarta.jdt.core.JakartaCorePlugin;

/**
*
* jararta.annotation Diagnostics
*
* <li>Diagnostic 1: @Inject fields cannot be final.</li>
*
* @see https://jakarta.ee/specifications/dependency-injection/2.0/jakarta-injection-spec-2.0.html
*
*/
public class DependencyInjectionDiagnosticsCollector implements DiagnosticsCollector{

@Override
public void completeDiagnostic(Diagnostic diagnostic) {
diagnostic.setSource(DIAGNOSTIC_SOURCE);
diagnostic.setSeverity(SEVERITY);
}

@Override
public void collectDiagnostics(ICompilationUnit unit, List<Diagnostic> diagnostics) {
if (unit == null)
return;
Diagnostic diagnostic;
IType[] alltypes;
IAnnotation[] allAnnotations;
try {
alltypes = unit.getAllTypes();
for (IType type : alltypes) {
allAnnotations = type.getAnnotations();

IField[] allFields = type.getFields();
for (IField field : allFields) {
int fieldFlags = field.getFlags();
List<IAnnotation> fieldAnnotations = Arrays.asList(field.getAnnotations());

boolean isInjectField = fieldAnnotations.stream()
.anyMatch(annotation -> annotation.getElementName()
.equals(DependencyInjectionConstants.INJECT));

boolean isFinal = Flags.isFinal(fieldFlags);

if (isFinal && isInjectField) {
ISourceRange nameRange = JDTUtils.getNameRange(field);
Range range = JDTUtils.toRange(unit, nameRange.getOffset(), nameRange.getLength());
String msg = "Injectable fields cannot be final";
diagnostic = new Diagnostic(range, msg);
diagnostic.setCode(DependencyInjectionConstants.DIAGNOSTIC_CODE_INJECT_FINAL);
diagnostic.setData(field.getElementType());
completeDiagnostic(diagnostic);
diagnostics.add(diagnostic);
}
}
}
} catch (JavaModelException e) {
JakartaCorePlugin.logException("Cannot calculate diagnostics", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2021 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
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation, Himanshu Chotwani - initial API and implementation
*******************************************************************************/
package org.eclipse.lsp4jakarta.jdt.core.di;

import org.eclipse.lsp4jakarta.jdt.codeAction.proposal.quickfix.RemoveModifierConflictQuickFix;


/**
*
* Quick fix for removing final when it is used for @Inject field
*
* @author Himanshu Chotwani
*
*/
public class RemoveFinalModifierQuickFix extends RemoveModifierConflictQuickFix {

public RemoveFinalModifierQuickFix() {
super(false, "final");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2021 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
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation, Himanshu Chotwani - initial API and implementation
*******************************************************************************/

package org.eclipse.lsp4jakarta.jdt.core.di;

import java.util.ArrayList;
import java.util.Arrays;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4jakarta.jdt.codeAction.IJavaCodeActionParticipant;
import org.eclipse.lsp4jakarta.jdt.codeAction.JavaCodeActionContext;
import org.eclipse.lsp4jakarta.jdt.codeAction.proposal.ModifyModifiersProposal;
import org.eclipse.lsp4jakarta.jdt.codeAction.proposal.quickfix.RemoveAnnotationConflictQuickFix;

/**
*
* Quick fix for removing @Inject when it is used for final field
*
* @author Himanshu Chotwani
*
*/

public class RemoveInjectAnnotationQuickFix extends RemoveAnnotationConflictQuickFix {

public RemoveInjectAnnotationQuickFix() {
super(false, "jakarta.inject.Inject");
}
}

0 comments on commit edfeb8f

Please sign in to comment.