diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/plugin.xml b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/plugin.xml index 0486cffc..dbd4ff75 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/plugin.xml +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/plugin.xml @@ -58,6 +58,9 @@ + @@ -84,7 +87,7 @@ class="org.eclipse.lsp4jakarta.jdt.internal.annotations.RemovePreDestroyAnnotationQuickFix" /> + class="org.eclipse.lsp4jakarta.jdt.internal.annotations.RemoveAllMethodParametersQuickFix" /> diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/commons/codeaction/JakartaCodeActionId.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/commons/codeaction/JakartaCodeActionId.java index 0bac83b9..46b3f9fd 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/commons/codeaction/JakartaCodeActionId.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/commons/codeaction/JakartaCodeActionId.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2023 Red Hat Inc. and others. +* Copyright (c) 2023, 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 @@ -28,6 +28,7 @@ public enum JakartaCodeActionId implements ICodeActionId { ChangeReturnTypeToVoid, InsertResourceAnnotationTypeAttribute, InsertResourceAnnotationNameAttribute, + InsertDefaultResourceAnnotationToResourcesAnnotation, RemoveAllParameters, RemoveAnnotationPreDestroy, RemoveAnnotationPostConstruct, diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/core/java/corrections/proposal/ModifyAnnotationProposal.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/core/java/corrections/proposal/ModifyAnnotationProposal.java index 8d8b40dd..2ca8ae07 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/core/java/corrections/proposal/ModifyAnnotationProposal.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/core/java/corrections/proposal/ModifyAnnotationProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2022 IBM Corporation and others. + * Copyright (c) 2021, 2022, 2024 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 @@ -19,19 +19,27 @@ import java.util.List; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; @@ -81,6 +89,7 @@ public ModifyAnnotationProposal(String label, ICompilationUnit targetCU, Compila @SuppressWarnings("unchecked") @Override protected ASTRewrite getRewrite() throws CoreException { + CompilationUnit fInvocationNode = getInvocationNode(); IBinding fBinding = getBinding(); String[] annotations = getAnnotations(); @@ -105,11 +114,13 @@ protected ASTRewrite getRewrite() throws CoreException { boolean isField = declNode instanceof VariableDeclarationFragment; boolean isSingleVarDecl = declNode instanceof SingleVariableDeclaration; + boolean isSingleMemberAnnotation = declNode instanceof SingleMemberAnnotation; if (isField) { declNode = declNode.getParent(); } + // if the Annotation is declared on a class field if (declNode.getNodeType() == ASTNode.FIELD_DECLARATION) { AST ast = declNode.getAST(); ASTRewrite rewrite = ASTRewrite.create(ast); @@ -144,46 +155,10 @@ protected ASTRewrite getRewrite() throws CoreException { // add new annotations to proposal (restoring those that were removed) for (Annotation a : existingAnnotations) { if (a instanceof NormalAnnotation) { - NormalAnnotation marker = ast.newNormalAnnotation(); - marker.setTypeName( - ast.newName(imports.addImport(a.getTypeName().toString(), importRewriteContext))); - List values = marker.values(); - - // add existing attributes to annotation - List existingValues = ((NormalAnnotation) a).values(); - for (MemberValuePair mvp : existingValues) { - boolean removeAttribute = this.attributesToRemove.contains(mvp.getName().getFullyQualifiedName()); - - // do not add attributes to be removed - if (!removeAttribute) { - MemberValuePair memberValuePair = ast.newMemberValuePair(); - memberValuePair.setName(ast.newSimpleName(mvp.getName().getFullyQualifiedName())); - StringLiteral stringValue = ast.newStringLiteral(); - - if (mvp.getValue() instanceof StringLiteral) { - StringLiteral stringLiteral = (StringLiteral) mvp.getValue(); - stringValue.setLiteralValue(stringLiteral.getLiteralValue()); - } else { - stringValue.setLiteralValue(""); - } - memberValuePair.setValue(stringValue); - values.add(memberValuePair); - } - } - - // add new attributes - for (String newAttr : this.attributesToAdd) { - // dont add duplicate attributes to an annotation - if (values.stream().noneMatch(v -> v.getName().toString().equals(newAttr))) { - MemberValuePair memberValuePair = ast.newMemberValuePair(); - memberValuePair.setName(ast.newSimpleName(newAttr)); - StringLiteral stringValue = ast.newStringLiteral(); - stringValue.setLiteralValue(""); - memberValuePair.setValue(stringValue); - values.add(memberValuePair); - } - } + NormalAnnotation marker = null; + marker = processNormalAnnotation(ast, imports, importRewriteContext, annotations, (NormalAnnotation) a); + // add new annotation proposal to the rewrite text edit rewrite.getListRewrite(declNode, isField ? FieldDeclaration.MODIFIERS2_PROPERTY : TypeDeclaration.MODIFIERS2_PROPERTY).insertFirst(marker, null); } @@ -191,6 +166,7 @@ protected ASTRewrite getRewrite() throws CoreException { return rewrite; } else if (declNode instanceof TypeDeclaration || isField || isSingleVarDecl) { + // Annotation in question is set on a class declaration or is a method parameter declaration AST ast = declNode.getAST(); ASTRewrite rewrite = ASTRewrite.create(ast); @@ -199,59 +175,41 @@ protected ASTRewrite getRewrite() throws CoreException { ChildListPropertyDescriptor property = isSingleVarDecl ? SingleVariableDeclaration.MODIFIERS2_PROPERTY : TypeDeclaration.MODIFIERS2_PROPERTY; List children = (List) declNode.getStructuralProperty(property); + boolean isCompositeAnnotation = false; + // find and save existing annotation, then remove it from ast + // this will cause the entire annotation to be deleted from the file for (ASTNode child : children) { + // for all existing annotations (that are the annotation we want) if (child instanceof Annotation) { Annotation annotation = (Annotation) child; boolean containsAnnotation = Arrays.stream(annotationShortNames).anyMatch(annotation.getTypeName().toString()::contains); if (containsAnnotation) { + // save the existing annotation for processing but remove it from the file existingAnnotations.add(annotation); rewrite.remove(child, null); } } } - // add new annotation with fields from existing annotation + // add a new annotation with fields from existing annotation for (String annotation : annotations) { - NormalAnnotation marker = ast.newNormalAnnotation(); - marker.setTypeName(ast.newName(imports.addImport(annotation, importRewriteContext))); - List values = marker.values(); - + Annotation newAnnotationToWrite = null; if (!existingAnnotations.isEmpty()) { for (Annotation a : existingAnnotations) { - if (a instanceof NormalAnnotation) { - List existingValues = ((NormalAnnotation) a).values(); - for (MemberValuePair mvp : existingValues) { - boolean removeAttribute = this.attributesToRemove.contains(mvp.getName().getFullyQualifiedName()); - - // do not add attribute to be removed - if (!removeAttribute) { - MemberValuePair memberValuePair = ast.newMemberValuePair(); - memberValuePair.setName(ast.newSimpleName(mvp.getName().getFullyQualifiedName())); - StringLiteral stringValue = ast.newStringLiteral(); - - if (mvp.getValue() instanceof StringLiteral) { - StringLiteral stringLiteral = (StringLiteral) mvp.getValue(); - stringValue.setLiteralValue(stringLiteral.getLiteralValue()); - } else { - stringValue.setLiteralValue(""); - } - memberValuePair.setValue(stringValue); - values.add(memberValuePair); - } - } + if (a instanceof SingleMemberAnnotation) { + // this type of annotation contains a single value which may be a list of other annotations, + // some of which need to be fixed + newAnnotationToWrite = processSingleMemberAnnotation(ast, imports, importRewriteContext, annotations, (SingleMemberAnnotation) a); + } else if (a instanceof NormalAnnotation) { + // this type of annotation is a base annotation containing a name value pair that needs to be fixed + newAnnotationToWrite = processNormalAnnotation(ast, imports, importRewriteContext, annotations, (NormalAnnotation) a); + } } - } - - // add new String attributes - for (String newAttr : this.attributesToAdd) { - MemberValuePair memberValuePair = ast.newMemberValuePair(); - memberValuePair.setName(ast.newSimpleName(newAttr)); - StringLiteral stringValue = ast.newStringLiteral(); - stringValue.setLiteralValue(""); - memberValuePair.setValue(stringValue); - values.add(memberValuePair); + } else { + // if if this annotation proposal is to add an annotation where one did not exist prior + newAnnotationToWrite = createNewAnnotation(ast, imports, importRewriteContext, annotation); } ChildListPropertyDescriptor newRewrite; @@ -263,10 +221,200 @@ protected ASTRewrite getRewrite() throws CoreException { newRewrite = TypeDeclaration.MODIFIERS2_PROPERTY; } - rewrite.getListRewrite(declNode, newRewrite).insertFirst(marker, null); + // add new annotation proposal to the rewrite text edit + rewrite.getListRewrite(declNode, newRewrite).insertFirst(newAnnotationToWrite, null); } return rewrite; } return null; } + + private SingleMemberAnnotation processSingleMemberAnnotation(AST ast, ImportRewrite imports, + ImportRewriteContext importRWCtx, String[] annotations, + SingleMemberAnnotation annotationToProcess) { + + // A SingleMemberAnnotation is an annotation that contains within it a single + // value (with no name associated with it) + // That value may be a list of additional annotations, IE: + // + // @Resources ({ @Resource(name = "aaa"), @Resource(type = Object.class) }) + // + // This method will process the annotations in the list, one by one, for quick + // fix actions on any applicable sub annotations + + // Create a new SingleMemebrAnnotation Object that will be used to store the + // updates + // and used ny the TextEdit to write them to the file + SingleMemberAnnotation newSingleMemberAnnotation = ast.newSingleMemberAnnotation(); + + // Internally the SingleMemberAnnotation maintains an ArrayInitilaizer which + // wraps a List + // of NormalAnnotations - create a new empty ArrayInitializer within the new + // SingleMemberAnnotation + ArrayInitializer newAIInstance = (ArrayInitializer) ast.createInstance(ArrayInitializer.class); + + newSingleMemberAnnotation.setTypeName( + ast.newName(imports.addImport(annotationToProcess.getTypeName().toString(), importRWCtx))); + + // Get the empty new List of NormalAnnotations from the new + // SingleMemberAnnotation object. This List will hold processed NormalAnnotations from the + // original SingleMemberAnnotation object passed into this method above. + List newCompositeAnnotationContents = newAIInstance.expressions(); + + // An ArrayInitializer 'ai' is the object that holds the list of sub annotations + // within the original SingleMemberAnnotation + ArrayInitializer ai = (ArrayInitializer) ((SingleMemberAnnotation) annotationToProcess).getValue(); + + // get the List of existing NormalAnnotations to process - the 'expressions()' method + // returns List + List normalAnnotations = ai.expressions(); + + if (normalAnnotations.isEmpty()) { + // We are fixing an invalid annotation of the form: + // @Resources ({}) - + // add a single default sub-annotation + NormalAnnotation newChildDefaultAnnotation = processNormalAnnotation(ast, imports, importRWCtx, annotations, + null); + newCompositeAnnotationContents.add(newChildDefaultAnnotation); + } else { + + // for each original annotation in the list, process it for the quick fix edit + for (NormalAnnotation na : normalAnnotations) { + // processNormalAnnotation will create a new NormalAnnotation containing the + // results of the quick fix + NormalAnnotation newNormalAnnotation = processNormalAnnotation(ast, imports, importRWCtx, annotations, + na); + // add this new updated NormalAnnotation directly to the new List of + // NormalAnnotations + newCompositeAnnotationContents.add(newNormalAnnotation); + } + } + + // now add all of the processed annotations to the new SingleMemberAnnotation to + // be written into the file + // The ArrayInitializer newAIInstance contains the Lst of NormalAnnotations via + // the + // newCompositeAnnotationContents.add(newNormalAnnotation); call in + // the previous for loop + newSingleMemberAnnotation.setValue(newAIInstance); + + return newSingleMemberAnnotation; + } + + private NormalAnnotation processNormalAnnotation(AST ast, ImportRewrite imports, ImportRewriteContext importRWCtx, + String[] annotations, NormalAnnotation annotationToProcess) { + + // A NormalAnnotation is an annotation that contains within it a name value pair + // IE: + // + // @Resource(name = "aaa", type = Object.class) + // + // 'name = "aaa"' is a MemberValuePair of the NormalAnnotation as is 'type = Object.class' + // + // This method will process this annotation in the list for quick + // fix actions on any applicable NormalAnnotation that is passed into this method + NormalAnnotation newNormalAnnotation = ast.newNormalAnnotation(); + + // for every annotation type we are fixing + for (String annotation : annotations) { + + // create a new NormalAnnotation to be written back to the file + newNormalAnnotation.setTypeName(ast.newName(imports.addImport(annotation, importRWCtx))); + List values = newNormalAnnotation.values(); + + if (annotationToProcess == null) { + // We are adding a new required default @Resource annotation to an empty + // @Resources annotation. + addNewAttributes(ast, values); + } else { + // get the existing name/value pairs from the existing NormalAnnotation that was + // passed into this method above + List existingValues = ((NormalAnnotation) annotationToProcess).values(); + for (MemberValuePair mvp : existingValues) { + // does the current existing mvp contain the attribute that needs to be added by + // this quickfix? + boolean containsAttributeToAdd = this.attributesToAdd.contains(mvp.getName().getFullyQualifiedName()); + // does the current existing mvp contain all the attributes that need to be + // added by this quickfix? + boolean containsAllToAdd = this.attributesToAdd.stream().allMatch(attr -> existingValues.stream().anyMatch(v -> v.equals(attr))); + // does the current existing mvp contain an attribute that is due to be removed + // by this quickfix? + boolean removeAttribute = this.attributesToRemove.contains(mvp.getName().getFullyQualifiedName()); + + if (!containsAttributeToAdd || !containsAllToAdd) { + // the existing NormalAnnotation currently being processed does not contain the + // attribute to be added by this quickfix + // so the quickfix should be applied to it. + // But the current existing MVP entry within the NormalAnnotation is valid and + // should continue to exist. + // Copy over any existing valid mvp pairs into a new MVP that will be written + // back to the file as part of this quickfix action + + // (but) do not add an existing mvp that is to be removed by this quick fix + if (!removeAttribute) { + // create a new MVP to hold the existing mvp + MemberValuePair newMemberValuePair = ast.newMemberValuePair(); + // copy the existing name portion of the MVP into the new MVP + newMemberValuePair.setName(ast.newSimpleName(mvp.getName().getFullyQualifiedName())); + + // copy the existing value into the new MVP, depending on what type it is + if (mvp.getValue() instanceof StringLiteral) { + newMemberValuePair.setValue((StringLiteral) mvp.getValue().copySubtree(ast, mvp.getValue())); + } else if (mvp.getValue() instanceof TypeLiteral) { + newMemberValuePair.setValue((TypeLiteral) mvp.getValue().copySubtree(ast, mvp.getValue())); + + } + + // add this new MVP into the new NormalAnnotation + values.add(newMemberValuePair); + } + } else { + // the current NormalAnnotation being processed already contains the attribute + // to be added by this quick fix action + // and so is not the one to have the quickfix applied to it + // return an as-is copy of the existing annotation + newNormalAnnotation = (NormalAnnotation) annotationToProcess.copySubtree(ast, annotationToProcess); + return newNormalAnnotation; + } + + } + + // now add the attribute for this quickfix action to the new NormalAnnotation + values = addNewAttributes(ast, values); + } + } + return newNormalAnnotation; + } + + private NormalAnnotation createNewAnnotation(AST ast, ImportRewrite imports, ImportRewriteContext importRWCtx, + String annotation) { + + NormalAnnotation marker = ast.newNormalAnnotation(); + + marker.setTypeName(ast.newName(imports.addImport(annotation, importRWCtx))); + List values = marker.values(); + + values = addNewAttributes(ast, values); + + return marker; + + } + + private List addNewAttributes(AST ast, List values) { + // add new String attributes + // we are adding empty strings for values because we cannot know what the user + // wishes to have - they will have to add that themselves + // ie: name="" or type="" + for (String newAttr : this.attributesToAdd) { + if (values.stream().noneMatch(v -> v.getName().toString().equals(newAttr))) { + MemberValuePair newMemberValuePair = ast.newMemberValuePair(); + newMemberValuePair.setName(ast.newSimpleName(newAttr)); + StringLiteral stringValue = ast.newStringLiteral(); + stringValue.setLiteralValue(""); + newMemberValuePair.setValue(stringValue); + values.add(newMemberValuePair); + } + } + return values; + } } diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/AnnotationDiagnosticsParticipant.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/AnnotationDiagnosticsParticipant.java index 368656fb..6d712b72 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/AnnotationDiagnosticsParticipant.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/AnnotationDiagnosticsParticipant.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2021, 2023 IBM Corporation and others. +* Copyright (c) 2021, 2023, 2024 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 @@ -65,7 +65,8 @@ public List collectDiagnostics(JavaDiagnosticsContext context, IProg ArrayList> annotatables = new ArrayList>(); String[] validAnnotations = { Constants.GENERATED_FQ_NAME }; String[] validTypeAnnotations = { Constants.GENERATED_FQ_NAME, - Constants.RESOURCE_FQ_NAME }; + Constants.RESOURCE_FQ_NAME, + Constants.RESOURCES_FQ_NAME }; String[] validMethodAnnotations = { Constants.GENERATED_FQ_NAME, Constants.POST_CONSTRUCT_FQ_NAME, Constants.PRE_DESTROY_FQ_NAME, Constants.RESOURCE_FQ_NAME }; @@ -120,6 +121,7 @@ public List collectDiagnostics(JavaDiagnosticsContext context, IProg IAnnotation annotation = annotatable.getFirst(); IAnnotatable element = annotatable.getSecond(); + // process Types? (class declarations) if (DiagnosticUtils.isMatchedAnnotation(unit, annotation, Constants.GENERATED_FQ_NAME)) { for (IMemberValuePair pair : annotation.getMemberValuePairs()) { // If date element exists and is non-empty, it must follow ISO 8601 format. @@ -178,7 +180,74 @@ public List collectDiagnostics(JavaDiagnosticsContext context, IProg } } } + } else if (DiagnosticUtils.isMatchedAnnotation(unit, annotation, Constants.RESOURCES_FQ_NAME)) { + if (element instanceof IType) { + for (IMemberValuePair internalAnnotation : annotation.getMemberValuePairs()) { + Object[] valuePairs = (Object[]) internalAnnotation.getValue(); + String diagnosticMessage; + Range annotationRange = null; + if (valuePairs.length == 0) { + annotationRange = PositionUtils.toNameRange(annotation, context.getUtils()); + diagnosticMessage = Messages.getMessage("ResourcesAnnotationMustDefineResourceAnnotation", + "@Resources", "@Resource"); + diagnostics.add(context.createDiagnostic(uri, diagnosticMessage, annotationRange, + Constants.DIAGNOSTIC_SOURCE, + ErrorCode.MissingResourceAnnotation, + DiagnosticSeverity.Error)); + } + int objKind = internalAnnotation.getValueKind(); + for (Object childAnnotationObj : valuePairs) { + if (objKind == IMemberValuePair.K_ANNOTATION) { + IAnnotation childAnnotation = (IAnnotation) childAnnotationObj; + if (DiagnosticUtils.isMatchedAnnotation(unit, childAnnotation, + Constants.RESOURCE_FQ_NAME)) { + if (element instanceof IType) { + IType type = (IType) element; + if (type.getElementType() == IJavaElement.TYPE + && ((IType) type).isClass()) { + annotationRange = PositionUtils.toNameRange(childAnnotation, + context.getUtils()); + Boolean nameEmpty = true; + Boolean typeEmpty = true; + for (IMemberValuePair pair : childAnnotation.getMemberValuePairs()) { + if (pair.getMemberName().equals("name")) { + nameEmpty = false; + } + if (pair.getMemberName().equals("type")) { + typeEmpty = false; + } + } + if (nameEmpty) { + diagnosticMessage = Messages.getMessage( + "AnnotationMustDefineAttribute", + "@Resource", "name"); + diagnostics.add(context.createDiagnostic(uri, diagnosticMessage, + annotationRange, + Constants.DIAGNOSTIC_SOURCE, + ErrorCode.MissingResourceNameAttribute, + DiagnosticSeverity.Error)); + } + + if (typeEmpty) { + diagnosticMessage = Messages.getMessage( + "AnnotationMustDefineAttribute", + "@Resource", "type"); + diagnostics.add(context.createDiagnostic(uri, diagnosticMessage, + annotationRange, + Constants.DIAGNOSTIC_SOURCE, + ErrorCode.MissingResourceTypeAttribute, + DiagnosticSeverity.Error)); + } + } + } + } + } + } + } + } } + + // process methods now? if (DiagnosticUtils.isMatchedAnnotation(unit, annotation, Constants.POST_CONSTRUCT_FQ_NAME)) { if (element instanceof IMethod) { IMethod method = (IMethod) element; diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/Constants.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/Constants.java index d1b740d7..e66c6a4e 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/Constants.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/Constants.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2021, 2023 IBM Corporation and others. +* Copyright (c) 2021, 2023, 2024 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 @@ -30,6 +30,10 @@ public class Constants { public static final String RESOURCE = "Resource"; public static final String RESOURCE_FQ_NAME = "jakarta.annotation.Resource"; + /* @Resources */ + public static final String RESOURCES = "Resources"; + public static final String RESOURCES_FQ_NAME = "jakarta.annotation.Resources"; + /* @PostConstruct */ public static final String POST_CONSTRUCT = "PostConstruct"; public static final String POST_CONSTRUCT_FQ_NAME = "jakarta.annotation.PostConstruct"; diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/ErrorCode.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/ErrorCode.java index 9c8406b7..f35dc602 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/ErrorCode.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/ErrorCode.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2023 IBM Corporation and others. +* Copyright (c) 2023, 2024 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 @@ -19,6 +19,7 @@ */ public enum ErrorCode implements IJavaErrorCode { InvalidDateFormat, + MissingResourceAnnotation, MissingResourceNameAttribute, MissingResourceTypeAttribute, PostConstructParams, diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/InsertDefaultResourceAnnotationToResourcesAnnotation.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/InsertDefaultResourceAnnotationToResourcesAnnotation.java new file mode 100644 index 00000000..f7a1eb5e --- /dev/null +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/java/org/eclipse/lsp4jakarta/jdt/internal/annotations/InsertDefaultResourceAnnotationToResourcesAnnotation.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright (c) 2024 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 - initial API and implementation +*******************************************************************************/ +package org.eclipse.lsp4jakarta.jdt.internal.annotations; + +import org.eclipse.lsp4jakarta.commons.codeaction.ICodeActionId; +import org.eclipse.lsp4jakarta.commons.codeaction.JakartaCodeActionId; +import org.eclipse.lsp4jakarta.jdt.core.java.codeaction.InsertAnnotationAttributesQuickFix; + +/** + * Inserts the name attribute to the @Resource annotation to the active element. + */ +public class InsertDefaultResourceAnnotationToResourcesAnnotation extends InsertAnnotationAttributesQuickFix { + + /** + * Constructor. + */ + public InsertDefaultResourceAnnotationToResourcesAnnotation() { + super("jakarta.annotation.Resource", "name", "type"); + } + + /** + * {@inheritDoc} + */ + @Override + public String getParticipantId() { + return InsertDefaultResourceAnnotationToResourcesAnnotation.class.getName(); + } + + /** + * {@inheritDoc} + */ + @Override + protected ICodeActionId getCodeActionId() { + return JakartaCodeActionId.InsertDefaultResourceAnnotationToResourcesAnnotation; + } +} diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/resources/org/eclipse/lsp4jakarta/jdt/core/messages/messages.properties b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/resources/org/eclipse/lsp4jakarta/jdt/core/messages/messages.properties index 22cfc6ce..9c434940 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/resources/org/eclipse/lsp4jakarta/jdt/core/messages/messages.properties +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.core/src/main/resources/org/eclipse/lsp4jakarta/jdt/core/messages/messages.properties @@ -18,6 +18,7 @@ MethodMustNotHaveParameters = A method with the {0} annotation must not have any MethodMustBeVoid = A method with the {0} annotation must be void. MethodMustNotBeStatic = A method with the {0} annotation must not be static. MethodMustNotThrow = A method with the {0} annotation must not throw checked exceptions. +ResourcesAnnotationMustDefineResourceAnnotation = The {0} annotation must define at least one sub-annotation ''{1}''. # HttpServletQuickFix LetClassExtend = Let ''{0}'' extend ''{1}'' diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourceAnnotation.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourceAnnotation.java index 967a5892..0f429e11 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourceAnnotation.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourceAnnotation.java @@ -1,23 +1,23 @@ package io.openliberty.sample.jakarta.annotations; import jakarta.annotation.Resource; +import jakarta.annotation.Resources; @Resource(type = Object.class, name = "aa") public class ResourceAnnotation { private Integer studentId; - @Resource(shareable = true) private boolean isHappy; @Resource(name = "test") private boolean isSad; - private String emailAddress; - - + + + } @Resource(name = "aa") diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourcesAnnotation.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourcesAnnotation.java new file mode 100644 index 00000000..3aaf2c5a --- /dev/null +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/projects/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/annotations/ResourcesAnnotation.java @@ -0,0 +1,28 @@ +/******************************************************************************* +* Copyright (c) 2024 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 - initial API and implementation +*******************************************************************************/ +package io.openliberty.sample.jakarta.annotations; + +import jakarta.annotation.Resource; +import jakarta.annotation.Resources; + +@Resources ({ @Resource(name = "aaa"), @Resource(type = Object.class) }) +public class ResourcesAnnotation { +} + +@Resources ({}) +class ResourcesAnnotationEmpty { +} + +@Resource(name = "aa", type = Object.class) +class DoctoralStudent { +} \ No newline at end of file diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourceAnnotationTest.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourceAnnotationTest.java index 40f8fec8..33a39f90 100644 --- a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourceAnnotationTest.java +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourceAnnotationTest.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2021, 2022 IBM Corporation and others. +* Copyright (c) 2021, 2022, 2024 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 @@ -63,10 +63,10 @@ public void GeneratedAnnotation() throws Exception { assertJavaCodeAction(codeActionParams, IJDT_UTILS, ca); JakartaJavaCodeActionParams codeActionParams1 = createCodeActionParams(uri, d2); - TextEdit te1 = te(39, 0, 39, 30, "@Resource(type = \"\", name = \"\")"); + TextEdit te1 = te(39, 0, 39, 30, "@Resource(type = Object.class, name = \"\")"); CodeAction ca1 = ca(uri, "Insert 'name' attribute to @Resource", d2, te1); assertJavaCodeAction(codeActionParams1, IJDT_UTILS, ca1); } -} +} \ No newline at end of file diff --git a/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourcesAnnotationTest.java b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourcesAnnotationTest.java new file mode 100644 index 00000000..bf48c3c7 --- /dev/null +++ b/jakarta.jdt/org.eclipse.lsp4jakarta.jdt.test/src/main/java/org/eclipse/lsp4jakarta/jdt/annotations/ResourcesAnnotationTest.java @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2024 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 - initial API and implementation +*******************************************************************************/ +package org.eclipse.lsp4jakarta.jdt.annotations; + +import static org.eclipse.lsp4jakarta.jdt.core.JakartaForJavaAssert.assertJavaCodeAction; +import static org.eclipse.lsp4jakarta.jdt.core.JakartaForJavaAssert.assertJavaDiagnostics; +import static org.eclipse.lsp4jakarta.jdt.core.JakartaForJavaAssert.ca; +import static org.eclipse.lsp4jakarta.jdt.core.JakartaForJavaAssert.createCodeActionParams; +import static org.eclipse.lsp4jakarta.jdt.core.JakartaForJavaAssert.d; +import static org.eclipse.lsp4jakarta.jdt.core.JakartaForJavaAssert.te; + +import java.util.Arrays; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4jakarta.commons.JakartaJavaCodeActionParams; +import org.eclipse.lsp4jakarta.commons.JakartaJavaDiagnosticsParams; +import org.eclipse.lsp4jakarta.jdt.core.BaseJakartaTest; +import org.eclipse.lsp4jakarta.jdt.core.utils.IJDTUtils; +import org.eclipse.lsp4jakarta.jdt.internal.core.ls.JDTUtilsLSImpl; +import org.junit.Test; + +public class ResourcesAnnotationTest extends BaseJakartaTest { + + protected static IJDTUtils IJDT_UTILS = JDTUtilsLSImpl.getInstance(); + + @Test + public void GeneratedAnnotation() throws Exception { + IJavaProject javaProject = loadJavaProject("jakarta-sample", ""); + IFile javaFile = javaProject.getProject().getFile(new Path("src/main/java/io/openliberty/sample/jakarta/annotations/ResourcesAnnotation.java")); + String uri = javaFile.getLocation().toFile().toURI().toString(); + + JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams(); + diagnosticsParams.setUris(Arrays.asList(uri)); + + // expected annotations + Diagnostic d1 = d(17, 14, 37, "The @Resource annotation must define the attribute 'type'.", + DiagnosticSeverity.Error, "jakarta-annotations", "MissingResourceTypeAttribute"); + + Diagnostic d2 = d(17, 39, 69, "The @Resource annotation must define the attribute 'name'.", + DiagnosticSeverity.Error, "jakarta-annotations", "MissingResourceNameAttribute"); + + Diagnostic d3 = d(21, 0, 15, "The @Resources annotation must define at least one sub-annotation '@Resource'.", + DiagnosticSeverity.Error, "jakarta-annotations", "MissingResourceAnnotation"); + + assertJavaDiagnostics(diagnosticsParams, IJDT_UTILS, d1, d2, d3); + + JakartaJavaCodeActionParams codeActionParams = createCodeActionParams(uri, d1); + TextEdit te1 = te(17, 0, 18, 0, "@Resources({ @Resource(name = \"aaa\", type = \"\"), @Resource(type = Object.class) })\n"); + CodeAction ca1 = ca(uri, "Insert 'type' attribute to @Resource", d1, te1); + assertJavaCodeAction(codeActionParams, IJDT_UTILS, ca1); + + JakartaJavaCodeActionParams codeActionParams1 = createCodeActionParams(uri, d2); + TextEdit te2 = te(17, 0, 18, 0, "@Resources({ @Resource(name = \"aaa\"), @Resource(type = Object.class, name = \"\") })\n"); + CodeAction ca2 = ca(uri, "Insert 'name' attribute to @Resource", d2, te2); + assertJavaCodeAction(codeActionParams1, IJDT_UTILS, ca2); + + JakartaJavaCodeActionParams codeActionParams2 = createCodeActionParams(uri, d3); + TextEdit te3 = te(21, 0, 21, 15, "@Resources({ @Resource(name = \"\", type = \"\") })"); + CodeAction ca3 = ca(uri, "Insert 'name,type' attributes to @Resource", d3, te3); + assertJavaCodeAction(codeActionParams2, IJDT_UTILS, ca3); + } +}